@ -33,7 +33,6 @@
 | 
				
			|||||||
		21AC354419419A4D80ADE43A /* include_juce_audio_plugin_client_AU_2.mm in Sources */ = {isa = PBXBuildFile; fileRef = 7E47C0A828016F7D0D63C0D6 /* include_juce_audio_plugin_client_AU_2.mm */; };
 | 
							21AC354419419A4D80ADE43A /* include_juce_audio_plugin_client_AU_2.mm in Sources */ = {isa = PBXBuildFile; fileRef = 7E47C0A828016F7D0D63C0D6 /* include_juce_audio_plugin_client_AU_2.mm */; };
 | 
				
			||||||
		2918F46AFD2AB89F9FA847DC /* include_juce_events.mm in Sources */ = {isa = PBXBuildFile; fileRef = 373EF982A53046CE00BECE68 /* include_juce_events.mm */; };
 | 
							2918F46AFD2AB89F9FA847DC /* include_juce_events.mm in Sources */ = {isa = PBXBuildFile; fileRef = 373EF982A53046CE00BECE68 /* include_juce_events.mm */; };
 | 
				
			||||||
		2E77C6FAF1BCDB9EB29D20B9 /* PluginProcessor.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D53CAB963D5051C786D3A52D /* PluginProcessor.cpp */; };
 | 
							2E77C6FAF1BCDB9EB29D20B9 /* PluginProcessor.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D53CAB963D5051C786D3A52D /* PluginProcessor.cpp */; };
 | 
				
			||||||
		305606C42BB0F2A12D382D34 /* SoundfontSynthVoice.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B5A057FEC371053E83A73E47 /* SoundfontSynthVoice.cpp */; };
 | 
					 | 
				
			||||||
		358E458C22BEE5090087ED8D /* RecentFilesMenuTemplate.nib in Resources */ = {isa = PBXBuildFile; fileRef = 78CC5234CCFE3B170585DDAD /* RecentFilesMenuTemplate.nib */; };
 | 
							358E458C22BEE5090087ED8D /* RecentFilesMenuTemplate.nib in Resources */ = {isa = PBXBuildFile; fileRef = 78CC5234CCFE3B170585DDAD /* RecentFilesMenuTemplate.nib */; };
 | 
				
			||||||
		358E458D22BEE5090087ED8D /* Accelerate.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C1616112041466F7324D7E19 /* Accelerate.framework */; };
 | 
							358E458D22BEE5090087ED8D /* Accelerate.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C1616112041466F7324D7E19 /* Accelerate.framework */; };
 | 
				
			||||||
		358E458E22BEE5090087ED8D /* AudioToolbox.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 28CA077CDD21D0FEC66FC290 /* AudioToolbox.framework */; };
 | 
							358E458E22BEE5090087ED8D /* AudioToolbox.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 28CA077CDD21D0FEC66FC290 /* AudioToolbox.framework */; };
 | 
				
			||||||
@ -123,11 +122,9 @@
 | 
				
			|||||||
		8502F736BECFB9CB752AC72F /* Pills.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 2C62C3F0621604CDB65B55A6 /* Pills.cpp */; };
 | 
							8502F736BECFB9CB752AC72F /* Pills.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 2C62C3F0621604CDB65B55A6 /* Pills.cpp */; };
 | 
				
			||||||
		85E6C3826F86B1258C407725 /* FluidSynthModel.cpp in Sources */ = {isa = PBXBuildFile; fileRef = DA25104C95E55D1822BFFBE2 /* FluidSynthModel.cpp */; };
 | 
							85E6C3826F86B1258C407725 /* FluidSynthModel.cpp in Sources */ = {isa = PBXBuildFile; fileRef = DA25104C95E55D1822BFFBE2 /* FluidSynthModel.cpp */; };
 | 
				
			||||||
		909EB835CB55BF0B86B4BD93 /* CoreMIDI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 26949DA45B5FE0F3A0355733 /* CoreMIDI.framework */; };
 | 
							909EB835CB55BF0B86B4BD93 /* CoreMIDI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 26949DA45B5FE0F3A0355733 /* CoreMIDI.framework */; };
 | 
				
			||||||
		9AF2F3DE22C71A7F465B2EAD /* BankAndPreset.cpp in Sources */ = {isa = PBXBuildFile; fileRef = DECFA95359BC1DDDD1CC86C3 /* BankAndPreset.cpp */; };
 | 
					 | 
				
			||||||
		9C107CE4B586E4B097D9D04E /* SurjectiveMidiKeyboardComponent.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4119A8200AC54674C00EFE66 /* SurjectiveMidiKeyboardComponent.cpp */; };
 | 
							9C107CE4B586E4B097D9D04E /* SurjectiveMidiKeyboardComponent.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4119A8200AC54674C00EFE66 /* SurjectiveMidiKeyboardComponent.cpp */; };
 | 
				
			||||||
		9C2580F953071AD611EB6166 /* AudioToolbox.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 28CA077CDD21D0FEC66FC290 /* AudioToolbox.framework */; };
 | 
							9C2580F953071AD611EB6166 /* AudioToolbox.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 28CA077CDD21D0FEC66FC290 /* AudioToolbox.framework */; };
 | 
				
			||||||
		AC5E4EF988D864A298E3650D /* TablesComponent.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 0DD5458189C039F5A4FAD62D /* TablesComponent.cpp */; };
 | 
							AC5E4EF988D864A298E3650D /* TablesComponent.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 0DD5458189C039F5A4FAD62D /* TablesComponent.cpp */; };
 | 
				
			||||||
		B66EBD76F6051D97D56C97AB /* SoundfontSynthSound.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 7D7B71BE20CA213D2FCD7FEE /* SoundfontSynthSound.cpp */; };
 | 
					 | 
				
			||||||
		B92F6EAB1D5ACC13AF0CD750 /* CoreAudioKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A67D09546C4E4831438F7DBD /* CoreAudioKit.framework */; };
 | 
							B92F6EAB1D5ACC13AF0CD750 /* CoreAudioKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A67D09546C4E4831438F7DBD /* CoreAudioKit.framework */; };
 | 
				
			||||||
		BB7C2221DA61425A1AC65694 /* include_juce_gui_extra.mm in Sources */ = {isa = PBXBuildFile; fileRef = 44FB953DA425CBBA8AC21417 /* include_juce_gui_extra.mm */; };
 | 
							BB7C2221DA61425A1AC65694 /* include_juce_gui_extra.mm in Sources */ = {isa = PBXBuildFile; fileRef = 44FB953DA425CBBA8AC21417 /* include_juce_gui_extra.mm */; };
 | 
				
			||||||
		BFD9EF2D67067FC1E5BA3546 /* MyColours.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 29F2CE1B40FAE1467C7876C5 /* MyColours.cpp */; };
 | 
							BFD9EF2D67067FC1E5BA3546 /* MyColours.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 29F2CE1B40FAE1467C7876C5 /* MyColours.cpp */; };
 | 
				
			||||||
@ -140,7 +137,6 @@
 | 
				
			|||||||
		CB8F898ACB35575C1695E223 /* include_juce_gui_basics.mm in Sources */ = {isa = PBXBuildFile; fileRef = F5276945E14F83CA02C05B41 /* include_juce_gui_basics.mm */; };
 | 
							CB8F898ACB35575C1695E223 /* include_juce_gui_basics.mm in Sources */ = {isa = PBXBuildFile; fileRef = F5276945E14F83CA02C05B41 /* include_juce_gui_basics.mm */; };
 | 
				
			||||||
		DB7F85571650636DB9ECE092 /* include_juce_audio_plugin_client_AU.r in Rez */ = {isa = PBXBuildFile; fileRef = 5704CA923F677280C02D97C6 /* include_juce_audio_plugin_client_AU.r */; };
 | 
							DB7F85571650636DB9ECE092 /* include_juce_audio_plugin_client_AU.r in Rez */ = {isa = PBXBuildFile; fileRef = 5704CA923F677280C02D97C6 /* include_juce_audio_plugin_client_AU.r */; };
 | 
				
			||||||
		DDF28AD28F639A561292FE28 /* include_juce_core.mm in Sources */ = {isa = PBXBuildFile; fileRef = F69B741A63932433977CFCD8 /* include_juce_core.mm */; };
 | 
							DDF28AD28F639A561292FE28 /* include_juce_core.mm in Sources */ = {isa = PBXBuildFile; fileRef = F69B741A63932433977CFCD8 /* include_juce_core.mm */; };
 | 
				
			||||||
		DF84F5E7E386AF7A38854939 /* Preset.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 59F9FEC807012C10B8A1FA07 /* Preset.cpp */; };
 | 
					 | 
				
			||||||
		E08B3A2AF85F9FCF991F1CA2 /* include_juce_audio_basics.mm in Sources */ = {isa = PBXBuildFile; fileRef = 13E201F5B25AC078DB396A9C /* include_juce_audio_basics.mm */; };
 | 
							E08B3A2AF85F9FCF991F1CA2 /* include_juce_audio_basics.mm in Sources */ = {isa = PBXBuildFile; fileRef = 13E201F5B25AC078DB396A9C /* include_juce_audio_basics.mm */; };
 | 
				
			||||||
		FDAB0F06D8758FF0407BB851 /* include_juce_data_structures.mm in Sources */ = {isa = PBXBuildFile; fileRef = 2C66D01D1DD9006E77E2E260 /* include_juce_data_structures.mm */; };
 | 
							FDAB0F06D8758FF0407BB851 /* include_juce_data_structures.mm in Sources */ = {isa = PBXBuildFile; fileRef = 2C66D01D1DD9006E77E2E260 /* include_juce_data_structures.mm */; };
 | 
				
			||||||
		FE0869D2DF902682B6E4C925 /* WebKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C481A535CF8A4CBC3E594003 /* WebKit.framework */; };
 | 
							FE0869D2DF902682B6E4C925 /* WebKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C481A535CF8A4CBC3E594003 /* WebKit.framework */; };
 | 
				
			||||||
@ -285,6 +281,7 @@
 | 
				
			|||||||
		2C66D01D1DD9006E77E2E260 /* include_juce_data_structures.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; name = include_juce_data_structures.mm; path = ../../JuceLibraryCode/include_juce_data_structures.mm; sourceTree = SOURCE_ROOT; };
 | 
							2C66D01D1DD9006E77E2E260 /* include_juce_data_structures.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; name = include_juce_data_structures.mm; path = ../../JuceLibraryCode/include_juce_data_structures.mm; sourceTree = SOURCE_ROOT; };
 | 
				
			||||||
		307CB49DF900DE4A612FF98E /* FluidSynthModel.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = FluidSynthModel.h; path = ../../Source/FluidSynthModel.h; sourceTree = SOURCE_ROOT; };
 | 
							307CB49DF900DE4A612FF98E /* FluidSynthModel.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = FluidSynthModel.h; path = ../../Source/FluidSynthModel.h; sourceTree = SOURCE_ROOT; };
 | 
				
			||||||
		35099D9022CA8EF500CD4523 /* Util.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = Util.h; path = ../../Source/Util.h; sourceTree = "<group>"; };
 | 
							35099D9022CA8EF500CD4523 /* Util.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = Util.h; path = ../../Source/Util.h; sourceTree = "<group>"; };
 | 
				
			||||||
 | 
							35099D9422CAB0A400CD4523 /* GuiConstants.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = GuiConstants.h; path = ../../Source/GuiConstants.h; sourceTree = "<group>"; };
 | 
				
			||||||
		35880F58CB540AD30D1B0ED3 /* TablesComponent.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = TablesComponent.h; path = ../../Source/TablesComponent.h; sourceTree = SOURCE_ROOT; };
 | 
							35880F58CB540AD30D1B0ED3 /* TablesComponent.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = TablesComponent.h; path = ../../Source/TablesComponent.h; sourceTree = SOURCE_ROOT; };
 | 
				
			||||||
		358E45B422BEE53A0087ED8D /* libpcre.1.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; path = libpcre.1.dylib; sourceTree = "<group>"; };
 | 
							358E45B422BEE53A0087ED8D /* libpcre.1.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; path = libpcre.1.dylib; sourceTree = "<group>"; };
 | 
				
			||||||
		358E45B522BEE53A0087ED8D /* libvorbisenc.2.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; path = libvorbisenc.2.dylib; sourceTree = "<group>"; };
 | 
							358E45B522BEE53A0087ED8D /* libvorbisenc.2.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; path = libvorbisenc.2.dylib; sourceTree = "<group>"; };
 | 
				
			||||||
@ -312,12 +309,10 @@
 | 
				
			|||||||
		5704CA923F677280C02D97C6 /* include_juce_audio_plugin_client_AU.r */ = {isa = PBXFileReference; lastKnownFileType = file.r; name = include_juce_audio_plugin_client_AU.r; path = ../../JuceLibraryCode/include_juce_audio_plugin_client_AU.r; sourceTree = SOURCE_ROOT; };
 | 
							5704CA923F677280C02D97C6 /* include_juce_audio_plugin_client_AU.r */ = {isa = PBXFileReference; lastKnownFileType = file.r; name = include_juce_audio_plugin_client_AU.r; path = ../../JuceLibraryCode/include_juce_audio_plugin_client_AU.r; sourceTree = SOURCE_ROOT; };
 | 
				
			||||||
		576A01FC6A3620A39BD1BDEE /* MyColours.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = MyColours.h; path = ../../Source/MyColours.h; sourceTree = SOURCE_ROOT; };
 | 
							576A01FC6A3620A39BD1BDEE /* MyColours.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = MyColours.h; path = ../../Source/MyColours.h; sourceTree = SOURCE_ROOT; };
 | 
				
			||||||
		5896415135C635B1EB2DC202 /* CoreAudio.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreAudio.framework; path = System/Library/Frameworks/CoreAudio.framework; sourceTree = SDKROOT; };
 | 
							5896415135C635B1EB2DC202 /* CoreAudio.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreAudio.framework; path = System/Library/Frameworks/CoreAudio.framework; sourceTree = SDKROOT; };
 | 
				
			||||||
		59F9FEC807012C10B8A1FA07 /* Preset.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = Preset.cpp; path = ../../Source/Preset.cpp; sourceTree = SOURCE_ROOT; };
 | 
					 | 
				
			||||||
		5A57BEB8628C7AE62ED1039F /* include_juce_audio_plugin_client_AU_1.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; name = include_juce_audio_plugin_client_AU_1.mm; path = ../../JuceLibraryCode/include_juce_audio_plugin_client_AU_1.mm; sourceTree = SOURCE_ROOT; };
 | 
							5A57BEB8628C7AE62ED1039F /* include_juce_audio_plugin_client_AU_1.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; name = include_juce_audio_plugin_client_AU_1.mm; path = ../../JuceLibraryCode/include_juce_audio_plugin_client_AU_1.mm; sourceTree = SOURCE_ROOT; };
 | 
				
			||||||
		60ADEC8B20DC559737F84180 /* juce_gui_basics */ = {isa = PBXFileReference; lastKnownFileType = folder; name = juce_gui_basics; path = /Applications/JUCE/modules/juce_gui_basics; sourceTree = "<absolute>"; };
 | 
							60ADEC8B20DC559737F84180 /* juce_gui_basics */ = {isa = PBXFileReference; lastKnownFileType = folder; name = juce_gui_basics; path = /Applications/JUCE/modules/juce_gui_basics; sourceTree = "<absolute>"; };
 | 
				
			||||||
		663ACFA11DCEC0D411B8497E /* juicysfplugin.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = juicysfplugin.entitlements; sourceTree = SOURCE_ROOT; };
 | 
							663ACFA11DCEC0D411B8497E /* juicysfplugin.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = juicysfplugin.entitlements; sourceTree = SOURCE_ROOT; };
 | 
				
			||||||
		6714B050717A7500EE7AE867 /* include_juce_audio_plugin_client_VST2.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = include_juce_audio_plugin_client_VST2.cpp; path = ../../JuceLibraryCode/include_juce_audio_plugin_client_VST2.cpp; sourceTree = SOURCE_ROOT; };
 | 
							6714B050717A7500EE7AE867 /* include_juce_audio_plugin_client_VST2.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = include_juce_audio_plugin_client_VST2.cpp; path = ../../JuceLibraryCode/include_juce_audio_plugin_client_VST2.cpp; sourceTree = SOURCE_ROOT; };
 | 
				
			||||||
		69DB3A0FB3D21F87D1E4B0C1 /* PresetsToBanks.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = PresetsToBanks.h; path = ../../Source/PresetsToBanks.h; sourceTree = SOURCE_ROOT; };
 | 
					 | 
				
			||||||
		6A7F287E4159FA5167131D2B /* juce_audio_processors */ = {isa = PBXFileReference; lastKnownFileType = folder; name = juce_audio_processors; path = /Applications/JUCE/modules/juce_audio_processors; sourceTree = "<absolute>"; };
 | 
							6A7F287E4159FA5167131D2B /* juce_audio_processors */ = {isa = PBXFileReference; lastKnownFileType = folder; name = juce_audio_processors; path = /Applications/JUCE/modules/juce_audio_processors; sourceTree = "<absolute>"; };
 | 
				
			||||||
		6C5DCE19B6DC0EF5BA12F99C /* juicysfplugin.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = juicysfplugin.app; sourceTree = BUILT_PRODUCTS_DIR; };
 | 
							6C5DCE19B6DC0EF5BA12F99C /* juicysfplugin.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = juicysfplugin.app; sourceTree = BUILT_PRODUCTS_DIR; };
 | 
				
			||||||
		6D94DCB335360BDC7B3673BF /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; };
 | 
							6D94DCB335360BDC7B3673BF /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; };
 | 
				
			||||||
@ -325,13 +320,10 @@
 | 
				
			|||||||
		6FA795817D2F3B3119FDD754 /* juce_core */ = {isa = PBXFileReference; lastKnownFileType = folder; name = juce_core; path = /Applications/JUCE/modules/juce_core; sourceTree = "<absolute>"; };
 | 
							6FA795817D2F3B3119FDD754 /* juce_core */ = {isa = PBXFileReference; lastKnownFileType = folder; name = juce_core; path = /Applications/JUCE/modules/juce_core; sourceTree = "<absolute>"; };
 | 
				
			||||||
		6FEF19AE08ED1DC1E3D9DF43 /* AppConfig.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = AppConfig.h; path = ../../JuceLibraryCode/AppConfig.h; sourceTree = SOURCE_ROOT; };
 | 
							6FEF19AE08ED1DC1E3D9DF43 /* AppConfig.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = AppConfig.h; path = ../../JuceLibraryCode/AppConfig.h; sourceTree = SOURCE_ROOT; };
 | 
				
			||||||
		706FF998202761F30811FA6B /* juce_audio_devices */ = {isa = PBXFileReference; lastKnownFileType = folder; name = juce_audio_devices; path = /Applications/JUCE/modules/juce_audio_devices; sourceTree = "<absolute>"; };
 | 
							706FF998202761F30811FA6B /* juce_audio_devices */ = {isa = PBXFileReference; lastKnownFileType = folder; name = juce_audio_devices; path = /Applications/JUCE/modules/juce_audio_devices; sourceTree = "<absolute>"; };
 | 
				
			||||||
		76724E30D8976FC4C2EE56FF /* SoundfontSynthSound.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SoundfontSynthSound.h; path = ../../Source/SoundfontSynthSound.h; sourceTree = SOURCE_ROOT; };
 | 
					 | 
				
			||||||
		78CC5234CCFE3B170585DDAD /* RecentFilesMenuTemplate.nib */ = {isa = PBXFileReference; lastKnownFileType = file.nib; path = RecentFilesMenuTemplate.nib; sourceTree = SOURCE_ROOT; };
 | 
							78CC5234CCFE3B170585DDAD /* RecentFilesMenuTemplate.nib */ = {isa = PBXFileReference; lastKnownFileType = file.nib; path = RecentFilesMenuTemplate.nib; sourceTree = SOURCE_ROOT; };
 | 
				
			||||||
		7C699A8B65F3F9FB5004F22D /* juce_gui_extra */ = {isa = PBXFileReference; lastKnownFileType = folder; name = juce_gui_extra; path = /Applications/JUCE/modules/juce_gui_extra; sourceTree = "<absolute>"; };
 | 
							7C699A8B65F3F9FB5004F22D /* juce_gui_extra */ = {isa = PBXFileReference; lastKnownFileType = folder; name = juce_gui_extra; path = /Applications/JUCE/modules/juce_gui_extra; sourceTree = "<absolute>"; };
 | 
				
			||||||
		7D2457AD994644752178FC82 /* include_juce_audio_plugin_client_VST_utils.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; name = include_juce_audio_plugin_client_VST_utils.mm; path = ../../JuceLibraryCode/include_juce_audio_plugin_client_VST_utils.mm; sourceTree = SOURCE_ROOT; };
 | 
							7D2457AD994644752178FC82 /* include_juce_audio_plugin_client_VST_utils.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; name = include_juce_audio_plugin_client_VST_utils.mm; path = ../../JuceLibraryCode/include_juce_audio_plugin_client_VST_utils.mm; sourceTree = SOURCE_ROOT; };
 | 
				
			||||||
		7D7B71BE20CA213D2FCD7FEE /* SoundfontSynthSound.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = SoundfontSynthSound.cpp; path = ../../Source/SoundfontSynthSound.cpp; sourceTree = SOURCE_ROOT; };
 | 
					 | 
				
			||||||
		7E47C0A828016F7D0D63C0D6 /* include_juce_audio_plugin_client_AU_2.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; name = include_juce_audio_plugin_client_AU_2.mm; path = ../../JuceLibraryCode/include_juce_audio_plugin_client_AU_2.mm; sourceTree = SOURCE_ROOT; };
 | 
							7E47C0A828016F7D0D63C0D6 /* include_juce_audio_plugin_client_AU_2.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; name = include_juce_audio_plugin_client_AU_2.mm; path = ../../JuceLibraryCode/include_juce_audio_plugin_client_AU_2.mm; sourceTree = SOURCE_ROOT; };
 | 
				
			||||||
		88ADEBF51BD04FEA9422D276 /* FilePickerFragment.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = FilePickerFragment.h; path = ../../Source/FilePickerFragment.h; sourceTree = SOURCE_ROOT; };
 | 
					 | 
				
			||||||
		8990F3EAFFBBD6A42247C663 /* PluginEditor.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = PluginEditor.h; path = ../../Source/PluginEditor.h; sourceTree = SOURCE_ROOT; };
 | 
							8990F3EAFFBBD6A42247C663 /* PluginEditor.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = PluginEditor.h; path = ../../Source/PluginEditor.h; sourceTree = SOURCE_ROOT; };
 | 
				
			||||||
		910F2E433646EE260D61A91B /* AudioUnit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AudioUnit.framework; path = System/Library/Frameworks/AudioUnit.framework; sourceTree = SDKROOT; };
 | 
							910F2E433646EE260D61A91B /* AudioUnit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AudioUnit.framework; path = System/Library/Frameworks/AudioUnit.framework; sourceTree = SDKROOT; };
 | 
				
			||||||
		91B7A726C6FDDEE3F364ED99 /* juce_events */ = {isa = PBXFileReference; lastKnownFileType = folder; name = juce_events; path = /Applications/JUCE/modules/juce_events; sourceTree = "<absolute>"; };
 | 
							91B7A726C6FDDEE3F364ED99 /* juce_events */ = {isa = PBXFileReference; lastKnownFileType = folder; name = juce_events; path = /Applications/JUCE/modules/juce_events; sourceTree = "<absolute>"; };
 | 
				
			||||||
@ -340,10 +332,6 @@
 | 
				
			|||||||
		A67D09546C4E4831438F7DBD /* CoreAudioKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreAudioKit.framework; path = System/Library/Frameworks/CoreAudioKit.framework; sourceTree = SDKROOT; };
 | 
							A67D09546C4E4831438F7DBD /* CoreAudioKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreAudioKit.framework; path = System/Library/Frameworks/CoreAudioKit.framework; sourceTree = SDKROOT; };
 | 
				
			||||||
		A6BC2528C1717DDC2B66215E /* include_juce_graphics.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; name = include_juce_graphics.mm; path = ../../JuceLibraryCode/include_juce_graphics.mm; sourceTree = SOURCE_ROOT; };
 | 
							A6BC2528C1717DDC2B66215E /* include_juce_graphics.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; name = include_juce_graphics.mm; path = ../../JuceLibraryCode/include_juce_graphics.mm; sourceTree = SOURCE_ROOT; };
 | 
				
			||||||
		ADC93C26314F163B963461E2 /* include_juce_audio_utils.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; name = include_juce_audio_utils.mm; path = ../../JuceLibraryCode/include_juce_audio_utils.mm; sourceTree = SOURCE_ROOT; };
 | 
							ADC93C26314F163B963461E2 /* include_juce_audio_utils.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; name = include_juce_audio_utils.mm; path = ../../JuceLibraryCode/include_juce_audio_utils.mm; sourceTree = SOURCE_ROOT; };
 | 
				
			||||||
		AE397302E7E3F3A14A0C5F3C /* Preset.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = Preset.h; path = ../../Source/Preset.h; sourceTree = SOURCE_ROOT; };
 | 
					 | 
				
			||||||
		B000E7A360C0C86ADD3C911D /* BankAndPreset.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = BankAndPreset.h; path = ../../Source/BankAndPreset.h; sourceTree = SOURCE_ROOT; };
 | 
					 | 
				
			||||||
		B40B7F24646CBA708718DE82 /* SoundfontSynthVoice.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SoundfontSynthVoice.h; path = ../../Source/SoundfontSynthVoice.h; sourceTree = SOURCE_ROOT; };
 | 
					 | 
				
			||||||
		B5A057FEC371053E83A73E47 /* SoundfontSynthVoice.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = SoundfontSynthVoice.cpp; path = ../../Source/SoundfontSynthVoice.cpp; sourceTree = SOURCE_ROOT; };
 | 
					 | 
				
			||||||
		B6D37AD919F9E83688578941 /* SurjectiveMidiKeyboardComponent.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SurjectiveMidiKeyboardComponent.h; path = ../../Source/SurjectiveMidiKeyboardComponent.h; sourceTree = SOURCE_ROOT; };
 | 
							B6D37AD919F9E83688578941 /* SurjectiveMidiKeyboardComponent.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SurjectiveMidiKeyboardComponent.h; path = ../../Source/SurjectiveMidiKeyboardComponent.h; sourceTree = SOURCE_ROOT; };
 | 
				
			||||||
		BFB39134DE6876F9005CFA61 /* Pills.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = Pills.h; path = ../../Source/Pills.h; sourceTree = SOURCE_ROOT; };
 | 
							BFB39134DE6876F9005CFA61 /* Pills.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = Pills.h; path = ../../Source/Pills.h; sourceTree = SOURCE_ROOT; };
 | 
				
			||||||
		BFF57868318157F12F087F07 /* Info-AU.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "Info-AU.plist"; sourceTree = SOURCE_ROOT; };
 | 
							BFF57868318157F12F087F07 /* Info-AU.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "Info-AU.plist"; sourceTree = SOURCE_ROOT; };
 | 
				
			||||||
@ -356,10 +344,8 @@
 | 
				
			|||||||
		D11295BAED9825695A4DEAB8 /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = System/Library/Frameworks/QuartzCore.framework; sourceTree = SDKROOT; };
 | 
							D11295BAED9825695A4DEAB8 /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = System/Library/Frameworks/QuartzCore.framework; sourceTree = SDKROOT; };
 | 
				
			||||||
		D53CAB963D5051C786D3A52D /* PluginProcessor.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = PluginProcessor.cpp; path = ../../Source/PluginProcessor.cpp; sourceTree = SOURCE_ROOT; };
 | 
							D53CAB963D5051C786D3A52D /* PluginProcessor.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = PluginProcessor.cpp; path = ../../Source/PluginProcessor.cpp; sourceTree = SOURCE_ROOT; };
 | 
				
			||||||
		DA25104C95E55D1822BFFBE2 /* FluidSynthModel.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = FluidSynthModel.cpp; path = ../../Source/FluidSynthModel.cpp; sourceTree = SOURCE_ROOT; };
 | 
							DA25104C95E55D1822BFFBE2 /* FluidSynthModel.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = FluidSynthModel.cpp; path = ../../Source/FluidSynthModel.cpp; sourceTree = SOURCE_ROOT; };
 | 
				
			||||||
		DECFA95359BC1DDDD1CC86C3 /* BankAndPreset.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = BankAndPreset.cpp; path = ../../Source/BankAndPreset.cpp; sourceTree = SOURCE_ROOT; };
 | 
					 | 
				
			||||||
		E4F84AFD6C449D10FDB5DB14 /* include_juce_audio_plugin_client_VST3.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = include_juce_audio_plugin_client_VST3.cpp; path = ../../JuceLibraryCode/include_juce_audio_plugin_client_VST3.cpp; sourceTree = SOURCE_ROOT; };
 | 
							E4F84AFD6C449D10FDB5DB14 /* include_juce_audio_plugin_client_VST3.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = include_juce_audio_plugin_client_VST3.cpp; path = ../../JuceLibraryCode/include_juce_audio_plugin_client_VST3.cpp; sourceTree = SOURCE_ROOT; };
 | 
				
			||||||
		E89ECA468FF133B4677F8327 /* juicysfplugin.vst */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = juicysfplugin.vst; sourceTree = BUILT_PRODUCTS_DIR; };
 | 
							E89ECA468FF133B4677F8327 /* juicysfplugin.vst */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = juicysfplugin.vst; sourceTree = BUILT_PRODUCTS_DIR; };
 | 
				
			||||||
		F1EB35E262DC717222E2F93D /* ExposesComponents.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = ExposesComponents.h; path = ../../Source/ExposesComponents.h; sourceTree = SOURCE_ROOT; };
 | 
					 | 
				
			||||||
		F5276945E14F83CA02C05B41 /* include_juce_gui_basics.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; name = include_juce_gui_basics.mm; path = ../../JuceLibraryCode/include_juce_gui_basics.mm; sourceTree = SOURCE_ROOT; };
 | 
							F5276945E14F83CA02C05B41 /* include_juce_gui_basics.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; name = include_juce_gui_basics.mm; path = ../../JuceLibraryCode/include_juce_gui_basics.mm; sourceTree = SOURCE_ROOT; };
 | 
				
			||||||
		F69B741A63932433977CFCD8 /* include_juce_core.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; name = include_juce_core.mm; path = ../../JuceLibraryCode/include_juce_core.mm; sourceTree = SOURCE_ROOT; };
 | 
							F69B741A63932433977CFCD8 /* include_juce_core.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; name = include_juce_core.mm; path = ../../JuceLibraryCode/include_juce_core.mm; sourceTree = SOURCE_ROOT; };
 | 
				
			||||||
		FE960C7D2CFA204401860C13 /* juce_audio_formats */ = {isa = PBXFileReference; lastKnownFileType = folder; name = juce_audio_formats; path = /Applications/JUCE/modules/juce_audio_formats; sourceTree = "<absolute>"; };
 | 
							FE960C7D2CFA204401860C13 /* juce_audio_formats */ = {isa = PBXFileReference; lastKnownFileType = folder; name = juce_audio_formats; path = /Applications/JUCE/modules/juce_audio_formats; sourceTree = "<absolute>"; };
 | 
				
			||||||
@ -521,10 +507,6 @@
 | 
				
			|||||||
		403EB0CF49CF1D62BF359002 /* Source */ = {
 | 
							403EB0CF49CF1D62BF359002 /* Source */ = {
 | 
				
			||||||
			isa = PBXGroup;
 | 
								isa = PBXGroup;
 | 
				
			||||||
			children = (
 | 
								children = (
 | 
				
			||||||
				DECFA95359BC1DDDD1CC86C3 /* BankAndPreset.cpp */,
 | 
					 | 
				
			||||||
				B000E7A360C0C86ADD3C911D /* BankAndPreset.h */,
 | 
					 | 
				
			||||||
				F1EB35E262DC717222E2F93D /* ExposesComponents.h */,
 | 
					 | 
				
			||||||
				88ADEBF51BD04FEA9422D276 /* FilePickerFragment.h */,
 | 
					 | 
				
			||||||
				C13A2FEAA636713EC7A905AF /* FilePicker.cpp */,
 | 
									C13A2FEAA636713EC7A905AF /* FilePicker.cpp */,
 | 
				
			||||||
				21828DE4341668D7E383F10A /* FilePicker.h */,
 | 
									21828DE4341668D7E383F10A /* FilePicker.h */,
 | 
				
			||||||
				DA25104C95E55D1822BFFBE2 /* FluidSynthModel.cpp */,
 | 
									DA25104C95E55D1822BFFBE2 /* FluidSynthModel.cpp */,
 | 
				
			||||||
@ -533,13 +515,6 @@
 | 
				
			|||||||
				576A01FC6A3620A39BD1BDEE /* MyColours.h */,
 | 
									576A01FC6A3620A39BD1BDEE /* MyColours.h */,
 | 
				
			||||||
				2C62C3F0621604CDB65B55A6 /* Pills.cpp */,
 | 
									2C62C3F0621604CDB65B55A6 /* Pills.cpp */,
 | 
				
			||||||
				BFB39134DE6876F9005CFA61 /* Pills.h */,
 | 
									BFB39134DE6876F9005CFA61 /* Pills.h */,
 | 
				
			||||||
				59F9FEC807012C10B8A1FA07 /* Preset.cpp */,
 | 
					 | 
				
			||||||
				AE397302E7E3F3A14A0C5F3C /* Preset.h */,
 | 
					 | 
				
			||||||
				69DB3A0FB3D21F87D1E4B0C1 /* PresetsToBanks.h */,
 | 
					 | 
				
			||||||
				7D7B71BE20CA213D2FCD7FEE /* SoundfontSynthSound.cpp */,
 | 
					 | 
				
			||||||
				76724E30D8976FC4C2EE56FF /* SoundfontSynthSound.h */,
 | 
					 | 
				
			||||||
				B5A057FEC371053E83A73E47 /* SoundfontSynthVoice.cpp */,
 | 
					 | 
				
			||||||
				B40B7F24646CBA708718DE82 /* SoundfontSynthVoice.h */,
 | 
					 | 
				
			||||||
				4119A8200AC54674C00EFE66 /* SurjectiveMidiKeyboardComponent.cpp */,
 | 
									4119A8200AC54674C00EFE66 /* SurjectiveMidiKeyboardComponent.cpp */,
 | 
				
			||||||
				B6D37AD919F9E83688578941 /* SurjectiveMidiKeyboardComponent.h */,
 | 
									B6D37AD919F9E83688578941 /* SurjectiveMidiKeyboardComponent.h */,
 | 
				
			||||||
				CE8C41308A31A71A1177D0D5 /* TableComponent.cpp */,
 | 
									CE8C41308A31A71A1177D0D5 /* TableComponent.cpp */,
 | 
				
			||||||
@ -554,6 +529,7 @@
 | 
				
			|||||||
				358E45F922C80DCA0087ED8D /* SlidersComponent.cpp */,
 | 
									358E45F922C80DCA0087ED8D /* SlidersComponent.cpp */,
 | 
				
			||||||
				358E45FA22C80DCA0087ED8D /* SlidersComponent.h */,
 | 
									358E45FA22C80DCA0087ED8D /* SlidersComponent.h */,
 | 
				
			||||||
				35099D9022CA8EF500CD4523 /* Util.h */,
 | 
									35099D9022CA8EF500CD4523 /* Util.h */,
 | 
				
			||||||
 | 
									35099D9422CAB0A400CD4523 /* GuiConstants.h */,
 | 
				
			||||||
			);
 | 
								);
 | 
				
			||||||
			name = Source;
 | 
								name = Source;
 | 
				
			||||||
			sourceTree = "<group>";
 | 
								sourceTree = "<group>";
 | 
				
			||||||
@ -936,14 +912,10 @@
 | 
				
			|||||||
			isa = PBXSourcesBuildPhase;
 | 
								isa = PBXSourcesBuildPhase;
 | 
				
			||||||
			buildActionMask = 2147483647;
 | 
								buildActionMask = 2147483647;
 | 
				
			||||||
			files = (
 | 
								files = (
 | 
				
			||||||
				9AF2F3DE22C71A7F465B2EAD /* BankAndPreset.cpp in Sources */,
 | 
					 | 
				
			||||||
				598516649859A6D6BB2856EF /* FilePicker.cpp in Sources */,
 | 
									598516649859A6D6BB2856EF /* FilePicker.cpp in Sources */,
 | 
				
			||||||
				85E6C3826F86B1258C407725 /* FluidSynthModel.cpp in Sources */,
 | 
									85E6C3826F86B1258C407725 /* FluidSynthModel.cpp in Sources */,
 | 
				
			||||||
				BFD9EF2D67067FC1E5BA3546 /* MyColours.cpp in Sources */,
 | 
									BFD9EF2D67067FC1E5BA3546 /* MyColours.cpp in Sources */,
 | 
				
			||||||
				8502F736BECFB9CB752AC72F /* Pills.cpp in Sources */,
 | 
									8502F736BECFB9CB752AC72F /* Pills.cpp in Sources */,
 | 
				
			||||||
				DF84F5E7E386AF7A38854939 /* Preset.cpp in Sources */,
 | 
					 | 
				
			||||||
				B66EBD76F6051D97D56C97AB /* SoundfontSynthSound.cpp in Sources */,
 | 
					 | 
				
			||||||
				305606C42BB0F2A12D382D34 /* SoundfontSynthVoice.cpp in Sources */,
 | 
					 | 
				
			||||||
				9C107CE4B586E4B097D9D04E /* SurjectiveMidiKeyboardComponent.cpp in Sources */,
 | 
									9C107CE4B586E4B097D9D04E /* SurjectiveMidiKeyboardComponent.cpp in Sources */,
 | 
				
			||||||
				4AE057561AEA78489D9E50F0 /* TableComponent.cpp in Sources */,
 | 
									4AE057561AEA78489D9E50F0 /* TableComponent.cpp in Sources */,
 | 
				
			||||||
				AC5E4EF988D864A298E3650D /* TablesComponent.cpp in Sources */,
 | 
									AC5E4EF988D864A298E3650D /* TablesComponent.cpp in Sources */,
 | 
				
			||||||
 | 
				
			|||||||
@ -0,0 +1,84 @@
 | 
				
			|||||||
 | 
					<?xml version="1.0" encoding="UTF-8"?>
 | 
				
			||||||
 | 
					<Scheme
 | 
				
			||||||
 | 
					   LastUpgradeVersion = "1020"
 | 
				
			||||||
 | 
					   version = "1.3">
 | 
				
			||||||
 | 
					   <BuildAction
 | 
				
			||||||
 | 
					      parallelizeBuildables = "YES"
 | 
				
			||||||
 | 
					      buildImplicitDependencies = "YES">
 | 
				
			||||||
 | 
					      <BuildActionEntries>
 | 
				
			||||||
 | 
					         <BuildActionEntry
 | 
				
			||||||
 | 
					            buildForTesting = "YES"
 | 
				
			||||||
 | 
					            buildForRunning = "YES"
 | 
				
			||||||
 | 
					            buildForProfiling = "YES"
 | 
				
			||||||
 | 
					            buildForArchiving = "YES"
 | 
				
			||||||
 | 
					            buildForAnalyzing = "YES">
 | 
				
			||||||
 | 
					            <BuildableReference
 | 
				
			||||||
 | 
					               BuildableIdentifier = "primary"
 | 
				
			||||||
 | 
					               BlueprintIdentifier = "345302E3C02BBFCDACE98BE7"
 | 
				
			||||||
 | 
					               BuildableName = "juicysfplugin.component"
 | 
				
			||||||
 | 
					               BlueprintName = "juicysfplugin - AU"
 | 
				
			||||||
 | 
					               ReferencedContainer = "container:juicysfplugin.xcodeproj">
 | 
				
			||||||
 | 
					            </BuildableReference>
 | 
				
			||||||
 | 
					         </BuildActionEntry>
 | 
				
			||||||
 | 
					      </BuildActionEntries>
 | 
				
			||||||
 | 
					   </BuildAction>
 | 
				
			||||||
 | 
					   <TestAction
 | 
				
			||||||
 | 
					      buildConfiguration = "Debug"
 | 
				
			||||||
 | 
					      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
 | 
				
			||||||
 | 
					      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
 | 
				
			||||||
 | 
					      shouldUseLaunchSchemeArgsEnv = "YES">
 | 
				
			||||||
 | 
					      <Testables>
 | 
				
			||||||
 | 
					      </Testables>
 | 
				
			||||||
 | 
					      <AdditionalOptions>
 | 
				
			||||||
 | 
					      </AdditionalOptions>
 | 
				
			||||||
 | 
					   </TestAction>
 | 
				
			||||||
 | 
					   <LaunchAction
 | 
				
			||||||
 | 
					      buildConfiguration = "Debug"
 | 
				
			||||||
 | 
					      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
 | 
				
			||||||
 | 
					      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
 | 
				
			||||||
 | 
					      launchStyle = "0"
 | 
				
			||||||
 | 
					      useCustomWorkingDirectory = "NO"
 | 
				
			||||||
 | 
					      ignoresPersistentStateOnLaunch = "NO"
 | 
				
			||||||
 | 
					      debugDocumentVersioning = "YES"
 | 
				
			||||||
 | 
					      debugServiceExtension = "internal"
 | 
				
			||||||
 | 
					      allowLocationSimulation = "YES">
 | 
				
			||||||
 | 
					      <PathRunnable
 | 
				
			||||||
 | 
					         runnableDebuggingMode = "0"
 | 
				
			||||||
 | 
					         FilePath = "/Applications/JUCE/extras/AudioPluginHost/Builds/MacOSX/build/Debug/AudioPluginHost.app">
 | 
				
			||||||
 | 
					      </PathRunnable>
 | 
				
			||||||
 | 
					      <MacroExpansion>
 | 
				
			||||||
 | 
					         <BuildableReference
 | 
				
			||||||
 | 
					            BuildableIdentifier = "primary"
 | 
				
			||||||
 | 
					            BlueprintIdentifier = "345302E3C02BBFCDACE98BE7"
 | 
				
			||||||
 | 
					            BuildableName = "juicysfplugin.component"
 | 
				
			||||||
 | 
					            BlueprintName = "juicysfplugin - AU"
 | 
				
			||||||
 | 
					            ReferencedContainer = "container:juicysfplugin.xcodeproj">
 | 
				
			||||||
 | 
					         </BuildableReference>
 | 
				
			||||||
 | 
					      </MacroExpansion>
 | 
				
			||||||
 | 
					      <AdditionalOptions>
 | 
				
			||||||
 | 
					      </AdditionalOptions>
 | 
				
			||||||
 | 
					   </LaunchAction>
 | 
				
			||||||
 | 
					   <ProfileAction
 | 
				
			||||||
 | 
					      buildConfiguration = "Release"
 | 
				
			||||||
 | 
					      shouldUseLaunchSchemeArgsEnv = "YES"
 | 
				
			||||||
 | 
					      savedToolIdentifier = ""
 | 
				
			||||||
 | 
					      useCustomWorkingDirectory = "NO"
 | 
				
			||||||
 | 
					      debugDocumentVersioning = "YES">
 | 
				
			||||||
 | 
					      <MacroExpansion>
 | 
				
			||||||
 | 
					         <BuildableReference
 | 
				
			||||||
 | 
					            BuildableIdentifier = "primary"
 | 
				
			||||||
 | 
					            BlueprintIdentifier = "345302E3C02BBFCDACE98BE7"
 | 
				
			||||||
 | 
					            BuildableName = "juicysfplugin.component"
 | 
				
			||||||
 | 
					            BlueprintName = "juicysfplugin - AU"
 | 
				
			||||||
 | 
					            ReferencedContainer = "container:juicysfplugin.xcodeproj">
 | 
				
			||||||
 | 
					         </BuildableReference>
 | 
				
			||||||
 | 
					      </MacroExpansion>
 | 
				
			||||||
 | 
					   </ProfileAction>
 | 
				
			||||||
 | 
					   <AnalyzeAction
 | 
				
			||||||
 | 
					      buildConfiguration = "Debug">
 | 
				
			||||||
 | 
					   </AnalyzeAction>
 | 
				
			||||||
 | 
					   <ArchiveAction
 | 
				
			||||||
 | 
					      buildConfiguration = "Release"
 | 
				
			||||||
 | 
					      revealArchiveInOrganizer = "YES">
 | 
				
			||||||
 | 
					   </ArchiveAction>
 | 
				
			||||||
 | 
					</Scheme>
 | 
				
			||||||
@ -33,7 +33,7 @@
 | 
				
			|||||||
      </AdditionalOptions>
 | 
					      </AdditionalOptions>
 | 
				
			||||||
   </TestAction>
 | 
					   </TestAction>
 | 
				
			||||||
   <LaunchAction
 | 
					   <LaunchAction
 | 
				
			||||||
      buildConfiguration = "Release"
 | 
					      buildConfiguration = "Debug"
 | 
				
			||||||
      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
 | 
					      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
 | 
				
			||||||
      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
 | 
					      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
 | 
				
			||||||
      launchStyle = "0"
 | 
					      launchStyle = "0"
 | 
				
			||||||
 | 
				
			|||||||
@ -51,8 +51,11 @@
 | 
				
			|||||||
      debugDocumentVersioning = "YES"
 | 
					      debugDocumentVersioning = "YES"
 | 
				
			||||||
      debugServiceExtension = "internal"
 | 
					      debugServiceExtension = "internal"
 | 
				
			||||||
      allowLocationSimulation = "YES">
 | 
					      allowLocationSimulation = "YES">
 | 
				
			||||||
      <BuildableProductRunnable
 | 
					      <PathRunnable
 | 
				
			||||||
         runnableDebuggingMode = "0">
 | 
					         runnableDebuggingMode = "0"
 | 
				
			||||||
 | 
					         FilePath = "/Applications/JUCE/extras/AudioPluginHost/Builds/MacOSX/build/Debug/AudioPluginHost.app">
 | 
				
			||||||
 | 
					      </PathRunnable>
 | 
				
			||||||
 | 
					      <MacroExpansion>
 | 
				
			||||||
         <BuildableReference
 | 
					         <BuildableReference
 | 
				
			||||||
            BuildableIdentifier = "primary"
 | 
					            BuildableIdentifier = "primary"
 | 
				
			||||||
            BlueprintIdentifier = "24C399ED93EC47D5BEB26F76"
 | 
					            BlueprintIdentifier = "24C399ED93EC47D5BEB26F76"
 | 
				
			||||||
@ -60,7 +63,7 @@
 | 
				
			|||||||
            BlueprintName = "juicysfplugin - Standalone Plugin"
 | 
					            BlueprintName = "juicysfplugin - Standalone Plugin"
 | 
				
			||||||
            ReferencedContainer = "container:juicysfplugin.xcodeproj">
 | 
					            ReferencedContainer = "container:juicysfplugin.xcodeproj">
 | 
				
			||||||
         </BuildableReference>
 | 
					         </BuildableReference>
 | 
				
			||||||
      </BuildableProductRunnable>
 | 
					      </MacroExpansion>
 | 
				
			||||||
      <AdditionalOptions>
 | 
					      <AdditionalOptions>
 | 
				
			||||||
      </AdditionalOptions>
 | 
					      </AdditionalOptions>
 | 
				
			||||||
   </LaunchAction>
 | 
					   </LaunchAction>
 | 
				
			||||||
 | 
				
			|||||||
@ -0,0 +1,84 @@
 | 
				
			|||||||
 | 
					<?xml version="1.0" encoding="UTF-8"?>
 | 
				
			||||||
 | 
					<Scheme
 | 
				
			||||||
 | 
					   LastUpgradeVersion = "1020"
 | 
				
			||||||
 | 
					   version = "1.3">
 | 
				
			||||||
 | 
					   <BuildAction
 | 
				
			||||||
 | 
					      parallelizeBuildables = "YES"
 | 
				
			||||||
 | 
					      buildImplicitDependencies = "YES">
 | 
				
			||||||
 | 
					      <BuildActionEntries>
 | 
				
			||||||
 | 
					         <BuildActionEntry
 | 
				
			||||||
 | 
					            buildForTesting = "YES"
 | 
				
			||||||
 | 
					            buildForRunning = "YES"
 | 
				
			||||||
 | 
					            buildForProfiling = "YES"
 | 
				
			||||||
 | 
					            buildForArchiving = "YES"
 | 
				
			||||||
 | 
					            buildForAnalyzing = "YES">
 | 
				
			||||||
 | 
					            <BuildableReference
 | 
				
			||||||
 | 
					               BuildableIdentifier = "primary"
 | 
				
			||||||
 | 
					               BlueprintIdentifier = "48A570326AA09CE818BE1901"
 | 
				
			||||||
 | 
					               BuildableName = "juicysfplugin.vst"
 | 
				
			||||||
 | 
					               BlueprintName = "juicysfplugin - VST"
 | 
				
			||||||
 | 
					               ReferencedContainer = "container:juicysfplugin.xcodeproj">
 | 
				
			||||||
 | 
					            </BuildableReference>
 | 
				
			||||||
 | 
					         </BuildActionEntry>
 | 
				
			||||||
 | 
					      </BuildActionEntries>
 | 
				
			||||||
 | 
					   </BuildAction>
 | 
				
			||||||
 | 
					   <TestAction
 | 
				
			||||||
 | 
					      buildConfiguration = "Debug"
 | 
				
			||||||
 | 
					      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
 | 
				
			||||||
 | 
					      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
 | 
				
			||||||
 | 
					      shouldUseLaunchSchemeArgsEnv = "YES">
 | 
				
			||||||
 | 
					      <Testables>
 | 
				
			||||||
 | 
					      </Testables>
 | 
				
			||||||
 | 
					      <AdditionalOptions>
 | 
				
			||||||
 | 
					      </AdditionalOptions>
 | 
				
			||||||
 | 
					   </TestAction>
 | 
				
			||||||
 | 
					   <LaunchAction
 | 
				
			||||||
 | 
					      buildConfiguration = "Debug"
 | 
				
			||||||
 | 
					      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
 | 
				
			||||||
 | 
					      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
 | 
				
			||||||
 | 
					      launchStyle = "0"
 | 
				
			||||||
 | 
					      useCustomWorkingDirectory = "NO"
 | 
				
			||||||
 | 
					      ignoresPersistentStateOnLaunch = "NO"
 | 
				
			||||||
 | 
					      debugDocumentVersioning = "YES"
 | 
				
			||||||
 | 
					      debugServiceExtension = "internal"
 | 
				
			||||||
 | 
					      allowLocationSimulation = "YES">
 | 
				
			||||||
 | 
					      <PathRunnable
 | 
				
			||||||
 | 
					         runnableDebuggingMode = "0"
 | 
				
			||||||
 | 
					         FilePath = "/Applications/JUCE/extras/AudioPluginHost/Builds/MacOSX/build/Debug/AudioPluginHost.app">
 | 
				
			||||||
 | 
					      </PathRunnable>
 | 
				
			||||||
 | 
					      <MacroExpansion>
 | 
				
			||||||
 | 
					         <BuildableReference
 | 
				
			||||||
 | 
					            BuildableIdentifier = "primary"
 | 
				
			||||||
 | 
					            BlueprintIdentifier = "48A570326AA09CE818BE1901"
 | 
				
			||||||
 | 
					            BuildableName = "juicysfplugin.vst"
 | 
				
			||||||
 | 
					            BlueprintName = "juicysfplugin - VST"
 | 
				
			||||||
 | 
					            ReferencedContainer = "container:juicysfplugin.xcodeproj">
 | 
				
			||||||
 | 
					         </BuildableReference>
 | 
				
			||||||
 | 
					      </MacroExpansion>
 | 
				
			||||||
 | 
					      <AdditionalOptions>
 | 
				
			||||||
 | 
					      </AdditionalOptions>
 | 
				
			||||||
 | 
					   </LaunchAction>
 | 
				
			||||||
 | 
					   <ProfileAction
 | 
				
			||||||
 | 
					      buildConfiguration = "Release"
 | 
				
			||||||
 | 
					      shouldUseLaunchSchemeArgsEnv = "YES"
 | 
				
			||||||
 | 
					      savedToolIdentifier = ""
 | 
				
			||||||
 | 
					      useCustomWorkingDirectory = "NO"
 | 
				
			||||||
 | 
					      debugDocumentVersioning = "YES">
 | 
				
			||||||
 | 
					      <MacroExpansion>
 | 
				
			||||||
 | 
					         <BuildableReference
 | 
				
			||||||
 | 
					            BuildableIdentifier = "primary"
 | 
				
			||||||
 | 
					            BlueprintIdentifier = "48A570326AA09CE818BE1901"
 | 
				
			||||||
 | 
					            BuildableName = "juicysfplugin.vst"
 | 
				
			||||||
 | 
					            BlueprintName = "juicysfplugin - VST"
 | 
				
			||||||
 | 
					            ReferencedContainer = "container:juicysfplugin.xcodeproj">
 | 
				
			||||||
 | 
					         </BuildableReference>
 | 
				
			||||||
 | 
					      </MacroExpansion>
 | 
				
			||||||
 | 
					   </ProfileAction>
 | 
				
			||||||
 | 
					   <AnalyzeAction
 | 
				
			||||||
 | 
					      buildConfiguration = "Debug">
 | 
				
			||||||
 | 
					   </AnalyzeAction>
 | 
				
			||||||
 | 
					   <ArchiveAction
 | 
				
			||||||
 | 
					      buildConfiguration = "Release"
 | 
				
			||||||
 | 
					      revealArchiveInOrganizer = "YES">
 | 
				
			||||||
 | 
					   </ArchiveAction>
 | 
				
			||||||
 | 
					</Scheme>
 | 
				
			||||||
@ -42,6 +42,11 @@
 | 
				
			|||||||
      debugDocumentVersioning = "YES"
 | 
					      debugDocumentVersioning = "YES"
 | 
				
			||||||
      debugServiceExtension = "internal"
 | 
					      debugServiceExtension = "internal"
 | 
				
			||||||
      allowLocationSimulation = "YES">
 | 
					      allowLocationSimulation = "YES">
 | 
				
			||||||
 | 
					      <PathRunnable
 | 
				
			||||||
 | 
					         runnableDebuggingMode = "0"
 | 
				
			||||||
 | 
					         BundleIdentifier = "com.roli.juce.pluginhost"
 | 
				
			||||||
 | 
					         FilePath = "/Applications/JUCE/extras/AudioPluginHost/Builds/MacOSX/build/Debug/AudioPluginHost.app">
 | 
				
			||||||
 | 
					      </PathRunnable>
 | 
				
			||||||
      <MacroExpansion>
 | 
					      <MacroExpansion>
 | 
				
			||||||
         <BuildableReference
 | 
					         <BuildableReference
 | 
				
			||||||
            BuildableIdentifier = "primary"
 | 
					            BuildableIdentifier = "primary"
 | 
				
			||||||
 | 
				
			|||||||
@ -1,19 +0,0 @@
 | 
				
			|||||||
//
 | 
					 | 
				
			||||||
// Created by Alex Birch on 13/04/2018.
 | 
					 | 
				
			||||||
// Copyright (c) 2018 Birchlabs. All rights reserved.
 | 
					 | 
				
			||||||
//
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#include "BankAndPreset.h"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
BankAndPreset::BankAndPreset(int bank, int preset)
 | 
					 | 
				
			||||||
        : bank(bank),
 | 
					 | 
				
			||||||
          preset(preset)
 | 
					 | 
				
			||||||
{}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
int BankAndPreset::getBank() {
 | 
					 | 
				
			||||||
    return bank;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
int BankAndPreset::getPreset() {
 | 
					 | 
				
			||||||
    return preset;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@ -1,23 +0,0 @@
 | 
				
			|||||||
//
 | 
					 | 
				
			||||||
// Created by Alex Birch on 13/04/2018.
 | 
					 | 
				
			||||||
// Copyright (c) 2018 Birchlabs. All rights reserved.
 | 
					 | 
				
			||||||
//
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#pragma once
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#include "../JuceLibraryCode/JuceHeader.h"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class BankAndPreset {
 | 
					 | 
				
			||||||
public:
 | 
					 | 
				
			||||||
    BankAndPreset(int bank, int preset);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    int getBank();
 | 
					 | 
				
			||||||
    int getPreset();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
private:
 | 
					 | 
				
			||||||
    int bank;
 | 
					 | 
				
			||||||
    int preset;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (BankAndPreset)
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@ -1,16 +0,0 @@
 | 
				
			|||||||
//
 | 
					 | 
				
			||||||
// Created by Alex Birch on 11/04/2018.
 | 
					 | 
				
			||||||
// Copyright (c) 2018 Birchlabs. All rights reserved.
 | 
					 | 
				
			||||||
//
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#pragma once
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#include "FilePickerFragment.h"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class ExposesComponents {
 | 
					 | 
				
			||||||
public:
 | 
					 | 
				
			||||||
    virtual ~ExposesComponents() {}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    virtual FilePickerFragment& getFilePicker() = 0;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
@ -4,11 +4,13 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
#include "FilePicker.h"
 | 
					#include "FilePicker.h"
 | 
				
			||||||
#include "MyColours.h"
 | 
					#include "MyColours.h"
 | 
				
			||||||
 | 
					#include "Util.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
FilePicker::FilePicker(
 | 
					FilePicker::FilePicker(
 | 
				
			||||||
        FluidSynthModel* fluidSynthModel
 | 
					    AudioProcessorValueTreeState& valueTreeState
 | 
				
			||||||
 | 
					    // FluidSynthModel& fluidSynthModel
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
: fileChooser(
 | 
					: fileChooser{
 | 
				
			||||||
    "File",
 | 
					    "File",
 | 
				
			||||||
    File(),
 | 
					    File(),
 | 
				
			||||||
    true,
 | 
					    true,
 | 
				
			||||||
@ -16,20 +18,25 @@ FilePicker::FilePicker(
 | 
				
			|||||||
    false,
 | 
					    false,
 | 
				
			||||||
    "*.sf2;*.sf3",
 | 
					    "*.sf2;*.sf3",
 | 
				
			||||||
    String(),
 | 
					    String(),
 | 
				
			||||||
        "Choose a Soundfont file to load into the synthesizer"
 | 
					    "Choose a Soundfont file to load into the synthesizer"}
 | 
				
			||||||
),
 | 
					, valueTreeState{valueTreeState}
 | 
				
			||||||
  fluidSynthModel(fluidSynthModel),
 | 
					// , fluidSynthModel{fluidSynthModel}
 | 
				
			||||||
  currentPath() {
 | 
					// , currentPath{}
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
    // faster (rounded edges introduce transparency)
 | 
					    // faster (rounded edges introduce transparency)
 | 
				
			||||||
    setOpaque (true);
 | 
					    setOpaque (true);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    setDisplayedFilePath(fluidSynthModel->getCurrentSoundFontAbsPath());
 | 
					    // setDisplayedFilePath(fluidSynthModel.getCurrentSoundFontAbsPath());
 | 
				
			||||||
 | 
					    setDisplayedFilePath(valueTreeState.state.getChildWithName("soundFont").getProperty("path", ""));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    addAndMakeVisible (fileChooser);
 | 
					    addAndMakeVisible (fileChooser);
 | 
				
			||||||
    fileChooser.addListener (this);
 | 
					    fileChooser.addListener (this);
 | 
				
			||||||
 | 
					    valueTreeState.state.addListener(this);
 | 
				
			||||||
 | 
					//    valueTreeState.state.getChildWithName("soundFont").sendPropertyChangeMessage("path");
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
FilePicker::~FilePicker() {
 | 
					FilePicker::~FilePicker() {
 | 
				
			||||||
    fileChooser.removeListener (this);
 | 
					    fileChooser.removeListener (this);
 | 
				
			||||||
 | 
					    valueTreeState.state.removeListener(this);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void FilePicker::resized() {
 | 
					void FilePicker::resized() {
 | 
				
			||||||
@ -46,15 +53,33 @@ void FilePicker::paint(Graphics& g)
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void FilePicker::filenameComponentChanged (FilenameComponent*) {
 | 
					void FilePicker::filenameComponentChanged (FilenameComponent*) {
 | 
				
			||||||
    currentPath = fileChooser.getCurrentFile().getFullPathName();
 | 
					    // currentPath = fileChooser.getCurrentFile().getFullPathName();
 | 
				
			||||||
    fluidSynthModel->onFileNameChanged(fileChooser.getCurrentFile().getFullPathName(), -1, -1);
 | 
					    // fluidSynthModel.onFileNameChanged(fileChooser.getCurrentFile().getFullPathName(), -1, -1);
 | 
				
			||||||
 | 
					    Value value{valueTreeState.state.getChildWithName("soundFont").getPropertyAsValue("path", nullptr)};
 | 
				
			||||||
 | 
					    value.setValue(fileChooser.getCurrentFile().getFullPathName());
 | 
				
			||||||
 | 
					//    value = fileChooser.getCurrentFile().getFullPathName();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void FilePicker::valueTreePropertyChanged(ValueTree& treeWhosePropertyHasChanged,
 | 
				
			||||||
 | 
					                                               const Identifier& property) {
 | 
				
			||||||
 | 
					    if (treeWhosePropertyHasChanged.getType() == StringRef("soundFont")) {
 | 
				
			||||||
 | 
					    // if (&treeWhosePropertyHasChanged == &valueTree) {
 | 
				
			||||||
 | 
					        if (property == StringRef("path")) {
 | 
				
			||||||
 | 
					            String soundFontPath{treeWhosePropertyHasChanged.getProperty("path", "")};
 | 
				
			||||||
 | 
					            DEBUG_PRINT(soundFontPath);
 | 
				
			||||||
 | 
					            setDisplayedFilePath(soundFontPath);
 | 
				
			||||||
 | 
					            // if (soundFontPath.isNotEmpty()) {
 | 
				
			||||||
 | 
					            //     loadFont(soundFontPath);
 | 
				
			||||||
 | 
					            // }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void FilePicker::setDisplayedFilePath(const String& path) {
 | 
					void FilePicker::setDisplayedFilePath(const String& path) {
 | 
				
			||||||
     if (!shouldChangeDisplayedFilePath(path)) {
 | 
					     if (!shouldChangeDisplayedFilePath(path)) {
 | 
				
			||||||
         return;
 | 
					         return;
 | 
				
			||||||
     }
 | 
					     }
 | 
				
			||||||
    currentPath = path;
 | 
					    // currentPath = path;
 | 
				
			||||||
    fileChooser.setCurrentFile(File(path), true, dontSendNotification);
 | 
					    fileChooser.setCurrentFile(File(path), true, dontSendNotification);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -6,26 +6,40 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
#include "../JuceLibraryCode/JuceHeader.h"
 | 
					#include "../JuceLibraryCode/JuceHeader.h"
 | 
				
			||||||
#include "FluidSynthModel.h"
 | 
					#include "FluidSynthModel.h"
 | 
				
			||||||
#include "FilePickerFragment.h"
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
class FilePicker: public Component,
 | 
					class FilePicker: public Component,
 | 
				
			||||||
                  public FilePickerFragment,
 | 
					                  public ValueTree::Listener,
 | 
				
			||||||
                  private FilenameComponentListener
 | 
					                  private FilenameComponentListener
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
public:
 | 
					public:
 | 
				
			||||||
    FilePicker(
 | 
					    FilePicker(
 | 
				
			||||||
            FluidSynthModel* fluidSynthModel
 | 
					        AudioProcessorValueTreeState& valueTreeState
 | 
				
			||||||
 | 
					        // FluidSynthModel& fluidSynthModel
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
    ~FilePicker();
 | 
					    ~FilePicker();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    void resized() override;
 | 
					    void resized() override;
 | 
				
			||||||
    void paint (Graphics& g) override;
 | 
					    void paint (Graphics& g) override;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    virtual void setDisplayedFilePath(const String&) override;
 | 
					    void setDisplayedFilePath(const String&);
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    virtual void valueTreePropertyChanged (ValueTree& treeWhosePropertyHasChanged,
 | 
				
			||||||
 | 
					                                           const Identifier& property) override;
 | 
				
			||||||
 | 
					    inline virtual void valueTreeChildAdded (ValueTree& parentTree,
 | 
				
			||||||
 | 
					                                             ValueTree& childWhichHasBeenAdded) override {};
 | 
				
			||||||
 | 
					    inline virtual void valueTreeChildRemoved (ValueTree& parentTree,
 | 
				
			||||||
 | 
					                                               ValueTree& childWhichHasBeenRemoved,
 | 
				
			||||||
 | 
					                                               int indexFromWhichChildWasRemoved) override {};
 | 
				
			||||||
 | 
					    inline virtual void valueTreeChildOrderChanged (ValueTree& parentTreeWhoseChildrenHaveMoved,
 | 
				
			||||||
 | 
					                                                    int oldIndex, int newIndex) override {};
 | 
				
			||||||
 | 
					    inline virtual void valueTreeParentChanged (ValueTree& treeWhoseParentHasChanged) override {};
 | 
				
			||||||
 | 
					    inline virtual void valueTreeRedirected (ValueTree& treeWhichHasBeenChanged) override {};
 | 
				
			||||||
private:
 | 
					private:
 | 
				
			||||||
    FilenameComponent fileChooser;
 | 
					    FilenameComponent fileChooser;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    FluidSynthModel* fluidSynthModel;
 | 
					    AudioProcessorValueTreeState& valueTreeState;
 | 
				
			||||||
 | 
					    // FluidSynthModel& fluidSynthModel;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    String currentPath;
 | 
					    String currentPath;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -1,14 +0,0 @@
 | 
				
			|||||||
//
 | 
					 | 
				
			||||||
// Created by Alex Birch on 11/04/2018.
 | 
					 | 
				
			||||||
// Copyright (c) 2018 Birchlabs. All rights reserved.
 | 
					 | 
				
			||||||
//
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#pragma once
 | 
					 | 
				
			||||||
#include "../JuceLibraryCode/JuceHeader.h"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class FilePickerFragment {
 | 
					 | 
				
			||||||
public:
 | 
					 | 
				
			||||||
    virtual ~FilePickerFragment() {}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    virtual void setDisplayedFilePath(const String&) = 0;
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
@ -3,329 +3,468 @@
 | 
				
			|||||||
//
 | 
					//
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include <iostream>
 | 
					#include <iostream>
 | 
				
			||||||
 | 
					#include <iterator>
 | 
				
			||||||
 | 
					#include <fluidsynth.h>
 | 
				
			||||||
#include "FluidSynthModel.h"
 | 
					#include "FluidSynthModel.h"
 | 
				
			||||||
#include "MidiConstants.h"
 | 
					#include "MidiConstants.h"
 | 
				
			||||||
 | 
					#include "Util.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
using namespace std;
 | 
					using namespace std;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
FluidSynthModel::FluidSynthModel(SharesParams& p)
 | 
					const map<fluid_midi_control_change, String> FluidSynthModel::controllerToParam{
 | 
				
			||||||
        : sharesParams(p),
 | 
					    {SOUND_CTRL2, "filterResonance"}, // MIDI CC 71 Timbre/Harmonic Intensity (filter resonance)
 | 
				
			||||||
          synth(nullptr),
 | 
					    {SOUND_CTRL3, "release"}, // MIDI CC 72 Release time
 | 
				
			||||||
          settings(nullptr),
 | 
					    {SOUND_CTRL4, "attack"}, // MIDI CC 73 Attack time
 | 
				
			||||||
          currentSoundFontAbsPath(),
 | 
					    {SOUND_CTRL5, "filterCutOff"}, // MIDI CC 74 Brightness (cutoff frequency, FILTERFC)
 | 
				
			||||||
          currentSampleRate(44100),
 | 
					    {SOUND_CTRL6, "decay"}, // MIDI CC 75 Decay Time
 | 
				
			||||||
          initialised(false),
 | 
					    {SOUND_CTRL10, "sustain"}}; // MIDI CC 79 undefined
 | 
				
			||||||
          sfont_id(0),
 | 
					 | 
				
			||||||
          channel(0)/*,
 | 
					 | 
				
			||||||
          mod(nullptr)*/
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
{}
 | 
					const map<String, fluid_midi_control_change> FluidSynthModel::paramToController{[]{
 | 
				
			||||||
 | 
					    map<String, fluid_midi_control_change> map;
 | 
				
			||||||
 | 
					    transform(
 | 
				
			||||||
 | 
					        controllerToParam.begin(),
 | 
				
			||||||
 | 
					        controllerToParam.end(),
 | 
				
			||||||
 | 
					        inserter(map, map.begin()),
 | 
				
			||||||
 | 
					        [](const pair<fluid_midi_control_change, String>& pair) {
 | 
				
			||||||
 | 
					            return make_pair(pair.second, pair.first);
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    return map;
 | 
				
			||||||
 | 
					}()};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					FluidSynthModel::FluidSynthModel(
 | 
				
			||||||
 | 
					    AudioProcessorValueTreeState& valueTreeState
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					: valueTreeState{valueTreeState}
 | 
				
			||||||
 | 
					, settings{nullptr, nullptr}
 | 
				
			||||||
 | 
					, synth{nullptr, nullptr}
 | 
				
			||||||
 | 
					, currentSampleRate{44100}
 | 
				
			||||||
 | 
					, sfont_id{-1}
 | 
				
			||||||
 | 
					, channel{0}
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    valueTreeState.addParameterListener("bank", this);
 | 
				
			||||||
 | 
					    valueTreeState.addParameterListener("preset", this);
 | 
				
			||||||
 | 
					    for (const auto &[param, controller]: paramToController) {
 | 
				
			||||||
 | 
					        valueTreeState.addParameterListener(param, this);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    valueTreeState.state.addListener(this);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
FluidSynthModel::~FluidSynthModel() {
 | 
					FluidSynthModel::~FluidSynthModel() {
 | 
				
			||||||
    if (initialised) {
 | 
					    for (const auto &[param, controller]: paramToController) {
 | 
				
			||||||
//        delete_fluid_audio_driver(driver);
 | 
					        valueTreeState.removeParameterListener(param, this);
 | 
				
			||||||
        delete_fluid_synth(synth);
 | 
					 | 
				
			||||||
        delete_fluid_settings(settings);
 | 
					 | 
				
			||||||
//        delete driver;
 | 
					 | 
				
			||||||
//        delete settings;
 | 
					 | 
				
			||||||
//        delete_fluid_mod(mod);
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					    valueTreeState.removeParameterListener("bank", this);
 | 
				
			||||||
 | 
					    valueTreeState.removeParameterListener("preset", this);
 | 
				
			||||||
 | 
					    valueTreeState.state.removeListener(this);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void FluidSynthModel::initialise() {
 | 
					void FluidSynthModel::initialise() {
 | 
				
			||||||
//    if (initialised) {
 | 
					    settings = { new_fluid_settings(), delete_fluid_settings };
 | 
				
			||||||
//        delete_fluid_synth(synth);
 | 
					
 | 
				
			||||||
//        delete_fluid_settings(settings);
 | 
					 | 
				
			||||||
//    }
 | 
					 | 
				
			||||||
    // deactivate all audio drivers in fluidsynth to avoid FL Studio deadlock when initialising CoreAudio
 | 
					    // deactivate all audio drivers in fluidsynth to avoid FL Studio deadlock when initialising CoreAudio
 | 
				
			||||||
    // after all: we only use fluidsynth to render blocks of audio. it doesn't output to audio driver.
 | 
					    // after all: we only use fluidsynth to render blocks of audio. it doesn't output to audio driver.
 | 
				
			||||||
    const char *DRV[] = {NULL};
 | 
					    const char *DRV[] {NULL};
 | 
				
			||||||
    fluid_audio_driver_register(DRV);
 | 
					    fluid_audio_driver_register(DRV);
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    settings = new_fluid_settings();
 | 
					 | 
				
			||||||
    // https://sourceforge.net/p/fluidsynth/wiki/FluidSettings/
 | 
					    // https://sourceforge.net/p/fluidsynth/wiki/FluidSettings/
 | 
				
			||||||
#if JUCE_DEBUG
 | 
					#if JUCE_DEBUG
 | 
				
			||||||
    fluid_settings_setint(settings, "synth.verbose", 1);
 | 
					    fluid_settings_setint(settings.get(), "synth.verbose", 1);
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    synth = new_fluid_synth(settings);
 | 
					    synth = { new_fluid_synth(settings.get()), delete_fluid_synth };
 | 
				
			||||||
    fluid_synth_set_sample_rate(synth, currentSampleRate);
 | 
					    fluid_synth_set_sample_rate(synth.get(), currentSampleRate);
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    if (sharesParams.getSoundFontPath().isNotEmpty()) {
 | 
					    ValueTree soundFont{valueTreeState.state.getChildWithName("soundFont")};
 | 
				
			||||||
        loadFont(sharesParams.getSoundFontPath());
 | 
					    String path{soundFont.getProperty("path", "")};
 | 
				
			||||||
        changePreset(sharesParams.getBank(), sharesParams.getPreset());
 | 
					    loadFont(path);
 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fluid_synth_set_gain(synth, 2.0);
 | 
					    // I can't hear a damned thing
 | 
				
			||||||
    
 | 
					    fluid_synth_set_gain(synth.get(), 2.0);
 | 
				
			||||||
    for(int i{SOUND_CTRL1}; i <= SOUND_CTRL10; i++)
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        setControllerValue(i, 0);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
//    fluid_synth_bank_select(synth, 0, 3);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
//    fluid_handle_inst
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
//    driver = new_fluid_audio_driver(settings, synth);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
//    changePreset(128, 13);
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
//    float env_amount(12000.0f);
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
//     http://www.synthfont.com/SoundFont_NRPNs.PDF
 | 
					 | 
				
			||||||
    float env_amount(20000.0f);
 | 
					 | 
				
			||||||
//    float env_amount(24000.0f);
 | 
					 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    // note: fluid_chan.c#fluid_channel_init_ctrl()
 | 
					    // note: fluid_chan.c#fluid_channel_init_ctrl()
 | 
				
			||||||
    // all SOUND_CTRL are inited with value of 64, not zero.
 | 
					    // > Just like panning, a value of 64 indicates no change for sound ctrls
 | 
				
			||||||
    // "Just like panning, a value of 64 indicates no change for sound ctrls"
 | 
					    // --
 | 
				
			||||||
 | 
					    // so, advice is to leave everything at 64
 | 
				
			||||||
 | 
					    // and yet, I'm finding that default modulators start at MIN,
 | 
				
			||||||
 | 
					    // i.e. we are forced to start at 0 and climb from there
 | 
				
			||||||
 | 
					    // --
 | 
				
			||||||
 | 
					    // let's loop through all audio params that we manage,
 | 
				
			||||||
 | 
					    // restore them to whatever value we have stored for them
 | 
				
			||||||
 | 
					    // (which by default would be 0)
 | 
				
			||||||
 | 
					    // super likely to be 0 regardless, since JuicySFAudioProcessor::initialise()
 | 
				
			||||||
 | 
					    // runs earlier than JuicySFAudioProcessor::setStateInformation()
 | 
				
			||||||
 | 
					    for (const auto &[controller, parameterID]: controllerToParam) {
 | 
				
			||||||
 | 
					        RangedAudioParameter *param{valueTreeState.getParameter(parameterID)};
 | 
				
			||||||
 | 
					        jassert(dynamic_cast<AudioParameterInt*>(param) != nullptr);
 | 
				
			||||||
 | 
					        AudioParameterInt* castParam{dynamic_cast<AudioParameterInt*>(param)};
 | 
				
			||||||
 | 
					        int value{castParam->get()};
 | 
				
			||||||
 | 
					        setControllerValue(static_cast<int>(controller), value);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    fluid_mod_t *mod(new_fluid_mod());
 | 
					    // http://www.synthfont.com/SoundFont_NRPNs.PDF
 | 
				
			||||||
//
 | 
					    float env_amount{20000.0f};
 | 
				
			||||||
    fluid_mod_set_source1(mod,
 | 
					    
 | 
				
			||||||
 | 
					    unique_ptr<fluid_mod_t, decltype(&delete_fluid_mod)> mod{new_fluid_mod(), delete_fluid_mod};
 | 
				
			||||||
 | 
					    fluid_mod_set_source1(mod.get(),
 | 
				
			||||||
                          static_cast<int>(SOUND_CTRL2), // MIDI CC 71 Timbre/Harmonic Intensity (filter resonance)
 | 
					                          static_cast<int>(SOUND_CTRL2), // MIDI CC 71 Timbre/Harmonic Intensity (filter resonance)
 | 
				
			||||||
                          FLUID_MOD_CC
 | 
					                          FLUID_MOD_CC
 | 
				
			||||||
                          | FLUID_MOD_UNIPOLAR
 | 
					                          | FLUID_MOD_UNIPOLAR
 | 
				
			||||||
                          | FLUID_MOD_CONCAVE
 | 
					                          | FLUID_MOD_CONCAVE
 | 
				
			||||||
                          | FLUID_MOD_POSITIVE);
 | 
					                          | FLUID_MOD_POSITIVE);
 | 
				
			||||||
    fluid_mod_set_source2(mod, 0, 0);
 | 
					    fluid_mod_set_source2(mod.get(), 0, 0);
 | 
				
			||||||
    fluid_mod_set_dest(mod, GEN_FILTERQ);
 | 
					    fluid_mod_set_dest(mod.get(), GEN_FILTERQ);
 | 
				
			||||||
    fluid_mod_set_amount(mod, FLUID_PEAK_ATTENUATION);
 | 
					    fluid_mod_set_amount(mod.get(), FLUID_PEAK_ATTENUATION);
 | 
				
			||||||
    fluid_synth_add_default_mod(synth, mod, FLUID_SYNTH_ADD);
 | 
					    fluid_synth_add_default_mod(synth.get(), mod.get(), FLUID_SYNTH_ADD);
 | 
				
			||||||
    delete_fluid_mod(mod);
 | 
					 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    mod = new_fluid_mod();
 | 
					    mod = {new_fluid_mod(), delete_fluid_mod};
 | 
				
			||||||
    fluid_mod_set_source1(mod,
 | 
					    fluid_mod_set_source1(mod.get(),
 | 
				
			||||||
                          static_cast<int>(SOUND_CTRL3), // MIDI CC 72 Release time
 | 
					                          static_cast<int>(SOUND_CTRL3), // MIDI CC 72 Release time
 | 
				
			||||||
                          FLUID_MOD_CC
 | 
					                          FLUID_MOD_CC
 | 
				
			||||||
                          | FLUID_MOD_UNIPOLAR
 | 
					                          | FLUID_MOD_UNIPOLAR
 | 
				
			||||||
                          | FLUID_MOD_LINEAR
 | 
					                          | FLUID_MOD_LINEAR
 | 
				
			||||||
                          | FLUID_MOD_POSITIVE);
 | 
					                          | FLUID_MOD_POSITIVE);
 | 
				
			||||||
    fluid_mod_set_source2(mod, 0, 0);
 | 
					    fluid_mod_set_source2(mod.get(), 0, 0);
 | 
				
			||||||
    fluid_mod_set_dest(mod, GEN_VOLENVRELEASE);
 | 
					    fluid_mod_set_dest(mod.get(), GEN_VOLENVRELEASE);
 | 
				
			||||||
//    fluid_mod_set_amount(mod, 15200.0f);
 | 
					    fluid_mod_set_amount(mod.get(), env_amount);
 | 
				
			||||||
    fluid_mod_set_amount(mod, env_amount);
 | 
					    fluid_synth_add_default_mod(synth.get(), mod.get(), FLUID_SYNTH_ADD);
 | 
				
			||||||
    fluid_synth_add_default_mod(synth, mod, FLUID_SYNTH_ADD);
 | 
					 | 
				
			||||||
    delete_fluid_mod(mod);
 | 
					 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    mod = new_fluid_mod();
 | 
					    mod = {new_fluid_mod(), delete_fluid_mod};
 | 
				
			||||||
    fluid_mod_set_source1(mod,
 | 
					    fluid_mod_set_source1(mod.get(),
 | 
				
			||||||
                          static_cast<int>(SOUND_CTRL4), // MIDI CC 73 Attack time
 | 
					                          static_cast<int>(SOUND_CTRL4), // MIDI CC 73 Attack time
 | 
				
			||||||
                          FLUID_MOD_CC
 | 
					                          FLUID_MOD_CC
 | 
				
			||||||
                          | FLUID_MOD_UNIPOLAR
 | 
					                          | FLUID_MOD_UNIPOLAR
 | 
				
			||||||
                          | FLUID_MOD_LINEAR
 | 
					                          | FLUID_MOD_LINEAR
 | 
				
			||||||
                          | FLUID_MOD_POSITIVE);
 | 
					                          | FLUID_MOD_POSITIVE);
 | 
				
			||||||
    fluid_mod_set_source2(mod, 0, 0);
 | 
					    fluid_mod_set_source2(mod.get(), 0, 0);
 | 
				
			||||||
    fluid_mod_set_dest(mod, GEN_VOLENVATTACK);
 | 
					    fluid_mod_set_dest(mod.get(), GEN_VOLENVATTACK);
 | 
				
			||||||
    fluid_mod_set_amount(mod, env_amount);
 | 
					    fluid_mod_set_amount(mod.get(), env_amount);
 | 
				
			||||||
    fluid_synth_add_default_mod(synth, mod, FLUID_SYNTH_ADD);
 | 
					    fluid_synth_add_default_mod(synth.get(), mod.get(), FLUID_SYNTH_ADD);
 | 
				
			||||||
    delete_fluid_mod(mod);
 | 
					 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    // soundfont spec says that if cutoff is >20kHz and resonance Q is 0, then no filtering occurs
 | 
					    // soundfont spec says that if cutoff is >20kHz and resonance Q is 0, then no filtering occurs
 | 
				
			||||||
    mod = new_fluid_mod();
 | 
					    mod = {new_fluid_mod(), delete_fluid_mod};
 | 
				
			||||||
    fluid_mod_set_source1(mod,
 | 
					    fluid_mod_set_source1(mod.get(),
 | 
				
			||||||
                          static_cast<int>(SOUND_CTRL5), // MIDI CC 74 Brightness (cutoff frequency, FILTERFC)
 | 
					                          static_cast<int>(SOUND_CTRL5), // MIDI CC 74 Brightness (cutoff frequency, FILTERFC)
 | 
				
			||||||
                          FLUID_MOD_CC
 | 
					                          FLUID_MOD_CC
 | 
				
			||||||
                          | FLUID_MOD_LINEAR
 | 
					                          | FLUID_MOD_LINEAR
 | 
				
			||||||
                          | FLUID_MOD_UNIPOLAR
 | 
					                          | FLUID_MOD_UNIPOLAR
 | 
				
			||||||
                          | FLUID_MOD_POSITIVE);
 | 
					                          | FLUID_MOD_POSITIVE);
 | 
				
			||||||
        fluid_mod_set_source2(mod, 0, 0);
 | 
					        fluid_mod_set_source2(mod.get(), 0, 0);
 | 
				
			||||||
    fluid_mod_set_dest(mod, GEN_FILTERFC);
 | 
					    fluid_mod_set_dest(mod.get(), GEN_FILTERFC);
 | 
				
			||||||
    fluid_mod_set_amount(mod, -2400.0f);
 | 
					    fluid_mod_set_amount(mod.get(), -2400.0f);
 | 
				
			||||||
    fluid_synth_add_default_mod(synth, mod, FLUID_SYNTH_ADD);
 | 
					    fluid_synth_add_default_mod(synth.get(), mod.get(), FLUID_SYNTH_ADD);
 | 
				
			||||||
    delete_fluid_mod(mod);
 | 
					 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    mod = new_fluid_mod();
 | 
					    mod = {new_fluid_mod(), delete_fluid_mod};
 | 
				
			||||||
    fluid_mod_set_source1(mod,
 | 
					    fluid_mod_set_source1(mod.get(),
 | 
				
			||||||
                          static_cast<int>(SOUND_CTRL6), // MIDI CC 75 Decay Time
 | 
					                          static_cast<int>(SOUND_CTRL6), // MIDI CC 75 Decay Time
 | 
				
			||||||
                          FLUID_MOD_CC
 | 
					                          FLUID_MOD_CC
 | 
				
			||||||
                          | FLUID_MOD_UNIPOLAR
 | 
					                          | FLUID_MOD_UNIPOLAR
 | 
				
			||||||
                          | FLUID_MOD_LINEAR
 | 
					                          | FLUID_MOD_LINEAR
 | 
				
			||||||
                          | FLUID_MOD_POSITIVE);
 | 
					                          | FLUID_MOD_POSITIVE);
 | 
				
			||||||
    fluid_mod_set_source2(mod, 0, 0);
 | 
					    fluid_mod_set_source2(mod.get(), 0, 0);
 | 
				
			||||||
    fluid_mod_set_dest(mod, GEN_VOLENVDECAY);
 | 
					    fluid_mod_set_dest(mod.get(), GEN_VOLENVDECAY);
 | 
				
			||||||
    fluid_mod_set_amount(mod, env_amount);
 | 
					    fluid_mod_set_amount(mod.get(), env_amount);
 | 
				
			||||||
    fluid_synth_add_default_mod(synth, mod, FLUID_SYNTH_ADD);
 | 
					    fluid_synth_add_default_mod(synth.get(), mod.get(), FLUID_SYNTH_ADD);
 | 
				
			||||||
    delete_fluid_mod(mod);
 | 
					 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    mod = new_fluid_mod();
 | 
					    mod = {new_fluid_mod(), delete_fluid_mod};
 | 
				
			||||||
    fluid_mod_set_source1(mod,
 | 
					    fluid_mod_set_source1(mod.get(),
 | 
				
			||||||
                          static_cast<int>(SOUND_CTRL10), // MIDI CC 79 undefined
 | 
					                          static_cast<int>(SOUND_CTRL10), // MIDI CC 79 undefined
 | 
				
			||||||
                          FLUID_MOD_CC
 | 
					                          FLUID_MOD_CC
 | 
				
			||||||
                          | FLUID_MOD_UNIPOLAR
 | 
					                          | FLUID_MOD_UNIPOLAR
 | 
				
			||||||
                          | FLUID_MOD_CONCAVE
 | 
					                          | FLUID_MOD_CONCAVE
 | 
				
			||||||
                          | FLUID_MOD_POSITIVE);
 | 
					                          | FLUID_MOD_POSITIVE);
 | 
				
			||||||
    fluid_mod_set_source2(mod, 0, 0);
 | 
					    fluid_mod_set_source2(mod.get(), 0, 0);
 | 
				
			||||||
    fluid_mod_set_dest(mod, GEN_VOLENVSUSTAIN);
 | 
					    fluid_mod_set_dest(mod.get(), GEN_VOLENVSUSTAIN);
 | 
				
			||||||
    // fluice_voice.c#fluid_voice_update_param()
 | 
					    // fluice_voice.c#fluid_voice_update_param()
 | 
				
			||||||
    // clamps the range to between 0 and 1000, so we'll copy that
 | 
					    // clamps the range to between 0 and 1000, so we'll copy that
 | 
				
			||||||
    fluid_mod_set_amount(mod, 1000.0f);
 | 
					    fluid_mod_set_amount(mod.get(), 1000.0f);
 | 
				
			||||||
    fluid_synth_add_default_mod(synth, mod, FLUID_SYNTH_ADD);
 | 
					    fluid_synth_add_default_mod(synth.get(), mod.get(), FLUID_SYNTH_ADD);
 | 
				
			||||||
    delete_fluid_mod(mod);
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    initialised = true;
 | 
					const StringArray FluidSynthModel::programChangeParams{"bank", "preset"};
 | 
				
			||||||
 | 
					void FluidSynthModel::parameterChanged(const String& parameterID, float newValue) {
 | 
				
			||||||
 | 
					    if (programChangeParams.contains(parameterID)) {
 | 
				
			||||||
 | 
					        int bank, preset;
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            RangedAudioParameter *param{valueTreeState.getParameter("bank")};
 | 
				
			||||||
 | 
					            jassert(dynamic_cast<AudioParameterInt*>(param) != nullptr);
 | 
				
			||||||
 | 
					            AudioParameterInt* castParam{dynamic_cast<AudioParameterInt*>(param)};
 | 
				
			||||||
 | 
					            bank = castParam->get();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            RangedAudioParameter *param{valueTreeState.getParameter("preset")};
 | 
				
			||||||
 | 
					            jassert(dynamic_cast<AudioParameterInt*>(param) != nullptr);
 | 
				
			||||||
 | 
					            AudioParameterInt* castParam{dynamic_cast<AudioParameterInt*>(param)};
 | 
				
			||||||
 | 
					            preset = castParam->get();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        int bankOffset{fluid_synth_get_bank_offset(synth.get(), sfont_id)};
 | 
				
			||||||
 | 
					        fluid_synth_program_select(
 | 
				
			||||||
 | 
					            synth.get(),
 | 
				
			||||||
 | 
					            channel,
 | 
				
			||||||
 | 
					            sfont_id,
 | 
				
			||||||
 | 
					            static_cast<unsigned int>(bankOffset + bank),
 | 
				
			||||||
 | 
					            static_cast<unsigned int>(preset));
 | 
				
			||||||
 | 
					    } else if (
 | 
				
			||||||
 | 
					        // https://stackoverflow.com/a/55482091/5257399
 | 
				
			||||||
 | 
					        auto it{paramToController.find(parameterID)};
 | 
				
			||||||
 | 
					        it != end(paramToController)) {
 | 
				
			||||||
 | 
					        RangedAudioParameter *param{valueTreeState.getParameter(parameterID)};
 | 
				
			||||||
 | 
					        jassert(dynamic_cast<AudioParameterInt*>(param) != nullptr);
 | 
				
			||||||
 | 
					        AudioParameterInt* castParam{dynamic_cast<AudioParameterInt*>(param)};
 | 
				
			||||||
 | 
					        int value{castParam->get()};
 | 
				
			||||||
 | 
					        int controllerNumber{static_cast<int>(it->second)};
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        fluid_synth_cc(
 | 
				
			||||||
 | 
					            synth.get(),
 | 
				
			||||||
 | 
					            channel,
 | 
				
			||||||
 | 
					            controllerNumber,
 | 
				
			||||||
 | 
					            value);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void FluidSynthModel::valueTreePropertyChanged(ValueTree& treeWhosePropertyHasChanged,
 | 
				
			||||||
 | 
					                                               const Identifier& property) {
 | 
				
			||||||
 | 
					    if (treeWhosePropertyHasChanged.getType() == StringRef("soundFont")) {
 | 
				
			||||||
 | 
					        if (property == StringRef("path")) {
 | 
				
			||||||
 | 
					            String soundFontPath{treeWhosePropertyHasChanged.getProperty("path", "")};
 | 
				
			||||||
 | 
					            if (soundFontPath.isNotEmpty()) {
 | 
				
			||||||
 | 
					                unloadAndLoadFont(soundFontPath);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void FluidSynthModel::setControllerValue(int controller, int value) {
 | 
					void FluidSynthModel::setControllerValue(int controller, int value) {
 | 
				
			||||||
    fluid_midi_event_t *midi_event(new_fluid_midi_event());
 | 
					    fluid_synth_cc(
 | 
				
			||||||
    fluid_midi_event_set_type(midi_event, static_cast<int>(CONTROL_CHANGE));
 | 
					        synth.get(),
 | 
				
			||||||
    fluid_midi_event_set_channel(midi_event, channel);
 | 
					        channel,
 | 
				
			||||||
    fluid_midi_event_set_control(midi_event, controller);
 | 
					        controller,
 | 
				
			||||||
    fluid_midi_event_set_value(midi_event, value);
 | 
					        value);
 | 
				
			||||||
    fluid_synth_handle_midi_event(synth, midi_event);
 | 
					 | 
				
			||||||
    delete_fluid_midi_event(midi_event);
 | 
					 | 
				
			||||||
    //        fluid_channel_set_cc(channel, i, 0);
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
int FluidSynthModel::getChannel() {
 | 
					int FluidSynthModel::getChannel() {
 | 
				
			||||||
    return channel;
 | 
					    return channel;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void FluidSynthModel::changePreset(int bank, int preset) {
 | 
					 | 
				
			||||||
    if (bank == -1 || preset == -1) {
 | 
					 | 
				
			||||||
        unique_ptr<BankAndPreset> bankAndPreset = getFirstBankAndPreset();
 | 
					 | 
				
			||||||
        bank = bankAndPreset->getBank();
 | 
					 | 
				
			||||||
        preset = bankAndPreset->getPreset();
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    changePresetImpl(bank, preset);
 | 
					 | 
				
			||||||
    sharesParams.setPreset(preset);
 | 
					 | 
				
			||||||
    sharesParams.setBank(bank);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void FluidSynthModel::changePresetImpl(int bank, int preset) {
 | 
					 | 
				
			||||||
    fluid_synth_program_select(synth, channel, sfont_id, static_cast<unsigned int>(bank), static_cast<unsigned int>(preset));
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
fluid_preset_t* FluidSynthModel::getFirstPreset() {
 | 
					 | 
				
			||||||
    fluid_sfont_t* sfont = fluid_synth_get_sfont_by_id(synth, sfont_id);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    jassert(sfont != nullptr);
 | 
					 | 
				
			||||||
    fluid_sfont_iteration_start(sfont);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return fluid_sfont_iteration_next(sfont);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
unique_ptr<BankAndPreset> FluidSynthModel::getFirstBankAndPreset() {
 | 
					 | 
				
			||||||
    fluid_preset_t* preset = getFirstPreset();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    int offset = fluid_synth_get_bank_offset(synth, sfont_id);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return make_unique<BankAndPreset>(fluid_preset_get_banknum(preset) + offset, fluid_preset_get_num(preset));
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void FluidSynthModel::selectFirstPreset() {
 | 
					 | 
				
			||||||
    fluid_preset_t* preset = getFirstPreset();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    int offset = fluid_synth_get_bank_offset(synth, sfont_id);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    changePreset(fluid_preset_get_banknum(preset) + offset, fluid_preset_get_num(preset));
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
BanksToPresets FluidSynthModel::getBanks() {
 | 
					 | 
				
			||||||
    BanksToPresets banksToPresets;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    int soundfontCount = fluid_synth_sfcount(synth);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (soundfontCount == 0) {
 | 
					 | 
				
			||||||
        // no soundfont selected
 | 
					 | 
				
			||||||
        return banksToPresets;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    fluid_sfont_t* sfont = fluid_synth_get_sfont_by_id(synth, sfont_id);
 | 
					 | 
				
			||||||
    if(sfont == nullptr) {
 | 
					 | 
				
			||||||
        // no soundfont found by that ID
 | 
					 | 
				
			||||||
        // the above guard (soundfontCount) protects us for the
 | 
					 | 
				
			||||||
        // main case we're expecting. this guard is just defensive programming.
 | 
					 | 
				
			||||||
        return banksToPresets;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    int offset = fluid_synth_get_bank_offset(synth, sfont_id);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    fluid_sfont_iteration_start(sfont);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    for(fluid_preset_t* preset = fluid_sfont_iteration_next(sfont);
 | 
					 | 
				
			||||||
    preset != nullptr;
 | 
					 | 
				
			||||||
    preset = fluid_sfont_iteration_next(sfont)) {
 | 
					 | 
				
			||||||
        banksToPresets.insert(BanksToPresets::value_type(
 | 
					 | 
				
			||||||
                fluid_preset_get_banknum(preset) + offset,
 | 
					 | 
				
			||||||
                *new Preset(
 | 
					 | 
				
			||||||
                        fluid_preset_get_num(preset),
 | 
					 | 
				
			||||||
                        fluid_preset_get_name(preset)
 | 
					 | 
				
			||||||
                )
 | 
					 | 
				
			||||||
        ));
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return banksToPresets;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
fluid_synth_t* FluidSynthModel::getSynth() {
 | 
					 | 
				
			||||||
    // https://msdn.microsoft.com/en-us/library/hh279669.aspx
 | 
					 | 
				
			||||||
    // You can pass a shared_ptr to another function in the following ways:
 | 
					 | 
				
			||||||
    // Pass the shared_ptr by value. This invokes the copy constructor, increments the reference count, and makes the callee an owner.
 | 
					 | 
				
			||||||
    return synth;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void FluidSynthModel::onFileNameChanged(const String &absPath, int bank, int preset) {
 | 
					 | 
				
			||||||
    if (!shouldLoadFont(absPath)) {
 | 
					 | 
				
			||||||
        return;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    unloadAndLoadFont(absPath);
 | 
					 | 
				
			||||||
    changePreset(bank, preset);
 | 
					 | 
				
			||||||
    sharesParams.setSoundFontPath(absPath);
 | 
					 | 
				
			||||||
    eventListeners.call(&FluidSynthModel::Listener::fontChanged, this, absPath);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void FluidSynthModel::unloadAndLoadFont(const String &absPath) {
 | 
					void FluidSynthModel::unloadAndLoadFont(const String &absPath) {
 | 
				
			||||||
    // in the base case, there is no font loaded
 | 
					    // in the base case, there is no font loaded
 | 
				
			||||||
    if (fluid_synth_sfcount(synth) > 0) {
 | 
					    if (fluid_synth_sfcount(synth.get()) > 0) {
 | 
				
			||||||
        fluid_synth_sfunload(synth, sfont_id, 1);
 | 
					        // if -1 is returned, that indicates failure
 | 
				
			||||||
 | 
					        // not really sure how to handle "fail to unload"
 | 
				
			||||||
 | 
					        fluid_synth_sfunload(synth.get(), sfont_id, 1);
 | 
				
			||||||
 | 
					        sfont_id = -1;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    loadFont(absPath);
 | 
					    loadFont(absPath);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void FluidSynthModel::loadFont(const String &absPath) {
 | 
					void FluidSynthModel::loadFont(const String &absPath) {
 | 
				
			||||||
    currentSoundFontAbsPath = absPath;
 | 
					    if (!absPath.isEmpty()) {
 | 
				
			||||||
    sfont_id++;
 | 
					        sfont_id = fluid_synth_sfload(synth.get(), absPath.toStdString().c_str(), 1);
 | 
				
			||||||
    fluid_synth_sfload(synth, absPath.toStdString().c_str(), 1);
 | 
					        // if -1 is returned, that indicates failure
 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
FluidSynthModel::Listener::~Listener() {
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
bool FluidSynthModel::shouldLoadFont(const String &absPath) {
 | 
					 | 
				
			||||||
    if (absPath.isEmpty()) {
 | 
					 | 
				
			||||||
        return false;
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    if (absPath == currentSoundFontAbsPath) {
 | 
					    // refresh regardless of success, if only to clear the table
 | 
				
			||||||
        return false;
 | 
					    refreshBanks();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void FluidSynthModel::refreshBanks() {
 | 
				
			||||||
 | 
					    ValueTree banks{"banks"};
 | 
				
			||||||
 | 
					    fluid_sfont_t* sfont{
 | 
				
			||||||
 | 
					        sfont_id == -1
 | 
				
			||||||
 | 
					        ? nullptr
 | 
				
			||||||
 | 
					        : fluid_synth_get_sfont_by_id(synth.get(), sfont_id)
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					    if (sfont) {
 | 
				
			||||||
 | 
					        int greatestEncounteredBank{-1};
 | 
				
			||||||
 | 
					        ValueTree bank;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        fluid_sfont_iteration_start(sfont);
 | 
				
			||||||
 | 
					        for(fluid_preset_t* preset {fluid_sfont_iteration_next(sfont)};
 | 
				
			||||||
 | 
					        preset != nullptr;
 | 
				
			||||||
 | 
					        preset = fluid_sfont_iteration_next(sfont)) {
 | 
				
			||||||
 | 
					            int bankNum{fluid_preset_get_banknum(preset)};
 | 
				
			||||||
 | 
					            if (bankNum > greatestEncounteredBank) {
 | 
				
			||||||
 | 
					                if (greatestEncounteredBank > -1) {
 | 
				
			||||||
 | 
					                    banks.appendChild(bank, nullptr);
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
    return true;
 | 
					                bank = { "bank", {
 | 
				
			||||||
}
 | 
					                    { "num", bankNum }
 | 
				
			||||||
 | 
					                } };
 | 
				
			||||||
 | 
					                greatestEncounteredBank = bankNum;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            bank.appendChild({ "preset", {
 | 
				
			||||||
 | 
					                { "num", fluid_preset_get_num(preset) },
 | 
				
			||||||
 | 
					                { "name", String{fluid_preset_get_name(preset)} }
 | 
				
			||||||
 | 
					            }, {} }, nullptr);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if (greatestEncounteredBank > -1) {
 | 
				
			||||||
 | 
					            banks.appendChild(bank, nullptr);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    valueTreeState.state.getChildWithName("banks").copyPropertiesAndChildrenFrom(banks, nullptr);
 | 
				
			||||||
 | 
					    valueTreeState.state.getChildWithName("banks").sendPropertyChangeMessage("synthetic");
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
void FluidSynthModel::Listener::fontChanged(FluidSynthModel * model, const String &absPath) {
 | 
					#if JUCE_DEBUG
 | 
				
			||||||
}
 | 
					//    unique_ptr<XmlElement> xml{valueTreeState.state.createXml()};
 | 
				
			||||||
 | 
					//    Logger::outputDebugString(xml->createDocument("",false,false));
 | 
				
			||||||
const String& FluidSynthModel::getCurrentSoundFontAbsPath() {
 | 
					#endif
 | 
				
			||||||
    return currentSoundFontAbsPath;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
//==============================================================================
 | 
					 | 
				
			||||||
void FluidSynthModel::addListener (FluidSynthModel::Listener* const newListener)
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
    eventListeners.add(newListener);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void FluidSynthModel::removeListener (FluidSynthModel::Listener* const listener)
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
    eventListeners.remove(listener);
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void FluidSynthModel::setSampleRate(float sampleRate) {
 | 
					void FluidSynthModel::setSampleRate(float sampleRate) {
 | 
				
			||||||
    currentSampleRate = sampleRate;
 | 
					    currentSampleRate = sampleRate;
 | 
				
			||||||
    if (!initialised) {
 | 
					    // https://stackoverflow.com/a/40856043/5257399
 | 
				
			||||||
 | 
					    // test if a smart pointer is null
 | 
				
			||||||
 | 
					    if (!synth) {
 | 
				
			||||||
        // don't worry; we'll do this in initialise phase regardless
 | 
					        // don't worry; we'll do this in initialise phase regardless
 | 
				
			||||||
        return;
 | 
					        return;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    fluid_synth_set_sample_rate(synth, sampleRate);
 | 
					    fluid_synth_set_sample_rate(synth.get(), sampleRate);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void FluidSynthModel::processBlock(AudioBuffer<float>& buffer, MidiBuffer& midiMessages) {
 | 
				
			||||||
 | 
					    MidiBuffer processedMidi;
 | 
				
			||||||
 | 
					    int time;
 | 
				
			||||||
 | 
					    MidiMessage m;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for (MidiBuffer::Iterator i{midiMessages}; i.getNextEvent(m, time);) {
 | 
				
			||||||
 | 
					        DEBUG_PRINT(m.getDescription());
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        if (m.isNoteOn()) {
 | 
				
			||||||
 | 
					            fluid_synth_noteon(
 | 
				
			||||||
 | 
					                synth.get(),
 | 
				
			||||||
 | 
					                channel,
 | 
				
			||||||
 | 
					                m.getNoteNumber(),
 | 
				
			||||||
 | 
					                m.getVelocity());
 | 
				
			||||||
 | 
					        } else if (m.isNoteOff()) {
 | 
				
			||||||
 | 
					            fluid_synth_noteoff(
 | 
				
			||||||
 | 
					                synth.get(),
 | 
				
			||||||
 | 
					                channel,
 | 
				
			||||||
 | 
					                m.getNoteNumber());
 | 
				
			||||||
 | 
					        } else if (m.isController()) {
 | 
				
			||||||
 | 
					            fluid_synth_cc(
 | 
				
			||||||
 | 
					                synth.get(),
 | 
				
			||||||
 | 
					                channel,
 | 
				
			||||||
 | 
					                m.getControllerNumber(),
 | 
				
			||||||
 | 
					                m.getControllerValue());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            fluid_midi_control_change controllerNum{static_cast<fluid_midi_control_change>(m.getControllerNumber())};
 | 
				
			||||||
 | 
					            if (auto it{controllerToParam.find(controllerNum)};
 | 
				
			||||||
 | 
					                it != end(controllerToParam)) {
 | 
				
			||||||
 | 
					                String parameterID{it->second};
 | 
				
			||||||
 | 
					                RangedAudioParameter *param{valueTreeState.getParameter(parameterID)};
 | 
				
			||||||
 | 
					                jassert(dynamic_cast<AudioParameterInt*>(param) != nullptr);
 | 
				
			||||||
 | 
					                AudioParameterInt* castParam{dynamic_cast<AudioParameterInt*>(param)};
 | 
				
			||||||
 | 
					                *castParam = m.getControllerValue();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        } else if (m.isProgramChange()) {
 | 
				
			||||||
 | 
					            int result{fluid_synth_program_change(
 | 
				
			||||||
 | 
					                synth.get(),
 | 
				
			||||||
 | 
					                channel,
 | 
				
			||||||
 | 
					                m.getProgramChangeNumber())};
 | 
				
			||||||
 | 
					            if (result == FLUID_OK) {
 | 
				
			||||||
 | 
					                RangedAudioParameter *param{valueTreeState.getParameter("preset")};
 | 
				
			||||||
 | 
					                jassert(dynamic_cast<AudioParameterInt*>(param) != nullptr);
 | 
				
			||||||
 | 
					                AudioParameterInt* castParam{dynamic_cast<AudioParameterInt*>(param)};
 | 
				
			||||||
 | 
					                *castParam = m.getProgramChangeNumber();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        } else if (m.isPitchWheel()) {
 | 
				
			||||||
 | 
					            fluid_synth_pitch_bend(
 | 
				
			||||||
 | 
					                synth.get(),
 | 
				
			||||||
 | 
					                channel,
 | 
				
			||||||
 | 
					                m.getPitchWheelValue());
 | 
				
			||||||
 | 
					        } else if (m.isChannelPressure()) {
 | 
				
			||||||
 | 
					            fluid_synth_channel_pressure(
 | 
				
			||||||
 | 
					                synth.get(),
 | 
				
			||||||
 | 
					                channel,
 | 
				
			||||||
 | 
					                m.getChannelPressureValue());
 | 
				
			||||||
 | 
					        } else if (m.isAftertouch()) {
 | 
				
			||||||
 | 
					            fluid_synth_key_pressure(
 | 
				
			||||||
 | 
					                synth.get(),
 | 
				
			||||||
 | 
					                channel,
 | 
				
			||||||
 | 
					                m.getNoteNumber(),
 | 
				
			||||||
 | 
					                m.getAfterTouchValue());
 | 
				
			||||||
 | 
					//        } else if (m.isMetaEvent()) {
 | 
				
			||||||
 | 
					//            fluid_midi_event_t *midi_event{new_fluid_midi_event()};
 | 
				
			||||||
 | 
					//            fluid_midi_event_set_type(midi_event, static_cast<int>(MIDI_SYSTEM_RESET));
 | 
				
			||||||
 | 
					//            fluid_synth_handle_midi_event(synth.get(), midi_event);
 | 
				
			||||||
 | 
					//            delete_fluid_midi_event(midi_event);
 | 
				
			||||||
 | 
					        } else if (m.isSysEx()) {
 | 
				
			||||||
 | 
					            fluid_synth_sysex(
 | 
				
			||||||
 | 
					                synth.get(),
 | 
				
			||||||
 | 
					                reinterpret_cast<const char*>(m.getSysExData()),
 | 
				
			||||||
 | 
					                m.getSysExDataSize(),
 | 
				
			||||||
 | 
					                nullptr, // no response pointer because we have no interest in handling response currently
 | 
				
			||||||
 | 
					                nullptr, // no response_len pointer because we have no interest in handling response currently
 | 
				
			||||||
 | 
					                nullptr, // no handled pointer because we have no interest in handling response currently
 | 
				
			||||||
 | 
					                static_cast<int>(false));
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // fluid_synth_get_cc(fluidSynth, 0, 73, &pval);
 | 
				
			||||||
 | 
					    // Logger::outputDebugString ( juce::String::formatted("hey: %d\n", pval) );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fluid_synth_process(
 | 
				
			||||||
 | 
					        synth.get(),
 | 
				
			||||||
 | 
					        buffer.getNumSamples(),
 | 
				
			||||||
 | 
					        0,
 | 
				
			||||||
 | 
					        nullptr,
 | 
				
			||||||
 | 
					        buffer.getNumChannels(),
 | 
				
			||||||
 | 
					        buffer.getArrayOfWritePointers());
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					int FluidSynthModel::getNumPrograms()
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    return 128;   // NB: some hosts don't cope very well if you tell them there are 0 programs,
 | 
				
			||||||
 | 
					                // so this should be at least 1, even if you're not really implementing programs.
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					int FluidSynthModel::getCurrentProgram()
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    RangedAudioParameter *param{valueTreeState.getParameter("preset")};
 | 
				
			||||||
 | 
					    jassert(dynamic_cast<AudioParameterInt*>(param) != nullptr);
 | 
				
			||||||
 | 
					    AudioParameterInt* castParam{dynamic_cast<AudioParameterInt*>(param)};
 | 
				
			||||||
 | 
					    return castParam->get();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void FluidSynthModel::setCurrentProgram(int index)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    RangedAudioParameter *param{valueTreeState.getParameter("preset")};
 | 
				
			||||||
 | 
					    jassert(dynamic_cast<AudioParameterInt*>(param) != nullptr);
 | 
				
			||||||
 | 
					    AudioParameterInt* castParam{dynamic_cast<AudioParameterInt*>(param)};
 | 
				
			||||||
 | 
					    // setCurrentProgram() gets invoked from non-message thread.
 | 
				
			||||||
 | 
					    // AudioParameterInt#operator= will activate any listeners of audio parameter "preset".
 | 
				
			||||||
 | 
					    // This includes TableComponent, who will update its UI.
 | 
				
			||||||
 | 
					    // we need to lock the message thread whilst it does that UI update.
 | 
				
			||||||
 | 
					    const MessageManagerLock mmLock;
 | 
				
			||||||
 | 
					    *castParam = index;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const String FluidSynthModel::getProgramName(int index)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					     fluid_sfont_t* sfont{
 | 
				
			||||||
 | 
					         sfont_id == -1
 | 
				
			||||||
 | 
					         ? nullptr
 | 
				
			||||||
 | 
					         : fluid_synth_get_sfont_by_id(synth.get(), sfont_id)
 | 
				
			||||||
 | 
					     };
 | 
				
			||||||
 | 
					     if (!sfont) {
 | 
				
			||||||
 | 
					         String presetName{"Preset "};
 | 
				
			||||||
 | 
					         return presetName << index;
 | 
				
			||||||
 | 
					     }
 | 
				
			||||||
 | 
					     RangedAudioParameter *param{valueTreeState.getParameter("bank")};
 | 
				
			||||||
 | 
					     jassert(dynamic_cast<AudioParameterInt*>(param) != nullptr);
 | 
				
			||||||
 | 
					     AudioParameterInt* castParam{dynamic_cast<AudioParameterInt*>(param)};
 | 
				
			||||||
 | 
					     int bank{castParam->get()};
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					     fluid_preset_t *preset{fluid_sfont_get_preset(
 | 
				
			||||||
 | 
					         sfont,
 | 
				
			||||||
 | 
					         bank,
 | 
				
			||||||
 | 
					         index)};
 | 
				
			||||||
 | 
					     if (!preset) {
 | 
				
			||||||
 | 
					         String presetName{"Preset "};
 | 
				
			||||||
 | 
					         return presetName << index;
 | 
				
			||||||
 | 
					     }
 | 
				
			||||||
 | 
					     return {fluid_preset_get_name(preset)};
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void FluidSynthModel::changeProgramName(int index, const String& newName)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    // no-op; we don't support modifying the soundfont, so let's not support modification of preset names.
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -5,93 +5,80 @@
 | 
				
			|||||||
#pragma once
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include "../JuceLibraryCode/JuceHeader.h"
 | 
					#include "../JuceLibraryCode/JuceHeader.h"
 | 
				
			||||||
#include "SharesParams.h"
 | 
					 | 
				
			||||||
#include <fluidsynth.h>
 | 
					#include <fluidsynth.h>
 | 
				
			||||||
#include <memory>
 | 
					#include <memory>
 | 
				
			||||||
#include "BankAndPreset.h"
 | 
					#include <map>
 | 
				
			||||||
#include "PresetsToBanks.h"
 | 
					#include "MidiConstants.h"
 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// https://stackoverflow.com/a/13446565/5257399
 | 
					 | 
				
			||||||
//using std::shared_ptr;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
using namespace std;
 | 
					using namespace std;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class FluidSynthModel {
 | 
					class FluidSynthModel
 | 
				
			||||||
 | 
					: public ValueTree::Listener
 | 
				
			||||||
 | 
					, public AudioProcessorValueTreeState::Listener {
 | 
				
			||||||
public:
 | 
					public:
 | 
				
			||||||
    FluidSynthModel(SharesParams& p);
 | 
					    FluidSynthModel(
 | 
				
			||||||
 | 
					        AudioProcessorValueTreeState& valueTreeState
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
     ~FluidSynthModel();
 | 
					     ~FluidSynthModel();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fluid_synth_t* getSynth();
 | 
					 | 
				
			||||||
    void initialise();
 | 
					    void initialise();
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    BanksToPresets getBanks();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    void changePreset(int bank, int preset);
 | 
					 | 
				
			||||||
    int getChannel();
 | 
					    int getChannel();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    void onFileNameChanged(const String &absPath, int bank, int preset);
 | 
					 | 
				
			||||||
    void setControllerValue(int controller, int value);
 | 
					    void setControllerValue(int controller, int value);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    //==============================================================================
 | 
					    void processBlock(AudioBuffer<float>& buffer, MidiBuffer& midiMessages);
 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
        Used to receive callbacks when a button is clicked.
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        @see Button::addListener, Button::removeListener
 | 
					 | 
				
			||||||
    */
 | 
					 | 
				
			||||||
    class Listener
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
    public:
 | 
					 | 
				
			||||||
        /** Destructor. */
 | 
					 | 
				
			||||||
        virtual ~Listener();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        /** Called when the button is clicked. */
 | 
					 | 
				
			||||||
        virtual void fontChanged (FluidSynthModel*, const String &absPath);
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /** Registers a listener to receive events when this button's state changes.
 | 
					 | 
				
			||||||
        If the listener is already registered, this will not register it again.
 | 
					 | 
				
			||||||
        @see removeListener
 | 
					 | 
				
			||||||
    */
 | 
					 | 
				
			||||||
    void addListener (Listener* newListener);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /** Removes a previously-registered button listener
 | 
					 | 
				
			||||||
        @see addListener
 | 
					 | 
				
			||||||
    */
 | 
					 | 
				
			||||||
    void removeListener (Listener* listener);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    void setSampleRate(float sampleRate);
 | 
					    void setSampleRate(float sampleRate);
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    const String& getCurrentSoundFontAbsPath();
 | 
					    //==============================================================================
 | 
				
			||||||
 | 
					    virtual void parameterChanged (const String& parameterID, float newValue) override;
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    virtual void valueTreePropertyChanged (ValueTree& treeWhosePropertyHasChanged,
 | 
				
			||||||
 | 
					                                           const Identifier& property) override;
 | 
				
			||||||
 | 
					    inline virtual void valueTreeChildAdded (ValueTree& parentTree,
 | 
				
			||||||
 | 
					                                             ValueTree& childWhichHasBeenAdded) override {};
 | 
				
			||||||
 | 
					    inline virtual void valueTreeChildRemoved (ValueTree& parentTree,
 | 
				
			||||||
 | 
					                                               ValueTree& childWhichHasBeenRemoved,
 | 
				
			||||||
 | 
					                                               int indexFromWhichChildWasRemoved) override {};
 | 
				
			||||||
 | 
					    inline virtual void valueTreeChildOrderChanged (ValueTree& parentTreeWhoseChildrenHaveMoved,
 | 
				
			||||||
 | 
					                                                    int oldIndex, int newIndex) override {};
 | 
				
			||||||
 | 
					    inline virtual void valueTreeParentChanged (ValueTree& treeWhoseParentHasChanged) override {};
 | 
				
			||||||
 | 
					    inline virtual void valueTreeRedirected (ValueTree& treeWhichHasBeenChanged) override {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    //==============================================================================
 | 
				
			||||||
 | 
					    int getNumPrograms();
 | 
				
			||||||
 | 
					    int getCurrentProgram();
 | 
				
			||||||
 | 
					    void setCurrentProgram(int index);
 | 
				
			||||||
 | 
					    const String getProgramName(int index);
 | 
				
			||||||
 | 
					    void changeProgramName(int index, const String& newName);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
private:
 | 
					private:
 | 
				
			||||||
    SharesParams& sharesParams;
 | 
					    static const StringArray programChangeParams;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fluid_synth_t* synth;
 | 
					    // there's no bimap in the standard library!
 | 
				
			||||||
    fluid_settings_t* settings;
 | 
					    static const map<fluid_midi_control_change, String> controllerToParam;
 | 
				
			||||||
//    fluid_audio_driver_t* driver;
 | 
					    static const map<String, fluid_midi_control_change> paramToController;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    String currentSoundFontAbsPath;
 | 
					    void refreshBanks();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    AudioProcessorValueTreeState& valueTreeState;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // https://stackoverflow.com/questions/38980315/is-stdunique-ptr-deletion-order-guaranteed
 | 
				
			||||||
 | 
					    // members are destroyed in reverse of the order they're declared
 | 
				
			||||||
 | 
					    // http://www.fluidsynth.org/api/
 | 
				
			||||||
 | 
					    // in their examples, they destroy the synth before destroying the settings
 | 
				
			||||||
 | 
					    unique_ptr<fluid_settings_t, decltype(&delete_fluid_settings)> settings;
 | 
				
			||||||
 | 
					    unique_ptr<fluid_synth_t, decltype(&delete_fluid_synth)> synth;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    float currentSampleRate;
 | 
					    float currentSampleRate;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fluid_preset_t* getFirstPreset();
 | 
					 | 
				
			||||||
    void selectFirstPreset();
 | 
					 | 
				
			||||||
    unique_ptr<BankAndPreset> getFirstBankAndPreset();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    void unloadAndLoadFont(const String &absPath);
 | 
					    void unloadAndLoadFont(const String &absPath);
 | 
				
			||||||
    void loadFont(const String &absPath);
 | 
					    void loadFont(const String &absPath);
 | 
				
			||||||
    bool shouldLoadFont(const String &absPath);
 | 
					 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    void changePresetImpl(int bank, int preset);
 | 
					    int sfont_id;
 | 
				
			||||||
 | 
					 | 
				
			||||||
    bool initialised;
 | 
					 | 
				
			||||||
    unsigned int sfont_id;
 | 
					 | 
				
			||||||
    unsigned int channel;
 | 
					    unsigned int channel;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
//    fluid_mod_t* mod;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    ListenerList<Listener> eventListeners;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (FluidSynthModel)
 | 
					    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (FluidSynthModel)
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										8
									
								
								Source/GuiConstants.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								Source/GuiConstants.h
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,8 @@
 | 
				
			|||||||
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct GuiConstants {
 | 
				
			||||||
 | 
					    inline static const int minWidth = 500;
 | 
				
			||||||
 | 
					    inline static const int maxWidth = 1900;
 | 
				
			||||||
 | 
					    inline static const int minHeight = 300;
 | 
				
			||||||
 | 
					    inline static const int maxHeight = 1000;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
@ -162,3 +162,8 @@ enum fluid_midi_control_change
 | 
				
			|||||||
 - for case (2) or (3), FLUID_NOISE_FLOOR should be the noise floor for 16 bits (i.e -90 dB).
 | 
					 - for case (2) or (3), FLUID_NOISE_FLOOR should be the noise floor for 16 bits (i.e -90 dB).
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
#define FLUID_PEAK_ATTENUATION  960.0f
 | 
					#define FLUID_PEAK_ATTENUATION  960.0f
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct MidiConstants {
 | 
				
			||||||
 | 
					    inline static const int midiMinValue = 0;
 | 
				
			||||||
 | 
					    inline static const int midiMaxValue = 127;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										200
									
								
								Source/Pills.cpp
									
									
									
									
									
								
							
							
						
						
									
										200
									
								
								Source/Pills.cpp
									
									
									
									
									
								
							@ -4,88 +4,170 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
#include "Pills.h"
 | 
					#include "Pills.h"
 | 
				
			||||||
#include "MyColours.h"
 | 
					#include "MyColours.h"
 | 
				
			||||||
 | 
					#include <algorithm>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
using namespace std;
 | 
					using namespace std;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Pill::Pill(
 | 
				
			||||||
 | 
					    AudioProcessorValueTreeState& valueTreeState,
 | 
				
			||||||
 | 
					    int bank,
 | 
				
			||||||
 | 
					    bool isFirst,
 | 
				
			||||||
 | 
					    bool isLast
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					: valueTreeState{valueTreeState}
 | 
				
			||||||
 | 
					, bank{bank}
 | 
				
			||||||
 | 
					, textButton{String(bank)}
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    setOpaque(true);
 | 
				
			||||||
 | 
					    textButton.setConnectedEdges (
 | 
				
			||||||
 | 
					            (isFirst ? 0 : Button::ConnectedOnLeft)
 | 
				
			||||||
 | 
					            | (isLast ? 0 : Button::ConnectedOnRight)
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					    textButton.setRadioGroupId(34567);
 | 
				
			||||||
 | 
					    textButton.setClickingTogglesState(true);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    addAndMakeVisible(textButton);
 | 
				
			||||||
 | 
					    textButton.addListener(this);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void Pill::paint (Graphics& g) {
 | 
				
			||||||
 | 
					    // (Our component is opaque, so we must completely fill the background with a solid colour)
 | 
				
			||||||
 | 
					    g.fillAll(getLookAndFeel().findColour(ResizableWindow::backgroundColourId));
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void Pill::resized() {
 | 
				
			||||||
 | 
					    textButton.setBounds(getLocalBounds());
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Pill::~Pill() {
 | 
				
			||||||
 | 
					    textButton.removeListener(this);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void Pill::bankChanged(int bank) {
 | 
				
			||||||
 | 
					    textButton.setToggleState(this->bank == bank, dontSendNotification);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void Pill::buttonClicked(Button* button) {
 | 
				
			||||||
 | 
					    ValueTree banks{valueTreeState.state.getChildWithName("banks")};
 | 
				
			||||||
 | 
					    int banksChildren{banks.getNumChildren()};
 | 
				
			||||||
 | 
					    ValueTree bank;
 | 
				
			||||||
 | 
					    for(int bankIx{0}; bankIx<banksChildren; bankIx++) {
 | 
				
			||||||
 | 
					        ValueTree currentBank{banks.getChild(bankIx)};
 | 
				
			||||||
 | 
					        int bankNum{currentBank.getProperty("num")};
 | 
				
			||||||
 | 
					        if (bankNum == this->bank) {
 | 
				
			||||||
 | 
					            bank = currentBank;
 | 
				
			||||||
 | 
					            break;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    ValueTree preset{bank.getChild(0)};
 | 
				
			||||||
 | 
					    int presetNum{preset.getProperty("num")};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        RangedAudioParameter *param{valueTreeState.getParameter("bank")};
 | 
				
			||||||
 | 
					        jassert(dynamic_cast<AudioParameterInt*>(param) != nullptr);
 | 
				
			||||||
 | 
					        AudioParameterInt* castParam{dynamic_cast<AudioParameterInt*>(param)};
 | 
				
			||||||
 | 
					        *castParam = this->bank;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        RangedAudioParameter *param{valueTreeState.getParameter("preset")};
 | 
				
			||||||
 | 
					        jassert(dynamic_cast<AudioParameterInt*>(param) != nullptr);
 | 
				
			||||||
 | 
					        AudioParameterInt* castParam{dynamic_cast<AudioParameterInt*>(param)};
 | 
				
			||||||
 | 
					        *castParam = presetNum;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Pills::Pills(
 | 
					Pills::Pills(
 | 
				
			||||||
        string label,
 | 
					    AudioProcessorValueTreeState& valueTreeState
 | 
				
			||||||
        const vector<string> &items,
 | 
					)
 | 
				
			||||||
        const function<void (int)> &onItemSelected,
 | 
					: valueTreeState{valueTreeState}
 | 
				
			||||||
        const function<int (const string&)> &itemToIDMapper,
 | 
					 | 
				
			||||||
        int initiallySelectedItem
 | 
					 | 
				
			||||||
) : label(label),
 | 
					 | 
				
			||||||
    items(items),
 | 
					 | 
				
			||||||
    onItemSelected(onItemSelected),
 | 
					 | 
				
			||||||
    itemToIDMapper(itemToIDMapper)
 | 
					 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    // faster (rounded edges introduce transparency)
 | 
					    // faster (rounded edges introduce transparency)
 | 
				
			||||||
    setOpaque (true);
 | 
					    setOpaque (true);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    populate(initiallySelectedItem);
 | 
					    ValueTree banks{valueTreeState.state.getChildWithName("banks")};
 | 
				
			||||||
 | 
					    loadModelFrom(banks);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    valueTreeState.state.addListener(this);
 | 
				
			||||||
 | 
					    valueTreeState.addParameterListener("bank", this);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void Pills::populate(int initiallySelectedItem) {
 | 
					Pills::~Pills() {
 | 
				
			||||||
    int index = 0;
 | 
					    valueTreeState.removeParameterListener("bank", this);
 | 
				
			||||||
    for (string item : items) {
 | 
					    valueTreeState.state.removeListener(this);
 | 
				
			||||||
        TextButton* pill = addToList(new TextButton(
 | 
					}
 | 
				
			||||||
                item
 | 
					
 | 
				
			||||||
        ));
 | 
					void Pills::parameterChanged(const String& parameterID, float newValue) {
 | 
				
			||||||
//        pill->setColour (TextButton::buttonOnColourId, Colours::blueviolet.brighter());
 | 
					    if (parameterID == "bank") {
 | 
				
			||||||
//        pill->setBounds(20 + index * 55, 260, 55, 24);
 | 
					        updatePillToggleStates();
 | 
				
			||||||
        pill->setConnectedEdges (
 | 
					 | 
				
			||||||
                (index == 0 ? 0 : Button::ConnectedOnLeft)
 | 
					 | 
				
			||||||
                        | (index == (items.size()-1) ? 0 : Button::ConnectedOnRight)
 | 
					 | 
				
			||||||
        );
 | 
					 | 
				
			||||||
        pill->setRadioGroupId(34567);
 | 
					 | 
				
			||||||
        if (index == initiallySelectedItem) {
 | 
					 | 
				
			||||||
            pill->setToggleState(true, dontSendNotification);
 | 
					 | 
				
			||||||
            selected = pill;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        pill->setClickingTogglesState(true);
 | 
					 | 
				
			||||||
        pill->addListener(this);
 | 
					 | 
				
			||||||
        index++;
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void Pills::setItems(
 | 
					void Pills::updatePillToggleStates() {
 | 
				
			||||||
        const vector<string> &items,
 | 
					    RangedAudioParameter *param {valueTreeState.getParameter("bank")};
 | 
				
			||||||
        int initiallySelectedItem
 | 
					    jassert(dynamic_cast<AudioParameterInt*> (param) != nullptr);
 | 
				
			||||||
) {
 | 
					    AudioParameterInt* castParam {dynamic_cast<AudioParameterInt*> (param)};
 | 
				
			||||||
    this->items = items;
 | 
					    int bank{castParam->get()};
 | 
				
			||||||
    for(TextButton* t : buttons) {
 | 
					    for (auto& pill: pills) {
 | 
				
			||||||
        t->removeListener(this);
 | 
					        pill->bankChanged(bank);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    buttons.clear(true);
 | 
					}
 | 
				
			||||||
    populate(initiallySelectedItem);
 | 
					
 | 
				
			||||||
 | 
					void Pills::valueTreePropertyChanged(
 | 
				
			||||||
 | 
					    ValueTree& treeWhosePropertyHasChanged,
 | 
				
			||||||
 | 
					    const Identifier& property) {
 | 
				
			||||||
 | 
					    if (treeWhosePropertyHasChanged.getType() == StringRef("banks")) {
 | 
				
			||||||
 | 
					        if (property == StringRef("synthetic")) {
 | 
				
			||||||
 | 
					            loadModelFrom(treeWhosePropertyHasChanged);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void Pills::loadModelFrom(ValueTree& banks) {
 | 
				
			||||||
 | 
					    pills.clear();
 | 
				
			||||||
 | 
					    int numChildren{banks.getNumChildren()};
 | 
				
			||||||
 | 
					    for(int i{0}; i < numChildren; i++) {
 | 
				
			||||||
 | 
					        ValueTree child{banks.getChild(i)};
 | 
				
			||||||
 | 
					        int num{child.getProperty("num")};
 | 
				
			||||||
 | 
					        unique_ptr<Pill> pill{make_unique<Pill>(
 | 
				
			||||||
 | 
					            valueTreeState,
 | 
				
			||||||
 | 
					            num,
 | 
				
			||||||
 | 
					            i == 0,
 | 
				
			||||||
 | 
					            i == numChildren - 1)};
 | 
				
			||||||
 | 
					        addAndMakeVisible(pill.get());
 | 
				
			||||||
 | 
					        pills.push_back(move(pill));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    updatePillToggleStates();
 | 
				
			||||||
    resized();
 | 
					    resized();
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void Pills::buttonClicked (Button* button) {
 | 
					 | 
				
			||||||
    selected = button;
 | 
					 | 
				
			||||||
    onItemSelected(itemToIDMapper(button->getName().toStdString()));
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
TextButton* Pills::addToList (TextButton* newButton) {
 | 
					 | 
				
			||||||
    buttons.add (newButton);
 | 
					 | 
				
			||||||
    addAndMakeVisible (newButton);
 | 
					 | 
				
			||||||
    return newButton;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void Pills::cycle(bool right) {
 | 
					void Pills::cycle(bool right) {
 | 
				
			||||||
    int currentIx = static_cast<const int>(distance(buttons.begin(), find(buttons.begin(), buttons.end(), selected)));
 | 
					    RangedAudioParameter *param{valueTreeState.getParameter("bank")};
 | 
				
			||||||
    currentIx += right ? 1 : buttons.size()-1;
 | 
					    jassert(dynamic_cast<AudioParameterInt*>(param) != nullptr);
 | 
				
			||||||
    buttons[currentIx % buttons.size()]->triggerClick();
 | 
					    AudioParameterInt* castParam{dynamic_cast<AudioParameterInt*> (param)};
 | 
				
			||||||
 | 
					    int bank{castParam->get()};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    int currentIx{static_cast<const int>(
 | 
				
			||||||
 | 
					        distance(
 | 
				
			||||||
 | 
					            pills.begin(),
 | 
				
			||||||
 | 
					            find_if(
 | 
				
			||||||
 | 
					                pills.begin(),
 | 
				
			||||||
 | 
					                pills.end(),
 | 
				
			||||||
 | 
					                    [bank](unique_ptr<Pill>& pill){
 | 
				
			||||||
 | 
					                        return pill->bank == bank;})))};
 | 
				
			||||||
 | 
					    currentIx += right ? 1 : pills.size()-1;
 | 
				
			||||||
 | 
					    pills[currentIx % pills.size()]->textButton.triggerClick();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void Pills::resized() {
 | 
					void Pills::resized() {
 | 
				
			||||||
    int index = 0;
 | 
					    int index{0};
 | 
				
			||||||
    Rectangle<int> r (getLocalBounds());
 | 
					    Rectangle<int> r{getLocalBounds()};
 | 
				
			||||||
    const int equalWidth = r.proportionOfWidth(buttons.size() <= 0 ? 1.0 : 1.0f/buttons.size());
 | 
					    const int equalWidth{r.proportionOfWidth(pills.size() <= 0 ? 1.0 : 1.0f/pills.size())};
 | 
				
			||||||
    for(TextButton* t : buttons) {
 | 
					    for(auto& pill : pills) {
 | 
				
			||||||
        Rectangle<int> r2 (getLocalBounds());
 | 
					        Rectangle<int> r2 (getLocalBounds());
 | 
				
			||||||
        r2.removeFromLeft(equalWidth * index);
 | 
					        r2.removeFromLeft(equalWidth * index);
 | 
				
			||||||
        r2.removeFromRight(equalWidth * (buttons.size()-index-1));
 | 
					        r2.removeFromRight(equalWidth * (static_cast<int>(pills.size())-index-1));
 | 
				
			||||||
        t->setBounds (r2);
 | 
					        pill->setBounds(r2);
 | 
				
			||||||
        index++;
 | 
					        index++;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -8,35 +8,69 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
using namespace std;
 | 
					using namespace std;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class Pills : public Component,
 | 
					class Pill
 | 
				
			||||||
              public Button::Listener {
 | 
					: public Component
 | 
				
			||||||
 | 
					, public Button::Listener
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					public:
 | 
				
			||||||
 | 
					    Pill(
 | 
				
			||||||
 | 
					        AudioProcessorValueTreeState& valueTreeState,
 | 
				
			||||||
 | 
					        int bank,
 | 
				
			||||||
 | 
					        bool isFirst,
 | 
				
			||||||
 | 
					        bool isLast
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					    ~Pill();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    void buttonClicked(Button* button) override;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    void resized() override;
 | 
				
			||||||
 | 
					    void paint(Graphics& g) override;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    void bankChanged(int bank);
 | 
				
			||||||
 | 
					private:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    AudioProcessorValueTreeState& valueTreeState;
 | 
				
			||||||
 | 
					    int bank;
 | 
				
			||||||
 | 
					    TextButton textButton;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    friend class Pills;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Pills
 | 
				
			||||||
 | 
					: public Component
 | 
				
			||||||
 | 
					, public ValueTree::Listener
 | 
				
			||||||
 | 
					, public AudioProcessorValueTreeState::Listener
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
public:
 | 
					public:
 | 
				
			||||||
    Pills(
 | 
					    Pills(
 | 
				
			||||||
            string label,
 | 
					        AudioProcessorValueTreeState& valueTreeState
 | 
				
			||||||
            const vector<string> &items,
 | 
					 | 
				
			||||||
            const function<void (int)> &onItemSelected,
 | 
					 | 
				
			||||||
            const function<int (const string&)> &itemToIDMapper,
 | 
					 | 
				
			||||||
            int initiallySelectedItem
 | 
					 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
 | 
					    ~Pills();
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    void setItems(
 | 
					 | 
				
			||||||
            const vector<string> &items,
 | 
					 | 
				
			||||||
            int initiallySelectedItem
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    void buttonClicked (Button* button) override;
 | 
					 | 
				
			||||||
    void cycle(bool right);
 | 
					    void cycle(bool right);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
private:
 | 
					    virtual void parameterChanged (const String& parameterID, float newValue) override;
 | 
				
			||||||
    string label;
 | 
					 | 
				
			||||||
    vector<string> items;
 | 
					 | 
				
			||||||
    function<void (int)> onItemSelected;
 | 
					 | 
				
			||||||
    function<int (const string&)> itemToIDMapper;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    OwnedArray<TextButton> buttons;
 | 
					    virtual void valueTreePropertyChanged (ValueTree& treeWhosePropertyHasChanged,
 | 
				
			||||||
 | 
					                                           const Identifier& property) override;
 | 
				
			||||||
 | 
					    inline virtual void valueTreeChildAdded (ValueTree& parentTree,
 | 
				
			||||||
 | 
					                                             ValueTree& childWhichHasBeenAdded) override {};
 | 
				
			||||||
 | 
					    inline virtual void valueTreeChildRemoved (ValueTree& parentTree,
 | 
				
			||||||
 | 
					                                               ValueTree& childWhichHasBeenRemoved,
 | 
				
			||||||
 | 
					                                               int indexFromWhichChildWasRemoved) override {};
 | 
				
			||||||
 | 
					    inline virtual void valueTreeChildOrderChanged (ValueTree& parentTreeWhoseChildrenHaveMoved,
 | 
				
			||||||
 | 
					                                                    int oldIndex, int newIndex) override {};
 | 
				
			||||||
 | 
					    inline virtual void valueTreeParentChanged (ValueTree& treeWhoseParentHasChanged) override {};
 | 
				
			||||||
 | 
					    inline virtual void valueTreeRedirected (ValueTree& treeWhichHasBeenChanged) override {};
 | 
				
			||||||
 | 
					private:
 | 
				
			||||||
 | 
					    void loadModelFrom(ValueTree& banks);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    AudioProcessorValueTreeState& valueTreeState;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    vector<unique_ptr<Pill>> pills;
 | 
				
			||||||
    Button *selected;
 | 
					    Button *selected;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    TextButton* addToList (TextButton* newButton);
 | 
					    void updatePillToggleStates();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    void populate(int initiallySelectedItem);
 | 
					    void populate(int initiallySelectedItem);
 | 
				
			||||||
    void resized() override;
 | 
					    void resized() override;
 | 
				
			||||||
 | 
				
			|||||||
@ -10,22 +10,35 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
#include "PluginProcessor.h"
 | 
					#include "PluginProcessor.h"
 | 
				
			||||||
#include "PluginEditor.h"
 | 
					#include "PluginEditor.h"
 | 
				
			||||||
 | 
					#include "GuiConstants.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
//==============================================================================
 | 
					//==============================================================================
 | 
				
			||||||
JuicySFAudioProcessorEditor::JuicySFAudioProcessorEditor (JuicySFAudioProcessor& p)
 | 
					JuicySFAudioProcessorEditor::JuicySFAudioProcessorEditor(
 | 
				
			||||||
    : AudioProcessorEditor (&p),
 | 
					    JuicySFAudioProcessor& p,
 | 
				
			||||||
      processor (p),
 | 
					    AudioProcessorValueTreeState& valueTreeState)
 | 
				
			||||||
      midiKeyboard (p.keyboardState, SurjectiveMidiKeyboardComponent::horizontalKeyboard),
 | 
					: AudioProcessorEditor{&p}
 | 
				
			||||||
      tablesComponent(p.getFluidSynthModel()),
 | 
					, processor{p}
 | 
				
			||||||
      filePicker(p.getFluidSynthModel()),
 | 
					, valueTreeState{valueTreeState}
 | 
				
			||||||
      slidersComponent{p.getFluidSynthModel()}
 | 
					, midiKeyboard{p.keyboardState, SurjectiveMidiKeyboardComponent::horizontalKeyboard}
 | 
				
			||||||
 | 
					, tablesComponent{valueTreeState}
 | 
				
			||||||
 | 
					, filePicker{valueTreeState}
 | 
				
			||||||
 | 
					, slidersComponent{valueTreeState, p.getFluidSynthModel()}
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    // set resize limits for this plug-in
 | 
					    // set resize limits for this plug-in
 | 
				
			||||||
    setResizeLimits (500, 300, 1900, 1000);
 | 
					    setResizeLimits(
 | 
				
			||||||
 | 
					        GuiConstants::minWidth,
 | 
				
			||||||
 | 
					        GuiConstants::minHeight,
 | 
				
			||||||
 | 
					        GuiConstants::maxWidth,
 | 
				
			||||||
 | 
					        GuiConstants::maxHeight);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    setSize (p.lastUIWidth, p.lastUIHeight);
 | 
					    lastUIWidth.referTo(valueTreeState.state.getChildWithName("uiState").getPropertyAsValue("width",  nullptr));
 | 
				
			||||||
 | 
					    lastUIHeight.referTo(valueTreeState.state.getChildWithName("uiState").getPropertyAsValue("height", nullptr));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
//    processor.subscribeToStateChanges(this);
 | 
					    // set our component's initial size to be the last one that was stored in the filter's settings
 | 
				
			||||||
 | 
					    setSize(lastUIWidth.getValue(), lastUIHeight.getValue());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    lastUIWidth.addListener(this);
 | 
				
			||||||
 | 
					    lastUIHeight.addListener(this);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    midiKeyboard.setName ("MIDI Keyboard");
 | 
					    midiKeyboard.setName ("MIDI Keyboard");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -33,7 +46,7 @@ JuicySFAudioProcessorEditor::JuicySFAudioProcessorEditor (JuicySFAudioProcessor&
 | 
				
			|||||||
    tablesComponent.setWantsKeyboardFocus(false);
 | 
					    tablesComponent.setWantsKeyboardFocus(false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    setWantsKeyboardFocus(true);
 | 
					    setWantsKeyboardFocus(true);
 | 
				
			||||||
    addAndMakeVisible (midiKeyboard);
 | 
					    addAndMakeVisible(midiKeyboard);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    addAndMakeVisible(slidersComponent);
 | 
					    addAndMakeVisible(slidersComponent);
 | 
				
			||||||
    addAndMakeVisible(tablesComponent);
 | 
					    addAndMakeVisible(tablesComponent);
 | 
				
			||||||
@ -41,22 +54,16 @@ JuicySFAudioProcessorEditor::JuicySFAudioProcessorEditor (JuicySFAudioProcessor&
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
JuicySFAudioProcessorEditor::~JuicySFAudioProcessorEditor()
 | 
					// called when the stored window size changes
 | 
				
			||||||
{
 | 
					void JuicySFAudioProcessorEditor::valueChanged(Value&) {
 | 
				
			||||||
//    processor.unsubscribeFromStateChanges(this);
 | 
					    setSize(lastUIWidth.getValue(), lastUIHeight.getValue());
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
//void JuicySFAudioProcessorEditor::getStateInformation (XmlElement& xml) {
 | 
					JuicySFAudioProcessorEditor::~JuicySFAudioProcessorEditor()
 | 
				
			||||||
//    // save
 | 
					{
 | 
				
			||||||
//    xml.setAttribute ("uiWidth", getWidth());
 | 
					    lastUIWidth.removeListener(this);
 | 
				
			||||||
//    xml.setAttribute ("uiHeight", getHeight());
 | 
					    lastUIHeight.removeListener(this);
 | 
				
			||||||
//}
 | 
					}
 | 
				
			||||||
//
 | 
					 | 
				
			||||||
//void JuicySFAudioProcessorEditor::setStateInformation (XmlElement* xmlState) {
 | 
					 | 
				
			||||||
//    // load
 | 
					 | 
				
			||||||
//    setSize (xmlState->getIntAttribute ("uiWidth", getWidth()),
 | 
					 | 
				
			||||||
//            xmlState->getIntAttribute ("uiHeight", getHeight()));
 | 
					 | 
				
			||||||
//}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
//==============================================================================
 | 
					//==============================================================================
 | 
				
			||||||
void JuicySFAudioProcessorEditor::paint (Graphics& g)
 | 
					void JuicySFAudioProcessorEditor::paint (Graphics& g)
 | 
				
			||||||
@ -64,10 +71,6 @@ void JuicySFAudioProcessorEditor::paint (Graphics& g)
 | 
				
			|||||||
    // (Our component is opaque, so we must completely fill the background with a solid colour)
 | 
					    // (Our component is opaque, so we must completely fill the background with a solid colour)
 | 
				
			||||||
    g.fillAll (getLookAndFeel().findColour (ResizableWindow::backgroundColourId));
 | 
					    g.fillAll (getLookAndFeel().findColour (ResizableWindow::backgroundColourId));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
//    g.setColour (Colours::white);
 | 
					 | 
				
			||||||
//    g.setFont (15.0f);
 | 
					 | 
				
			||||||
//    g.drawFittedText ("Hello World!", getLocalBounds(), Justification::centred, 1);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (!focusInitialized) {
 | 
					    if (!focusInitialized) {
 | 
				
			||||||
        if (!hasKeyboardFocus(false) && isVisible()) {
 | 
					        if (!hasKeyboardFocus(false) && isVisible()) {
 | 
				
			||||||
            grabKeyboardFocus();
 | 
					            grabKeyboardFocus();
 | 
				
			||||||
@ -83,13 +86,9 @@ void JuicySFAudioProcessorEditor::resized()
 | 
				
			|||||||
    const int padding{8};
 | 
					    const int padding{8};
 | 
				
			||||||
    const int pianoHeight{70};
 | 
					    const int pianoHeight{70};
 | 
				
			||||||
    const int filePickerHeight{25};
 | 
					    const int filePickerHeight{25};
 | 
				
			||||||
    // const int slidersHeight{150};
 | 
					 | 
				
			||||||
    Rectangle<int> r{getLocalBounds()};
 | 
					    Rectangle<int> r{getLocalBounds()};
 | 
				
			||||||
    filePicker.setBounds(r.removeFromTop(filePickerHeight + padding).reduced(padding, 0).withTrimmedTop(padding));
 | 
					    filePicker.setBounds(r.removeFromTop(filePickerHeight + padding).reduced(padding, 0).withTrimmedTop(padding));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Rectangle<int> r2 (getLocalBounds());
 | 
					 | 
				
			||||||
    // slidersComponent.setBounds(r2.removeFromLeft(filePickerWidth + padding).reduced(padding, 0).withTrimmedLeft(padding));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    midiKeyboard.setBounds (r.removeFromBottom (pianoHeight).reduced(padding, 0));
 | 
					    midiKeyboard.setBounds (r.removeFromBottom (pianoHeight).reduced(padding, 0));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    Rectangle<int> rContent{r.reduced(0, padding)};
 | 
					    Rectangle<int> rContent{r.reduced(0, padding)};
 | 
				
			||||||
@ -97,28 +96,11 @@ void JuicySFAudioProcessorEditor::resized()
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    tablesComponent.setBounds(rContent);
 | 
					    tablesComponent.setBounds(rContent);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    lastUIWidth = getWidth();
 | 
				
			||||||
    processor.lastUIWidth = getWidth();
 | 
					    lastUIHeight = getHeight();
 | 
				
			||||||
    processor.lastUIHeight = getHeight();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
//    Rectangle<int> r2 (getLocalBounds());
 | 
					 | 
				
			||||||
//    r2.reduce(0, padding);
 | 
					 | 
				
			||||||
//    r2.removeFromBottom(pianoHeight);
 | 
					 | 
				
			||||||
//    r2.removeFromTop(filePickerHeight);
 | 
					 | 
				
			||||||
//    tablesComponent.setBounds (r2);
 | 
					 | 
				
			||||||
//
 | 
					 | 
				
			||||||
//    Rectangle<int> r3 (getLocalBounds());
 | 
					 | 
				
			||||||
//    r3.removeFromTop(filePickerHeight);
 | 
					 | 
				
			||||||
//
 | 
					 | 
				
			||||||
//    filePicker.setBounds(r3);
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
bool JuicySFAudioProcessorEditor::keyPressed(const KeyPress &key) {
 | 
					bool JuicySFAudioProcessorEditor::keyPressed(const KeyPress &key) {
 | 
				
			||||||
//    if (!hasKeyboardFocus(false))
 | 
					 | 
				
			||||||
//        return false;
 | 
					 | 
				
			||||||
//    if (key.getKeyCode() == KeyPress::upKey){
 | 
					 | 
				
			||||||
//    }
 | 
					 | 
				
			||||||
//    cout << "hey\n";
 | 
					 | 
				
			||||||
    const int cursorKeys[] = {
 | 
					    const int cursorKeys[] = {
 | 
				
			||||||
            KeyPress::leftKey,
 | 
					            KeyPress::leftKey,
 | 
				
			||||||
            KeyPress::rightKey,
 | 
					            KeyPress::rightKey,
 | 
				
			||||||
@ -134,20 +116,9 @@ bool JuicySFAudioProcessorEditor::keyPressed(const KeyPress &key) {
 | 
				
			|||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
        return midiKeyboard.keyPressed(key);
 | 
					        return midiKeyboard.keyPressed(key);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
//    for(auto childComponent : getChildren()) {
 | 
					 | 
				
			||||||
//        if (childComponent->keyPressed(key)) return true;
 | 
					 | 
				
			||||||
//    }
 | 
					 | 
				
			||||||
    return false;
 | 
					    return false;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
bool JuicySFAudioProcessorEditor::keyStateChanged (bool isKeyDown) {
 | 
					bool JuicySFAudioProcessorEditor::keyStateChanged (bool isKeyDown) {
 | 
				
			||||||
    return midiKeyboard.keyStateChanged(isKeyDown);
 | 
					    return midiKeyboard.keyStateChanged(isKeyDown);
 | 
				
			||||||
//    for(auto childComponent : getChildren()) {
 | 
					 | 
				
			||||||
//        if (childComponent->keyStateChanged(isKeyDown)) return true;
 | 
					 | 
				
			||||||
//    }
 | 
					 | 
				
			||||||
//    return false;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
FilePickerFragment& JuicySFAudioProcessorEditor::getFilePicker() {
 | 
					 | 
				
			||||||
    return filePicker;
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@ -14,21 +14,21 @@
 | 
				
			|||||||
#include "PluginProcessor.h"
 | 
					#include "PluginProcessor.h"
 | 
				
			||||||
#include "TablesComponent.h"
 | 
					#include "TablesComponent.h"
 | 
				
			||||||
#include "SurjectiveMidiKeyboardComponent.h"
 | 
					#include "SurjectiveMidiKeyboardComponent.h"
 | 
				
			||||||
#include "FilePickerFragment.h"
 | 
					 | 
				
			||||||
#include "ExposesComponents.h"
 | 
					 | 
				
			||||||
#include "FilePicker.h"
 | 
					#include "FilePicker.h"
 | 
				
			||||||
#include "StateChangeSubscriber.h"
 | 
					 | 
				
			||||||
#include "SlidersComponent.h"
 | 
					#include "SlidersComponent.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
//==============================================================================
 | 
					//==============================================================================
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
*/
 | 
					*/
 | 
				
			||||||
class JuicySFAudioProcessorEditor  : public AudioProcessorEditor,
 | 
					class JuicySFAudioProcessorEditor
 | 
				
			||||||
                                     public ExposesComponents/*,
 | 
					: public AudioProcessorEditor
 | 
				
			||||||
                                     public StateChangeSubscriber*/
 | 
					, private Value::Listener
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
public:
 | 
					public:
 | 
				
			||||||
    JuicySFAudioProcessorEditor (JuicySFAudioProcessor&);
 | 
					    JuicySFAudioProcessorEditor(
 | 
				
			||||||
 | 
					      JuicySFAudioProcessor&,
 | 
				
			||||||
 | 
					      AudioProcessorValueTreeState& valueTreeState
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
    ~JuicySFAudioProcessorEditor();
 | 
					    ~JuicySFAudioProcessorEditor();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    //==============================================================================
 | 
					    //==============================================================================
 | 
				
			||||||
@ -38,16 +38,20 @@ public:
 | 
				
			|||||||
    bool keyPressed(const KeyPress &key) override;
 | 
					    bool keyPressed(const KeyPress &key) override;
 | 
				
			||||||
    bool keyStateChanged (bool isKeyDown) override;
 | 
					    bool keyStateChanged (bool isKeyDown) override;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
//    void getStateInformation (XmlElement& xml) override;
 | 
					 | 
				
			||||||
//    void setStateInformation (XmlElement* xmlState) override;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    virtual FilePickerFragment& getFilePicker() override;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
private:
 | 
					private:
 | 
				
			||||||
 | 
					    void valueChanged (Value&) override;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // This reference is provided as a quick way for your editor to
 | 
					    // This reference is provided as a quick way for your editor to
 | 
				
			||||||
    // access the processor object that created it.
 | 
					    // access the processor object that created it.
 | 
				
			||||||
    JuicySFAudioProcessor& processor;
 | 
					    JuicySFAudioProcessor& processor;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    AudioProcessorValueTreeState& valueTreeState;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // these are used to persist the UI's size - the values are stored along with the
 | 
				
			||||||
 | 
					    // filter's other parameters, and the UI component will update them when it gets
 | 
				
			||||||
 | 
					    // resized.
 | 
				
			||||||
 | 
					    Value lastUIWidth, lastUIHeight;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    SurjectiveMidiKeyboardComponent midiKeyboard;
 | 
					    SurjectiveMidiKeyboardComponent midiKeyboard;
 | 
				
			||||||
    TablesComponent tablesComponent;
 | 
					    TablesComponent tablesComponent;
 | 
				
			||||||
    FilePicker filePicker;
 | 
					    FilePicker filePicker;
 | 
				
			||||||
 | 
				
			|||||||
@ -10,47 +10,66 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
#include "PluginProcessor.h"
 | 
					#include "PluginProcessor.h"
 | 
				
			||||||
#include "PluginEditor.h"
 | 
					#include "PluginEditor.h"
 | 
				
			||||||
#include "SoundfontSynthVoice.h"
 | 
					 | 
				
			||||||
#include "SoundfontSynthSound.h"
 | 
					 | 
				
			||||||
#include "ExposesComponents.h"
 | 
					 | 
				
			||||||
#include "MidiConstants.h"
 | 
					#include "MidiConstants.h"
 | 
				
			||||||
#include "Util.h"
 | 
					#include "Util.h"
 | 
				
			||||||
 | 
					#include "GuiConstants.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					using namespace std;
 | 
				
			||||||
 | 
					using Parameter = AudioProcessorValueTreeState::Parameter;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
AudioProcessor* JUCE_CALLTYPE createPluginFilter();
 | 
					AudioProcessor* JUCE_CALLTYPE createPluginFilter();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
//==============================================================================
 | 
					//==============================================================================
 | 
				
			||||||
JuicySFAudioProcessor::JuicySFAudioProcessor()
 | 
					JuicySFAudioProcessor::JuicySFAudioProcessor()
 | 
				
			||||||
     : AudioProcessor (getBusesProperties()),
 | 
					: AudioProcessor{getBusesProperties()}
 | 
				
			||||||
       lastUIWidth(400),
 | 
					, valueTreeState{
 | 
				
			||||||
       lastUIHeight(300),
 | 
					    *this,
 | 
				
			||||||
       soundFontPath(String()),
 | 
					    nullptr,
 | 
				
			||||||
       lastPreset(-1),
 | 
					    "MYPLUGINSETTINGS",
 | 
				
			||||||
       lastBank(-1),
 | 
					    createParameterLayout()}
 | 
				
			||||||
       fluidSynthModel(*this)/*,
 | 
					, fluidSynthModel{valueTreeState}
 | 
				
			||||||
       pluginEditor(nullptr)*/
 | 
					 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
 | 
					    valueTreeState.state.appendChild({ "uiState", {
 | 
				
			||||||
 | 
					            { "width", GuiConstants::minWidth },
 | 
				
			||||||
 | 
					            { "height", GuiConstants::minHeight }
 | 
				
			||||||
 | 
					        }, {} }, nullptr);
 | 
				
			||||||
 | 
					    valueTreeState.state.appendChild({ "soundFont", {
 | 
				
			||||||
 | 
					        { "path", "" },
 | 
				
			||||||
 | 
					    }, {} }, nullptr);
 | 
				
			||||||
 | 
					    // no properties, no subtrees (yet)
 | 
				
			||||||
 | 
					    valueTreeState.state.appendChild({ "banks", {}, {} }, nullptr);
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
    initialiseSynth();
 | 
					    initialiseSynth();
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					AudioProcessorValueTreeState::ParameterLayout JuicySFAudioProcessor::createParameterLayout() {
 | 
				
			||||||
 | 
					    // https://stackoverflow.com/a/8469002/5257399
 | 
				
			||||||
 | 
					    unique_ptr<AudioParameterInt> params[] {
 | 
				
			||||||
 | 
					        // SoundFont 2.4 spec section 7.2: zero through 127, or 128.
 | 
				
			||||||
 | 
					        make_unique<AudioParameterInt>("bank", "which bank is selected in the soundfont", MidiConstants::midiMinValue, 128, MidiConstants::midiMinValue, "Bank" ),
 | 
				
			||||||
 | 
					        // note: banks may be sparse, and lack a 0th preset. so defend against this.
 | 
				
			||||||
 | 
					        make_unique<AudioParameterInt>("preset", "which patch (aka patch, program, instrument) is selected in the soundfont", MidiConstants::midiMinValue, MidiConstants::midiMaxValue, MidiConstants::midiMinValue, "Preset" ),
 | 
				
			||||||
 | 
					        make_unique<AudioParameterInt>("attack", "volume envelope attack time", MidiConstants::midiMinValue, MidiConstants::midiMaxValue, MidiConstants::midiMinValue, "A" ),
 | 
				
			||||||
 | 
					        make_unique<AudioParameterInt>("decay", "volume envelope sustain attentuation", MidiConstants::midiMinValue, MidiConstants::midiMaxValue, MidiConstants::midiMinValue, "D" ),
 | 
				
			||||||
 | 
					        make_unique<AudioParameterInt>("sustain", "volume envelope decay time", MidiConstants::midiMinValue, MidiConstants::midiMaxValue, MidiConstants::midiMinValue, "S" ),
 | 
				
			||||||
 | 
					        make_unique<AudioParameterInt>("release", "volume envelope release time", MidiConstants::midiMinValue, MidiConstants::midiMaxValue, MidiConstants::midiMinValue, "R" ),
 | 
				
			||||||
 | 
					        make_unique<AudioParameterInt>("filterCutOff", "low-pass filter cut-off frequency", MidiConstants::midiMinValue, MidiConstants::midiMaxValue, MidiConstants::midiMinValue, "Cut" ),
 | 
				
			||||||
 | 
					        make_unique<AudioParameterInt>("filterResonance", "low-pass filter resonance attentuation", MidiConstants::midiMinValue, MidiConstants::midiMaxValue, MidiConstants::midiMinValue, "Res" ),
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    return {
 | 
				
			||||||
 | 
					        make_move_iterator(begin(params)),
 | 
				
			||||||
 | 
					        make_move_iterator(end(params))
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
JuicySFAudioProcessor::~JuicySFAudioProcessor()
 | 
					JuicySFAudioProcessor::~JuicySFAudioProcessor()
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
//    delete fluidSynthModel;
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void JuicySFAudioProcessor::initialiseSynth() {
 | 
					void JuicySFAudioProcessor::initialiseSynth() {
 | 
				
			||||||
    fluidSynthModel.initialise();
 | 
					    fluidSynthModel.initialise();
 | 
				
			||||||
 | 
					 | 
				
			||||||
    fluidSynth = fluidSynthModel.getSynth();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const int numVoices = 8;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // Add some voices...
 | 
					 | 
				
			||||||
    for (int i = numVoices; --i >= 0;)
 | 
					 | 
				
			||||||
        synth.addVoice (new SoundfontSynthVoice(fluidSynthModel.getSynth()));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // ..and give the synth a sound to play
 | 
					 | 
				
			||||||
    synth.addSound (new SoundfontSynthSound());
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
//==============================================================================
 | 
					//==============================================================================
 | 
				
			||||||
@ -84,22 +103,23 @@ double JuicySFAudioProcessor::getTailLengthSeconds() const
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
int JuicySFAudioProcessor::getNumPrograms()
 | 
					int JuicySFAudioProcessor::getNumPrograms()
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    return 1;   // NB: some hosts don't cope very well if you tell them there are 0 programs,
 | 
					    return fluidSynthModel.getNumPrograms();   // NB: some hosts don't cope very well if you tell them there are 0 programs,
 | 
				
			||||||
                // so this should be at least 1, even if you're not really implementing programs.
 | 
					                // so this should be at least 1, even if you're not really implementing programs.
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
int JuicySFAudioProcessor::getCurrentProgram()
 | 
					int JuicySFAudioProcessor::getCurrentProgram()
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    return 0;
 | 
					    return fluidSynthModel.getCurrentProgram();
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void JuicySFAudioProcessor::setCurrentProgram (int index)
 | 
					void JuicySFAudioProcessor::setCurrentProgram(int index)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
 | 
					    fluidSynthModel.setCurrentProgram(index);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const String JuicySFAudioProcessor::getProgramName (int index)
 | 
					const String JuicySFAudioProcessor::getProgramName(int index)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    return {};
 | 
					    return fluidSynthModel.getProgramName(index);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void JuicySFAudioProcessor::changeProgramName (int index, const String& newName)
 | 
					void JuicySFAudioProcessor::changeProgramName (int index, const String& newName)
 | 
				
			||||||
@ -148,89 +168,17 @@ AudioProcessor::BusesProperties JuicySFAudioProcessor::getBusesProperties() {
 | 
				
			|||||||
            .withOutput ("Output", AudioChannelSet::stereo(), true);
 | 
					            .withOutput ("Output", AudioChannelSet::stereo(), true);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void JuicySFAudioProcessor::processBlock (AudioBuffer<float>& buffer, MidiBuffer& midiMessages) {
 | 
					void JuicySFAudioProcessor::processBlock(AudioBuffer<float>& buffer, MidiBuffer& midiMessages) {
 | 
				
			||||||
    jassert (!isUsingDoublePrecision());
 | 
					    jassert (!isUsingDoublePrecision());
 | 
				
			||||||
    const int numSamples = buffer.getNumSamples();
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Now pass any incoming midi messages to our keyboard state object, and let it
 | 
					    // Now pass any incoming midi messages to our keyboard state object, and let it
 | 
				
			||||||
    // add messages to the buffer if the user is clicking on the on-screen keys
 | 
					    // add messages to the buffer if the user is clicking on the on-screen keys
 | 
				
			||||||
    keyboardState.processNextMidiBuffer (midiMessages, 0, numSamples, true);
 | 
					    keyboardState.processNextMidiBuffer(midiMessages, 0, buffer.getNumSamples(), true);
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    MidiBuffer processedMidi;
 | 
					    fluidSynthModel.processBlock(buffer, midiMessages);
 | 
				
			||||||
    int time;
 | 
					 | 
				
			||||||
    MidiMessage m;
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    // TODO: factor into a MidiCollector
 | 
					 | 
				
			||||||
    for (MidiBuffer::Iterator i (midiMessages); i.getNextEvent (m, time);) {
 | 
					 | 
				
			||||||
        DEBUG_PRINT ( m.getDescription() );
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        // explicitly not handling note_on/off, or pitch_bend, because these are (for better or worse)
 | 
					 | 
				
			||||||
        // responsibilities of SoundfontSynthVoice.
 | 
					 | 
				
			||||||
        // well, by that logic maybe I should move program change onto Voice. but it doesn't feel like a per-voice concern.
 | 
					 | 
				
			||||||
        if (m.isController()) {
 | 
					 | 
				
			||||||
            fluid_midi_event_t *midi_event(new_fluid_midi_event());
 | 
					 | 
				
			||||||
            fluid_midi_event_set_type(midi_event, static_cast<int>(CONTROL_CHANGE));
 | 
					 | 
				
			||||||
            fluid_midi_event_set_channel(midi_event, fluidSynthModel.getChannel());
 | 
					 | 
				
			||||||
            fluid_midi_event_set_control(midi_event, m.getControllerNumber());
 | 
					 | 
				
			||||||
            fluid_midi_event_set_value(midi_event, m.getControllerValue());
 | 
					 | 
				
			||||||
            fluid_synth_handle_midi_event(fluidSynth, midi_event);
 | 
					 | 
				
			||||||
            delete_fluid_midi_event(midi_event);
 | 
					 | 
				
			||||||
        } else if (m.isProgramChange()) {
 | 
					 | 
				
			||||||
            fluid_midi_event_t *midi_event(new_fluid_midi_event());
 | 
					 | 
				
			||||||
            fluid_midi_event_set_type(midi_event, static_cast<int>(PROGRAM_CHANGE));
 | 
					 | 
				
			||||||
            fluid_midi_event_set_channel(midi_event, fluidSynthModel.getChannel());
 | 
					 | 
				
			||||||
            fluid_midi_event_set_program(midi_event, m.getProgramChangeNumber());
 | 
					 | 
				
			||||||
            fluid_synth_handle_midi_event(fluidSynth, midi_event);
 | 
					 | 
				
			||||||
            delete_fluid_midi_event(midi_event);
 | 
					 | 
				
			||||||
        } else if (m.isPitchWheel()) {
 | 
					 | 
				
			||||||
            fluid_midi_event_t *midi_event(new_fluid_midi_event());
 | 
					 | 
				
			||||||
            fluid_midi_event_set_type(midi_event, static_cast<int>(PITCH_BEND));
 | 
					 | 
				
			||||||
            fluid_midi_event_set_channel(midi_event, fluidSynthModel.getChannel());
 | 
					 | 
				
			||||||
            fluid_midi_event_set_pitch(midi_event, m.getPitchWheelValue());
 | 
					 | 
				
			||||||
            fluid_synth_handle_midi_event(fluidSynth, midi_event);
 | 
					 | 
				
			||||||
            delete_fluid_midi_event(midi_event);
 | 
					 | 
				
			||||||
        } else if (m.isChannelPressure()) {
 | 
					 | 
				
			||||||
            fluid_midi_event_t *midi_event(new_fluid_midi_event());
 | 
					 | 
				
			||||||
            fluid_midi_event_set_type(midi_event, static_cast<int>(CHANNEL_PRESSURE));
 | 
					 | 
				
			||||||
            fluid_midi_event_set_channel(midi_event, fluidSynthModel.getChannel());
 | 
					 | 
				
			||||||
            fluid_midi_event_set_program(midi_event, m.getChannelPressureValue());
 | 
					 | 
				
			||||||
            fluid_synth_handle_midi_event(fluidSynth, midi_event);
 | 
					 | 
				
			||||||
            delete_fluid_midi_event(midi_event);
 | 
					 | 
				
			||||||
        } else if (m.isAftertouch()) {
 | 
					 | 
				
			||||||
            fluid_midi_event_t *midi_event(new_fluid_midi_event());
 | 
					 | 
				
			||||||
            fluid_midi_event_set_type(midi_event, static_cast<int>(KEY_PRESSURE));
 | 
					 | 
				
			||||||
            fluid_midi_event_set_channel(midi_event, fluidSynthModel.getChannel());
 | 
					 | 
				
			||||||
            fluid_midi_event_set_key(midi_event, m.getNoteNumber());
 | 
					 | 
				
			||||||
            fluid_midi_event_set_value(midi_event, m.getAfterTouchValue());
 | 
					 | 
				
			||||||
            fluid_synth_handle_midi_event(fluidSynth, midi_event);
 | 
					 | 
				
			||||||
            delete_fluid_midi_event(midi_event);
 | 
					 | 
				
			||||||
//        } else if (m.isMetaEvent()) {
 | 
					 | 
				
			||||||
//            fluid_midi_event_t *midi_event(new_fluid_midi_event());
 | 
					 | 
				
			||||||
//            fluid_midi_event_set_type(midi_event, static_cast<int>(MIDI_SYSTEM_RESET));
 | 
					 | 
				
			||||||
//            fluid_synth_handle_midi_event(fluidSynth, midi_event);
 | 
					 | 
				
			||||||
//            delete_fluid_midi_event(midi_event);
 | 
					 | 
				
			||||||
        } else if (m.isSysEx()) {
 | 
					 | 
				
			||||||
            fluid_midi_event_t *midi_event(new_fluid_midi_event());
 | 
					 | 
				
			||||||
            fluid_midi_event_set_type(midi_event, static_cast<int>(MIDI_SYSEX));
 | 
					 | 
				
			||||||
            // I assume that the MidiMessage's sysex buffer would be freed anyway when MidiMessage is destroyed, so set dynamic=false
 | 
					 | 
				
			||||||
            // to ensure that fluidsynth does not attempt to free the sysex buffer during delete_fluid_midi_event()
 | 
					 | 
				
			||||||
            fluid_midi_event_set_sysex(midi_event, const_cast<juce::uint8*>(m.getSysExData()), m.getSysExDataSize(), static_cast<int>(false));
 | 
					 | 
				
			||||||
            fluid_synth_handle_midi_event(fluidSynth, midi_event);
 | 
					 | 
				
			||||||
            delete_fluid_midi_event(midi_event);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
//    int pval;
 | 
					 | 
				
			||||||
    // 73: 64 attack
 | 
					 | 
				
			||||||
    // 75: decay
 | 
					 | 
				
			||||||
    // 79: sustain
 | 
					 | 
				
			||||||
    // 72: 64 release
 | 
					 | 
				
			||||||
//    fluid_synth_get_cc(fluidSynth, 0, 73, &pval);
 | 
					 | 
				
			||||||
//    Logger::outputDebugString ( juce::String::formatted("hey: %d\n", pval) );
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // and now get our synth to process these midi events and generate its output.
 | 
					    // and now get our synth to process these midi events and generate its output.
 | 
				
			||||||
    synth.renderNextBlock (buffer, midiMessages, 0, numSamples);
 | 
					    // synth.renderNextBlock(buffer, midiMessages, 0, numSamples);
 | 
				
			||||||
    fluid_synth_process(fluidSynth, numSamples, 0, nullptr, buffer.getNumChannels(), buffer.getArrayOfWritePointers());
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // (see juce_VST3_Wrapper.cpp for the assertion this would trip otherwise)
 | 
					    // (see juce_VST3_Wrapper.cpp for the assertion this would trip otherwise)
 | 
				
			||||||
    // we are !JucePlugin_ProducesMidiOutput, so clear remaining MIDI messages from our buffer
 | 
					    // we are !JucePlugin_ProducesMidiOutput, so clear remaining MIDI messages from our buffer
 | 
				
			||||||
@ -255,7 +203,7 @@ bool JuicySFAudioProcessor::hasEditor() const
 | 
				
			|||||||
AudioProcessorEditor* JuicySFAudioProcessor::createEditor()
 | 
					AudioProcessorEditor* JuicySFAudioProcessor::createEditor()
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    // grab a raw pointer to it for our own use
 | 
					    // grab a raw pointer to it for our own use
 | 
				
			||||||
    return /*pluginEditor = */new JuicySFAudioProcessorEditor (*this);
 | 
					    return /*pluginEditor = */new JuicySFAudioProcessorEditor (*this, valueTreeState);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
//==============================================================================
 | 
					//==============================================================================
 | 
				
			||||||
@ -266,27 +214,39 @@ void JuicySFAudioProcessor::getStateInformation (MemoryBlock& destData)
 | 
				
			|||||||
    // as intermediaries to make it easy to save and load complex data.
 | 
					    // as intermediaries to make it easy to save and load complex data.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Create an outer XML element..
 | 
					    // Create an outer XML element..
 | 
				
			||||||
    XmlElement xml ("MYPLUGINSETTINGS");
 | 
					    XmlElement xml{"MYPLUGINSETTINGS"};
 | 
				
			||||||
 | 
					 | 
				
			||||||
    // add some attributes to it..
 | 
					 | 
				
			||||||
    xml.setAttribute ("uiWidth", lastUIWidth);
 | 
					 | 
				
			||||||
    xml.setAttribute ("uiHeight", lastUIHeight);
 | 
					 | 
				
			||||||
    xml.setAttribute ("soundFontPath", soundFontPath);
 | 
					 | 
				
			||||||
    xml.setAttribute ("preset", lastPreset);
 | 
					 | 
				
			||||||
    xml.setAttribute ("bank", lastBank);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
//    list<StateChangeSubscriber*>::iterator p;
 | 
					 | 
				
			||||||
//    for(p = stateChangeSubscribers.begin(); p != stateChangeSubscribers.end(); p++) {
 | 
					 | 
				
			||||||
//        (*p)->getStateInformation(xml);
 | 
					 | 
				
			||||||
//    }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Store the values of all our parameters, using their param ID as the XML attribute
 | 
					    // Store the values of all our parameters, using their param ID as the XML attribute
 | 
				
			||||||
    for (auto* param : getParameters())
 | 
					    XmlElement* params{xml.createNewChildElement("params")};
 | 
				
			||||||
        if (auto* p = dynamic_cast<AudioProcessorParameterWithID*> (param))
 | 
					    for (auto* param : getParameters()) {
 | 
				
			||||||
            xml.setAttribute (p->paramID, p->getValue());
 | 
					         if (auto* p = dynamic_cast<AudioProcessorParameterWithID*> (param)) {
 | 
				
			||||||
 | 
					             params->setAttribute(p->paramID, p->getValue());
 | 
				
			||||||
 | 
					         }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        ValueTree tree{valueTreeState.state.getChildWithName("uiState")};
 | 
				
			||||||
 | 
					        XmlElement* newElement{xml.createNewChildElement("uiState")};
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            double value{tree.getProperty("width", GuiConstants::minWidth)};
 | 
				
			||||||
 | 
					            newElement->setAttribute("width", value);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            double value{tree.getProperty("height", GuiConstants::minHeight)};
 | 
				
			||||||
 | 
					            newElement->setAttribute("height", value);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        ValueTree tree{valueTreeState.state.getChildWithName("soundFont")};
 | 
				
			||||||
 | 
					        XmlElement* newElement{xml.createNewChildElement("soundFont")};
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            String value{tree.getProperty("path", "")};
 | 
				
			||||||
 | 
					            newElement->setAttribute("path", value);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    // then use this helper function to stuff it into the binary blob and return it..
 | 
					    DEBUG_PRINT(xml.createDocument("",false,false));
 | 
				
			||||||
    copyXmlToBinary (xml, destData);
 | 
					    
 | 
				
			||||||
 | 
					    copyXmlToBinary(xml, destData);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void JuicySFAudioProcessor::setStateInformation (const void* data, int sizeInBytes)
 | 
					void JuicySFAudioProcessor::setStateInformation (const void* data, int sizeInBytes)
 | 
				
			||||||
@ -294,84 +254,51 @@ void JuicySFAudioProcessor::setStateInformation (const void* data, int sizeInByt
 | 
				
			|||||||
    // You should use this method to restore your parameters from this memory block,
 | 
					    // You should use this method to restore your parameters from this memory block,
 | 
				
			||||||
    // whose contents will have been created by the getStateInformation() call.
 | 
					    // whose contents will have been created by the getStateInformation() call.
 | 
				
			||||||
    // This getXmlFromBinary() helper function retrieves our XML from the binary blob..
 | 
					    // This getXmlFromBinary() helper function retrieves our XML from the binary blob..
 | 
				
			||||||
    ScopedPointer<XmlElement> xmlState (getXmlFromBinary (data, sizeInBytes));
 | 
					    shared_ptr<XmlElement> xmlState{getXmlFromBinary(data, sizeInBytes)};
 | 
				
			||||||
 | 
					    DEBUG_PRINT(xmlState->createDocument("",false,false));
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    if (xmlState != nullptr)
 | 
					    if (xmlState.get() != nullptr) {
 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        // make sure that it's actually our type of XML object..
 | 
					        // make sure that it's actually our type of XML object..
 | 
				
			||||||
        if (xmlState->hasTagName ("MYPLUGINSETTINGS"))
 | 
					        if (xmlState->hasTagName(valueTreeState.state.getType())) {
 | 
				
			||||||
        {
 | 
					            XmlElement* params{xmlState->getChildByName("params")};
 | 
				
			||||||
//            list<StateChangeSubscriber*>::iterator p;
 | 
					            if (params)
 | 
				
			||||||
//            for(p = stateChangeSubscribers.begin(); p != stateChangeSubscribers.end(); p++) {
 | 
					 | 
				
			||||||
//                (*p)->setStateInformation(xmlState);
 | 
					 | 
				
			||||||
//            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            // ok, now pull out our last window size..
 | 
					 | 
				
			||||||
            lastUIWidth  = jmax (xmlState->getIntAttribute ("uiWidth", lastUIWidth), 400);
 | 
					 | 
				
			||||||
            lastUIHeight = jmax (xmlState->getIntAttribute ("uiHeight", lastUIHeight), 300);
 | 
					 | 
				
			||||||
            soundFontPath = xmlState->getStringAttribute ("soundFontPath", soundFontPath);
 | 
					 | 
				
			||||||
            lastPreset = xmlState->getIntAttribute ("preset", lastPreset);
 | 
					 | 
				
			||||||
            lastBank = xmlState->getIntAttribute ("bank", lastBank);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            // Now reload our parameters..
 | 
					 | 
				
			||||||
                for (auto* param : getParameters())
 | 
					                for (auto* param : getParameters())
 | 
				
			||||||
                if (auto* p = dynamic_cast<AudioProcessorParameterWithID*> (param))
 | 
					                    if (auto* p = dynamic_cast<AudioProcessorParameterWithID*>(param))
 | 
				
			||||||
                    p->setValue ((float) xmlState->getDoubleAttribute (p->paramID, p->getValue()));
 | 
					                        p->setValue(static_cast<float>(params->getDoubleAttribute(p->paramID, p->getValue())));
 | 
				
			||||||
            
 | 
					            
 | 
				
			||||||
            fluidSynthModel.onFileNameChanged(soundFontPath, lastBank, lastPreset);
 | 
					            {
 | 
				
			||||||
 | 
					                XmlElement* xmlElement{xmlState->getChildByName("soundFont")};
 | 
				
			||||||
            AudioProcessorEditor* editor = getActiveEditor();
 | 
					                if (xmlElement) {
 | 
				
			||||||
            if (editor != nullptr) {
 | 
					                    ValueTree tree{valueTreeState.state.getChildWithName("soundFont")};
 | 
				
			||||||
                editor->setSize(lastUIWidth, lastUIHeight);
 | 
					                    Value value{tree.getPropertyAsValue("path", nullptr)};
 | 
				
			||||||
 | 
					                    value = xmlElement->getStringAttribute("path", value.getValue());
 | 
				
			||||||
                jassert(dynamic_cast<ExposesComponents*> (editor) != nullptr);
 | 
					                }
 | 
				
			||||||
                ExposesComponents* exposesComponents = dynamic_cast<ExposesComponents*> (editor);
 | 
					            }
 | 
				
			||||||
                exposesComponents->getFilePicker().setDisplayedFilePath(soundFontPath);
 | 
					            {
 | 
				
			||||||
 | 
					                ValueTree tree{valueTreeState.state.getChildWithName("uiState")};
 | 
				
			||||||
 | 
					                XmlElement* xmlElement{xmlState->getChildByName("uiState")};
 | 
				
			||||||
 | 
					                if (xmlElement) {
 | 
				
			||||||
 | 
					                    {
 | 
				
			||||||
 | 
					                        Value value{tree.getPropertyAsValue("width", nullptr)};
 | 
				
			||||||
 | 
					                        value = xmlElement->getIntAttribute("width", value.getValue());
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                    {
 | 
				
			||||||
 | 
					                        Value value{tree.getPropertyAsValue("height", nullptr)};
 | 
				
			||||||
 | 
					                        value = xmlElement->getIntAttribute("height", value.getValue());
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					 | 
				
			||||||
//            const String& currentSoundFontAbsPath = fluidSynthModel->getCurrentSoundFontAbsPath();
 | 
					 | 
				
			||||||
//            if (currentSoundFontAbsPath.isNotEmpty()) {
 | 
					 | 
				
			||||||
//                fileChooser.setCurrentFile(File(currentSoundFontAbsPath), true, dontSendNotification);
 | 
					 | 
				
			||||||
//            }
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
//void JuicySFAudioProcessor::subscribeToStateChanges(StateChangeSubscriber* subscriber) {
 | 
					 | 
				
			||||||
//    stateChangeSubscribers.push_back(subscriber);
 | 
					 | 
				
			||||||
//}
 | 
					 | 
				
			||||||
//
 | 
					 | 
				
			||||||
//void JuicySFAudioProcessor::unsubscribeFromStateChanges(StateChangeSubscriber* subscriber) {
 | 
					 | 
				
			||||||
//    stateChangeSubscribers.remove(subscriber);
 | 
					 | 
				
			||||||
//}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// FluidSynth only supports float in its process function, so that's all we can support.
 | 
					// FluidSynth only supports float in its process function, so that's all we can support.
 | 
				
			||||||
bool JuicySFAudioProcessor::supportsDoublePrecisionProcessing() const {
 | 
					bool JuicySFAudioProcessor::supportsDoublePrecisionProcessing() const {
 | 
				
			||||||
    return false;
 | 
					    return false;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
FluidSynthModel* JuicySFAudioProcessor::getFluidSynthModel() {
 | 
					FluidSynthModel& JuicySFAudioProcessor::getFluidSynthModel() {
 | 
				
			||||||
    return &fluidSynthModel;
 | 
					    return fluidSynthModel;
 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void JuicySFAudioProcessor::setSoundFontPath(const String& value) {
 | 
					 | 
				
			||||||
    soundFontPath = value;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
String& JuicySFAudioProcessor::getSoundFontPath() {
 | 
					 | 
				
			||||||
    return soundFontPath;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
int JuicySFAudioProcessor::getPreset() {
 | 
					 | 
				
			||||||
    return lastPreset;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
int JuicySFAudioProcessor::getBank() {
 | 
					 | 
				
			||||||
    return lastBank;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
void JuicySFAudioProcessor::setPreset(int preset) {
 | 
					 | 
				
			||||||
    lastPreset = preset;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
void JuicySFAudioProcessor::setBank(int bank) {
 | 
					 | 
				
			||||||
    lastBank = bank;
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
//==============================================================================
 | 
					//==============================================================================
 | 
				
			||||||
 | 
				
			|||||||
@ -12,8 +12,6 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
#include "../JuceLibraryCode/JuceHeader.h"
 | 
					#include "../JuceLibraryCode/JuceHeader.h"
 | 
				
			||||||
#include "FluidSynthModel.h"
 | 
					#include "FluidSynthModel.h"
 | 
				
			||||||
#include "StateChangeSubscriber.h"
 | 
					 | 
				
			||||||
#include "SharesParams.h"
 | 
					 | 
				
			||||||
#include <list>
 | 
					#include <list>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
using namespace std;
 | 
					using namespace std;
 | 
				
			||||||
@ -21,8 +19,7 @@ using namespace std;
 | 
				
			|||||||
//==============================================================================
 | 
					//==============================================================================
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
*/
 | 
					*/
 | 
				
			||||||
class JuicySFAudioProcessor  : public AudioProcessor,
 | 
					class JuicySFAudioProcessor  : public AudioProcessor
 | 
				
			||||||
                               public SharesParams
 | 
					 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
public:
 | 
					public:
 | 
				
			||||||
    //==============================================================================
 | 
					    //==============================================================================
 | 
				
			||||||
@ -63,41 +60,22 @@ public:
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    bool supportsDoublePrecisionProcessing() const override;
 | 
					    bool supportsDoublePrecisionProcessing() const override;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    FluidSynthModel* getFluidSynthModel();
 | 
					    FluidSynthModel& getFluidSynthModel();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    MidiKeyboardState keyboardState;
 | 
					    MidiKeyboardState keyboardState;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    virtual void setSoundFontPath(const String& value) override;
 | 
					 | 
				
			||||||
    virtual String& getSoundFontPath() override;
 | 
					 | 
				
			||||||
    virtual int getPreset() override;
 | 
					 | 
				
			||||||
    virtual void setPreset(int preset) override;
 | 
					 | 
				
			||||||
    virtual int getBank() override;
 | 
					 | 
				
			||||||
    virtual void setBank(int bank) override;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
//    void subscribeToStateChanges(StateChangeSubscriber* subscriber);
 | 
					 | 
				
			||||||
//    void unsubscribeFromStateChanges(StateChangeSubscriber* subscriber);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    int lastUIWidth, lastUIHeight;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
private:
 | 
					private:
 | 
				
			||||||
    void initialiseSynth();
 | 
					    void initialiseSynth();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    String soundFontPath;
 | 
					    AudioProcessorValueTreeState valueTreeState;
 | 
				
			||||||
    int lastPreset;
 | 
					 | 
				
			||||||
    int lastBank;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    FluidSynthModel fluidSynthModel;
 | 
					    FluidSynthModel fluidSynthModel;
 | 
				
			||||||
    fluid_synth_t* fluidSynth;
 | 
					 | 
				
			||||||
    Synthesiser synth;
 | 
					    Synthesiser synth;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
//    // just a raw pointer; we do not own
 | 
					    AudioProcessorValueTreeState::ParameterLayout createParameterLayout();
 | 
				
			||||||
//    AudioProcessorEditor* pluginEditor;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
//    list<StateChangeSubscriber*> stateChangeSubscribers;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    static BusesProperties getBusesProperties();
 | 
					    static BusesProperties getBusesProperties();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
//    Model* model;
 | 
					 | 
				
			||||||
    //==============================================================================
 | 
					    //==============================================================================
 | 
				
			||||||
    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (JuicySFAudioProcessor)
 | 
					    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (JuicySFAudioProcessor)
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
				
			|||||||
@ -1,21 +0,0 @@
 | 
				
			|||||||
//
 | 
					 | 
				
			||||||
// Created by Alex Birch on 17/09/2017.
 | 
					 | 
				
			||||||
//
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#include "Preset.h"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
using namespace std;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Preset::Preset(
 | 
					 | 
				
			||||||
        int preset,
 | 
					 | 
				
			||||||
        string name
 | 
					 | 
				
			||||||
) : preset(preset),
 | 
					 | 
				
			||||||
    name(name) {}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
int Preset::getPreset() {
 | 
					 | 
				
			||||||
    return preset;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
string Preset::getName() {
 | 
					 | 
				
			||||||
    return name;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@ -1,24 +0,0 @@
 | 
				
			|||||||
//
 | 
					 | 
				
			||||||
// Created by Alex Birch on 17/09/2017.
 | 
					 | 
				
			||||||
//
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#pragma once
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#include "../JuceLibraryCode/JuceHeader.h"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
using namespace std;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class Preset {
 | 
					 | 
				
			||||||
public:
 | 
					 | 
				
			||||||
    Preset(
 | 
					 | 
				
			||||||
            int preset,
 | 
					 | 
				
			||||||
            string name
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    int getPreset();
 | 
					 | 
				
			||||||
    string getName();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
private:
 | 
					 | 
				
			||||||
    int preset;
 | 
					 | 
				
			||||||
    string name;
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
@ -1,10 +0,0 @@
 | 
				
			|||||||
//
 | 
					 | 
				
			||||||
// Created by Alex Birch on 17/09/2017.
 | 
					 | 
				
			||||||
//
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#pragma once
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#include "Preset.h"
 | 
					 | 
				
			||||||
#include <map>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
typedef std::multimap<int, Preset> BanksToPresets;
 | 
					 | 
				
			||||||
@ -1,23 +0,0 @@
 | 
				
			|||||||
//
 | 
					 | 
				
			||||||
// Created by Alex Birch on 10/04/2018.
 | 
					 | 
				
			||||||
// Copyright (c) 2018 Birchlabs. All rights reserved.
 | 
					 | 
				
			||||||
//
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#pragma once
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#include "../JuceLibraryCode/JuceHeader.h"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class SharesParams {
 | 
					 | 
				
			||||||
public:
 | 
					 | 
				
			||||||
    virtual ~SharesParams() {}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    virtual void setSoundFontPath(const String& value) = 0;
 | 
					 | 
				
			||||||
    virtual String& getSoundFontPath() = 0;
 | 
					 | 
				
			||||||
    virtual int getPreset() = 0;
 | 
					 | 
				
			||||||
    virtual void setPreset(int preset) = 0;
 | 
					 | 
				
			||||||
    virtual int getBank() = 0;
 | 
					 | 
				
			||||||
    virtual void setBank(int bank) = 0;
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@ -9,11 +9,21 @@
 | 
				
			|||||||
#include "SlidersComponent.h"
 | 
					#include "SlidersComponent.h"
 | 
				
			||||||
#include "FluidSynthModel.h"
 | 
					#include "FluidSynthModel.h"
 | 
				
			||||||
#include "MidiConstants.h"
 | 
					#include "MidiConstants.h"
 | 
				
			||||||
 | 
					#include "Util.h"
 | 
				
			||||||
 | 
					using SliderAttachment = AudioProcessorValueTreeState::SliderAttachment;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
std::function<void()> SlidersComponent::makeSliderListener(Slider& slider, int controller) {
 | 
					std::function<void()> SlidersComponent::makeSliderListener(Slider& slider, int controller) {
 | 
				
			||||||
    return [this, controller, &slider]{
 | 
					    return [this, controller, &slider]{
 | 
				
			||||||
 | 
					        // RangedAudioParameter *param{valueTreeState.getParameter("release")};
 | 
				
			||||||
 | 
					        // jassert(dynamic_cast<AudioParameterInt*>(param) != nullptr);
 | 
				
			||||||
 | 
					        // AudioParameterInt* castParam{dynamic_cast<AudioParameterInt*>(param)};
 | 
				
			||||||
 | 
					        // int value{castParam->get()};
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        // String s{"slider "};
 | 
				
			||||||
 | 
					        // s << slider.getComponentID() << ", controller " << controller << ", value " << slider.getValue() << ", xmlReleaseValue " << value;
 | 
				
			||||||
 | 
					        // DEBUG_PRINT(s);
 | 
				
			||||||
//        slider.setValue(slider.getValue(), NotificationType::dontSendNotification);
 | 
					//        slider.setValue(slider.getValue(), NotificationType::dontSendNotification);
 | 
				
			||||||
        fluidSynthModel->setControllerValue(controller, slider.getValue());
 | 
					        fluidSynthModel.setControllerValue(controller, slider.getValue());
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -58,10 +68,38 @@ void SlidersComponent::resized() {
 | 
				
			|||||||
    filterResonanceSlider.setBounds(rFilter.removeFromLeft(sliderWidth + sliderXMargin).withTrimmedTop(labelHeight).withTrimmedLeft(sliderXMargin));
 | 
					    filterResonanceSlider.setBounds(rFilter.removeFromLeft(sliderWidth + sliderXMargin).withTrimmedTop(labelHeight).withTrimmedLeft(sliderXMargin));
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
SlidersComponent::SlidersComponent(FluidSynthModel* fluidSynthModel) :
 | 
					void SlidersComponent::acceptMidiControlEvent(int controller, int value) {
 | 
				
			||||||
fluidSynthModel{fluidSynthModel},
 | 
					    switch(static_cast<fluid_midi_control_change>(controller)) {
 | 
				
			||||||
envelopeGroup{"envelopeGroup", "Envelope"},
 | 
					        case SOUND_CTRL2: // MIDI CC 71 Timbre/Harmonic Intensity (filter resonance)
 | 
				
			||||||
filterGroup{"filterGroup", "Filter"}
 | 
					            filterResonanceSlider.setValue(value, NotificationType::dontSendNotification);
 | 
				
			||||||
 | 
					            break;
 | 
				
			||||||
 | 
					        case SOUND_CTRL3: // MIDI CC 72 Release time
 | 
				
			||||||
 | 
					            releaseSlider.setValue(value, NotificationType::dontSendNotification);
 | 
				
			||||||
 | 
					            break;
 | 
				
			||||||
 | 
					        case SOUND_CTRL4: // MIDI CC 73 Attack time
 | 
				
			||||||
 | 
					            attackSlider.setValue(value, NotificationType::dontSendNotification);
 | 
				
			||||||
 | 
					            break;
 | 
				
			||||||
 | 
					        case SOUND_CTRL5: // MIDI CC 74 Brightness (cutoff frequency, FILTERFC)
 | 
				
			||||||
 | 
					            filterCutOffSlider.setValue(value, NotificationType::dontSendNotification);
 | 
				
			||||||
 | 
					            break;
 | 
				
			||||||
 | 
					        case SOUND_CTRL6: // MIDI CC 75 Decay Time
 | 
				
			||||||
 | 
					            decaySlider.setValue(value, NotificationType::dontSendNotification);
 | 
				
			||||||
 | 
					            break;
 | 
				
			||||||
 | 
					        case SOUND_CTRL10: // MIDI CC 79 undefined
 | 
				
			||||||
 | 
					            sustainSlider.setValue(value, NotificationType::dontSendNotification);
 | 
				
			||||||
 | 
					            break;
 | 
				
			||||||
 | 
					        default:
 | 
				
			||||||
 | 
					            break;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					SlidersComponent::SlidersComponent(
 | 
				
			||||||
 | 
					    AudioProcessorValueTreeState& valueTreeState,
 | 
				
			||||||
 | 
					    FluidSynthModel& fluidSynthModel)
 | 
				
			||||||
 | 
					: valueTreeState{valueTreeState}
 | 
				
			||||||
 | 
					, fluidSynthModel{fluidSynthModel}
 | 
				
			||||||
 | 
					, envelopeGroup{"envelopeGroup", "Envelope"}
 | 
				
			||||||
 | 
					, filterGroup{"filterGroup", "Filter"}
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    const Slider::SliderStyle style{Slider::SliderStyle::LinearVertical};
 | 
					    const Slider::SliderStyle style{Slider::SliderStyle::LinearVertical};
 | 
				
			||||||
    const double rangeMin(0);
 | 
					    const double rangeMin(0);
 | 
				
			||||||
@ -70,33 +108,39 @@ filterGroup{"filterGroup", "Filter"}
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    attackSlider.setSliderStyle(style);
 | 
					    attackSlider.setSliderStyle(style);
 | 
				
			||||||
    attackSlider.setRange(rangeMin, rangeMax, rangeStep);
 | 
					    attackSlider.setRange(rangeMin, rangeMax, rangeStep);
 | 
				
			||||||
    attackSlider.onValueChange = makeSliderListener(attackSlider, static_cast<int>(SOUND_CTRL4));
 | 
					    attackSlider.onDragEnd = makeSliderListener(attackSlider, static_cast<int>(SOUND_CTRL4));
 | 
				
			||||||
    attackSlider.setTextBoxStyle(Slider::TextBoxBelow, true, attackSlider.getTextBoxWidth(), attackSlider.getTextBoxHeight());
 | 
					    attackSlider.setTextBoxStyle(Slider::TextBoxBelow, true, attackSlider.getTextBoxWidth(), attackSlider.getTextBoxHeight());
 | 
				
			||||||
 | 
					    attackSliderAttachment = make_unique<SliderAttachment>(valueTreeState, "attack", attackSlider);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    decaySlider.setSliderStyle(style);
 | 
					    decaySlider.setSliderStyle(style);
 | 
				
			||||||
    decaySlider.setRange(rangeMin, rangeMax, rangeStep);
 | 
					    decaySlider.setRange(rangeMin, rangeMax, rangeStep);
 | 
				
			||||||
    decaySlider.onValueChange = makeSliderListener(decaySlider, static_cast<int>(SOUND_CTRL6));
 | 
					    decaySlider.onDragEnd = makeSliderListener(decaySlider, static_cast<int>(SOUND_CTRL6));
 | 
				
			||||||
    decaySlider.setTextBoxStyle(Slider::TextBoxBelow, true, decaySlider.getTextBoxWidth(), decaySlider.getTextBoxHeight());
 | 
					    decaySlider.setTextBoxStyle(Slider::TextBoxBelow, true, decaySlider.getTextBoxWidth(), decaySlider.getTextBoxHeight());
 | 
				
			||||||
 | 
					    decaySliderAttachment = make_unique<SliderAttachment>(valueTreeState, "decay", decaySlider);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    sustainSlider.setSliderStyle(style);
 | 
					    sustainSlider.setSliderStyle(style);
 | 
				
			||||||
    sustainSlider.setRange(rangeMin, rangeMax, rangeStep);
 | 
					    sustainSlider.setRange(rangeMin, rangeMax, rangeStep);
 | 
				
			||||||
    sustainSlider.onValueChange = makeSliderListener(sustainSlider, static_cast<int>(SOUND_CTRL10));
 | 
					    sustainSlider.onDragEnd = makeSliderListener(sustainSlider, static_cast<int>(SOUND_CTRL10));
 | 
				
			||||||
    sustainSlider.setTextBoxStyle(Slider::TextBoxBelow, true, sustainSlider.getTextBoxWidth(), sustainSlider.getTextBoxHeight());
 | 
					    sustainSlider.setTextBoxStyle(Slider::TextBoxBelow, true, sustainSlider.getTextBoxWidth(), sustainSlider.getTextBoxHeight());
 | 
				
			||||||
 | 
					    sustainSliderAttachment = make_unique<SliderAttachment>(valueTreeState, "sustain", sustainSlider);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    releaseSlider.setSliderStyle(style);
 | 
					    releaseSlider.setSliderStyle(style);
 | 
				
			||||||
    releaseSlider.setRange(rangeMin, rangeMax, rangeStep);
 | 
					    releaseSlider.setRange(rangeMin, rangeMax, rangeStep);
 | 
				
			||||||
    releaseSlider.onValueChange = makeSliderListener(releaseSlider, static_cast<int>(SOUND_CTRL3));
 | 
					    releaseSlider.onDragEnd = makeSliderListener(releaseSlider, static_cast<int>(SOUND_CTRL3));
 | 
				
			||||||
    releaseSlider.setTextBoxStyle(Slider::TextBoxBelow, true, releaseSlider.getTextBoxWidth(), releaseSlider.getTextBoxHeight());
 | 
					    releaseSlider.setTextBoxStyle(Slider::TextBoxBelow, true, releaseSlider.getTextBoxWidth(), releaseSlider.getTextBoxHeight());
 | 
				
			||||||
 | 
					    releaseSliderAttachment = make_unique<SliderAttachment>(valueTreeState, "release", releaseSlider);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    filterCutOffSlider.setSliderStyle(style);
 | 
					    filterCutOffSlider.setSliderStyle(style);
 | 
				
			||||||
    filterCutOffSlider.setRange(rangeMin, rangeMax, rangeStep);
 | 
					    filterCutOffSlider.setRange(rangeMin, rangeMax, rangeStep);
 | 
				
			||||||
    filterCutOffSlider.onValueChange = makeSliderListener(filterCutOffSlider, static_cast<int>(SOUND_CTRL5));
 | 
					    filterCutOffSlider.onDragEnd = makeSliderListener(filterCutOffSlider, static_cast<int>(SOUND_CTRL5));
 | 
				
			||||||
    filterCutOffSlider.setTextBoxStyle(Slider::TextBoxBelow, true, filterCutOffSlider.getTextBoxWidth(), filterCutOffSlider.getTextBoxHeight());
 | 
					    filterCutOffSlider.setTextBoxStyle(Slider::TextBoxBelow, true, filterCutOffSlider.getTextBoxWidth(), filterCutOffSlider.getTextBoxHeight());
 | 
				
			||||||
 | 
					    filterCutOffSliderAttachment = make_unique<SliderAttachment>(valueTreeState, "filterCutOff", filterCutOffSlider);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    filterResonanceSlider.setSliderStyle(style);
 | 
					    filterResonanceSlider.setSliderStyle(style);
 | 
				
			||||||
    filterResonanceSlider.setRange(rangeMin, rangeMax, rangeStep);
 | 
					    filterResonanceSlider.setRange(rangeMin, rangeMax, rangeStep);
 | 
				
			||||||
    filterResonanceSlider.onValueChange = makeSliderListener(filterResonanceSlider, static_cast<int>(SOUND_CTRL2));
 | 
					    filterResonanceSlider.onDragEnd = makeSliderListener(filterResonanceSlider, static_cast<int>(SOUND_CTRL2));
 | 
				
			||||||
    filterResonanceSlider.setTextBoxStyle(Slider::TextBoxBelow, true, filterResonanceSlider.getTextBoxWidth(), filterResonanceSlider.getTextBoxHeight());
 | 
					    filterResonanceSlider.setTextBoxStyle(Slider::TextBoxBelow, true, filterResonanceSlider.getTextBoxWidth(), filterResonanceSlider.getTextBoxHeight());
 | 
				
			||||||
 | 
					    filterResonanceSliderAttachment = make_unique<SliderAttachment>(valueTreeState, "filterResonance", filterResonanceSlider);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    addAndMakeVisible(attackSlider);
 | 
					    addAndMakeVisible(attackSlider);
 | 
				
			||||||
    addAndMakeVisible(decaySlider);
 | 
					    addAndMakeVisible(decaySlider);
 | 
				
			||||||
 | 
				
			|||||||
@ -4,43 +4,55 @@
 | 
				
			|||||||
#include "FluidSynthModel.h"
 | 
					#include "FluidSynthModel.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
using namespace std;
 | 
					using namespace std;
 | 
				
			||||||
 | 
					using SliderAttachment = AudioProcessorValueTreeState::SliderAttachment;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class SlidersComponent : public Component
 | 
					class SlidersComponent : public Component
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
public:
 | 
					public:
 | 
				
			||||||
    SlidersComponent(FluidSynthModel* fluidSynthModel);
 | 
					    SlidersComponent(
 | 
				
			||||||
 | 
					        AudioProcessorValueTreeState& valueTreeState,
 | 
				
			||||||
 | 
					        FluidSynthModel& fluidSynthModel);
 | 
				
			||||||
    ~SlidersComponent();
 | 
					    ~SlidersComponent();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    void resized() override;
 | 
					    void resized() override;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const int getDesiredWidth();
 | 
					    const int getDesiredWidth();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    void acceptMidiControlEvent(int controller, int value);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
private:
 | 
					private:
 | 
				
			||||||
    std::function<void()> makeSliderListener(Slider& slider, int controller);
 | 
					    std::function<void()> makeSliderListener(Slider& slider, int controller);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    FluidSynthModel* fluidSynthModel;
 | 
					    AudioProcessorValueTreeState& valueTreeState;
 | 
				
			||||||
 | 
					    FluidSynthModel& fluidSynthModel;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    GroupComponent envelopeGroup;
 | 
					    GroupComponent envelopeGroup;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    Slider attackSlider;
 | 
					    Slider attackSlider;
 | 
				
			||||||
    Label attackLabel;
 | 
					    Label attackLabel;
 | 
				
			||||||
 | 
					    unique_ptr<SliderAttachment> attackSliderAttachment;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    Slider decaySlider;
 | 
					    Slider decaySlider;
 | 
				
			||||||
    Label decayLabel;
 | 
					    Label decayLabel;
 | 
				
			||||||
 | 
					    unique_ptr<SliderAttachment> decaySliderAttachment;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    Slider sustainSlider;
 | 
					    Slider sustainSlider;
 | 
				
			||||||
    Label sustainLabel;
 | 
					    Label sustainLabel;
 | 
				
			||||||
 | 
					    unique_ptr<SliderAttachment> sustainSliderAttachment;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    Slider releaseSlider;
 | 
					    Slider releaseSlider;
 | 
				
			||||||
    Label releaseLabel;
 | 
					    Label releaseLabel;
 | 
				
			||||||
 | 
					    unique_ptr<SliderAttachment> releaseSliderAttachment;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    GroupComponent filterGroup;
 | 
					    GroupComponent filterGroup;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    Slider filterCutOffSlider;
 | 
					    Slider filterCutOffSlider;
 | 
				
			||||||
    Label filterCutOffLabel;
 | 
					    Label filterCutOffLabel;
 | 
				
			||||||
 | 
					    unique_ptr<SliderAttachment> filterCutOffSliderAttachment;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    Slider filterResonanceSlider;
 | 
					    Slider filterResonanceSlider;
 | 
				
			||||||
    Label filterResonanceLabel;
 | 
					    Label filterResonanceLabel;
 | 
				
			||||||
 | 
					    unique_ptr<SliderAttachment> filterResonanceSliderAttachment;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (SlidersComponent)
 | 
					    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (SlidersComponent)
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
				
			|||||||
@ -1,13 +0,0 @@
 | 
				
			|||||||
//
 | 
					 | 
				
			||||||
// Created by Alex Birch on 07/09/2017.
 | 
					 | 
				
			||||||
//
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#include "SoundfontSynthSound.h"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
bool SoundfontSynthSound::appliesToChannel(int) {
 | 
					 | 
				
			||||||
    return true;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
bool SoundfontSynthSound::appliesToNote(int) {
 | 
					 | 
				
			||||||
    return true;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@ -1,13 +0,0 @@
 | 
				
			|||||||
//
 | 
					 | 
				
			||||||
// Created by Alex Birch on 07/09/2017.
 | 
					 | 
				
			||||||
//
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#pragma once
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#include "../JuceLibraryCode/JuceHeader.h"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class SoundfontSynthSound : public SynthesiserSound {
 | 
					 | 
				
			||||||
public:
 | 
					 | 
				
			||||||
    bool appliesToNote (int /*midiNoteNumber*/) override;
 | 
					 | 
				
			||||||
    bool appliesToChannel (int /*midiChannel*/) override;
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
@ -1,109 +0,0 @@
 | 
				
			|||||||
//
 | 
					 | 
				
			||||||
// Created by Alex Birch on 07/09/2017.
 | 
					 | 
				
			||||||
//
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#include "SoundfontSynthVoice.h"
 | 
					 | 
				
			||||||
#include "SoundfontSynthSound.h"
 | 
					 | 
				
			||||||
#include "Util.h"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
SoundfontSynthVoice::SoundfontSynthVoice(fluid_synth_t* synth)
 | 
					 | 
				
			||||||
: tailOff (0.0),
 | 
					 | 
				
			||||||
level(0.0),
 | 
					 | 
				
			||||||
currentAngle(0.0),
 | 
					 | 
				
			||||||
angleDelta(0.0),
 | 
					 | 
				
			||||||
midiNoteNumber(0),
 | 
					 | 
				
			||||||
synth(synth)
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
bool SoundfontSynthVoice::canPlaySound(SynthesiserSound* sound) {
 | 
					 | 
				
			||||||
    return dynamic_cast<SoundfontSynthSound*> (sound) != nullptr;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
void SoundfontSynthVoice::startNote(
 | 
					 | 
				
			||||||
        int midiNoteNumber,
 | 
					 | 
				
			||||||
        float velocity,
 | 
					 | 
				
			||||||
        SynthesiserSound* sound,
 | 
					 | 
				
			||||||
        int /*currentPitchWheelPosition*/) {
 | 
					 | 
				
			||||||
    this->midiNoteNumber = midiNoteNumber;
 | 
					 | 
				
			||||||
    DEBUG_PRINT ( juce::String::formatted("JUCE noteon: %d, %d\n", midiNoteNumber, velocity) );
 | 
					 | 
				
			||||||
    fluid_synth_noteon(synth, 0, midiNoteNumber, static_cast<int>(velocity * 127));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
//    currentAngle = 0.0;
 | 
					 | 
				
			||||||
//    level = velocity * 0.15;
 | 
					 | 
				
			||||||
//    tailOff = 0.0;
 | 
					 | 
				
			||||||
//
 | 
					 | 
				
			||||||
//    double cyclesPerSecond = MidiMessage::getMidiNoteInHertz (midiNoteNumber);
 | 
					 | 
				
			||||||
//    double cyclesPerSample = cyclesPerSecond / getSampleRate();
 | 
					 | 
				
			||||||
//
 | 
					 | 
				
			||||||
//    angleDelta = cyclesPerSample * 2.0 * double_Pi;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
//    jassert(dynamic_cast<SoundfontSynthSound*> (sound) != nullptr);
 | 
					 | 
				
			||||||
//    SoundfontSynthSound* sfsynth = dynamic_cast<SoundfontSynthSound*> (sound);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void SoundfontSynthVoice::stopNote (float /*velocity*/, bool allowTailOff) {
 | 
					 | 
				
			||||||
//    if (allowTailOff) {
 | 
					 | 
				
			||||||
//        // start a tail-off by setting this flag. The render callback will pick up on
 | 
					 | 
				
			||||||
//        // this and do a fade out, calling clearCurrentNote() when it's finished.
 | 
					 | 
				
			||||||
//
 | 
					 | 
				
			||||||
//        // we only need to begin a tail-off if it's not already doing so - the
 | 
					 | 
				
			||||||
//        if (tailOff == 0.0) {
 | 
					 | 
				
			||||||
//            // stopNote method could be called more than once.
 | 
					 | 
				
			||||||
//            tailOff = 1.0;
 | 
					 | 
				
			||||||
//        }
 | 
					 | 
				
			||||||
//    } else {
 | 
					 | 
				
			||||||
//        // we're being told to stop playing immediately, so reset everything..
 | 
					 | 
				
			||||||
//
 | 
					 | 
				
			||||||
//        clearCurrentNote();
 | 
					 | 
				
			||||||
//        angleDelta = 0.0;
 | 
					 | 
				
			||||||
//    }
 | 
					 | 
				
			||||||
    DEBUG_PRINT ( juce::String("JUCE noteoff\n") );
 | 
					 | 
				
			||||||
    clearCurrentNote();
 | 
					 | 
				
			||||||
    fluid_synth_noteoff(synth, 0, this->midiNoteNumber);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// receives input as MIDI 0 to 16383, with 8192 being center
 | 
					 | 
				
			||||||
// this is also exactly the input fluidsynth requires
 | 
					 | 
				
			||||||
void SoundfontSynthVoice::pitchWheelMoved (int newValue) {
 | 
					 | 
				
			||||||
//    fluid_synth_pitch_bend(synth, 0, newValue);
 | 
					 | 
				
			||||||
//    int ppitch_bend;
 | 
					 | 
				
			||||||
//    fluid_synth_get_pitch_bend(synth, 0, &ppitch_bend);
 | 
					 | 
				
			||||||
//    int ppitch_bend_sens;
 | 
					 | 
				
			||||||
//    fluid_synth_get_pitch_wheel_sens(synth, 0, &ppitch_bend_sens);
 | 
					 | 
				
			||||||
//    Logger::outputDebugString ( juce::String::formatted("Pitch wheel: %d %d %d\n", newValue, ppitch_bend, ppitch_bend_sens) );
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void SoundfontSynthVoice::controllerMoved (int controllerNumber, int newValue) {
 | 
					 | 
				
			||||||
    // this seems to be "program change" event
 | 
					 | 
				
			||||||
    DEBUG_PRINT ( juce::String::formatted("Controller moved: %d, %d\n", controllerNumber, newValue) );
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void SoundfontSynthVoice::renderNextBlock (AudioBuffer<float>& outputBuffer, int startSample, int numSamples) {
 | 
					 | 
				
			||||||
    //fluid_synth_process(synth.get(), numSamples, 1, nullptr, outputBuffer.getNumChannels(), outputBuffer.getArrayOfWritePointers());
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
//void SoundfontSynthVoice::renderBlock (AudioBuffer<float>& outputBuffer, int startSample, int numSamples) {
 | 
					 | 
				
			||||||
//    fluid_synth_process(synth.get(), numSamples, 1, nullptr, outputBuffer.getNumChannels(), outputBuffer.getArrayOfWritePointers());
 | 
					 | 
				
			||||||
//    if (angleDelta == 0.0) {
 | 
					 | 
				
			||||||
//        return;
 | 
					 | 
				
			||||||
//    }
 | 
					 | 
				
			||||||
//    while (--numSamples >= 0) {
 | 
					 | 
				
			||||||
//        double qualifiedTailOff = tailOff > 0 ? tailOff : 1.0;
 | 
					 | 
				
			||||||
//        auto currentSample = static_cast<FloatType> (std::sin (currentAngle) * level * qualifiedTailOff);
 | 
					 | 
				
			||||||
//        for (int i = outputBuffer.getNumChannels(); --i >= 0;)
 | 
					 | 
				
			||||||
//            outputBuffer.addSample (i, startSample, currentSample);
 | 
					 | 
				
			||||||
//
 | 
					 | 
				
			||||||
//        currentAngle += angleDelta;
 | 
					 | 
				
			||||||
//        ++startSample;
 | 
					 | 
				
			||||||
//
 | 
					 | 
				
			||||||
//        if (tailOff > 0) {
 | 
					 | 
				
			||||||
//            tailOff *= 0.99;
 | 
					 | 
				
			||||||
//
 | 
					 | 
				
			||||||
//            if (tailOff <= 0.005) {
 | 
					 | 
				
			||||||
//                clearCurrentNote();
 | 
					 | 
				
			||||||
//                angleDelta = 0.0;
 | 
					 | 
				
			||||||
//                break;
 | 
					 | 
				
			||||||
//            }
 | 
					 | 
				
			||||||
//        }
 | 
					 | 
				
			||||||
//    }
 | 
					 | 
				
			||||||
//}
 | 
					 | 
				
			||||||
@ -1,39 +0,0 @@
 | 
				
			|||||||
//
 | 
					 | 
				
			||||||
// Created by Alex Birch on 07/09/2017.
 | 
					 | 
				
			||||||
//
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#pragma once
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#include<memory>
 | 
					 | 
				
			||||||
#include<fluidsynth.h>
 | 
					 | 
				
			||||||
#include "../JuceLibraryCode/JuceHeader.h"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
using std::shared_ptr;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class SoundfontSynthVoice : public SynthesiserVoice {
 | 
					 | 
				
			||||||
public:
 | 
					 | 
				
			||||||
    SoundfontSynthVoice(fluid_synth_t* synth);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    bool canPlaySound (SynthesiserSound* sound) override;
 | 
					 | 
				
			||||||
    void startNote (
 | 
					 | 
				
			||||||
            int midiNoteNumber,
 | 
					 | 
				
			||||||
            float velocity,
 | 
					 | 
				
			||||||
            SynthesiserSound* /*sound*/,
 | 
					 | 
				
			||||||
            int /*currentPitchWheelPosition*/) override;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    void stopNote (float /*velocity*/, bool allowTailOff) override;
 | 
					 | 
				
			||||||
    void pitchWheelMoved (int /*newValue*/) override;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    void controllerMoved (int /*controllerNumber*/, int /*newValue*/) override;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    void renderNextBlock (AudioBuffer<float>& outputBuffer, int startSample, int numSamples) override;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
private:
 | 
					 | 
				
			||||||
    double tailOff;
 | 
					 | 
				
			||||||
    double level;
 | 
					 | 
				
			||||||
    double currentAngle;
 | 
					 | 
				
			||||||
    double angleDelta;
 | 
					 | 
				
			||||||
    int midiNoteNumber;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    fluid_synth_t* synth;
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
@ -1,15 +0,0 @@
 | 
				
			|||||||
//
 | 
					 | 
				
			||||||
// Created by Alex Birch on 18/03/2018.
 | 
					 | 
				
			||||||
// Copyright (c) 2018 Birchlabs. All rights reserved.
 | 
					 | 
				
			||||||
//
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#pragma once
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#include "../JuceLibraryCode/JuceHeader.h"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class StateChangeSubscriber {
 | 
					 | 
				
			||||||
public:
 | 
					 | 
				
			||||||
    virtual ~StateChangeSubscriber() {} // pass pointer ownership to another party without exposing the concrete derived class
 | 
					 | 
				
			||||||
    virtual void getStateInformation (XmlElement& xml) = 0;
 | 
					 | 
				
			||||||
    virtual void setStateInformation (XmlElement* xmlState) = 0;
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
@ -7,25 +7,23 @@
 | 
				
			|||||||
//
 | 
					//
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include "TableComponent.h"
 | 
					#include "TableComponent.h"
 | 
				
			||||||
 | 
					#include "Util.h"
 | 
				
			||||||
 | 
					#include <functional>
 | 
				
			||||||
 | 
					#include <iterator>
 | 
				
			||||||
 | 
					#include <map>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
using namespace std;
 | 
					using namespace std;
 | 
				
			||||||
 | 
					using namespace Util;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
//==============================================================================
 | 
					//==============================================================================
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
    This class shows how to implement a TableListBoxModel to show in a TableListBox.
 | 
					    This class shows how to implement a TableListBoxModel to show in a TableListBox.
 | 
				
			||||||
*/
 | 
					*/
 | 
				
			||||||
TableComponent::TableComponent(
 | 
					TableComponent::TableComponent(
 | 
				
			||||||
        const vector<string> &columns,
 | 
					    AudioProcessorValueTreeState& valueTreeState
 | 
				
			||||||
        const vector<vector<string>> &rows,
 | 
					 | 
				
			||||||
        const function<void (int)> &onRowSelected,
 | 
					 | 
				
			||||||
        const function<int (const vector<string>&)> &rowToIDMapper,
 | 
					 | 
				
			||||||
        int initiallySelectedRow
 | 
					 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
        : font (14.0f),
 | 
					: valueTreeState{valueTreeState}
 | 
				
			||||||
          columns(columns),
 | 
					, font{14.0f}
 | 
				
			||||||
          rows(rows),
 | 
					 | 
				
			||||||
          onRowSelected(onRowSelected),
 | 
					 | 
				
			||||||
          rowToIDMapper(rowToIDMapper)
 | 
					 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    // Create our table component and add it to this component..
 | 
					    // Create our table component and add it to this component..
 | 
				
			||||||
    addAndMakeVisible (table);
 | 
					    addAndMakeVisible (table);
 | 
				
			||||||
@ -37,43 +35,104 @@ TableComponent::TableComponent(
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    int columnIx = 1;
 | 
					    int columnIx = 1;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Add some columns to the table header, based on the column list in our database..
 | 
					 | 
				
			||||||
    for (auto &column : columns) // access by reference to avoid copying
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        const int colWidth{ columnIx == 1 ? 30 : 200 };
 | 
					 | 
				
			||||||
    table.getHeader().addColumn (
 | 
					    table.getHeader().addColumn (
 | 
				
			||||||
                String(column),
 | 
					            String("#"),
 | 
				
			||||||
            columnIx++,
 | 
					            columnIx++,
 | 
				
			||||||
                colWidth, // column width
 | 
					            30, // column width
 | 
				
			||||||
 | 
					            30, // min width
 | 
				
			||||||
 | 
					            400, // max width
 | 
				
			||||||
 | 
					            TableHeaderComponent::defaultFlags
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					    table.getHeader().addColumn (
 | 
				
			||||||
 | 
					            String("Name"),
 | 
				
			||||||
 | 
					            columnIx++,
 | 
				
			||||||
 | 
					            200, // column width
 | 
				
			||||||
            30, // min width
 | 
					            30, // min width
 | 
				
			||||||
            400, // max width
 | 
					            400, // max width
 | 
				
			||||||
            TableHeaderComponent::defaultFlags
 | 
					            TableHeaderComponent::defaultFlags
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    table.setWantsKeyboardFocus(false);
 | 
					    table.setWantsKeyboardFocus(false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    table.selectRow(initiallySelectedRow);
 | 
					    ValueTree banks{valueTreeState.state.getChildWithName("banks")};
 | 
				
			||||||
 | 
					    loadModelFrom(banks);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // we could now change some initial settings..
 | 
					    // we could now change some initial settings..
 | 
				
			||||||
    table.getHeader().setSortColumnId (1, false); // sort ascending by ID column
 | 
					    table.getHeader().setSortColumnId(1, false); // sort ascending by ID column
 | 
				
			||||||
//    table.getHeader().setColumnVisible (7, false); // hide the "length" column until the user shows it
 | 
					    valueTreeState.state.addListener(this);
 | 
				
			||||||
 | 
					    valueTreeState.addParameterListener("bank", this);
 | 
				
			||||||
    // un-comment this line to have a go of stretch-to-fit mode
 | 
					    valueTreeState.addParameterListener("preset", this);
 | 
				
			||||||
    // table.getHeader().setStretchToFitActive (true);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
//    table.setMultipleSelectionEnabled (false);
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void TableComponent::setRows(const vector<vector<string>>& rows, int initiallySelectedRow) {
 | 
					TableComponent::~TableComponent() {
 | 
				
			||||||
    this->rows = rows;
 | 
					    valueTreeState.removeParameterListener("bank", this);
 | 
				
			||||||
 | 
					    valueTreeState.removeParameterListener("preset", this);
 | 
				
			||||||
 | 
					    valueTreeState.state.removeListener(this);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void TableComponent::loadModelFrom(ValueTree& banks) {
 | 
				
			||||||
 | 
					    banksToPresets.clear();
 | 
				
			||||||
 | 
					    int banksChildren{banks.getNumChildren()};
 | 
				
			||||||
 | 
					    for(int bankIx{0}; bankIx<banksChildren; bankIx++) {
 | 
				
			||||||
 | 
					        ValueTree bank{banks.getChild(bankIx)};
 | 
				
			||||||
 | 
					        int bankNum{bank.getProperty("num")};
 | 
				
			||||||
 | 
					        int bankChildren{bank.getNumChildren()};
 | 
				
			||||||
 | 
					        for(int presetIx{0}; presetIx<bankChildren; presetIx++) {
 | 
				
			||||||
 | 
					            ValueTree preset{bank.getChild(presetIx)};
 | 
				
			||||||
 | 
					            int presetNum{preset.getProperty("num")};
 | 
				
			||||||
 | 
					            String presetName{preset.getProperty("name")};
 | 
				
			||||||
 | 
					            TableRow row{presetNum, move(presetName)};
 | 
				
			||||||
 | 
					            banksToPresets.emplace(bankNum, move(row));
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    repopulateTable();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void TableComponent::parameterChanged(const String& parameterID, float newValue) {
 | 
				
			||||||
 | 
					    if (parameterID == "bank") {
 | 
				
			||||||
 | 
					        repopulateTable();
 | 
				
			||||||
 | 
					    } else if (parameterID == "preset") {
 | 
				
			||||||
 | 
					        selectCurrentPreset();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void TableComponent::repopulateTable() {
 | 
				
			||||||
 | 
					    rows.clear();
 | 
				
			||||||
 | 
					    RangedAudioParameter *param{valueTreeState.getParameter("bank")};
 | 
				
			||||||
 | 
					    jassert(dynamic_cast<AudioParameterInt*>(param) != nullptr);
 | 
				
			||||||
 | 
					    AudioParameterInt* castParam{dynamic_cast<AudioParameterInt*>(param)};
 | 
				
			||||||
 | 
					    int bank{castParam->get()};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    BanksToPresets::iterator lowerBound{banksToPresets.lower_bound(bank)};
 | 
				
			||||||
 | 
					    BanksToPresets::iterator upperBound{banksToPresets.upper_bound(bank)};
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    // basic syntaxes for a lambda which return's a pair's .second
 | 
				
			||||||
 | 
					    // https://stackoverflow.com/questions/2568194/populate-a-vector-with-all-multimap-values-with-a-given-key
 | 
				
			||||||
 | 
					    // shorter syntax with mem_fn()
 | 
				
			||||||
 | 
					    // https://stackoverflow.com/a/36775400/5257399
 | 
				
			||||||
 | 
					    transform(
 | 
				
			||||||
 | 
					        lowerBound,
 | 
				
			||||||
 | 
					        upperBound,
 | 
				
			||||||
 | 
					        back_inserter(rows),
 | 
				
			||||||
 | 
					        mem_fn(&BanksToPresets::value_type::second)
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
    table.deselectAllRows();
 | 
					    table.deselectAllRows();
 | 
				
			||||||
    table.updateContent();
 | 
					    table.updateContent();
 | 
				
			||||||
    table.getHeader().setSortColumnId(0, true);
 | 
					    table.getHeader().setSortColumnId(0, true);
 | 
				
			||||||
    table.selectRow(initiallySelectedRow);
 | 
					    selectCurrentPreset();
 | 
				
			||||||
    table.repaint();
 | 
					    table.repaint();
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void TableComponent::valueTreePropertyChanged(
 | 
				
			||||||
 | 
					    ValueTree& treeWhosePropertyHasChanged,
 | 
				
			||||||
 | 
					    const Identifier& property) {
 | 
				
			||||||
 | 
					    if (treeWhosePropertyHasChanged.getType() == StringRef("banks")) {
 | 
				
			||||||
 | 
					        if (property == StringRef("synthetic")) {
 | 
				
			||||||
 | 
					            loadModelFrom(treeWhosePropertyHasChanged);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// This is overloaded from TableListBoxModel, and must return the total number of rows in our table
 | 
					// This is overloaded from TableListBoxModel, and must return the total number of rows in our table
 | 
				
			||||||
int TableComponent::getNumRows()
 | 
					int TableComponent::getNumRows()
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
@ -96,6 +155,13 @@ void TableComponent::paintRowBackground (
 | 
				
			|||||||
        g.fillAll (alternateColour);
 | 
					        g.fillAll (alternateColour);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					String TableRow::getStringContents(int columnId) {
 | 
				
			||||||
 | 
					    if (columnId <= 1) {
 | 
				
			||||||
 | 
					        return String(preset);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return name;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// This is overloaded from TableListBoxModel, and must paint any cells that aren't using custom
 | 
					// This is overloaded from TableListBoxModel, and must paint any cells that aren't using custom
 | 
				
			||||||
// components.
 | 
					// components.
 | 
				
			||||||
void TableComponent::paintCell (
 | 
					void TableComponent::paintCell (
 | 
				
			||||||
@ -109,7 +175,9 @@ void TableComponent::paintCell (
 | 
				
			|||||||
    g.setColour (getLookAndFeel().findColour (ListBox::textColourId));
 | 
					    g.setColour (getLookAndFeel().findColour (ListBox::textColourId));
 | 
				
			||||||
    g.setFont (font);
 | 
					    g.setFont (font);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    g.drawText (rows[rowNumber][columnId-1], 2, 0, width - 4, height, Justification::centredLeft, true);
 | 
					    TableRow& row{rows[rowNumber]};
 | 
				
			||||||
 | 
					    String text{row.getStringContents(columnId)};
 | 
				
			||||||
 | 
					    g.drawText (text, 2, 0, width - 4, height, Justification::centredLeft, true);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    g.setColour (getLookAndFeel().findColour (ListBox::backgroundColourId));
 | 
					    g.setColour (getLookAndFeel().findColour (ListBox::backgroundColourId));
 | 
				
			||||||
    g.fillRect (width - 1, 0, 1, height);
 | 
					    g.fillRect (width - 1, 0, 1, height);
 | 
				
			||||||
@ -122,43 +190,44 @@ void TableComponent::sortOrderChanged (
 | 
				
			|||||||
        bool isForwards
 | 
					        bool isForwards
 | 
				
			||||||
) {
 | 
					) {
 | 
				
			||||||
    if (newSortColumnId != 0) {
 | 
					    if (newSortColumnId != 0) {
 | 
				
			||||||
        int selectedRowIx = table.getSelectedRow();
 | 
					 | 
				
			||||||
        vector<string> selectedRow;
 | 
					 | 
				
			||||||
        if (selectedRowIx >= 0) {
 | 
					 | 
				
			||||||
            selectedRow = rows[selectedRowIx];
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        TableComponent::DataSorter sorter (newSortColumnId, isForwards);
 | 
					        TableComponent::DataSorter sorter (newSortColumnId, isForwards);
 | 
				
			||||||
        sort(rows.begin(), rows.end(), sorter);
 | 
					        sort(rows.begin(), rows.end(), sorter);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        table.updateContent();
 | 
					        table.updateContent();
 | 
				
			||||||
 | 
					        selectCurrentPreset();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (selectedRowIx >= 0) {
 | 
					void TableComponent::selectCurrentPreset() {
 | 
				
			||||||
            for (auto it = rows.begin(); it != rows.end(); ++it) {
 | 
					    table.deselectAllRows();
 | 
				
			||||||
                if(*it == selectedRow) {
 | 
					    RangedAudioParameter *param{valueTreeState.getParameter("preset")};
 | 
				
			||||||
                    int index = static_cast<int>(std::distance(rows.begin(), it));
 | 
					    jassert(dynamic_cast<AudioParameterInt*>(param) != nullptr);
 | 
				
			||||||
 | 
					    AudioParameterInt* castParam{dynamic_cast<AudioParameterInt*>(param)};
 | 
				
			||||||
 | 
					    int value{castParam->get()};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for (auto it{rows.begin()}; it != rows.end(); ++it) {
 | 
				
			||||||
 | 
					        if(it->preset == value) {
 | 
				
			||||||
 | 
					            int index{static_cast<int>(distance(rows.begin(), it))};
 | 
				
			||||||
            table.selectRow(index);
 | 
					            table.selectRow(index);
 | 
				
			||||||
            break;
 | 
					            break;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// This is overloaded from TableListBoxModel, and should choose the best width for the specified
 | 
					// This is overloaded from TableListBoxModel, and should choose the best width for the specified
 | 
				
			||||||
// column.
 | 
					// column.
 | 
				
			||||||
int TableComponent::getColumnAutoSizeWidth (int columnId) {
 | 
					int TableComponent::getColumnAutoSizeWidth (int columnId) {
 | 
				
			||||||
//    if (columnId == 5)
 | 
					 | 
				
			||||||
//        return 100; // (this is the ratings column, containing a custom combobox component)
 | 
					 | 
				
			||||||
    if (columnId == 1)
 | 
					    if (columnId == 1)
 | 
				
			||||||
        return 30; // (this is the ratings column, containing a custom combobox component)
 | 
					        return 30;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    int widest = 32;
 | 
					    int widest = 32;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // find the widest bit of text in this column..
 | 
					    // find the widest bit of text in this column..
 | 
				
			||||||
    for (int i = getNumRows(); --i >= 0;) {
 | 
					    for (int i{getNumRows()}; --i >= 0;) {
 | 
				
			||||||
        widest = jmax (widest, font.getStringWidth (rows[i][columnId-1]));
 | 
					        TableRow& row{rows[i]};
 | 
				
			||||||
 | 
					        String text{row.getStringContents(columnId)};
 | 
				
			||||||
 | 
					        widest = jmax (widest, font.getStringWidth (text));
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return widest + 8;
 | 
					    return widest + 8;
 | 
				
			||||||
@ -177,20 +246,24 @@ TableComponent::DataSorter::DataSorter (
 | 
				
			|||||||
        int columnByWhichToSort,
 | 
					        int columnByWhichToSort,
 | 
				
			||||||
        bool forwards
 | 
					        bool forwards
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
        : columnByWhichToSort (columnByWhichToSort),
 | 
					: columnByWhichToSort (columnByWhichToSort)
 | 
				
			||||||
          direction (forwards ? 1 : -1)
 | 
					, direction (forwards ? 1 : -1)
 | 
				
			||||||
{}
 | 
					{}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
bool TableComponent::DataSorter::operator ()(
 | 
					bool TableComponent::DataSorter::operator ()(
 | 
				
			||||||
        vector<string> first,
 | 
					        TableRow first,
 | 
				
			||||||
        vector<string> second
 | 
					        TableRow second
 | 
				
			||||||
) {
 | 
					) {
 | 
				
			||||||
    int result = String(first[columnByWhichToSort-1])
 | 
					    int result;
 | 
				
			||||||
            .compareNatural (String(second[columnByWhichToSort-1]));
 | 
					    if (columnByWhichToSort <= 1) {
 | 
				
			||||||
 | 
					        result = compare(first.preset, second.preset);
 | 
				
			||||||
    if (result == 0)
 | 
					    } else {
 | 
				
			||||||
        result = String(first[0])
 | 
					        result = first.name
 | 
				
			||||||
                .compareNatural (String(second[0]));
 | 
					            .compareNatural (second.name);
 | 
				
			||||||
 | 
					        if (result == 0) {
 | 
				
			||||||
 | 
					            result = compare(first.preset, second.preset);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    result *= direction;
 | 
					    result *= direction;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -201,9 +274,21 @@ void TableComponent::selectedRowsChanged (int row) {
 | 
				
			|||||||
    if (row < 0) {
 | 
					    if (row < 0) {
 | 
				
			||||||
        return;
 | 
					        return;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    onRowSelected(rowToIDMapper(rows[row]));
 | 
					    int newPreset{rows[row].preset};
 | 
				
			||||||
 | 
					    RangedAudioParameter *param{valueTreeState.getParameter("preset")};
 | 
				
			||||||
 | 
					    jassert(dynamic_cast<AudioParameterInt*>(param) != nullptr);
 | 
				
			||||||
 | 
					    AudioParameterInt* castParam{dynamic_cast<AudioParameterInt*>(param)};
 | 
				
			||||||
 | 
					    *castParam = newPreset;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
bool TableComponent::keyPressed(const KeyPress &key) {
 | 
					bool TableComponent::keyPressed(const KeyPress &key) {
 | 
				
			||||||
    return table.keyPressed(key);
 | 
					    return table.keyPressed(key);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					TableRow::TableRow(
 | 
				
			||||||
 | 
					    int preset,
 | 
				
			||||||
 | 
					    String name
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					: preset{preset}
 | 
				
			||||||
 | 
					, name{name}
 | 
				
			||||||
 | 
					{}
 | 
				
			||||||
 | 
				
			|||||||
@ -9,22 +9,38 @@
 | 
				
			|||||||
#pragma once
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include "../JuceLibraryCode/JuceHeader.h"
 | 
					#include "../JuceLibraryCode/JuceHeader.h"
 | 
				
			||||||
#include "PresetsToBanks.h"
 | 
					 | 
				
			||||||
#include <memory>
 | 
					#include <memory>
 | 
				
			||||||
#include <string>
 | 
					#include <string>
 | 
				
			||||||
 | 
					#include <map>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
using namespace std;
 | 
					using namespace std;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class TableRow {
 | 
				
			||||||
 | 
					public:
 | 
				
			||||||
 | 
					    TableRow(
 | 
				
			||||||
 | 
					             int preset,
 | 
				
			||||||
 | 
					             String name
 | 
				
			||||||
 | 
					             );
 | 
				
			||||||
 | 
					private:
 | 
				
			||||||
 | 
					    /** 1-indexed */
 | 
				
			||||||
 | 
					    String getStringContents(int columnId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    int preset;
 | 
				
			||||||
 | 
					    String name;
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    friend class TableComponent;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class TableComponent    : public Component,
 | 
					class TableComponent    : public Component,
 | 
				
			||||||
                          public TableListBoxModel {
 | 
					                          public TableListBoxModel,
 | 
				
			||||||
 | 
					                          public ValueTree::Listener,
 | 
				
			||||||
 | 
					                          public AudioProcessorValueTreeState::Listener {
 | 
				
			||||||
public:
 | 
					public:
 | 
				
			||||||
    TableComponent(
 | 
					    TableComponent(
 | 
				
			||||||
            const vector<string> &columns,
 | 
					            AudioProcessorValueTreeState& valueTreeState
 | 
				
			||||||
            const vector<vector<string>> &rows,
 | 
					 | 
				
			||||||
            const function<void (int)> &onRowSelected,
 | 
					 | 
				
			||||||
            const function<int (const vector<string>&)> &rowToIDMapper,
 | 
					 | 
				
			||||||
            int initiallySelectedRow
 | 
					 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
 | 
					    ~TableComponent();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    int getNumRows() override;
 | 
					    int getNumRows() override;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -52,19 +68,35 @@ public:
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    void resized() override;
 | 
					    void resized() override;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    void setRows(const vector<vector<string>>& rows, int initiallySelectedRow);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    bool keyPressed(const KeyPress &key) override;
 | 
					    bool keyPressed(const KeyPress &key) override;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    virtual void parameterChanged (const String& parameterID, float newValue) override;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    virtual void valueTreePropertyChanged (ValueTree& treeWhosePropertyHasChanged,
 | 
				
			||||||
 | 
					                                           const Identifier& property) override;
 | 
				
			||||||
 | 
					    inline virtual void valueTreeChildAdded (ValueTree& parentTree,
 | 
				
			||||||
 | 
					                                             ValueTree& childWhichHasBeenAdded) override {};
 | 
				
			||||||
 | 
					    inline virtual void valueTreeChildRemoved (ValueTree& parentTree,
 | 
				
			||||||
 | 
					                                               ValueTree& childWhichHasBeenRemoved,
 | 
				
			||||||
 | 
					                                               int indexFromWhichChildWasRemoved) override {};
 | 
				
			||||||
 | 
					    inline virtual void valueTreeChildOrderChanged (ValueTree& parentTreeWhoseChildrenHaveMoved,
 | 
				
			||||||
 | 
					                                                    int oldIndex, int newIndex) override {};
 | 
				
			||||||
 | 
					    inline virtual void valueTreeParentChanged (ValueTree& treeWhoseParentHasChanged) override {};
 | 
				
			||||||
 | 
					    inline virtual void valueTreeRedirected (ValueTree& treeWhichHasBeenChanged) override {};
 | 
				
			||||||
private:
 | 
					private:
 | 
				
			||||||
 | 
					    void loadModelFrom(ValueTree& banks);
 | 
				
			||||||
 | 
					    void repopulateTable();
 | 
				
			||||||
 | 
					    void selectCurrentPreset();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    AudioProcessorValueTreeState& valueTreeState;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    TableListBox table;     // the table component itself
 | 
					    TableListBox table;     // the table component itself
 | 
				
			||||||
    Font font;
 | 
					    Font font;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    vector<string> columns;
 | 
					    typedef multimap<int, TableRow> BanksToPresets;
 | 
				
			||||||
    vector<vector<string>> rows;
 | 
					    BanksToPresets banksToPresets;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    function<void (int)> onRowSelected;
 | 
					    vector<TableRow> rows;
 | 
				
			||||||
    function<int (const vector<string>&)> rowToIDMapper;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // A comparator used to sort our data when the user clicks a column header
 | 
					    // A comparator used to sort our data when the user clicks a column header
 | 
				
			||||||
    class DataSorter {
 | 
					    class DataSorter {
 | 
				
			||||||
@ -75,8 +107,8 @@ private:
 | 
				
			|||||||
        );
 | 
					        );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        bool operator ()(
 | 
					        bool operator ()(
 | 
				
			||||||
                vector<string> first,
 | 
					                TableRow first,
 | 
				
			||||||
                vector<string> second
 | 
					                TableRow second
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private:
 | 
					    private:
 | 
				
			||||||
 | 
				
			|||||||
@ -8,180 +8,32 @@ using namespace std;
 | 
				
			|||||||
using namespace placeholders;
 | 
					using namespace placeholders;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
TablesComponent::TablesComponent(
 | 
					TablesComponent::TablesComponent(
 | 
				
			||||||
        FluidSynthModel* fluidSynthModel
 | 
					    AudioProcessorValueTreeState& valueTreeState
 | 
				
			||||||
) : fluidSynthModel(fluidSynthModel),
 | 
					)
 | 
				
			||||||
    banksToPresets(fluidSynthModel->getBanks()),
 | 
					: valueTreeState{valueTreeState}
 | 
				
			||||||
    initialised(false)
 | 
					, banks{valueTreeState}
 | 
				
			||||||
 | 
					, presetTable{valueTreeState}
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    fluid_preset_t* currentPreset = getCurrentPreset();
 | 
					 | 
				
			||||||
    selectedBank = -1;
 | 
					 | 
				
			||||||
    int selectedPreset = -1;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (currentPreset != nullptr) {
 | 
					    presetTable.setWantsKeyboardFocus(false);
 | 
				
			||||||
        selectedBank = fluid_preset_get_banknum(currentPreset);
 | 
					 | 
				
			||||||
        selectedPreset = fluid_preset_get_num(currentPreset);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    auto rowToPresetMapper = [this](const vector<string> &row) {
 | 
					 | 
				
			||||||
        return stoi(row[0]);
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
    auto itemToBankMapper = [](const string &item) {
 | 
					 | 
				
			||||||
        return stoi(item);
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    presetTable = new TableComponent(
 | 
					 | 
				
			||||||
            {"#", "Name"},
 | 
					 | 
				
			||||||
            mapPresets(
 | 
					 | 
				
			||||||
                    banksToPresets,
 | 
					 | 
				
			||||||
                    selectedBank
 | 
					 | 
				
			||||||
            ),
 | 
					 | 
				
			||||||
            [this](int preset){
 | 
					 | 
				
			||||||
                this->onPresetSelected(preset);
 | 
					 | 
				
			||||||
            },
 | 
					 | 
				
			||||||
            rowToPresetMapper,
 | 
					 | 
				
			||||||
            presetToIndexMapper(selectedPreset)
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
    banks = new Pills(
 | 
					 | 
				
			||||||
            "Banks",
 | 
					 | 
				
			||||||
            mapBanks(banksToPresets),
 | 
					 | 
				
			||||||
            [this](int bank){
 | 
					 | 
				
			||||||
                this->onBankSelected(bank);
 | 
					 | 
				
			||||||
            },
 | 
					 | 
				
			||||||
            itemToBankMapper,
 | 
					 | 
				
			||||||
            selectedBank
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    presetTable->setWantsKeyboardFocus(false);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    addAndMakeVisible(presetTable);
 | 
					    addAndMakeVisible(presetTable);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    addAndMakeVisible(banks);
 | 
					    addAndMakeVisible(banks);
 | 
				
			||||||
 | 
					 | 
				
			||||||
    initialised = true;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    fluidSynthModel->addListener(this);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
fluid_preset_t* TablesComponent::getCurrentPreset() {
 | 
					 | 
				
			||||||
    fluid_synth_t* synth = fluidSynthModel->getSynth();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return fluid_synth_get_channel_preset(synth, fluidSynthModel->getChannel());
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Preset TablesComponent::getFirstPresetInBank(int bank) {
 | 
					 | 
				
			||||||
    pair<BanksToPresets::const_iterator, BanksToPresets::const_iterator> iterators = banksToPresets.equal_range(bank);
 | 
					 | 
				
			||||||
    BanksToPresets::const_iterator it = iterators.first;
 | 
					 | 
				
			||||||
    return it->second;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void TablesComponent::onBankSelected(int bank) {
 | 
					 | 
				
			||||||
    if (!initialised || bank == -1) {
 | 
					 | 
				
			||||||
        return;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    cout << "Bank " << bank << endl;
 | 
					 | 
				
			||||||
    selectedBank = bank;
 | 
					 | 
				
			||||||
    Preset firstPresetInBank = getFirstPresetInBank(bank);
 | 
					 | 
				
			||||||
    presetTable->setRows(
 | 
					 | 
				
			||||||
            mapPresets(
 | 
					 | 
				
			||||||
                    banksToPresets,
 | 
					 | 
				
			||||||
                    bank
 | 
					 | 
				
			||||||
            ),
 | 
					 | 
				
			||||||
            presetToIndexMapper(firstPresetInBank.getPreset())
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
int TablesComponent::presetToIndexMapper(int preset) {
 | 
					 | 
				
			||||||
    int ix = 0;
 | 
					 | 
				
			||||||
    pair<BanksToPresets::const_iterator, BanksToPresets::const_iterator> iterators = this->banksToPresets.equal_range(this->selectedBank);
 | 
					 | 
				
			||||||
    for (auto it = iterators.first; it != iterators.second; ++it, ix++) {
 | 
					 | 
				
			||||||
        Preset b = it->second;
 | 
					 | 
				
			||||||
        if (preset == b.getPreset()) {
 | 
					 | 
				
			||||||
            return ix;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    return 0;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void TablesComponent::onPresetSelected(int preset) {
 | 
					 | 
				
			||||||
    if (!initialised || preset == -1) {
 | 
					 | 
				
			||||||
        return;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    cout << "Preset " << preset << endl;
 | 
					 | 
				
			||||||
//    selectedPreset = preset;
 | 
					 | 
				
			||||||
    fluidSynthModel->changePreset(selectedBank, preset);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
TablesComponent::~TablesComponent() {
 | 
					 | 
				
			||||||
    delete presetTable;
 | 
					 | 
				
			||||||
    delete banks;
 | 
					 | 
				
			||||||
    fluidSynthModel->removeListener(this);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
vector<string> TablesComponent::mapBanks(const BanksToPresets &banksToPresets) {
 | 
					 | 
				
			||||||
    vector<string> rows;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const auto compareKey = [](const BanksToPresets::value_type& lhs, const BanksToPresets::value_type& rhs) {
 | 
					 | 
				
			||||||
        return lhs.first < rhs.first;
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    for(auto i = banksToPresets.begin(); i != banksToPresets.end(); i = std::upper_bound(i, banksToPresets.end(), *i, compareKey)) {
 | 
					 | 
				
			||||||
        rows.push_back(to_string(i->first));
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return rows;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
vector<vector<string>> TablesComponent::mapPresets(const BanksToPresets &banksToPresets, int bank) {
 | 
					 | 
				
			||||||
    vector<vector<string>> rows;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    pair<BanksToPresets::const_iterator, BanksToPresets::const_iterator> iterators = banksToPresets.equal_range(bank);
 | 
					 | 
				
			||||||
    for (auto it = iterators.first; it != iterators.second; ++it) {
 | 
					 | 
				
			||||||
        Preset b = it->second;
 | 
					 | 
				
			||||||
        vector<string> row;
 | 
					 | 
				
			||||||
        row.push_back(to_string(b.getPreset()));
 | 
					 | 
				
			||||||
        row.push_back(b.getName());
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        rows.push_back(row);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return rows;
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void TablesComponent::resized() {
 | 
					void TablesComponent::resized() {
 | 
				
			||||||
    Rectangle<int> r (getLocalBounds());
 | 
					    Rectangle<int> r (getLocalBounds());
 | 
				
			||||||
    banks->setBounds (r.removeFromTop(27).reduced(5,0));
 | 
					    banks.setBounds (r.removeFromTop(27).reduced(5,0));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    presetTable->setBounds (r);
 | 
					    presetTable.setBounds (r);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
bool TablesComponent::keyPressed(const KeyPress &key) {
 | 
					bool TablesComponent::keyPressed(const KeyPress &key) {
 | 
				
			||||||
    if (key.getKeyCode() == KeyPress::leftKey
 | 
					    if (key.getKeyCode() == KeyPress::leftKey
 | 
				
			||||||
            || key.getKeyCode() == KeyPress::rightKey) {
 | 
					            || key.getKeyCode() == KeyPress::rightKey) {
 | 
				
			||||||
        banks->cycle(key.getKeyCode() == KeyPress::rightKey);
 | 
					        banks.cycle(key.getKeyCode() == KeyPress::rightKey);
 | 
				
			||||||
        return true;
 | 
					        return true;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    return presetTable->keyPressed(key);
 | 
					    return presetTable.keyPressed(key);
 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void TablesComponent::fontChanged(FluidSynthModel *, const String &) {
 | 
					 | 
				
			||||||
    banksToPresets = fluidSynthModel->getBanks();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    fluid_preset_t* currentPreset = getCurrentPreset();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    selectedBank = fluid_preset_get_banknum(currentPreset);
 | 
					 | 
				
			||||||
    int selectedPreset = fluid_preset_get_num(currentPreset);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    presetTable->setRows(
 | 
					 | 
				
			||||||
            mapPresets(
 | 
					 | 
				
			||||||
                    banksToPresets,
 | 
					 | 
				
			||||||
                    selectedBank
 | 
					 | 
				
			||||||
            ),
 | 
					 | 
				
			||||||
            presetToIndexMapper(selectedPreset)
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    banks->setItems(
 | 
					 | 
				
			||||||
            mapBanks(banksToPresets),
 | 
					 | 
				
			||||||
            selectedBank
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -7,48 +7,28 @@
 | 
				
			|||||||
#include "../JuceLibraryCode/JuceHeader.h"
 | 
					#include "../JuceLibraryCode/JuceHeader.h"
 | 
				
			||||||
#include "Pills.h"
 | 
					#include "Pills.h"
 | 
				
			||||||
#include "TableComponent.h"
 | 
					#include "TableComponent.h"
 | 
				
			||||||
#include "Preset.h"
 | 
					 | 
				
			||||||
#include "PresetsToBanks.h"
 | 
					 | 
				
			||||||
#include "FluidSynthModel.h"
 | 
					#include "FluidSynthModel.h"
 | 
				
			||||||
#include <memory>
 | 
					#include <memory>
 | 
				
			||||||
#include <fluidsynth.h>
 | 
					#include <fluidsynth.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
using namespace std;
 | 
					using namespace std;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class TablesComponent : public Component,
 | 
					class TablesComponent : public Component
 | 
				
			||||||
                        public FluidSynthModel::Listener
 | 
					 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
public:
 | 
					public:
 | 
				
			||||||
    TablesComponent(
 | 
					    TablesComponent(
 | 
				
			||||||
            FluidSynthModel* fluidSynthModel
 | 
					        AudioProcessorValueTreeState& valueTreeState
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
    ~TablesComponent();
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    void resized() override;
 | 
					    void resized() override;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    bool keyPressed(const KeyPress &key) override;
 | 
					    bool keyPressed(const KeyPress &key) override;
 | 
				
			||||||
    void fontChanged(FluidSynthModel *, const String &) override;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
private:
 | 
					private:
 | 
				
			||||||
    FluidSynthModel* fluidSynthModel;
 | 
					    AudioProcessorValueTreeState& valueTreeState;
 | 
				
			||||||
    int selectedBank;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    Pills* banks;
 | 
					    Pills banks;
 | 
				
			||||||
    TableComponent* presetTable;
 | 
					    TableComponent presetTable;
 | 
				
			||||||
 | 
					 | 
				
			||||||
    BanksToPresets banksToPresets;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    static vector<vector<string>> mapPresets(const BanksToPresets &banksToPresets, int bank);
 | 
					 | 
				
			||||||
    static vector<string> mapBanks(const BanksToPresets &banksToPresets);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    void onBankSelected(int bank);
 | 
					 | 
				
			||||||
    void onPresetSelected(int preset);
 | 
					 | 
				
			||||||
    int presetToIndexMapper(int preset);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    fluid_preset_t* getCurrentPreset();
 | 
					 | 
				
			||||||
    Preset getFirstPresetInBank(int bank);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    bool initialised;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TablesComponent)
 | 
					    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TablesComponent)
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
				
			|||||||
@ -9,3 +9,11 @@
 | 
				
			|||||||
    #define DEBUG_PRINT(str)
 | 
					    #define DEBUG_PRINT(str)
 | 
				
			||||||
  #endif
 | 
					  #endif
 | 
				
			||||||
#endif
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace Util {
 | 
				
			||||||
 | 
					  inline int compare(int a, int b) {
 | 
				
			||||||
 | 
					    if (a > b) return 1;
 | 
				
			||||||
 | 
					    if (a == b) return 0;
 | 
				
			||||||
 | 
					    return -1;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
		Reference in New Issue
	
	Block a user