diff --git a/Builds/MacOSX/juicysfplugin.xcodeproj/project.pbxproj b/Builds/MacOSX/juicysfplugin.xcodeproj/project.pbxproj index 02c0a02..140f999 100644 --- a/Builds/MacOSX/juicysfplugin.xcodeproj/project.pbxproj +++ b/Builds/MacOSX/juicysfplugin.xcodeproj/project.pbxproj @@ -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 */; }; 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 */; }; - 305606C42BB0F2A12D382D34 /* SoundfontSynthVoice.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B5A057FEC371053E83A73E47 /* SoundfontSynthVoice.cpp */; }; 358E458C22BEE5090087ED8D /* RecentFilesMenuTemplate.nib in Resources */ = {isa = PBXBuildFile; fileRef = 78CC5234CCFE3B170585DDAD /* RecentFilesMenuTemplate.nib */; }; 358E458D22BEE5090087ED8D /* Accelerate.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C1616112041466F7324D7E19 /* Accelerate.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 */; }; 85E6C3826F86B1258C407725 /* FluidSynthModel.cpp in Sources */ = {isa = PBXBuildFile; fileRef = DA25104C95E55D1822BFFBE2 /* FluidSynthModel.cpp */; }; 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 */; }; 9C2580F953071AD611EB6166 /* AudioToolbox.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 28CA077CDD21D0FEC66FC290 /* AudioToolbox.framework */; }; 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 */; }; 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 */; }; @@ -140,7 +137,6 @@ 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 */; }; 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 */; }; 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 */; }; @@ -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; }; 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 = ""; }; + 35099D9422CAB0A400CD4523 /* GuiConstants.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = GuiConstants.h; path = ../../Source/GuiConstants.h; sourceTree = ""; }; 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 = ""; }; 358E45B522BEE53A0087ED8D /* libvorbisenc.2.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; path = libvorbisenc.2.dylib; sourceTree = ""; }; @@ -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; }; 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; }; - 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; }; 60ADEC8B20DC559737F84180 /* juce_gui_basics */ = {isa = PBXFileReference; lastKnownFileType = folder; name = juce_gui_basics; path = /Applications/JUCE/modules/juce_gui_basics; sourceTree = ""; }; 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; }; - 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 = ""; }; 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; }; @@ -325,13 +320,10 @@ 6FA795817D2F3B3119FDD754 /* juce_core */ = {isa = PBXFileReference; lastKnownFileType = folder; name = juce_core; path = /Applications/JUCE/modules/juce_core; sourceTree = ""; }; 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 = ""; }; - 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; }; 7C699A8B65F3F9FB5004F22D /* juce_gui_extra */ = {isa = PBXFileReference; lastKnownFileType = folder; name = juce_gui_extra; path = /Applications/JUCE/modules/juce_gui_extra; sourceTree = ""; }; 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; }; - 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; }; 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 = ""; }; @@ -340,10 +332,6 @@ 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; }; 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; }; 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; }; @@ -356,10 +344,8 @@ 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; }; 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; }; 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; }; 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 = ""; }; @@ -521,10 +507,6 @@ 403EB0CF49CF1D62BF359002 /* Source */ = { isa = PBXGroup; children = ( - DECFA95359BC1DDDD1CC86C3 /* BankAndPreset.cpp */, - B000E7A360C0C86ADD3C911D /* BankAndPreset.h */, - F1EB35E262DC717222E2F93D /* ExposesComponents.h */, - 88ADEBF51BD04FEA9422D276 /* FilePickerFragment.h */, C13A2FEAA636713EC7A905AF /* FilePicker.cpp */, 21828DE4341668D7E383F10A /* FilePicker.h */, DA25104C95E55D1822BFFBE2 /* FluidSynthModel.cpp */, @@ -533,13 +515,6 @@ 576A01FC6A3620A39BD1BDEE /* MyColours.h */, 2C62C3F0621604CDB65B55A6 /* Pills.cpp */, 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 */, B6D37AD919F9E83688578941 /* SurjectiveMidiKeyboardComponent.h */, CE8C41308A31A71A1177D0D5 /* TableComponent.cpp */, @@ -554,6 +529,7 @@ 358E45F922C80DCA0087ED8D /* SlidersComponent.cpp */, 358E45FA22C80DCA0087ED8D /* SlidersComponent.h */, 35099D9022CA8EF500CD4523 /* Util.h */, + 35099D9422CAB0A400CD4523 /* GuiConstants.h */, ); name = Source; sourceTree = ""; @@ -936,14 +912,10 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 9AF2F3DE22C71A7F465B2EAD /* BankAndPreset.cpp in Sources */, 598516649859A6D6BB2856EF /* FilePicker.cpp in Sources */, 85E6C3826F86B1258C407725 /* FluidSynthModel.cpp in Sources */, BFD9EF2D67067FC1E5BA3546 /* MyColours.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 */, 4AE057561AEA78489D9E50F0 /* TableComponent.cpp in Sources */, AC5E4EF988D864A298E3650D /* TablesComponent.cpp in Sources */, diff --git a/Builds/MacOSX/juicysfplugin.xcodeproj/xcshareddata/xcschemes/juicysfplugin - AU.xcscheme b/Builds/MacOSX/juicysfplugin.xcodeproj/xcshareddata/xcschemes/juicysfplugin - AU.xcscheme new file mode 100644 index 0000000..6ec7b0e --- /dev/null +++ b/Builds/MacOSX/juicysfplugin.xcodeproj/xcshareddata/xcschemes/juicysfplugin - AU.xcscheme @@ -0,0 +1,84 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Builds/MacOSX/juicysfplugin.xcodeproj/xcshareddata/xcschemes/juicysfplugin - All.xcscheme b/Builds/MacOSX/juicysfplugin.xcodeproj/xcshareddata/xcschemes/juicysfplugin - All.xcscheme index bee1ae6..f8aae0e 100644 --- a/Builds/MacOSX/juicysfplugin.xcodeproj/xcshareddata/xcschemes/juicysfplugin - All.xcscheme +++ b/Builds/MacOSX/juicysfplugin.xcodeproj/xcshareddata/xcschemes/juicysfplugin - All.xcscheme @@ -33,7 +33,7 @@ - + + + - + diff --git a/Builds/MacOSX/juicysfplugin.xcodeproj/xcshareddata/xcschemes/juicysfplugin - VST.xcscheme b/Builds/MacOSX/juicysfplugin.xcodeproj/xcshareddata/xcschemes/juicysfplugin - VST.xcscheme new file mode 100644 index 0000000..0d99cec --- /dev/null +++ b/Builds/MacOSX/juicysfplugin.xcodeproj/xcshareddata/xcschemes/juicysfplugin - VST.xcscheme @@ -0,0 +1,84 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Builds/MacOSX/juicysfplugin.xcodeproj/xcshareddata/xcschemes/juicysfplugin - VST3.xcscheme b/Builds/MacOSX/juicysfplugin.xcodeproj/xcshareddata/xcschemes/juicysfplugin - VST3.xcscheme index f1ab27e..f373512 100644 --- a/Builds/MacOSX/juicysfplugin.xcodeproj/xcshareddata/xcschemes/juicysfplugin - VST3.xcscheme +++ b/Builds/MacOSX/juicysfplugin.xcodeproj/xcshareddata/xcschemes/juicysfplugin - VST3.xcscheme @@ -42,6 +42,11 @@ debugDocumentVersioning = "YES" debugServiceExtension = "internal" allowLocationSimulation = "YES"> + + getCurrentSoundFontAbsPath()); + // setDisplayedFilePath(fluidSynthModel.getCurrentSoundFontAbsPath()); + setDisplayedFilePath(valueTreeState.state.getChildWithName("soundFont").getProperty("path", "")); addAndMakeVisible (fileChooser); fileChooser.addListener (this); + valueTreeState.state.addListener(this); +// valueTreeState.state.getChildWithName("soundFont").sendPropertyChangeMessage("path"); } FilePicker::~FilePicker() { fileChooser.removeListener (this); + valueTreeState.state.removeListener(this); } void FilePicker::resized() { @@ -46,15 +53,33 @@ void FilePicker::paint(Graphics& g) } void FilePicker::filenameComponentChanged (FilenameComponent*) { - currentPath = fileChooser.getCurrentFile().getFullPathName(); - fluidSynthModel->onFileNameChanged(fileChooser.getCurrentFile().getFullPathName(), -1, -1); + // currentPath = fileChooser.getCurrentFile().getFullPathName(); + // 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) { - if (!shouldChangeDisplayedFilePath(path)) { - return; - } - currentPath = path; + if (!shouldChangeDisplayedFilePath(path)) { + return; + } + // currentPath = path; fileChooser.setCurrentFile(File(path), true, dontSendNotification); } @@ -66,4 +91,4 @@ bool FilePicker::shouldChangeDisplayedFilePath(const String &path) { return false; } return true; -} \ No newline at end of file +} diff --git a/Source/FilePicker.h b/Source/FilePicker.h index 8a1c3bc..a8bf7c1 100644 --- a/Source/FilePicker.h +++ b/Source/FilePicker.h @@ -6,26 +6,40 @@ #include "../JuceLibraryCode/JuceHeader.h" #include "FluidSynthModel.h" -#include "FilePickerFragment.h" class FilePicker: public Component, - public FilePickerFragment, + public ValueTree::Listener, private FilenameComponentListener { public: FilePicker( - FluidSynthModel* fluidSynthModel + AudioProcessorValueTreeState& valueTreeState + // FluidSynthModel& fluidSynthModel ); ~FilePicker(); void resized() 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: FilenameComponent fileChooser; - FluidSynthModel* fluidSynthModel; + AudioProcessorValueTreeState& valueTreeState; + // FluidSynthModel& fluidSynthModel; String currentPath; @@ -34,4 +48,4 @@ private: bool shouldChangeDisplayedFilePath(const String &path); JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (FilePicker) -}; \ No newline at end of file +}; diff --git a/Source/FilePickerFragment.h b/Source/FilePickerFragment.h deleted file mode 100644 index 96968db..0000000 --- a/Source/FilePickerFragment.h +++ /dev/null @@ -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; -}; \ No newline at end of file diff --git a/Source/FluidSynthModel.cpp b/Source/FluidSynthModel.cpp index 40bddf3..e04845e 100644 --- a/Source/FluidSynthModel.cpp +++ b/Source/FluidSynthModel.cpp @@ -3,329 +3,468 @@ // #include +#include +#include #include "FluidSynthModel.h" #include "MidiConstants.h" +#include "Util.h" using namespace std; -FluidSynthModel::FluidSynthModel(SharesParams& p) - : sharesParams(p), - synth(nullptr), - settings(nullptr), - currentSoundFontAbsPath(), - currentSampleRate(44100), - initialised(false), - sfont_id(0), - channel(0)/*, - mod(nullptr)*/ +const map FluidSynthModel::controllerToParam{ + {SOUND_CTRL2, "filterResonance"}, // MIDI CC 71 Timbre/Harmonic Intensity (filter resonance) + {SOUND_CTRL3, "release"}, // MIDI CC 72 Release time + {SOUND_CTRL4, "attack"}, // MIDI CC 73 Attack time + {SOUND_CTRL5, "filterCutOff"}, // MIDI CC 74 Brightness (cutoff frequency, FILTERFC) + {SOUND_CTRL6, "decay"}, // MIDI CC 75 Decay Time + {SOUND_CTRL10, "sustain"}}; // MIDI CC 79 undefined -{} +const map FluidSynthModel::paramToController{[]{ + map map; + transform( + controllerToParam.begin(), + controllerToParam.end(), + inserter(map, map.begin()), + [](const pair& 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() { - if (initialised) { -// delete_fluid_audio_driver(driver); - delete_fluid_synth(synth); - delete_fluid_settings(settings); -// delete driver; -// delete settings; -// delete_fluid_mod(mod); + for (const auto &[param, controller]: paramToController) { + valueTreeState.removeParameterListener(param, this); } + valueTreeState.removeParameterListener("bank", this); + valueTreeState.removeParameterListener("preset", this); + valueTreeState.state.removeListener(this); } void FluidSynthModel::initialise() { -// if (initialised) { -// delete_fluid_synth(synth); -// delete_fluid_settings(settings); -// } + settings = { new_fluid_settings(), delete_fluid_settings }; + // 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. - const char *DRV[] = {NULL}; + const char *DRV[] {NULL}; fluid_audio_driver_register(DRV); - - settings = new_fluid_settings(); + // https://sourceforge.net/p/fluidsynth/wiki/FluidSettings/ #if JUCE_DEBUG - fluid_settings_setint(settings, "synth.verbose", 1); + fluid_settings_setint(settings.get(), "synth.verbose", 1); #endif - synth = new_fluid_synth(settings); - fluid_synth_set_sample_rate(synth, currentSampleRate); - - if (sharesParams.getSoundFontPath().isNotEmpty()) { - loadFont(sharesParams.getSoundFontPath()); - changePreset(sharesParams.getBank(), sharesParams.getPreset()); - } - - fluid_synth_set_gain(synth, 2.0); + synth = { new_fluid_synth(settings.get()), delete_fluid_synth }; + fluid_synth_set_sample_rate(synth.get(), currentSampleRate); - for(int i{SOUND_CTRL1}; i <= SOUND_CTRL10; i++) - { - setControllerValue(i, 0); - } + ValueTree soundFont{valueTreeState.state.getChildWithName("soundFont")}; + String path{soundFont.getProperty("path", "")}; + loadFont(path); -// 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); + // I can't hear a damned thing + fluid_synth_set_gain(synth.get(), 2.0); // 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(param) != nullptr); + AudioParameterInt* castParam{dynamic_cast(param)}; + int value{castParam->get()}; + setControllerValue(static_cast(controller), value); + } - fluid_mod_t *mod(new_fluid_mod()); -// - fluid_mod_set_source1(mod, + // http://www.synthfont.com/SoundFont_NRPNs.PDF + float env_amount{20000.0f}; + + unique_ptr mod{new_fluid_mod(), delete_fluid_mod}; + fluid_mod_set_source1(mod.get(), static_cast(SOUND_CTRL2), // MIDI CC 71 Timbre/Harmonic Intensity (filter resonance) FLUID_MOD_CC | FLUID_MOD_UNIPOLAR | FLUID_MOD_CONCAVE | FLUID_MOD_POSITIVE); - fluid_mod_set_source2(mod, 0, 0); - fluid_mod_set_dest(mod, GEN_FILTERQ); - fluid_mod_set_amount(mod, FLUID_PEAK_ATTENUATION); - fluid_synth_add_default_mod(synth, mod, FLUID_SYNTH_ADD); - delete_fluid_mod(mod); + fluid_mod_set_source2(mod.get(), 0, 0); + fluid_mod_set_dest(mod.get(), GEN_FILTERQ); + fluid_mod_set_amount(mod.get(), FLUID_PEAK_ATTENUATION); + fluid_synth_add_default_mod(synth.get(), mod.get(), FLUID_SYNTH_ADD); - mod = new_fluid_mod(); - fluid_mod_set_source1(mod, + mod = {new_fluid_mod(), delete_fluid_mod}; + fluid_mod_set_source1(mod.get(), static_cast(SOUND_CTRL3), // MIDI CC 72 Release time FLUID_MOD_CC | FLUID_MOD_UNIPOLAR | FLUID_MOD_LINEAR | FLUID_MOD_POSITIVE); - fluid_mod_set_source2(mod, 0, 0); - fluid_mod_set_dest(mod, GEN_VOLENVRELEASE); -// fluid_mod_set_amount(mod, 15200.0f); - fluid_mod_set_amount(mod, env_amount); - fluid_synth_add_default_mod(synth, mod, FLUID_SYNTH_ADD); - delete_fluid_mod(mod); + fluid_mod_set_source2(mod.get(), 0, 0); + fluid_mod_set_dest(mod.get(), GEN_VOLENVRELEASE); + fluid_mod_set_amount(mod.get(), env_amount); + fluid_synth_add_default_mod(synth.get(), mod.get(), FLUID_SYNTH_ADD); - mod = new_fluid_mod(); - fluid_mod_set_source1(mod, + mod = {new_fluid_mod(), delete_fluid_mod}; + fluid_mod_set_source1(mod.get(), static_cast(SOUND_CTRL4), // MIDI CC 73 Attack time FLUID_MOD_CC | FLUID_MOD_UNIPOLAR | FLUID_MOD_LINEAR | FLUID_MOD_POSITIVE); - fluid_mod_set_source2(mod, 0, 0); - fluid_mod_set_dest(mod, GEN_VOLENVATTACK); - fluid_mod_set_amount(mod, env_amount); - fluid_synth_add_default_mod(synth, mod, FLUID_SYNTH_ADD); - delete_fluid_mod(mod); + fluid_mod_set_source2(mod.get(), 0, 0); + fluid_mod_set_dest(mod.get(), GEN_VOLENVATTACK); + fluid_mod_set_amount(mod.get(), env_amount); + fluid_synth_add_default_mod(synth.get(), mod.get(), FLUID_SYNTH_ADD); // soundfont spec says that if cutoff is >20kHz and resonance Q is 0, then no filtering occurs - mod = new_fluid_mod(); - fluid_mod_set_source1(mod, + mod = {new_fluid_mod(), delete_fluid_mod}; + fluid_mod_set_source1(mod.get(), static_cast(SOUND_CTRL5), // MIDI CC 74 Brightness (cutoff frequency, FILTERFC) FLUID_MOD_CC | FLUID_MOD_LINEAR | FLUID_MOD_UNIPOLAR | FLUID_MOD_POSITIVE); - fluid_mod_set_source2(mod, 0, 0); - fluid_mod_set_dest(mod, GEN_FILTERFC); - fluid_mod_set_amount(mod, -2400.0f); - fluid_synth_add_default_mod(synth, mod, FLUID_SYNTH_ADD); - delete_fluid_mod(mod); + fluid_mod_set_source2(mod.get(), 0, 0); + fluid_mod_set_dest(mod.get(), GEN_FILTERFC); + fluid_mod_set_amount(mod.get(), -2400.0f); + fluid_synth_add_default_mod(synth.get(), mod.get(), FLUID_SYNTH_ADD); - mod = new_fluid_mod(); - fluid_mod_set_source1(mod, + mod = {new_fluid_mod(), delete_fluid_mod}; + fluid_mod_set_source1(mod.get(), static_cast(SOUND_CTRL6), // MIDI CC 75 Decay Time FLUID_MOD_CC | FLUID_MOD_UNIPOLAR | FLUID_MOD_LINEAR | FLUID_MOD_POSITIVE); - fluid_mod_set_source2(mod, 0, 0); - fluid_mod_set_dest(mod, GEN_VOLENVDECAY); - fluid_mod_set_amount(mod, env_amount); - fluid_synth_add_default_mod(synth, mod, FLUID_SYNTH_ADD); - delete_fluid_mod(mod); + fluid_mod_set_source2(mod.get(), 0, 0); + fluid_mod_set_dest(mod.get(), GEN_VOLENVDECAY); + fluid_mod_set_amount(mod.get(), env_amount); + fluid_synth_add_default_mod(synth.get(), mod.get(), FLUID_SYNTH_ADD); - mod = new_fluid_mod(); - fluid_mod_set_source1(mod, + mod = {new_fluid_mod(), delete_fluid_mod}; + fluid_mod_set_source1(mod.get(), static_cast(SOUND_CTRL10), // MIDI CC 79 undefined FLUID_MOD_CC | FLUID_MOD_UNIPOLAR | FLUID_MOD_CONCAVE | FLUID_MOD_POSITIVE); - fluid_mod_set_source2(mod, 0, 0); - fluid_mod_set_dest(mod, GEN_VOLENVSUSTAIN); + fluid_mod_set_source2(mod.get(), 0, 0); + fluid_mod_set_dest(mod.get(), GEN_VOLENVSUSTAIN); // fluice_voice.c#fluid_voice_update_param() // clamps the range to between 0 and 1000, so we'll copy that - fluid_mod_set_amount(mod, 1000.0f); - fluid_synth_add_default_mod(synth, mod, FLUID_SYNTH_ADD); - delete_fluid_mod(mod); + fluid_mod_set_amount(mod.get(), 1000.0f); + fluid_synth_add_default_mod(synth.get(), mod.get(), FLUID_SYNTH_ADD); +} - 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(param) != nullptr); + AudioParameterInt* castParam{dynamic_cast(param)}; + bank = castParam->get(); + } + { + RangedAudioParameter *param{valueTreeState.getParameter("preset")}; + jassert(dynamic_cast(param) != nullptr); + AudioParameterInt* castParam{dynamic_cast(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(bankOffset + bank), + static_cast(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(param) != nullptr); + AudioParameterInt* castParam{dynamic_cast(param)}; + int value{castParam->get()}; + int controllerNumber{static_cast(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) { - fluid_midi_event_t *midi_event(new_fluid_midi_event()); - fluid_midi_event_set_type(midi_event, static_cast(CONTROL_CHANGE)); - fluid_midi_event_set_channel(midi_event, channel); - fluid_midi_event_set_control(midi_event, controller); - fluid_midi_event_set_value(midi_event, value); - fluid_synth_handle_midi_event(synth, midi_event); - delete_fluid_midi_event(midi_event); - // fluid_channel_set_cc(channel, i, 0); + fluid_synth_cc( + synth.get(), + channel, + controller, + value); } int FluidSynthModel::getChannel() { return channel; } -void FluidSynthModel::changePreset(int bank, int preset) { - if (bank == -1 || preset == -1) { - unique_ptr 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(bank), static_cast(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 FluidSynthModel::getFirstBankAndPreset() { - fluid_preset_t* preset = getFirstPreset(); - - int offset = fluid_synth_get_bank_offset(synth, sfont_id); - - return make_unique(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) { // in the base case, there is no font loaded - if (fluid_synth_sfcount(synth) > 0) { - fluid_synth_sfunload(synth, sfont_id, 1); + if (fluid_synth_sfcount(synth.get()) > 0) { + // 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); } void FluidSynthModel::loadFont(const String &absPath) { - currentSoundFontAbsPath = absPath; - sfont_id++; - fluid_synth_sfload(synth, absPath.toStdString().c_str(), 1); -} - -FluidSynthModel::Listener::~Listener() { -} - -bool FluidSynthModel::shouldLoadFont(const String &absPath) { - if (absPath.isEmpty()) { - return false; + if (!absPath.isEmpty()) { + sfont_id = fluid_synth_sfload(synth.get(), absPath.toStdString().c_str(), 1); + // if -1 is returned, that indicates failure } - if (absPath == currentSoundFontAbsPath) { - return false; + // refresh regardless of success, if only to clear the table + 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); + } + 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); + } } - return true; -} - -void FluidSynthModel::Listener::fontChanged(FluidSynthModel * model, const String &absPath) { -} - -const String& FluidSynthModel::getCurrentSoundFontAbsPath() { - return currentSoundFontAbsPath; -} - -//============================================================================== -void FluidSynthModel::addListener (FluidSynthModel::Listener* const newListener) -{ - eventListeners.add(newListener); -} - -void FluidSynthModel::removeListener (FluidSynthModel::Listener* const listener) -{ - eventListeners.remove(listener); + valueTreeState.state.getChildWithName("banks").copyPropertiesAndChildrenFrom(banks, nullptr); + valueTreeState.state.getChildWithName("banks").sendPropertyChangeMessage("synthetic"); + +#if JUCE_DEBUG +// unique_ptr xml{valueTreeState.state.createXml()}; +// Logger::outputDebugString(xml->createDocument("",false,false)); +#endif } void FluidSynthModel::setSampleRate(float 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 return; } - fluid_synth_set_sample_rate(synth, sampleRate); + fluid_synth_set_sample_rate(synth.get(), sampleRate); +} + +void FluidSynthModel::processBlock(AudioBuffer& 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(m.getControllerNumber())}; + if (auto it{controllerToParam.find(controllerNum)}; + it != end(controllerToParam)) { + String parameterID{it->second}; + RangedAudioParameter *param{valueTreeState.getParameter(parameterID)}; + jassert(dynamic_cast(param) != nullptr); + AudioParameterInt* castParam{dynamic_cast(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(param) != nullptr); + AudioParameterInt* castParam{dynamic_cast(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(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(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(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(param) != nullptr); + AudioParameterInt* castParam{dynamic_cast(param)}; + return castParam->get(); +} + +void FluidSynthModel::setCurrentProgram(int index) +{ + RangedAudioParameter *param{valueTreeState.getParameter("preset")}; + jassert(dynamic_cast(param) != nullptr); + AudioParameterInt* castParam{dynamic_cast(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(param) != nullptr); + AudioParameterInt* castParam{dynamic_cast(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. } diff --git a/Source/FluidSynthModel.h b/Source/FluidSynthModel.h index a630cdf..af14ae3 100644 --- a/Source/FluidSynthModel.h +++ b/Source/FluidSynthModel.h @@ -5,93 +5,80 @@ #pragma once #include "../JuceLibraryCode/JuceHeader.h" -#include "SharesParams.h" #include #include -#include "BankAndPreset.h" -#include "PresetsToBanks.h" - - -// https://stackoverflow.com/a/13446565/5257399 -//using std::shared_ptr; +#include +#include "MidiConstants.h" using namespace std; -class FluidSynthModel { +class FluidSynthModel +: public ValueTree::Listener +, public AudioProcessorValueTreeState::Listener { public: - FluidSynthModel(SharesParams& p); - ~FluidSynthModel(); + FluidSynthModel( + AudioProcessorValueTreeState& valueTreeState + ); + ~FluidSynthModel(); - fluid_synth_t* getSynth(); void initialise(); - - BanksToPresets getBanks(); - - void changePreset(int bank, int preset); + int getChannel(); - void onFileNameChanged(const String &absPath, int bank, int preset); void setControllerValue(int controller, int value); - //============================================================================== - /** - Used to receive callbacks when a button is clicked. + void processBlock(AudioBuffer& buffer, MidiBuffer& midiMessages); - @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); + + //============================================================================== + 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 {}; - const String& getCurrentSoundFontAbsPath(); + //============================================================================== + int getNumPrograms(); + int getCurrentProgram(); + void setCurrentProgram(int index); + const String getProgramName(int index); + void changeProgramName(int index, const String& newName); private: - SharesParams& sharesParams; + static const StringArray programChangeParams; - fluid_synth_t* synth; - fluid_settings_t* settings; -// fluid_audio_driver_t* driver; + // there's no bimap in the standard library! + static const map controllerToParam; + static const map 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 settings; + unique_ptr synth; float currentSampleRate; - fluid_preset_t* getFirstPreset(); - void selectFirstPreset(); - unique_ptr getFirstBankAndPreset(); - void unloadAndLoadFont(const String &absPath); void loadFont(const String &absPath); - bool shouldLoadFont(const String &absPath); - - void changePresetImpl(int bank, int preset); - - bool initialised; - unsigned int sfont_id; + + int sfont_id; unsigned int channel; -// fluid_mod_t* mod; - - ListenerList eventListeners; - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (FluidSynthModel) }; diff --git a/Source/GuiConstants.h b/Source/GuiConstants.h new file mode 100644 index 0000000..8223443 --- /dev/null +++ b/Source/GuiConstants.h @@ -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; +}; diff --git a/Source/MidiConstants.h b/Source/MidiConstants.h index f31a69a..68617f2 100644 --- a/Source/MidiConstants.h +++ b/Source/MidiConstants.h @@ -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). */ #define FLUID_PEAK_ATTENUATION 960.0f + +struct MidiConstants { + inline static const int midiMinValue = 0; + inline static const int midiMaxValue = 127; +}; diff --git a/Source/Pills.cpp b/Source/Pills.cpp index 6e5afc5..654fb66 100644 --- a/Source/Pills.cpp +++ b/Source/Pills.cpp @@ -4,88 +4,170 @@ #include "Pills.h" #include "MyColours.h" +#include 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}; bankIxbank) { + bank = currentBank; + break; + } + } + ValueTree preset{bank.getChild(0)}; + int presetNum{preset.getProperty("num")}; + + { + RangedAudioParameter *param{valueTreeState.getParameter("bank")}; + jassert(dynamic_cast(param) != nullptr); + AudioParameterInt* castParam{dynamic_cast(param)}; + *castParam = this->bank; + } + { + RangedAudioParameter *param{valueTreeState.getParameter("preset")}; + jassert(dynamic_cast(param) != nullptr); + AudioParameterInt* castParam{dynamic_cast(param)}; + *castParam = presetNum; + } +} + Pills::Pills( - string label, - const vector &items, - const function &onItemSelected, - const function &itemToIDMapper, - int initiallySelectedItem -) : label(label), - items(items), - onItemSelected(onItemSelected), - itemToIDMapper(itemToIDMapper) + AudioProcessorValueTreeState& valueTreeState +) +: valueTreeState{valueTreeState} { // faster (rounded edges introduce transparency) 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) { - int index = 0; - for (string item : items) { - TextButton* pill = addToList(new TextButton( - item - )); -// pill->setColour (TextButton::buttonOnColourId, Colours::blueviolet.brighter()); -// pill->setBounds(20 + index * 55, 260, 55, 24); - 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; +Pills::~Pills() { + valueTreeState.removeParameterListener("bank", this); + valueTreeState.state.removeListener(this); +} + +void Pills::parameterChanged(const String& parameterID, float newValue) { + if (parameterID == "bank") { + updatePillToggleStates(); + } +} + +void Pills::updatePillToggleStates() { + RangedAudioParameter *param {valueTreeState.getParameter("bank")}; + jassert(dynamic_cast (param) != nullptr); + AudioParameterInt* castParam {dynamic_cast (param)}; + int bank{castParam->get()}; + for (auto& pill: pills) { + pill->bankChanged(bank); + } +} + +void Pills::valueTreePropertyChanged( + ValueTree& treeWhosePropertyHasChanged, + const Identifier& property) { + if (treeWhosePropertyHasChanged.getType() == StringRef("banks")) { + if (property == StringRef("synthetic")) { + loadModelFrom(treeWhosePropertyHasChanged); } - pill->setClickingTogglesState(true); - pill->addListener(this); - index++; } } -void Pills::setItems( - const vector &items, - int initiallySelectedItem -) { - this->items = items; - for(TextButton* t : buttons) { - t->removeListener(this); +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{make_unique( + valueTreeState, + num, + i == 0, + i == numChildren - 1)}; + addAndMakeVisible(pill.get()); + pills.push_back(move(pill)); } - buttons.clear(true); - populate(initiallySelectedItem); + updatePillToggleStates(); 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) { - int currentIx = static_cast(distance(buttons.begin(), find(buttons.begin(), buttons.end(), selected))); - currentIx += right ? 1 : buttons.size()-1; - buttons[currentIx % buttons.size()]->triggerClick(); + RangedAudioParameter *param{valueTreeState.getParameter("bank")}; + jassert(dynamic_cast(param) != nullptr); + AudioParameterInt* castParam{dynamic_cast (param)}; + int bank{castParam->get()}; + + int currentIx{static_cast( + distance( + pills.begin(), + find_if( + pills.begin(), + pills.end(), + [bank](unique_ptr& pill){ + return pill->bank == bank;})))}; + currentIx += right ? 1 : pills.size()-1; + pills[currentIx % pills.size()]->textButton.triggerClick(); + } void Pills::resized() { - int index = 0; - Rectangle r (getLocalBounds()); - const int equalWidth = r.proportionOfWidth(buttons.size() <= 0 ? 1.0 : 1.0f/buttons.size()); - for(TextButton* t : buttons) { + int index{0}; + Rectangle r{getLocalBounds()}; + const int equalWidth{r.proportionOfWidth(pills.size() <= 0 ? 1.0 : 1.0f/pills.size())}; + for(auto& pill : pills) { Rectangle r2 (getLocalBounds()); r2.removeFromLeft(equalWidth * index); - r2.removeFromRight(equalWidth * (buttons.size()-index-1)); - t->setBounds (r2); + r2.removeFromRight(equalWidth * (static_cast(pills.size())-index-1)); + pill->setBounds(r2); index++; } } @@ -96,4 +178,4 @@ void Pills::resized() { void Pills::paint(Graphics& g) { g.fillAll(MyColours::getUIColourIfAvailable(LookAndFeel_V4::ColourScheme::UIColour::windowBackground, Colours::lightgrey)); -} \ No newline at end of file +} diff --git a/Source/Pills.h b/Source/Pills.h index 530a0eb..fae2e49 100644 --- a/Source/Pills.h +++ b/Source/Pills.h @@ -8,39 +8,73 @@ using namespace std; -class Pills : public Component, - public Button::Listener { +class Pill +: 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: Pills( - string label, - const vector &items, - const function &onItemSelected, - const function &itemToIDMapper, - int initiallySelectedItem + AudioProcessorValueTreeState& valueTreeState ); - - void setItems( - const vector &items, - int initiallySelectedItem - ); - - void buttonClicked (Button* button) override; + ~Pills(); + void cycle(bool right); -private: - string label; - vector items; - function onItemSelected; - function itemToIDMapper; + virtual void parameterChanged (const String& parameterID, float newValue) override; - OwnedArray 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> pills; Button *selected; - TextButton* addToList (TextButton* newButton); + void updatePillToggleStates(); void populate(int initiallySelectedItem); void resized() override; void paint(Graphics& g) override; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Pills) -}; \ No newline at end of file +}; diff --git a/Source/PluginEditor.cpp b/Source/PluginEditor.cpp index 8edc3c4..76fa638 100644 --- a/Source/PluginEditor.cpp +++ b/Source/PluginEditor.cpp @@ -10,22 +10,35 @@ #include "PluginProcessor.h" #include "PluginEditor.h" +#include "GuiConstants.h" //============================================================================== -JuicySFAudioProcessorEditor::JuicySFAudioProcessorEditor (JuicySFAudioProcessor& p) - : AudioProcessorEditor (&p), - processor (p), - midiKeyboard (p.keyboardState, SurjectiveMidiKeyboardComponent::horizontalKeyboard), - tablesComponent(p.getFluidSynthModel()), - filePicker(p.getFluidSynthModel()), - slidersComponent{p.getFluidSynthModel()} +JuicySFAudioProcessorEditor::JuicySFAudioProcessorEditor( + JuicySFAudioProcessor& p, + AudioProcessorValueTreeState& valueTreeState) +: AudioProcessorEditor{&p} +, processor{p} +, valueTreeState{valueTreeState} +, midiKeyboard{p.keyboardState, SurjectiveMidiKeyboardComponent::horizontalKeyboard} +, tablesComponent{valueTreeState} +, filePicker{valueTreeState} +, slidersComponent{valueTreeState, p.getFluidSynthModel()} { // 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"); @@ -33,7 +46,7 @@ JuicySFAudioProcessorEditor::JuicySFAudioProcessorEditor (JuicySFAudioProcessor& tablesComponent.setWantsKeyboardFocus(false); setWantsKeyboardFocus(true); - addAndMakeVisible (midiKeyboard); + addAndMakeVisible(midiKeyboard); addAndMakeVisible(slidersComponent); addAndMakeVisible(tablesComponent); @@ -41,22 +54,16 @@ JuicySFAudioProcessorEditor::JuicySFAudioProcessorEditor (JuicySFAudioProcessor& } -JuicySFAudioProcessorEditor::~JuicySFAudioProcessorEditor() -{ -// processor.unsubscribeFromStateChanges(this); +// called when the stored window size changes +void JuicySFAudioProcessorEditor::valueChanged(Value&) { + setSize(lastUIWidth.getValue(), lastUIHeight.getValue()); } -//void JuicySFAudioProcessorEditor::getStateInformation (XmlElement& xml) { -// // save -// xml.setAttribute ("uiWidth", getWidth()); -// xml.setAttribute ("uiHeight", getHeight()); -//} -// -//void JuicySFAudioProcessorEditor::setStateInformation (XmlElement* xmlState) { -// // load -// setSize (xmlState->getIntAttribute ("uiWidth", getWidth()), -// xmlState->getIntAttribute ("uiHeight", getHeight())); -//} +JuicySFAudioProcessorEditor::~JuicySFAudioProcessorEditor() +{ + lastUIWidth.removeListener(this); + lastUIHeight.removeListener(this); +} //============================================================================== 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) 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 (!hasKeyboardFocus(false) && isVisible()) { grabKeyboardFocus(); @@ -83,13 +86,9 @@ void JuicySFAudioProcessorEditor::resized() const int padding{8}; const int pianoHeight{70}; const int filePickerHeight{25}; - // const int slidersHeight{150}; Rectangle r{getLocalBounds()}; filePicker.setBounds(r.removeFromTop(filePickerHeight + padding).reduced(padding, 0).withTrimmedTop(padding)); - // Rectangle r2 (getLocalBounds()); - // slidersComponent.setBounds(r2.removeFromLeft(filePickerWidth + padding).reduced(padding, 0).withTrimmedLeft(padding)); - midiKeyboard.setBounds (r.removeFromBottom (pianoHeight).reduced(padding, 0)); Rectangle rContent{r.reduced(0, padding)}; @@ -97,28 +96,11 @@ void JuicySFAudioProcessorEditor::resized() tablesComponent.setBounds(rContent); - - processor.lastUIWidth = getWidth(); - processor.lastUIHeight = getHeight(); - -// Rectangle r2 (getLocalBounds()); -// r2.reduce(0, padding); -// r2.removeFromBottom(pianoHeight); -// r2.removeFromTop(filePickerHeight); -// tablesComponent.setBounds (r2); -// -// Rectangle r3 (getLocalBounds()); -// r3.removeFromTop(filePickerHeight); -// -// filePicker.setBounds(r3); + lastUIWidth = getWidth(); + lastUIHeight = getHeight(); } bool JuicySFAudioProcessorEditor::keyPressed(const KeyPress &key) { -// if (!hasKeyboardFocus(false)) -// return false; -// if (key.getKeyCode() == KeyPress::upKey){ -// } -// cout << "hey\n"; const int cursorKeys[] = { KeyPress::leftKey, KeyPress::rightKey, @@ -134,20 +116,9 @@ bool JuicySFAudioProcessorEditor::keyPressed(const KeyPress &key) { } else { return midiKeyboard.keyPressed(key); } -// for(auto childComponent : getChildren()) { -// if (childComponent->keyPressed(key)) return true; -// } return false; } bool JuicySFAudioProcessorEditor::keyStateChanged (bool isKeyDown) { return midiKeyboard.keyStateChanged(isKeyDown); -// for(auto childComponent : getChildren()) { -// if (childComponent->keyStateChanged(isKeyDown)) return true; -// } -// return false; } - -FilePickerFragment& JuicySFAudioProcessorEditor::getFilePicker() { - return filePicker; -} \ No newline at end of file diff --git a/Source/PluginEditor.h b/Source/PluginEditor.h index d0da863..ef0b8b6 100644 --- a/Source/PluginEditor.h +++ b/Source/PluginEditor.h @@ -14,21 +14,21 @@ #include "PluginProcessor.h" #include "TablesComponent.h" #include "SurjectiveMidiKeyboardComponent.h" -#include "FilePickerFragment.h" -#include "ExposesComponents.h" #include "FilePicker.h" -#include "StateChangeSubscriber.h" #include "SlidersComponent.h" //============================================================================== /** */ -class JuicySFAudioProcessorEditor : public AudioProcessorEditor, - public ExposesComponents/*, - public StateChangeSubscriber*/ +class JuicySFAudioProcessorEditor +: public AudioProcessorEditor +, private Value::Listener { public: - JuicySFAudioProcessorEditor (JuicySFAudioProcessor&); + JuicySFAudioProcessorEditor( + JuicySFAudioProcessor&, + AudioProcessorValueTreeState& valueTreeState + ); ~JuicySFAudioProcessorEditor(); //============================================================================== @@ -38,16 +38,20 @@ public: bool keyPressed(const KeyPress &key) override; bool keyStateChanged (bool isKeyDown) override; -// void getStateInformation (XmlElement& xml) override; -// void setStateInformation (XmlElement* xmlState) override; - - virtual FilePickerFragment& getFilePicker() override; - private: + void valueChanged (Value&) override; + // This reference is provided as a quick way for your editor to // access the processor object that created it. 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; TablesComponent tablesComponent; FilePicker filePicker; diff --git a/Source/PluginProcessor.cpp b/Source/PluginProcessor.cpp index 61f1736..1df3466 100644 --- a/Source/PluginProcessor.cpp +++ b/Source/PluginProcessor.cpp @@ -10,47 +10,66 @@ #include "PluginProcessor.h" #include "PluginEditor.h" -#include "SoundfontSynthVoice.h" -#include "SoundfontSynthSound.h" -#include "ExposesComponents.h" #include "MidiConstants.h" #include "Util.h" +#include "GuiConstants.h" + +using namespace std; +using Parameter = AudioProcessorValueTreeState::Parameter; AudioProcessor* JUCE_CALLTYPE createPluginFilter(); //============================================================================== JuicySFAudioProcessor::JuicySFAudioProcessor() - : AudioProcessor (getBusesProperties()), - lastUIWidth(400), - lastUIHeight(300), - soundFontPath(String()), - lastPreset(-1), - lastBank(-1), - fluidSynthModel(*this)/*, - pluginEditor(nullptr)*/ +: AudioProcessor{getBusesProperties()} +, valueTreeState{ + *this, + nullptr, + "MYPLUGINSETTINGS", + createParameterLayout()} +, fluidSynthModel{valueTreeState} { + 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(); } +AudioProcessorValueTreeState::ParameterLayout JuicySFAudioProcessor::createParameterLayout() { + // https://stackoverflow.com/a/8469002/5257399 + unique_ptr params[] { + // SoundFont 2.4 spec section 7.2: zero through 127, or 128. + make_unique("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("preset", "which patch (aka patch, program, instrument) is selected in the soundfont", MidiConstants::midiMinValue, MidiConstants::midiMaxValue, MidiConstants::midiMinValue, "Preset" ), + make_unique("attack", "volume envelope attack time", MidiConstants::midiMinValue, MidiConstants::midiMaxValue, MidiConstants::midiMinValue, "A" ), + make_unique("decay", "volume envelope sustain attentuation", MidiConstants::midiMinValue, MidiConstants::midiMaxValue, MidiConstants::midiMinValue, "D" ), + make_unique("sustain", "volume envelope decay time", MidiConstants::midiMinValue, MidiConstants::midiMaxValue, MidiConstants::midiMinValue, "S" ), + make_unique("release", "volume envelope release time", MidiConstants::midiMinValue, MidiConstants::midiMaxValue, MidiConstants::midiMinValue, "R" ), + make_unique("filterCutOff", "low-pass filter cut-off frequency", MidiConstants::midiMinValue, MidiConstants::midiMaxValue, MidiConstants::midiMinValue, "Cut" ), + make_unique("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() { -// delete fluidSynthModel; } void JuicySFAudioProcessor::initialiseSynth() { 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() { - 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. } 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) @@ -148,89 +168,17 @@ AudioProcessor::BusesProperties JuicySFAudioProcessor::getBusesProperties() { .withOutput ("Output", AudioChannelSet::stereo(), true); } -void JuicySFAudioProcessor::processBlock (AudioBuffer& buffer, MidiBuffer& midiMessages) { +void JuicySFAudioProcessor::processBlock(AudioBuffer& buffer, MidiBuffer& midiMessages) { jassert (!isUsingDoublePrecision()); - const int numSamples = buffer.getNumSamples(); // 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 - keyboardState.processNextMidiBuffer (midiMessages, 0, numSamples, true); + keyboardState.processNextMidiBuffer(midiMessages, 0, buffer.getNumSamples(), true); - MidiBuffer processedMidi; - 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(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(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(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(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(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(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(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(m.getSysExData()), m.getSysExDataSize(), static_cast(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) ); + fluidSynthModel.processBlock(buffer, midiMessages); // and now get our synth to process these midi events and generate its output. - synth.renderNextBlock (buffer, midiMessages, 0, numSamples); - fluid_synth_process(fluidSynth, numSamples, 0, nullptr, buffer.getNumChannels(), buffer.getArrayOfWritePointers()); + // synth.renderNextBlock(buffer, midiMessages, 0, numSamples); // (see juce_VST3_Wrapper.cpp for the assertion this would trip otherwise) // we are !JucePlugin_ProducesMidiOutput, so clear remaining MIDI messages from our buffer @@ -255,7 +203,7 @@ bool JuicySFAudioProcessor::hasEditor() const AudioProcessorEditor* JuicySFAudioProcessor::createEditor() { // 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. // Create an outer XML element.. - 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::iterator p; -// for(p = stateChangeSubscribers.begin(); p != stateChangeSubscribers.end(); p++) { -// (*p)->getStateInformation(xml); -// } + XmlElement xml{"MYPLUGINSETTINGS"}; // Store the values of all our parameters, using their param ID as the XML attribute - for (auto* param : getParameters()) - if (auto* p = dynamic_cast (param)) - xml.setAttribute (p->paramID, p->getValue()); - - // then use this helper function to stuff it into the binary blob and return it.. - copyXmlToBinary (xml, destData); + XmlElement* params{xml.createNewChildElement("params")}; + for (auto* param : getParameters()) { + if (auto* p = dynamic_cast (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); + } + } + + DEBUG_PRINT(xml.createDocument("",false,false)); + + copyXmlToBinary(xml, destData); } 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, // whose contents will have been created by the getStateInformation() call. // This getXmlFromBinary() helper function retrieves our XML from the binary blob.. - ScopedPointer xmlState (getXmlFromBinary (data, sizeInBytes)); - - if (xmlState != nullptr) - { + shared_ptr xmlState{getXmlFromBinary(data, sizeInBytes)}; + DEBUG_PRINT(xmlState->createDocument("",false,false)); + + if (xmlState.get() != nullptr) { // make sure that it's actually our type of XML object.. - if (xmlState->hasTagName ("MYPLUGINSETTINGS")) - { -// list::iterator p; -// 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()) - if (auto* p = dynamic_cast (param)) - p->setValue ((float) xmlState->getDoubleAttribute (p->paramID, p->getValue())); - - fluidSynthModel.onFileNameChanged(soundFontPath, lastBank, lastPreset); - - AudioProcessorEditor* editor = getActiveEditor(); - if (editor != nullptr) { - editor->setSize(lastUIWidth, lastUIHeight); - - jassert(dynamic_cast (editor) != nullptr); - ExposesComponents* exposesComponents = dynamic_cast (editor); - exposesComponents->getFilePicker().setDisplayedFilePath(soundFontPath); + if (xmlState->hasTagName(valueTreeState.state.getType())) { + XmlElement* params{xmlState->getChildByName("params")}; + if (params) + for (auto* param : getParameters()) + if (auto* p = dynamic_cast(param)) + p->setValue(static_cast(params->getDoubleAttribute(p->paramID, p->getValue()))); + + { + XmlElement* xmlElement{xmlState->getChildByName("soundFont")}; + if (xmlElement) { + ValueTree tree{valueTreeState.state.getChildWithName("soundFont")}; + Value value{tree.getPropertyAsValue("path", nullptr)}; + value = xmlElement->getStringAttribute("path", value.getValue()); + } + } + { + 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. bool JuicySFAudioProcessor::supportsDoublePrecisionProcessing() const { return false; } -FluidSynthModel* JuicySFAudioProcessor::getFluidSynthModel() { - 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; +FluidSynthModel& JuicySFAudioProcessor::getFluidSynthModel() { + return fluidSynthModel; } //============================================================================== diff --git a/Source/PluginProcessor.h b/Source/PluginProcessor.h index 8d60769..f079b73 100644 --- a/Source/PluginProcessor.h +++ b/Source/PluginProcessor.h @@ -12,8 +12,6 @@ #include "../JuceLibraryCode/JuceHeader.h" #include "FluidSynthModel.h" -#include "StateChangeSubscriber.h" -#include "SharesParams.h" #include using namespace std; @@ -21,8 +19,7 @@ using namespace std; //============================================================================== /** */ -class JuicySFAudioProcessor : public AudioProcessor, - public SharesParams +class JuicySFAudioProcessor : public AudioProcessor { public: //============================================================================== @@ -63,41 +60,22 @@ public: bool supportsDoublePrecisionProcessing() const override; - FluidSynthModel* getFluidSynthModel(); + FluidSynthModel& getFluidSynthModel(); 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: void initialiseSynth(); - String soundFontPath; - int lastPreset; - int lastBank; + AudioProcessorValueTreeState valueTreeState; FluidSynthModel fluidSynthModel; - fluid_synth_t* fluidSynth; Synthesiser synth; -// // just a raw pointer; we do not own -// AudioProcessorEditor* pluginEditor; - -// list stateChangeSubscribers; + AudioProcessorValueTreeState::ParameterLayout createParameterLayout(); static BusesProperties getBusesProperties(); -// Model* model; //============================================================================== JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (JuicySFAudioProcessor) }; diff --git a/Source/Preset.cpp b/Source/Preset.cpp deleted file mode 100644 index aac7a84..0000000 --- a/Source/Preset.cpp +++ /dev/null @@ -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; -} \ No newline at end of file diff --git a/Source/Preset.h b/Source/Preset.h deleted file mode 100644 index 03b46b5..0000000 --- a/Source/Preset.h +++ /dev/null @@ -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; -}; \ No newline at end of file diff --git a/Source/PresetsToBanks.h b/Source/PresetsToBanks.h deleted file mode 100644 index ea70353..0000000 --- a/Source/PresetsToBanks.h +++ /dev/null @@ -1,10 +0,0 @@ -// -// Created by Alex Birch on 17/09/2017. -// - -#pragma once - -#include "Preset.h" -#include - -typedef std::multimap BanksToPresets; \ No newline at end of file diff --git a/Source/SharesParams.h b/Source/SharesParams.h deleted file mode 100644 index 7cae325..0000000 --- a/Source/SharesParams.h +++ /dev/null @@ -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; -}; - - - diff --git a/Source/SlidersComponent.cpp b/Source/SlidersComponent.cpp index 4d008d5..ba6f81c 100644 --- a/Source/SlidersComponent.cpp +++ b/Source/SlidersComponent.cpp @@ -9,11 +9,21 @@ #include "SlidersComponent.h" #include "FluidSynthModel.h" #include "MidiConstants.h" +#include "Util.h" +using SliderAttachment = AudioProcessorValueTreeState::SliderAttachment; std::function SlidersComponent::makeSliderListener(Slider& slider, int controller) { return [this, controller, &slider]{ + // RangedAudioParameter *param{valueTreeState.getParameter("release")}; + // jassert(dynamic_cast(param) != nullptr); + // AudioParameterInt* castParam{dynamic_cast(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); - 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)); } -SlidersComponent::SlidersComponent(FluidSynthModel* fluidSynthModel) : -fluidSynthModel{fluidSynthModel}, -envelopeGroup{"envelopeGroup", "Envelope"}, -filterGroup{"filterGroup", "Filter"} +void SlidersComponent::acceptMidiControlEvent(int controller, int value) { + switch(static_cast(controller)) { + case SOUND_CTRL2: // MIDI CC 71 Timbre/Harmonic Intensity (filter resonance) + 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 double rangeMin(0); @@ -70,33 +108,39 @@ filterGroup{"filterGroup", "Filter"} attackSlider.setSliderStyle(style); attackSlider.setRange(rangeMin, rangeMax, rangeStep); - attackSlider.onValueChange = makeSliderListener(attackSlider, static_cast(SOUND_CTRL4)); + attackSlider.onDragEnd = makeSliderListener(attackSlider, static_cast(SOUND_CTRL4)); attackSlider.setTextBoxStyle(Slider::TextBoxBelow, true, attackSlider.getTextBoxWidth(), attackSlider.getTextBoxHeight()); + attackSliderAttachment = make_unique(valueTreeState, "attack", attackSlider); decaySlider.setSliderStyle(style); decaySlider.setRange(rangeMin, rangeMax, rangeStep); - decaySlider.onValueChange = makeSliderListener(decaySlider, static_cast(SOUND_CTRL6)); + decaySlider.onDragEnd = makeSliderListener(decaySlider, static_cast(SOUND_CTRL6)); decaySlider.setTextBoxStyle(Slider::TextBoxBelow, true, decaySlider.getTextBoxWidth(), decaySlider.getTextBoxHeight()); + decaySliderAttachment = make_unique(valueTreeState, "decay", decaySlider); sustainSlider.setSliderStyle(style); sustainSlider.setRange(rangeMin, rangeMax, rangeStep); - sustainSlider.onValueChange = makeSliderListener(sustainSlider, static_cast(SOUND_CTRL10)); + sustainSlider.onDragEnd = makeSliderListener(sustainSlider, static_cast(SOUND_CTRL10)); sustainSlider.setTextBoxStyle(Slider::TextBoxBelow, true, sustainSlider.getTextBoxWidth(), sustainSlider.getTextBoxHeight()); + sustainSliderAttachment = make_unique(valueTreeState, "sustain", sustainSlider); releaseSlider.setSliderStyle(style); releaseSlider.setRange(rangeMin, rangeMax, rangeStep); - releaseSlider.onValueChange = makeSliderListener(releaseSlider, static_cast(SOUND_CTRL3)); + releaseSlider.onDragEnd = makeSliderListener(releaseSlider, static_cast(SOUND_CTRL3)); releaseSlider.setTextBoxStyle(Slider::TextBoxBelow, true, releaseSlider.getTextBoxWidth(), releaseSlider.getTextBoxHeight()); + releaseSliderAttachment = make_unique(valueTreeState, "release", releaseSlider); filterCutOffSlider.setSliderStyle(style); filterCutOffSlider.setRange(rangeMin, rangeMax, rangeStep); - filterCutOffSlider.onValueChange = makeSliderListener(filterCutOffSlider, static_cast(SOUND_CTRL5)); + filterCutOffSlider.onDragEnd = makeSliderListener(filterCutOffSlider, static_cast(SOUND_CTRL5)); filterCutOffSlider.setTextBoxStyle(Slider::TextBoxBelow, true, filterCutOffSlider.getTextBoxWidth(), filterCutOffSlider.getTextBoxHeight()); + filterCutOffSliderAttachment = make_unique(valueTreeState, "filterCutOff", filterCutOffSlider); filterResonanceSlider.setSliderStyle(style); filterResonanceSlider.setRange(rangeMin, rangeMax, rangeStep); - filterResonanceSlider.onValueChange = makeSliderListener(filterResonanceSlider, static_cast(SOUND_CTRL2)); + filterResonanceSlider.onDragEnd = makeSliderListener(filterResonanceSlider, static_cast(SOUND_CTRL2)); filterResonanceSlider.setTextBoxStyle(Slider::TextBoxBelow, true, filterResonanceSlider.getTextBoxWidth(), filterResonanceSlider.getTextBoxHeight()); + filterResonanceSliderAttachment = make_unique(valueTreeState, "filterResonance", filterResonanceSlider); addAndMakeVisible(attackSlider); addAndMakeVisible(decaySlider); diff --git a/Source/SlidersComponent.h b/Source/SlidersComponent.h index ede78d3..5182b68 100644 --- a/Source/SlidersComponent.h +++ b/Source/SlidersComponent.h @@ -4,43 +4,55 @@ #include "FluidSynthModel.h" using namespace std; +using SliderAttachment = AudioProcessorValueTreeState::SliderAttachment; class SlidersComponent : public Component { public: - SlidersComponent(FluidSynthModel* fluidSynthModel); + SlidersComponent( + AudioProcessorValueTreeState& valueTreeState, + FluidSynthModel& fluidSynthModel); ~SlidersComponent(); void resized() override; const int getDesiredWidth(); + void acceptMidiControlEvent(int controller, int value); + private: std::function makeSliderListener(Slider& slider, int controller); - FluidSynthModel* fluidSynthModel; + AudioProcessorValueTreeState& valueTreeState; + FluidSynthModel& fluidSynthModel; GroupComponent envelopeGroup; Slider attackSlider; Label attackLabel; + unique_ptr attackSliderAttachment; Slider decaySlider; Label decayLabel; + unique_ptr decaySliderAttachment; Slider sustainSlider; Label sustainLabel; + unique_ptr sustainSliderAttachment; Slider releaseSlider; Label releaseLabel; + unique_ptr releaseSliderAttachment; GroupComponent filterGroup; Slider filterCutOffSlider; Label filterCutOffLabel; + unique_ptr filterCutOffSliderAttachment; Slider filterResonanceSlider; Label filterResonanceLabel; + unique_ptr filterResonanceSliderAttachment; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (SlidersComponent) }; diff --git a/Source/SoundfontSynthSound.cpp b/Source/SoundfontSynthSound.cpp deleted file mode 100644 index 110fa33..0000000 --- a/Source/SoundfontSynthSound.cpp +++ /dev/null @@ -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; -} \ No newline at end of file diff --git a/Source/SoundfontSynthSound.h b/Source/SoundfontSynthSound.h deleted file mode 100644 index 52e0764..0000000 --- a/Source/SoundfontSynthSound.h +++ /dev/null @@ -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; -}; \ No newline at end of file diff --git a/Source/SoundfontSynthVoice.cpp b/Source/SoundfontSynthVoice.cpp deleted file mode 100644 index 652b995..0000000 --- a/Source/SoundfontSynthVoice.cpp +++ /dev/null @@ -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 (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(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 (sound) != nullptr); -// SoundfontSynthSound* sfsynth = dynamic_cast (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& outputBuffer, int startSample, int numSamples) { - //fluid_synth_process(synth.get(), numSamples, 1, nullptr, outputBuffer.getNumChannels(), outputBuffer.getArrayOfWritePointers()); -} - -//void SoundfontSynthVoice::renderBlock (AudioBuffer& 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 (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; -// } -// } -// } -//} diff --git a/Source/SoundfontSynthVoice.h b/Source/SoundfontSynthVoice.h deleted file mode 100644 index e920882..0000000 --- a/Source/SoundfontSynthVoice.h +++ /dev/null @@ -1,39 +0,0 @@ -// -// Created by Alex Birch on 07/09/2017. -// - -#pragma once - -#include -#include -#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& outputBuffer, int startSample, int numSamples) override; - -private: - double tailOff; - double level; - double currentAngle; - double angleDelta; - int midiNoteNumber; - - fluid_synth_t* synth; -}; diff --git a/Source/StateChangeSubscriber.h b/Source/StateChangeSubscriber.h deleted file mode 100644 index 420a26a..0000000 --- a/Source/StateChangeSubscriber.h +++ /dev/null @@ -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; -}; diff --git a/Source/TableComponent.cpp b/Source/TableComponent.cpp index 75487e5..e52ea81 100644 --- a/Source/TableComponent.cpp +++ b/Source/TableComponent.cpp @@ -7,25 +7,23 @@ // #include "TableComponent.h" +#include "Util.h" +#include +#include +#include using namespace std; +using namespace Util; //============================================================================== /** This class shows how to implement a TableListBoxModel to show in a TableListBox. */ TableComponent::TableComponent( - const vector &columns, - const vector> &rows, - const function &onRowSelected, - const function&)> &rowToIDMapper, - int initiallySelectedRow + AudioProcessorValueTreeState& valueTreeState ) - : font (14.0f), - columns(columns), - rows(rows), - onRowSelected(onRowSelected), - rowToIDMapper(rowToIDMapper) +: valueTreeState{valueTreeState} +, font{14.0f} { // Create our table component and add it to this component.. addAndMakeVisible (table); @@ -37,43 +35,104 @@ TableComponent::TableComponent( 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 ( - String(column), - columnIx++, - colWidth, // column width - 30, // min width - 400, // max width - TableHeaderComponent::defaultFlags - ); - } + table.getHeader().addColumn ( + String("#"), + columnIx++, + 30, // column width + 30, // min width + 400, // max width + TableHeaderComponent::defaultFlags + ); + table.getHeader().addColumn ( + String("Name"), + columnIx++, + 200, // column width + 30, // min width + 400, // max width + TableHeaderComponent::defaultFlags + ); table.setWantsKeyboardFocus(false); - table.selectRow(initiallySelectedRow); + ValueTree banks{valueTreeState.state.getChildWithName("banks")}; + loadModelFrom(banks); // we could now change some initial settings.. - table.getHeader().setSortColumnId (1, false); // sort ascending by ID column -// table.getHeader().setColumnVisible (7, false); // hide the "length" column until the user shows it - - // un-comment this line to have a go of stretch-to-fit mode - // table.getHeader().setStretchToFitActive (true); - -// table.setMultipleSelectionEnabled (false); + table.getHeader().setSortColumnId(1, false); // sort ascending by ID column + valueTreeState.state.addListener(this); + valueTreeState.addParameterListener("bank", this); + valueTreeState.addParameterListener("preset", this); } -void TableComponent::setRows(const vector>& rows, int initiallySelectedRow) { - this->rows = rows; +TableComponent::~TableComponent() { + 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(param) != nullptr); + AudioParameterInt* castParam{dynamic_cast(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.updateContent(); table.getHeader().setSortColumnId(0, true); - table.selectRow(initiallySelectedRow); + selectCurrentPreset(); 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 int TableComponent::getNumRows() { @@ -96,6 +155,13 @@ void TableComponent::paintRowBackground ( 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 // components. void TableComponent::paintCell ( @@ -109,7 +175,9 @@ void TableComponent::paintCell ( g.setColour (getLookAndFeel().findColour (ListBox::textColourId)); 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.fillRect (width - 1, 0, 1, height); @@ -122,25 +190,26 @@ void TableComponent::sortOrderChanged ( bool isForwards ) { if (newSortColumnId != 0) { - int selectedRowIx = table.getSelectedRow(); - vector selectedRow; - if (selectedRowIx >= 0) { - selectedRow = rows[selectedRowIx]; - } - TableComponent::DataSorter sorter (newSortColumnId, isForwards); sort(rows.begin(), rows.end(), sorter); table.updateContent(); + selectCurrentPreset(); + } +} - if (selectedRowIx >= 0) { - for (auto it = rows.begin(); it != rows.end(); ++it) { - if(*it == selectedRow) { - int index = static_cast(std::distance(rows.begin(), it)); - table.selectRow(index); - break; - } - } +void TableComponent::selectCurrentPreset() { + table.deselectAllRows(); + RangedAudioParameter *param{valueTreeState.getParameter("preset")}; + jassert(dynamic_cast(param) != nullptr); + AudioParameterInt* castParam{dynamic_cast(param)}; + int value{castParam->get()}; + + for (auto it{rows.begin()}; it != rows.end(); ++it) { + if(it->preset == value) { + int index{static_cast(distance(rows.begin(), it))}; + table.selectRow(index); + break; } } } @@ -148,17 +217,17 @@ void TableComponent::sortOrderChanged ( // This is overloaded from TableListBoxModel, and should choose the best width for the specified // column. int TableComponent::getColumnAutoSizeWidth (int columnId) { -// if (columnId == 5) -// return 100; // (this is the ratings column, containing a custom combobox component) if (columnId == 1) - return 30; // (this is the ratings column, containing a custom combobox component) + return 30; int widest = 32; // find the widest bit of text in this column.. - for (int i = getNumRows(); --i >= 0;) { - widest = jmax (widest, font.getStringWidth (rows[i][columnId-1])); + for (int i{getNumRows()}; --i >= 0;) { + TableRow& row{rows[i]}; + String text{row.getStringContents(columnId)}; + widest = jmax (widest, font.getStringWidth (text)); } return widest + 8; @@ -177,20 +246,24 @@ TableComponent::DataSorter::DataSorter ( int columnByWhichToSort, bool forwards ) - : columnByWhichToSort (columnByWhichToSort), - direction (forwards ? 1 : -1) +: columnByWhichToSort (columnByWhichToSort) +, direction (forwards ? 1 : -1) {} bool TableComponent::DataSorter::operator ()( - vector first, - vector second + TableRow first, + TableRow second ) { - int result = String(first[columnByWhichToSort-1]) - .compareNatural (String(second[columnByWhichToSort-1])); - - if (result == 0) - result = String(first[0]) - .compareNatural (String(second[0])); + int result; + if (columnByWhichToSort <= 1) { + result = compare(first.preset, second.preset); + } else { + result = first.name + .compareNatural (second.name); + if (result == 0) { + result = compare(first.preset, second.preset); + } + } result *= direction; @@ -201,9 +274,21 @@ void TableComponent::selectedRowsChanged (int row) { if (row < 0) { return; } - onRowSelected(rowToIDMapper(rows[row])); + int newPreset{rows[row].preset}; + RangedAudioParameter *param{valueTreeState.getParameter("preset")}; + jassert(dynamic_cast(param) != nullptr); + AudioParameterInt* castParam{dynamic_cast(param)}; + *castParam = newPreset; } bool TableComponent::keyPressed(const KeyPress &key) { return table.keyPressed(key); } + +TableRow::TableRow( + int preset, + String name +) +: preset{preset} +, name{name} +{} diff --git a/Source/TableComponent.h b/Source/TableComponent.h index d436f4c..16d76a5 100644 --- a/Source/TableComponent.h +++ b/Source/TableComponent.h @@ -9,22 +9,38 @@ #pragma once #include "../JuceLibraryCode/JuceHeader.h" -#include "PresetsToBanks.h" #include #include +#include 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, - public TableListBoxModel { + public TableListBoxModel, + public ValueTree::Listener, + public AudioProcessorValueTreeState::Listener { public: TableComponent( - const vector &columns, - const vector> &rows, - const function &onRowSelected, - const function&)> &rowToIDMapper, - int initiallySelectedRow + AudioProcessorValueTreeState& valueTreeState ); + ~TableComponent(); int getNumRows() override; @@ -52,19 +68,35 @@ public: void resized() override; - void setRows(const vector>& rows, int initiallySelectedRow); - 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: + void loadModelFrom(ValueTree& banks); + void repopulateTable(); + void selectCurrentPreset(); + + AudioProcessorValueTreeState& valueTreeState; + TableListBox table; // the table component itself Font font; - vector columns; - vector> rows; + typedef multimap BanksToPresets; + BanksToPresets banksToPresets; - function onRowSelected; - function&)> rowToIDMapper; + vector rows; // A comparator used to sort our data when the user clicks a column header class DataSorter { @@ -75,8 +107,8 @@ private: ); bool operator ()( - vector first, - vector second + TableRow first, + TableRow second ); private: @@ -85,4 +117,4 @@ private: }; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TableComponent) -}; \ No newline at end of file +}; diff --git a/Source/TablesComponent.cpp b/Source/TablesComponent.cpp index 67f40de..ede0ea9 100644 --- a/Source/TablesComponent.cpp +++ b/Source/TablesComponent.cpp @@ -8,180 +8,32 @@ using namespace std; using namespace placeholders; TablesComponent::TablesComponent( - FluidSynthModel* fluidSynthModel -) : fluidSynthModel(fluidSynthModel), - banksToPresets(fluidSynthModel->getBanks()), - initialised(false) + AudioProcessorValueTreeState& valueTreeState +) +: valueTreeState{valueTreeState} +, banks{valueTreeState} +, presetTable{valueTreeState} { - fluid_preset_t* currentPreset = getCurrentPreset(); - selectedBank = -1; - int selectedPreset = -1; - if (currentPreset != nullptr) { - selectedBank = fluid_preset_get_banknum(currentPreset); - selectedPreset = fluid_preset_get_num(currentPreset); - } - - auto rowToPresetMapper = [this](const vector &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); + presetTable.setWantsKeyboardFocus(false); addAndMakeVisible(presetTable); 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 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 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 TablesComponent::mapBanks(const BanksToPresets &banksToPresets) { - vector 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> TablesComponent::mapPresets(const BanksToPresets &banksToPresets, int bank) { - vector> rows; - - pair iterators = banksToPresets.equal_range(bank); - for (auto it = iterators.first; it != iterators.second; ++it) { - Preset b = it->second; - vector row; - row.push_back(to_string(b.getPreset())); - row.push_back(b.getName()); - - rows.push_back(row); - } - - return rows; } void TablesComponent::resized() { Rectangle 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) { if (key.getKeyCode() == KeyPress::leftKey || key.getKeyCode() == KeyPress::rightKey) { - banks->cycle(key.getKeyCode() == KeyPress::rightKey); + banks.cycle(key.getKeyCode() == KeyPress::rightKey); return true; } - 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 - ); + return presetTable.keyPressed(key); } diff --git a/Source/TablesComponent.h b/Source/TablesComponent.h index d86e7d9..ea157ff 100644 --- a/Source/TablesComponent.h +++ b/Source/TablesComponent.h @@ -7,48 +7,28 @@ #include "../JuceLibraryCode/JuceHeader.h" #include "Pills.h" #include "TableComponent.h" -#include "Preset.h" -#include "PresetsToBanks.h" #include "FluidSynthModel.h" #include #include using namespace std; -class TablesComponent : public Component, - public FluidSynthModel::Listener +class TablesComponent : public Component { public: TablesComponent( - FluidSynthModel* fluidSynthModel + AudioProcessorValueTreeState& valueTreeState ); - ~TablesComponent(); void resized() override; bool keyPressed(const KeyPress &key) override; - void fontChanged(FluidSynthModel *, const String &) override; private: - FluidSynthModel* fluidSynthModel; - int selectedBank; + AudioProcessorValueTreeState& valueTreeState; - Pills* banks; - TableComponent* presetTable; - - BanksToPresets banksToPresets; - - static vector> mapPresets(const BanksToPresets &banksToPresets, int bank); - static vector 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; + Pills banks; + TableComponent presetTable; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TablesComponent) }; diff --git a/Source/Util.h b/Source/Util.h index c186f83..85de943 100644 --- a/Source/Util.h +++ b/Source/Util.h @@ -9,3 +9,11 @@ #define DEBUG_PRINT(str) #endif #endif + +namespace Util { + inline int compare(int a, int b) { + if (a > b) return 1; + if (a == b) return 0; + return -1; + } +} \ No newline at end of file