commit
5989267615
|
@ -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 = "<group>"; };
|
||||
35099D9422CAB0A400CD4523 /* GuiConstants.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = GuiConstants.h; path = ../../Source/GuiConstants.h; sourceTree = "<group>"; };
|
||||
35880F58CB540AD30D1B0ED3 /* TablesComponent.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = TablesComponent.h; path = ../../Source/TablesComponent.h; sourceTree = SOURCE_ROOT; };
|
||||
358E45B422BEE53A0087ED8D /* libpcre.1.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; path = libpcre.1.dylib; sourceTree = "<group>"; };
|
||||
358E45B522BEE53A0087ED8D /* libvorbisenc.2.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; path = libvorbisenc.2.dylib; sourceTree = "<group>"; };
|
||||
|
@ -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 = "<absolute>"; };
|
||||
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 = "<absolute>"; };
|
||||
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 = "<absolute>"; };
|
||||
6FEF19AE08ED1DC1E3D9DF43 /* AppConfig.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = AppConfig.h; path = ../../JuceLibraryCode/AppConfig.h; sourceTree = SOURCE_ROOT; };
|
||||
706FF998202761F30811FA6B /* juce_audio_devices */ = {isa = PBXFileReference; lastKnownFileType = folder; name = juce_audio_devices; path = /Applications/JUCE/modules/juce_audio_devices; sourceTree = "<absolute>"; };
|
||||
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 = "<absolute>"; };
|
||||
7D2457AD994644752178FC82 /* include_juce_audio_plugin_client_VST_utils.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; name = include_juce_audio_plugin_client_VST_utils.mm; path = ../../JuceLibraryCode/include_juce_audio_plugin_client_VST_utils.mm; sourceTree = SOURCE_ROOT; };
|
||||
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 = "<absolute>"; };
|
||||
|
@ -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 = "<absolute>"; };
|
||||
|
@ -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 = "<group>";
|
||||
|
@ -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 */,
|
||||
|
|
|
@ -0,0 +1,84 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1020"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "345302E3C02BBFCDACE98BE7"
|
||||
BuildableName = "juicysfplugin.component"
|
||||
BlueprintName = "juicysfplugin - AU"
|
||||
ReferencedContainer = "container:juicysfplugin.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<Testables>
|
||||
</Testables>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES">
|
||||
<PathRunnable
|
||||
runnableDebuggingMode = "0"
|
||||
FilePath = "/Applications/JUCE/extras/AudioPluginHost/Builds/MacOSX/build/Debug/AudioPluginHost.app">
|
||||
</PathRunnable>
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "345302E3C02BBFCDACE98BE7"
|
||||
BuildableName = "juicysfplugin.component"
|
||||
BlueprintName = "juicysfplugin - AU"
|
||||
ReferencedContainer = "container:juicysfplugin.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "345302E3C02BBFCDACE98BE7"
|
||||
BuildableName = "juicysfplugin.component"
|
||||
BlueprintName = "juicysfplugin - AU"
|
||||
ReferencedContainer = "container:juicysfplugin.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
|
@ -33,7 +33,7 @@
|
|||
</AdditionalOptions>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Release"
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
|
|
|
@ -51,8 +51,11 @@
|
|||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<PathRunnable
|
||||
runnableDebuggingMode = "0"
|
||||
FilePath = "/Applications/JUCE/extras/AudioPluginHost/Builds/MacOSX/build/Debug/AudioPluginHost.app">
|
||||
</PathRunnable>
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "24C399ED93EC47D5BEB26F76"
|
||||
|
@ -60,7 +63,7 @@
|
|||
BlueprintName = "juicysfplugin - Standalone Plugin"
|
||||
ReferencedContainer = "container:juicysfplugin.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
</MacroExpansion>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</LaunchAction>
|
||||
|
|
|
@ -0,0 +1,84 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1020"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "48A570326AA09CE818BE1901"
|
||||
BuildableName = "juicysfplugin.vst"
|
||||
BlueprintName = "juicysfplugin - VST"
|
||||
ReferencedContainer = "container:juicysfplugin.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<Testables>
|
||||
</Testables>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES">
|
||||
<PathRunnable
|
||||
runnableDebuggingMode = "0"
|
||||
FilePath = "/Applications/JUCE/extras/AudioPluginHost/Builds/MacOSX/build/Debug/AudioPluginHost.app">
|
||||
</PathRunnable>
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "48A570326AA09CE818BE1901"
|
||||
BuildableName = "juicysfplugin.vst"
|
||||
BlueprintName = "juicysfplugin - VST"
|
||||
ReferencedContainer = "container:juicysfplugin.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "48A570326AA09CE818BE1901"
|
||||
BuildableName = "juicysfplugin.vst"
|
||||
BlueprintName = "juicysfplugin - VST"
|
||||
ReferencedContainer = "container:juicysfplugin.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
|
@ -42,6 +42,11 @@
|
|||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES">
|
||||
<PathRunnable
|
||||
runnableDebuggingMode = "0"
|
||||
BundleIdentifier = "com.roli.juce.pluginhost"
|
||||
FilePath = "/Applications/JUCE/extras/AudioPluginHost/Builds/MacOSX/build/Debug/AudioPluginHost.app">
|
||||
</PathRunnable>
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
|
|
|
@ -1,19 +0,0 @@
|
|||
//
|
||||
// Created by Alex Birch on 13/04/2018.
|
||||
// Copyright (c) 2018 Birchlabs. All rights reserved.
|
||||
//
|
||||
|
||||
#include "BankAndPreset.h"
|
||||
|
||||
BankAndPreset::BankAndPreset(int bank, int preset)
|
||||
: bank(bank),
|
||||
preset(preset)
|
||||
{}
|
||||
|
||||
int BankAndPreset::getBank() {
|
||||
return bank;
|
||||
}
|
||||
|
||||
int BankAndPreset::getPreset() {
|
||||
return preset;
|
||||
}
|
|
@ -1,23 +0,0 @@
|
|||
//
|
||||
// Created by Alex Birch on 13/04/2018.
|
||||
// Copyright (c) 2018 Birchlabs. All rights reserved.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../JuceLibraryCode/JuceHeader.h"
|
||||
|
||||
class BankAndPreset {
|
||||
public:
|
||||
BankAndPreset(int bank, int preset);
|
||||
|
||||
int getBank();
|
||||
int getPreset();
|
||||
|
||||
private:
|
||||
int bank;
|
||||
int preset;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (BankAndPreset)
|
||||
};
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
//
|
||||
// Created by Alex Birch on 11/04/2018.
|
||||
// Copyright (c) 2018 Birchlabs. All rights reserved.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "FilePickerFragment.h"
|
||||
|
||||
class ExposesComponents {
|
||||
public:
|
||||
virtual ~ExposesComponents() {}
|
||||
|
||||
virtual FilePickerFragment& getFilePicker() = 0;
|
||||
|
||||
};
|
|
@ -4,11 +4,13 @@
|
|||
|
||||
#include "FilePicker.h"
|
||||
#include "MyColours.h"
|
||||
#include "Util.h"
|
||||
|
||||
FilePicker::FilePicker(
|
||||
FluidSynthModel* fluidSynthModel
|
||||
AudioProcessorValueTreeState& valueTreeState
|
||||
// FluidSynthModel& fluidSynthModel
|
||||
)
|
||||
: fileChooser(
|
||||
: fileChooser{
|
||||
"File",
|
||||
File(),
|
||||
true,
|
||||
|
@ -16,20 +18,25 @@ FilePicker::FilePicker(
|
|||
false,
|
||||
"*.sf2;*.sf3",
|
||||
String(),
|
||||
"Choose a Soundfont file to load into the synthesizer"
|
||||
),
|
||||
fluidSynthModel(fluidSynthModel),
|
||||
currentPath() {
|
||||
"Choose a Soundfont file to load into the synthesizer"}
|
||||
, valueTreeState{valueTreeState}
|
||||
// , fluidSynthModel{fluidSynthModel}
|
||||
// , currentPath{}
|
||||
{
|
||||
// faster (rounded edges introduce transparency)
|
||||
setOpaque (true);
|
||||
|
||||
setDisplayedFilePath(fluidSynthModel->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;
|
||||
// currentPath = path;
|
||||
fileChooser.setCurrentFile(File(path), true, dontSendNotification);
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -1,14 +0,0 @@
|
|||
//
|
||||
// Created by Alex Birch on 11/04/2018.
|
||||
// Copyright (c) 2018 Birchlabs. All rights reserved.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
#include "../JuceLibraryCode/JuceHeader.h"
|
||||
|
||||
class FilePickerFragment {
|
||||
public:
|
||||
virtual ~FilePickerFragment() {}
|
||||
|
||||
virtual void setDisplayedFilePath(const String&) = 0;
|
||||
};
|
|
@ -3,329 +3,468 @@
|
|||
//
|
||||
|
||||
#include <iostream>
|
||||
#include <iterator>
|
||||
#include <fluidsynth.h>
|
||||
#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<fluid_midi_control_change, String> 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<String, fluid_midi_control_change> FluidSynthModel::paramToController{[]{
|
||||
map<String, fluid_midi_control_change> map;
|
||||
transform(
|
||||
controllerToParam.begin(),
|
||||
controllerToParam.end(),
|
||||
inserter(map, map.begin()),
|
||||
[](const pair<fluid_midi_control_change, String>& pair) {
|
||||
return make_pair(pair.second, pair.first);
|
||||
});
|
||||
return map;
|
||||
}()};
|
||||
|
||||
FluidSynthModel::FluidSynthModel(
|
||||
AudioProcessorValueTreeState& valueTreeState
|
||||
)
|
||||
: valueTreeState{valueTreeState}
|
||||
, settings{nullptr, nullptr}
|
||||
, synth{nullptr, nullptr}
|
||||
, currentSampleRate{44100}
|
||||
, sfont_id{-1}
|
||||
, channel{0}
|
||||
{
|
||||
valueTreeState.addParameterListener("bank", this);
|
||||
valueTreeState.addParameterListener("preset", this);
|
||||
for (const auto &[param, controller]: paramToController) {
|
||||
valueTreeState.addParameterListener(param, this);
|
||||
}
|
||||
valueTreeState.state.addListener(this);
|
||||
}
|
||||
|
||||
FluidSynthModel::~FluidSynthModel() {
|
||||
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);
|
||||
synth = { new_fluid_synth(settings.get()), delete_fluid_synth };
|
||||
fluid_synth_set_sample_rate(synth.get(), currentSampleRate);
|
||||
|
||||
if (sharesParams.getSoundFontPath().isNotEmpty()) {
|
||||
loadFont(sharesParams.getSoundFontPath());
|
||||
changePreset(sharesParams.getBank(), sharesParams.getPreset());
|
||||
}
|
||||
ValueTree soundFont{valueTreeState.state.getChildWithName("soundFont")};
|
||||
String path{soundFont.getProperty("path", "")};
|
||||
loadFont(path);
|
||||
|
||||
fluid_synth_set_gain(synth, 2.0);
|
||||
|
||||
for(int i{SOUND_CTRL1}; i <= SOUND_CTRL10; i++)
|
||||
{
|
||||
setControllerValue(i, 0);
|
||||
}
|
||||
|
||||
// fluid_synth_bank_select(synth, 0, 3);
|
||||
|
||||
// fluid_handle_inst
|
||||
|
||||
// driver = new_fluid_audio_driver(settings, synth);
|
||||
|
||||
// changePreset(128, 13);
|
||||
|
||||
// float env_amount(12000.0f);
|
||||
|
||||
// http://www.synthfont.com/SoundFont_NRPNs.PDF
|
||||
float env_amount(20000.0f);
|
||||
// float env_amount(24000.0f);
|
||||
// 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<AudioParameterInt*>(param) != nullptr);
|
||||
AudioParameterInt* castParam{dynamic_cast<AudioParameterInt*>(param)};
|
||||
int value{castParam->get()};
|
||||
setControllerValue(static_cast<int>(controller), value);
|
||||
}
|
||||
|
||||
fluid_mod_t *mod(new_fluid_mod());
|
||||
//
|
||||
fluid_mod_set_source1(mod,
|
||||
// http://www.synthfont.com/SoundFont_NRPNs.PDF
|
||||
float env_amount{20000.0f};
|
||||
|
||||
unique_ptr<fluid_mod_t, decltype(&delete_fluid_mod)> mod{new_fluid_mod(), delete_fluid_mod};
|
||||
fluid_mod_set_source1(mod.get(),
|
||||
static_cast<int>(SOUND_CTRL2), // MIDI CC 71 Timbre/Harmonic Intensity (filter resonance)
|
||||
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<int>(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<int>(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<int>(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<int>(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<int>(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<AudioParameterInt*>(param) != nullptr);
|
||||
AudioParameterInt* castParam{dynamic_cast<AudioParameterInt*>(param)};
|
||||
bank = castParam->get();
|
||||
}
|
||||
{
|
||||
RangedAudioParameter *param{valueTreeState.getParameter("preset")};
|
||||
jassert(dynamic_cast<AudioParameterInt*>(param) != nullptr);
|
||||
AudioParameterInt* castParam{dynamic_cast<AudioParameterInt*>(param)};
|
||||
preset = castParam->get();
|
||||
}
|
||||
int bankOffset{fluid_synth_get_bank_offset(synth.get(), sfont_id)};
|
||||
fluid_synth_program_select(
|
||||
synth.get(),
|
||||
channel,
|
||||
sfont_id,
|
||||
static_cast<unsigned int>(bankOffset + bank),
|
||||
static_cast<unsigned int>(preset));
|
||||
} else if (
|
||||
// https://stackoverflow.com/a/55482091/5257399
|
||||
auto it{paramToController.find(parameterID)};
|
||||
it != end(paramToController)) {
|
||||
RangedAudioParameter *param{valueTreeState.getParameter(parameterID)};
|
||||
jassert(dynamic_cast<AudioParameterInt*>(param) != nullptr);
|
||||
AudioParameterInt* castParam{dynamic_cast<AudioParameterInt*>(param)};
|
||||
int value{castParam->get()};
|
||||
int controllerNumber{static_cast<int>(it->second)};
|
||||
|
||||
fluid_synth_cc(
|
||||
synth.get(),
|
||||
channel,
|
||||
controllerNumber,
|
||||
value);
|
||||
}
|
||||
}
|
||||
|
||||
void FluidSynthModel::valueTreePropertyChanged(ValueTree& treeWhosePropertyHasChanged,
|
||||
const Identifier& property) {
|
||||
if (treeWhosePropertyHasChanged.getType() == StringRef("soundFont")) {
|
||||
if (property == StringRef("path")) {
|
||||
String soundFontPath{treeWhosePropertyHasChanged.getProperty("path", "")};
|
||||
if (soundFontPath.isNotEmpty()) {
|
||||
unloadAndLoadFont(soundFontPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FluidSynthModel::setControllerValue(int controller, int value) {
|
||||
fluid_midi_event_t *midi_event(new_fluid_midi_event());
|
||||
fluid_midi_event_set_type(midi_event, static_cast<int>(CONTROL_CHANGE));
|
||||
fluid_midi_event_set_channel(midi_event, 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> bankAndPreset = getFirstBankAndPreset();
|
||||
bank = bankAndPreset->getBank();
|
||||
preset = bankAndPreset->getPreset();
|
||||
}
|
||||
changePresetImpl(bank, preset);
|
||||
sharesParams.setPreset(preset);
|
||||
sharesParams.setBank(bank);
|
||||
}
|
||||
|
||||
void FluidSynthModel::changePresetImpl(int bank, int preset) {
|
||||
fluid_synth_program_select(synth, channel, sfont_id, static_cast<unsigned int>(bank), static_cast<unsigned int>(preset));
|
||||
}
|
||||
|
||||
fluid_preset_t* FluidSynthModel::getFirstPreset() {
|
||||
fluid_sfont_t* sfont = fluid_synth_get_sfont_by_id(synth, sfont_id);
|
||||
|
||||
jassert(sfont != nullptr);
|
||||
fluid_sfont_iteration_start(sfont);
|
||||
|
||||
return fluid_sfont_iteration_next(sfont);
|
||||
}
|
||||
|
||||
unique_ptr<BankAndPreset> FluidSynthModel::getFirstBankAndPreset() {
|
||||
fluid_preset_t* preset = getFirstPreset();
|
||||
|
||||
int offset = fluid_synth_get_bank_offset(synth, sfont_id);
|
||||
|
||||
return make_unique<BankAndPreset>(fluid_preset_get_banknum(preset) + offset, fluid_preset_get_num(preset));
|
||||
};
|
||||
|
||||
void FluidSynthModel::selectFirstPreset() {
|
||||
fluid_preset_t* preset = getFirstPreset();
|
||||
|
||||
int offset = fluid_synth_get_bank_offset(synth, sfont_id);
|
||||
|
||||
changePreset(fluid_preset_get_banknum(preset) + offset, fluid_preset_get_num(preset));
|
||||
}
|
||||
|
||||
BanksToPresets FluidSynthModel::getBanks() {
|
||||
BanksToPresets banksToPresets;
|
||||
|
||||
int soundfontCount = fluid_synth_sfcount(synth);
|
||||
|
||||
if (soundfontCount == 0) {
|
||||
// no soundfont selected
|
||||
return banksToPresets;
|
||||
}
|
||||
|
||||
fluid_sfont_t* sfont = fluid_synth_get_sfont_by_id(synth, sfont_id);
|
||||
if(sfont == nullptr) {
|
||||
// no soundfont found by that ID
|
||||
// the above guard (soundfontCount) protects us for the
|
||||
// main case we're expecting. this guard is just defensive programming.
|
||||
return banksToPresets;
|
||||
}
|
||||
|
||||
int offset = fluid_synth_get_bank_offset(synth, sfont_id);
|
||||
|
||||
fluid_sfont_iteration_start(sfont);
|
||||
|
||||
for(fluid_preset_t* preset = fluid_sfont_iteration_next(sfont);
|
||||
preset != nullptr;
|
||||
preset = fluid_sfont_iteration_next(sfont)) {
|
||||
banksToPresets.insert(BanksToPresets::value_type(
|
||||
fluid_preset_get_banknum(preset) + offset,
|
||||
*new Preset(
|
||||
fluid_preset_get_num(preset),
|
||||
fluid_preset_get_name(preset)
|
||||
)
|
||||
));
|
||||
}
|
||||
|
||||
return banksToPresets;
|
||||
}
|
||||
|
||||
fluid_synth_t* FluidSynthModel::getSynth() {
|
||||
// https://msdn.microsoft.com/en-us/library/hh279669.aspx
|
||||
// You can pass a shared_ptr to another function in the following ways:
|
||||
// Pass the shared_ptr by value. This invokes the copy constructor, increments the reference count, and makes the callee an owner.
|
||||
return synth;
|
||||
}
|
||||
|
||||
void FluidSynthModel::onFileNameChanged(const String &absPath, int bank, int preset) {
|
||||
if (!shouldLoadFont(absPath)) {
|
||||
return;
|
||||
}
|
||||
unloadAndLoadFont(absPath);
|
||||
changePreset(bank, preset);
|
||||
sharesParams.setSoundFontPath(absPath);
|
||||
eventListeners.call(&FluidSynthModel::Listener::fontChanged, this, absPath);
|
||||
}
|
||||
|
||||
void FluidSynthModel::unloadAndLoadFont(const String &absPath) {
|
||||
// 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);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
bank = { "bank", {
|
||||
{ "num", bankNum }
|
||||
} };
|
||||
greatestEncounteredBank = bankNum;
|
||||
}
|
||||
bank.appendChild({ "preset", {
|
||||
{ "num", fluid_preset_get_num(preset) },
|
||||
{ "name", String{fluid_preset_get_name(preset)} }
|
||||
}, {} }, nullptr);
|
||||
}
|
||||
if (greatestEncounteredBank > -1) {
|
||||
banks.appendChild(bank, nullptr);
|
||||
}
|
||||
}
|
||||
valueTreeState.state.getChildWithName("banks").copyPropertiesAndChildrenFrom(banks, nullptr);
|
||||
valueTreeState.state.getChildWithName("banks").sendPropertyChangeMessage("synthetic");
|
||||
|
||||
void FluidSynthModel::Listener::fontChanged(FluidSynthModel * model, const String &absPath) {
|
||||
}
|
||||
|
||||
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);
|
||||
#if JUCE_DEBUG
|
||||
// unique_ptr<XmlElement> 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<float>& buffer, MidiBuffer& midiMessages) {
|
||||
MidiBuffer processedMidi;
|
||||
int time;
|
||||
MidiMessage m;
|
||||
|
||||
for (MidiBuffer::Iterator i{midiMessages}; i.getNextEvent(m, time);) {
|
||||
DEBUG_PRINT(m.getDescription());
|
||||
|
||||
if (m.isNoteOn()) {
|
||||
fluid_synth_noteon(
|
||||
synth.get(),
|
||||
channel,
|
||||
m.getNoteNumber(),
|
||||
m.getVelocity());
|
||||
} else if (m.isNoteOff()) {
|
||||
fluid_synth_noteoff(
|
||||
synth.get(),
|
||||
channel,
|
||||
m.getNoteNumber());
|
||||
} else if (m.isController()) {
|
||||
fluid_synth_cc(
|
||||
synth.get(),
|
||||
channel,
|
||||
m.getControllerNumber(),
|
||||
m.getControllerValue());
|
||||
|
||||
fluid_midi_control_change controllerNum{static_cast<fluid_midi_control_change>(m.getControllerNumber())};
|
||||
if (auto it{controllerToParam.find(controllerNum)};
|
||||
it != end(controllerToParam)) {
|
||||
String parameterID{it->second};
|
||||
RangedAudioParameter *param{valueTreeState.getParameter(parameterID)};
|
||||
jassert(dynamic_cast<AudioParameterInt*>(param) != nullptr);
|
||||
AudioParameterInt* castParam{dynamic_cast<AudioParameterInt*>(param)};
|
||||
*castParam = m.getControllerValue();
|
||||
}
|
||||
} else if (m.isProgramChange()) {
|
||||
int result{fluid_synth_program_change(
|
||||
synth.get(),
|
||||
channel,
|
||||
m.getProgramChangeNumber())};
|
||||
if (result == FLUID_OK) {
|
||||
RangedAudioParameter *param{valueTreeState.getParameter("preset")};
|
||||
jassert(dynamic_cast<AudioParameterInt*>(param) != nullptr);
|
||||
AudioParameterInt* castParam{dynamic_cast<AudioParameterInt*>(param)};
|
||||
*castParam = m.getProgramChangeNumber();
|
||||
}
|
||||
} else if (m.isPitchWheel()) {
|
||||
fluid_synth_pitch_bend(
|
||||
synth.get(),
|
||||
channel,
|
||||
m.getPitchWheelValue());
|
||||
} else if (m.isChannelPressure()) {
|
||||
fluid_synth_channel_pressure(
|
||||
synth.get(),
|
||||
channel,
|
||||
m.getChannelPressureValue());
|
||||
} else if (m.isAftertouch()) {
|
||||
fluid_synth_key_pressure(
|
||||
synth.get(),
|
||||
channel,
|
||||
m.getNoteNumber(),
|
||||
m.getAfterTouchValue());
|
||||
// } else if (m.isMetaEvent()) {
|
||||
// fluid_midi_event_t *midi_event{new_fluid_midi_event()};
|
||||
// fluid_midi_event_set_type(midi_event, static_cast<int>(MIDI_SYSTEM_RESET));
|
||||
// fluid_synth_handle_midi_event(synth.get(), midi_event);
|
||||
// delete_fluid_midi_event(midi_event);
|
||||
} else if (m.isSysEx()) {
|
||||
fluid_synth_sysex(
|
||||
synth.get(),
|
||||
reinterpret_cast<const char*>(m.getSysExData()),
|
||||
m.getSysExDataSize(),
|
||||
nullptr, // no response pointer because we have no interest in handling response currently
|
||||
nullptr, // no response_len pointer because we have no interest in handling response currently
|
||||
nullptr, // no handled pointer because we have no interest in handling response currently
|
||||
static_cast<int>(false));
|
||||
}
|
||||
}
|
||||
|
||||
// fluid_synth_get_cc(fluidSynth, 0, 73, &pval);
|
||||
// Logger::outputDebugString ( juce::String::formatted("hey: %d\n", pval) );
|
||||
|
||||
fluid_synth_process(
|
||||
synth.get(),
|
||||
buffer.getNumSamples(),
|
||||
0,
|
||||
nullptr,
|
||||
buffer.getNumChannels(),
|
||||
buffer.getArrayOfWritePointers());
|
||||
}
|
||||
|
||||
int FluidSynthModel::getNumPrograms()
|
||||
{
|
||||
return 128; // NB: some hosts don't cope very well if you tell them there are 0 programs,
|
||||
// so this should be at least 1, even if you're not really implementing programs.
|
||||
}
|
||||
|
||||
int FluidSynthModel::getCurrentProgram()
|
||||
{
|
||||
RangedAudioParameter *param{valueTreeState.getParameter("preset")};
|
||||
jassert(dynamic_cast<AudioParameterInt*>(param) != nullptr);
|
||||
AudioParameterInt* castParam{dynamic_cast<AudioParameterInt*>(param)};
|
||||
return castParam->get();
|
||||
}
|
||||
|
||||
void FluidSynthModel::setCurrentProgram(int index)
|
||||
{
|
||||
RangedAudioParameter *param{valueTreeState.getParameter("preset")};
|
||||
jassert(dynamic_cast<AudioParameterInt*>(param) != nullptr);
|
||||
AudioParameterInt* castParam{dynamic_cast<AudioParameterInt*>(param)};
|
||||
// setCurrentProgram() gets invoked from non-message thread.
|
||||
// AudioParameterInt#operator= will activate any listeners of audio parameter "preset".
|
||||
// This includes TableComponent, who will update its UI.
|
||||
// we need to lock the message thread whilst it does that UI update.
|
||||
const MessageManagerLock mmLock;
|
||||
*castParam = index;
|
||||
}
|
||||
|
||||
const String FluidSynthModel::getProgramName(int index)
|
||||
{
|
||||
fluid_sfont_t* sfont{
|
||||
sfont_id == -1
|
||||
? nullptr
|
||||
: fluid_synth_get_sfont_by_id(synth.get(), sfont_id)
|
||||
};
|
||||
if (!sfont) {
|
||||
String presetName{"Preset "};
|
||||
return presetName << index;
|
||||
}
|
||||
RangedAudioParameter *param{valueTreeState.getParameter("bank")};
|
||||
jassert(dynamic_cast<AudioParameterInt*>(param) != nullptr);
|
||||
AudioParameterInt* castParam{dynamic_cast<AudioParameterInt*>(param)};
|
||||
int bank{castParam->get()};
|
||||
|
||||
fluid_preset_t *preset{fluid_sfont_get_preset(
|
||||
sfont,
|
||||
bank,
|
||||
index)};
|
||||
if (!preset) {
|
||||
String presetName{"Preset "};
|
||||
return presetName << index;
|
||||
}
|
||||
return {fluid_preset_get_name(preset)};
|
||||
}
|
||||
|
||||
void FluidSynthModel::changeProgramName(int index, const String& newName)
|
||||
{
|
||||
// no-op; we don't support modifying the soundfont, so let's not support modification of preset names.
|
||||
}
|
||||
|
|
|
@ -5,93 +5,80 @@
|
|||
#pragma once
|
||||
|
||||
#include "../JuceLibraryCode/JuceHeader.h"
|
||||
#include "SharesParams.h"
|
||||
#include <fluidsynth.h>
|
||||
#include <memory>
|
||||
#include "BankAndPreset.h"
|
||||
#include "PresetsToBanks.h"
|
||||
|
||||
|
||||
// https://stackoverflow.com/a/13446565/5257399
|
||||
//using std::shared_ptr;
|
||||
#include <map>
|
||||
#include "MidiConstants.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
class FluidSynthModel {
|
||||
class FluidSynthModel
|
||||
: public ValueTree::Listener
|
||||
, public AudioProcessorValueTreeState::Listener {
|
||||
public:
|
||||
FluidSynthModel(SharesParams& p);
|
||||
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<float>& 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);
|
||||
|
||||
const String& getCurrentSoundFontAbsPath();
|
||||
//==============================================================================
|
||||
virtual void parameterChanged (const String& parameterID, float newValue) override;
|
||||
|
||||
virtual void valueTreePropertyChanged (ValueTree& treeWhosePropertyHasChanged,
|
||||
const Identifier& property) override;
|
||||
inline virtual void valueTreeChildAdded (ValueTree& parentTree,
|
||||
ValueTree& childWhichHasBeenAdded) override {};
|
||||
inline virtual void valueTreeChildRemoved (ValueTree& parentTree,
|
||||
ValueTree& childWhichHasBeenRemoved,
|
||||
int indexFromWhichChildWasRemoved) override {};
|
||||
inline virtual void valueTreeChildOrderChanged (ValueTree& parentTreeWhoseChildrenHaveMoved,
|
||||
int oldIndex, int newIndex) override {};
|
||||
inline virtual void valueTreeParentChanged (ValueTree& treeWhoseParentHasChanged) override {};
|
||||
inline virtual void valueTreeRedirected (ValueTree& treeWhichHasBeenChanged) override {};
|
||||
|
||||
//==============================================================================
|
||||
int getNumPrograms();
|
||||
int getCurrentProgram();
|
||||
void setCurrentProgram(int index);
|
||||
const String getProgramName(int index);
|
||||
void changeProgramName(int index, const String& newName);
|
||||
|
||||
private:
|
||||
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<fluid_midi_control_change, String> controllerToParam;
|
||||
static const map<String, fluid_midi_control_change> paramToController;
|
||||
|
||||
String currentSoundFontAbsPath;
|
||||
void refreshBanks();
|
||||
|
||||
AudioProcessorValueTreeState& valueTreeState;
|
||||
|
||||
// https://stackoverflow.com/questions/38980315/is-stdunique-ptr-deletion-order-guaranteed
|
||||
// members are destroyed in reverse of the order they're declared
|
||||
// http://www.fluidsynth.org/api/
|
||||
// in their examples, they destroy the synth before destroying the settings
|
||||
unique_ptr<fluid_settings_t, decltype(&delete_fluid_settings)> settings;
|
||||
unique_ptr<fluid_synth_t, decltype(&delete_fluid_synth)> synth;
|
||||
|
||||
float currentSampleRate;
|
||||
|
||||
fluid_preset_t* getFirstPreset();
|
||||
void selectFirstPreset();
|
||||
unique_ptr<BankAndPreset> 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<Listener> eventListeners;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (FluidSynthModel)
|
||||
};
|
||||
|
|
8
Source/GuiConstants.h
Normal file
8
Source/GuiConstants.h
Normal file
|
@ -0,0 +1,8 @@
|
|||
#pragma once
|
||||
|
||||
struct GuiConstants {
|
||||
inline static const int minWidth = 500;
|
||||
inline static const int maxWidth = 1900;
|
||||
inline static const int minHeight = 300;
|
||||
inline static const int maxHeight = 1000;
|
||||
};
|
|
@ -162,3 +162,8 @@ enum fluid_midi_control_change
|
|||
- for case (2) or (3), FLUID_NOISE_FLOOR should be the noise floor for 16 bits (i.e -90 dB).
|
||||
*/
|
||||
#define FLUID_PEAK_ATTENUATION 960.0f
|
||||
|
||||
struct MidiConstants {
|
||||
inline static const int midiMinValue = 0;
|
||||
inline static const int midiMaxValue = 127;
|
||||
};
|
||||
|
|
200
Source/Pills.cpp
200
Source/Pills.cpp
|
@ -4,88 +4,170 @@
|
|||
|
||||
#include "Pills.h"
|
||||
#include "MyColours.h"
|
||||
#include <algorithm>
|
||||
|
||||
using namespace std;
|
||||
|
||||
Pill::Pill(
|
||||
AudioProcessorValueTreeState& valueTreeState,
|
||||
int bank,
|
||||
bool isFirst,
|
||||
bool isLast
|
||||
)
|
||||
: valueTreeState{valueTreeState}
|
||||
, bank{bank}
|
||||
, textButton{String(bank)}
|
||||
{
|
||||
setOpaque(true);
|
||||
textButton.setConnectedEdges (
|
||||
(isFirst ? 0 : Button::ConnectedOnLeft)
|
||||
| (isLast ? 0 : Button::ConnectedOnRight)
|
||||
);
|
||||
textButton.setRadioGroupId(34567);
|
||||
textButton.setClickingTogglesState(true);
|
||||
|
||||
addAndMakeVisible(textButton);
|
||||
textButton.addListener(this);
|
||||
}
|
||||
|
||||
void Pill::paint (Graphics& g) {
|
||||
// (Our component is opaque, so we must completely fill the background with a solid colour)
|
||||
g.fillAll(getLookAndFeel().findColour(ResizableWindow::backgroundColourId));
|
||||
}
|
||||
|
||||
void Pill::resized() {
|
||||
textButton.setBounds(getLocalBounds());
|
||||
}
|
||||
|
||||
Pill::~Pill() {
|
||||
textButton.removeListener(this);
|
||||
}
|
||||
|
||||
void Pill::bankChanged(int bank) {
|
||||
textButton.setToggleState(this->bank == bank, dontSendNotification);
|
||||
}
|
||||
|
||||
void Pill::buttonClicked(Button* button) {
|
||||
ValueTree banks{valueTreeState.state.getChildWithName("banks")};
|
||||
int banksChildren{banks.getNumChildren()};
|
||||
ValueTree bank;
|
||||
for(int bankIx{0}; bankIx<banksChildren; bankIx++) {
|
||||
ValueTree currentBank{banks.getChild(bankIx)};
|
||||
int bankNum{currentBank.getProperty("num")};
|
||||
if (bankNum == this->bank) {
|
||||
bank = currentBank;
|
||||
break;
|
||||
}
|
||||
}
|
||||
ValueTree preset{bank.getChild(0)};
|
||||
int presetNum{preset.getProperty("num")};
|
||||
|
||||
{
|
||||
RangedAudioParameter *param{valueTreeState.getParameter("bank")};
|
||||
jassert(dynamic_cast<AudioParameterInt*>(param) != nullptr);
|
||||
AudioParameterInt* castParam{dynamic_cast<AudioParameterInt*>(param)};
|
||||
*castParam = this->bank;
|
||||
}
|
||||
{
|
||||
RangedAudioParameter *param{valueTreeState.getParameter("preset")};
|
||||
jassert(dynamic_cast<AudioParameterInt*>(param) != nullptr);
|
||||
AudioParameterInt* castParam{dynamic_cast<AudioParameterInt*>(param)};
|
||||
*castParam = presetNum;
|
||||
}
|
||||
}
|
||||
|
||||
Pills::Pills(
|
||||
string label,
|
||||
const vector<string> &items,
|
||||
const function<void (int)> &onItemSelected,
|
||||
const function<int (const string&)> &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;
|
||||
}
|
||||
pill->setClickingTogglesState(true);
|
||||
pill->addListener(this);
|
||||
index++;
|
||||
Pills::~Pills() {
|
||||
valueTreeState.removeParameterListener("bank", this);
|
||||
valueTreeState.state.removeListener(this);
|
||||
}
|
||||
|
||||
void Pills::parameterChanged(const String& parameterID, float newValue) {
|
||||
if (parameterID == "bank") {
|
||||
updatePillToggleStates();
|
||||
}
|
||||
}
|
||||
|
||||
void Pills::setItems(
|
||||
const vector<string> &items,
|
||||
int initiallySelectedItem
|
||||
) {
|
||||
this->items = items;
|
||||
for(TextButton* t : buttons) {
|
||||
t->removeListener(this);
|
||||
void Pills::updatePillToggleStates() {
|
||||
RangedAudioParameter *param {valueTreeState.getParameter("bank")};
|
||||
jassert(dynamic_cast<AudioParameterInt*> (param) != nullptr);
|
||||
AudioParameterInt* castParam {dynamic_cast<AudioParameterInt*> (param)};
|
||||
int bank{castParam->get()};
|
||||
for (auto& pill: pills) {
|
||||
pill->bankChanged(bank);
|
||||
}
|
||||
buttons.clear(true);
|
||||
populate(initiallySelectedItem);
|
||||
}
|
||||
|
||||
void Pills::valueTreePropertyChanged(
|
||||
ValueTree& treeWhosePropertyHasChanged,
|
||||
const Identifier& property) {
|
||||
if (treeWhosePropertyHasChanged.getType() == StringRef("banks")) {
|
||||
if (property == StringRef("synthetic")) {
|
||||
loadModelFrom(treeWhosePropertyHasChanged);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Pills::loadModelFrom(ValueTree& banks) {
|
||||
pills.clear();
|
||||
int numChildren{banks.getNumChildren()};
|
||||
for(int i{0}; i < numChildren; i++) {
|
||||
ValueTree child{banks.getChild(i)};
|
||||
int num{child.getProperty("num")};
|
||||
unique_ptr<Pill> pill{make_unique<Pill>(
|
||||
valueTreeState,
|
||||
num,
|
||||
i == 0,
|
||||
i == numChildren - 1)};
|
||||
addAndMakeVisible(pill.get());
|
||||
pills.push_back(move(pill));
|
||||
}
|
||||
updatePillToggleStates();
|
||||
resized();
|
||||
}
|
||||
|
||||
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<const int>(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<AudioParameterInt*>(param) != nullptr);
|
||||
AudioParameterInt* castParam{dynamic_cast<AudioParameterInt*> (param)};
|
||||
int bank{castParam->get()};
|
||||
|
||||
int currentIx{static_cast<const int>(
|
||||
distance(
|
||||
pills.begin(),
|
||||
find_if(
|
||||
pills.begin(),
|
||||
pills.end(),
|
||||
[bank](unique_ptr<Pill>& pill){
|
||||
return pill->bank == bank;})))};
|
||||
currentIx += right ? 1 : pills.size()-1;
|
||||
pills[currentIx % pills.size()]->textButton.triggerClick();
|
||||
|
||||
}
|
||||
|
||||
void Pills::resized() {
|
||||
int index = 0;
|
||||
Rectangle<int> r (getLocalBounds());
|
||||
const int equalWidth = r.proportionOfWidth(buttons.size() <= 0 ? 1.0 : 1.0f/buttons.size());
|
||||
for(TextButton* t : buttons) {
|
||||
int index{0};
|
||||
Rectangle<int> r{getLocalBounds()};
|
||||
const int equalWidth{r.proportionOfWidth(pills.size() <= 0 ? 1.0 : 1.0f/pills.size())};
|
||||
for(auto& pill : pills) {
|
||||
Rectangle<int> r2 (getLocalBounds());
|
||||
r2.removeFromLeft(equalWidth * index);
|
||||
r2.removeFromRight(equalWidth * (buttons.size()-index-1));
|
||||
t->setBounds (r2);
|
||||
r2.removeFromRight(equalWidth * (static_cast<int>(pills.size())-index-1));
|
||||
pill->setBounds(r2);
|
||||
index++;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,35 +8,69 @@
|
|||
|
||||
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<string> &items,
|
||||
const function<void (int)> &onItemSelected,
|
||||
const function<int (const string&)> &itemToIDMapper,
|
||||
int initiallySelectedItem
|
||||
AudioProcessorValueTreeState& valueTreeState
|
||||
);
|
||||
~Pills();
|
||||
|
||||
void setItems(
|
||||
const vector<string> &items,
|
||||
int initiallySelectedItem
|
||||
);
|
||||
|
||||
void buttonClicked (Button* button) override;
|
||||
void cycle(bool right);
|
||||
|
||||
private:
|
||||
string label;
|
||||
vector<string> items;
|
||||
function<void (int)> onItemSelected;
|
||||
function<int (const string&)> itemToIDMapper;
|
||||
virtual void parameterChanged (const String& parameterID, float newValue) override;
|
||||
|
||||
OwnedArray<TextButton> buttons;
|
||||
virtual void valueTreePropertyChanged (ValueTree& treeWhosePropertyHasChanged,
|
||||
const Identifier& property) override;
|
||||
inline virtual void valueTreeChildAdded (ValueTree& parentTree,
|
||||
ValueTree& childWhichHasBeenAdded) override {};
|
||||
inline virtual void valueTreeChildRemoved (ValueTree& parentTree,
|
||||
ValueTree& childWhichHasBeenRemoved,
|
||||
int indexFromWhichChildWasRemoved) override {};
|
||||
inline virtual void valueTreeChildOrderChanged (ValueTree& parentTreeWhoseChildrenHaveMoved,
|
||||
int oldIndex, int newIndex) override {};
|
||||
inline virtual void valueTreeParentChanged (ValueTree& treeWhoseParentHasChanged) override {};
|
||||
inline virtual void valueTreeRedirected (ValueTree& treeWhichHasBeenChanged) override {};
|
||||
private:
|
||||
void loadModelFrom(ValueTree& banks);
|
||||
|
||||
AudioProcessorValueTreeState& valueTreeState;
|
||||
|
||||
vector<unique_ptr<Pill>> pills;
|
||||
Button *selected;
|
||||
|
||||
TextButton* addToList (TextButton* newButton);
|
||||
void updatePillToggleStates();
|
||||
|
||||
void populate(int initiallySelectedItem);
|
||||
void resized() override;
|
||||
|
|
|
@ -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<int> r{getLocalBounds()};
|
||||
filePicker.setBounds(r.removeFromTop(filePickerHeight + padding).reduced(padding, 0).withTrimmedTop(padding));
|
||||
|
||||
// Rectangle<int> r2 (getLocalBounds());
|
||||
// slidersComponent.setBounds(r2.removeFromLeft(filePickerWidth + padding).reduced(padding, 0).withTrimmedLeft(padding));
|
||||
|
||||
midiKeyboard.setBounds (r.removeFromBottom (pianoHeight).reduced(padding, 0));
|
||||
|
||||
Rectangle<int> rContent{r.reduced(0, padding)};
|
||||
|
@ -97,28 +96,11 @@ void JuicySFAudioProcessorEditor::resized()
|
|||
|
||||
tablesComponent.setBounds(rContent);
|
||||
|
||||
|
||||
processor.lastUIWidth = getWidth();
|
||||
processor.lastUIHeight = getHeight();
|
||||
|
||||
// Rectangle<int> r2 (getLocalBounds());
|
||||
// r2.reduce(0, padding);
|
||||
// r2.removeFromBottom(pianoHeight);
|
||||
// r2.removeFromTop(filePickerHeight);
|
||||
// tablesComponent.setBounds (r2);
|
||||
//
|
||||
// Rectangle<int> r3 (getLocalBounds());
|
||||
// r3.removeFromTop(filePickerHeight);
|
||||
//
|
||||
// filePicker.setBounds(r3);
|
||||
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;
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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<AudioParameterInt> params[] {
|
||||
// SoundFont 2.4 spec section 7.2: zero through 127, or 128.
|
||||
make_unique<AudioParameterInt>("bank", "which bank is selected in the soundfont", MidiConstants::midiMinValue, 128, MidiConstants::midiMinValue, "Bank" ),
|
||||
// note: banks may be sparse, and lack a 0th preset. so defend against this.
|
||||
make_unique<AudioParameterInt>("preset", "which patch (aka patch, program, instrument) is selected in the soundfont", MidiConstants::midiMinValue, MidiConstants::midiMaxValue, MidiConstants::midiMinValue, "Preset" ),
|
||||
make_unique<AudioParameterInt>("attack", "volume envelope attack time", MidiConstants::midiMinValue, MidiConstants::midiMaxValue, MidiConstants::midiMinValue, "A" ),
|
||||
make_unique<AudioParameterInt>("decay", "volume envelope sustain attentuation", MidiConstants::midiMinValue, MidiConstants::midiMaxValue, MidiConstants::midiMinValue, "D" ),
|
||||
make_unique<AudioParameterInt>("sustain", "volume envelope decay time", MidiConstants::midiMinValue, MidiConstants::midiMaxValue, MidiConstants::midiMinValue, "S" ),
|
||||
make_unique<AudioParameterInt>("release", "volume envelope release time", MidiConstants::midiMinValue, MidiConstants::midiMaxValue, MidiConstants::midiMinValue, "R" ),
|
||||
make_unique<AudioParameterInt>("filterCutOff", "low-pass filter cut-off frequency", MidiConstants::midiMinValue, MidiConstants::midiMaxValue, MidiConstants::midiMinValue, "Cut" ),
|
||||
make_unique<AudioParameterInt>("filterResonance", "low-pass filter resonance attentuation", MidiConstants::midiMinValue, MidiConstants::midiMaxValue, MidiConstants::midiMinValue, "Res" ),
|
||||
};
|
||||
|
||||
return {
|
||||
make_move_iterator(begin(params)),
|
||||
make_move_iterator(end(params))
|
||||
};
|
||||
}
|
||||
|
||||
JuicySFAudioProcessor::~JuicySFAudioProcessor()
|
||||
{
|
||||
// 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<float>& buffer, MidiBuffer& midiMessages) {
|
||||
void JuicySFAudioProcessor::processBlock(AudioBuffer<float>& 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<int>(CONTROL_CHANGE));
|
||||
fluid_midi_event_set_channel(midi_event, fluidSynthModel.getChannel());
|
||||
fluid_midi_event_set_control(midi_event, m.getControllerNumber());
|
||||
fluid_midi_event_set_value(midi_event, m.getControllerValue());
|
||||
fluid_synth_handle_midi_event(fluidSynth, midi_event);
|
||||
delete_fluid_midi_event(midi_event);
|
||||
} else if (m.isProgramChange()) {
|
||||
fluid_midi_event_t *midi_event(new_fluid_midi_event());
|
||||
fluid_midi_event_set_type(midi_event, static_cast<int>(PROGRAM_CHANGE));
|
||||
fluid_midi_event_set_channel(midi_event, fluidSynthModel.getChannel());
|
||||
fluid_midi_event_set_program(midi_event, m.getProgramChangeNumber());
|
||||
fluid_synth_handle_midi_event(fluidSynth, midi_event);
|
||||
delete_fluid_midi_event(midi_event);
|
||||
} else if (m.isPitchWheel()) {
|
||||
fluid_midi_event_t *midi_event(new_fluid_midi_event());
|
||||
fluid_midi_event_set_type(midi_event, static_cast<int>(PITCH_BEND));
|
||||
fluid_midi_event_set_channel(midi_event, fluidSynthModel.getChannel());
|
||||
fluid_midi_event_set_pitch(midi_event, m.getPitchWheelValue());
|
||||
fluid_synth_handle_midi_event(fluidSynth, midi_event);
|
||||
delete_fluid_midi_event(midi_event);
|
||||
} else if (m.isChannelPressure()) {
|
||||
fluid_midi_event_t *midi_event(new_fluid_midi_event());
|
||||
fluid_midi_event_set_type(midi_event, static_cast<int>(CHANNEL_PRESSURE));
|
||||
fluid_midi_event_set_channel(midi_event, fluidSynthModel.getChannel());
|
||||
fluid_midi_event_set_program(midi_event, m.getChannelPressureValue());
|
||||
fluid_synth_handle_midi_event(fluidSynth, midi_event);
|
||||
delete_fluid_midi_event(midi_event);
|
||||
} else if (m.isAftertouch()) {
|
||||
fluid_midi_event_t *midi_event(new_fluid_midi_event());
|
||||
fluid_midi_event_set_type(midi_event, static_cast<int>(KEY_PRESSURE));
|
||||
fluid_midi_event_set_channel(midi_event, fluidSynthModel.getChannel());
|
||||
fluid_midi_event_set_key(midi_event, m.getNoteNumber());
|
||||
fluid_midi_event_set_value(midi_event, m.getAfterTouchValue());
|
||||
fluid_synth_handle_midi_event(fluidSynth, midi_event);
|
||||
delete_fluid_midi_event(midi_event);
|
||||
// } else if (m.isMetaEvent()) {
|
||||
// fluid_midi_event_t *midi_event(new_fluid_midi_event());
|
||||
// fluid_midi_event_set_type(midi_event, static_cast<int>(MIDI_SYSTEM_RESET));
|
||||
// fluid_synth_handle_midi_event(fluidSynth, midi_event);
|
||||
// delete_fluid_midi_event(midi_event);
|
||||
} else if (m.isSysEx()) {
|
||||
fluid_midi_event_t *midi_event(new_fluid_midi_event());
|
||||
fluid_midi_event_set_type(midi_event, static_cast<int>(MIDI_SYSEX));
|
||||
// I assume that the MidiMessage's sysex buffer would be freed anyway when MidiMessage is destroyed, so set dynamic=false
|
||||
// to ensure that fluidsynth does not attempt to free the sysex buffer during delete_fluid_midi_event()
|
||||
fluid_midi_event_set_sysex(midi_event, const_cast<juce::uint8*>(m.getSysExData()), m.getSysExDataSize(), static_cast<int>(false));
|
||||
fluid_synth_handle_midi_event(fluidSynth, midi_event);
|
||||
delete_fluid_midi_event(midi_event);
|
||||
}
|
||||
}
|
||||
|
||||
// int pval;
|
||||
// 73: 64 attack
|
||||
// 75: decay
|
||||
// 79: sustain
|
||||
// 72: 64 release
|
||||
// fluid_synth_get_cc(fluidSynth, 0, 73, &pval);
|
||||
// Logger::outputDebugString ( juce::String::formatted("hey: %d\n", pval) );
|
||||
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<StateChangeSubscriber*>::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<AudioProcessorParameterWithID*> (param))
|
||||
xml.setAttribute (p->paramID, p->getValue());
|
||||
XmlElement* params{xml.createNewChildElement("params")};
|
||||
for (auto* param : getParameters()) {
|
||||
if (auto* p = dynamic_cast<AudioProcessorParameterWithID*> (param)) {
|
||||
params->setAttribute(p->paramID, p->getValue());
|
||||
}
|
||||
}
|
||||
{
|
||||
ValueTree tree{valueTreeState.state.getChildWithName("uiState")};
|
||||
XmlElement* newElement{xml.createNewChildElement("uiState")};
|
||||
{
|
||||
double value{tree.getProperty("width", GuiConstants::minWidth)};
|
||||
newElement->setAttribute("width", value);
|
||||
}
|
||||
{
|
||||
double value{tree.getProperty("height", GuiConstants::minHeight)};
|
||||
newElement->setAttribute("height", value);
|
||||
}
|
||||
}
|
||||
{
|
||||
ValueTree tree{valueTreeState.state.getChildWithName("soundFont")};
|
||||
XmlElement* newElement{xml.createNewChildElement("soundFont")};
|
||||
{
|
||||
String value{tree.getProperty("path", "")};
|
||||
newElement->setAttribute("path", value);
|
||||
}
|
||||
}
|
||||
|
||||
// then use this helper function to stuff it into the binary blob and return it..
|
||||
copyXmlToBinary (xml, destData);
|
||||
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<XmlElement> xmlState (getXmlFromBinary (data, sizeInBytes));
|
||||
shared_ptr<XmlElement> xmlState{getXmlFromBinary(data, sizeInBytes)};
|
||||
DEBUG_PRINT(xmlState->createDocument("",false,false));
|
||||
|
||||
if (xmlState != nullptr)
|
||||
{
|
||||
if (xmlState.get() != nullptr) {
|
||||
// make sure that it's actually our type of XML object..
|
||||
if (xmlState->hasTagName ("MYPLUGINSETTINGS"))
|
||||
{
|
||||
// list<StateChangeSubscriber*>::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..
|
||||
if (xmlState->hasTagName(valueTreeState.state.getType())) {
|
||||
XmlElement* params{xmlState->getChildByName("params")};
|
||||
if (params)
|
||||
for (auto* param : getParameters())
|
||||
if (auto* p = dynamic_cast<AudioProcessorParameterWithID*> (param))
|
||||
p->setValue ((float) xmlState->getDoubleAttribute (p->paramID, p->getValue()));
|
||||
if (auto* p = dynamic_cast<AudioProcessorParameterWithID*>(param))
|
||||
p->setValue(static_cast<float>(params->getDoubleAttribute(p->paramID, p->getValue())));
|
||||
|
||||
fluidSynthModel.onFileNameChanged(soundFontPath, lastBank, lastPreset);
|
||||
|
||||
AudioProcessorEditor* editor = getActiveEditor();
|
||||
if (editor != nullptr) {
|
||||
editor->setSize(lastUIWidth, lastUIHeight);
|
||||
|
||||
jassert(dynamic_cast<ExposesComponents*> (editor) != nullptr);
|
||||
ExposesComponents* exposesComponents = dynamic_cast<ExposesComponents*> (editor);
|
||||
exposesComponents->getFilePicker().setDisplayedFilePath(soundFontPath);
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
|
|
|
@ -12,8 +12,6 @@
|
|||
|
||||
#include "../JuceLibraryCode/JuceHeader.h"
|
||||
#include "FluidSynthModel.h"
|
||||
#include "StateChangeSubscriber.h"
|
||||
#include "SharesParams.h"
|
||||
#include <list>
|
||||
|
||||
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<StateChangeSubscriber*> stateChangeSubscribers;
|
||||
AudioProcessorValueTreeState::ParameterLayout createParameterLayout();
|
||||
|
||||
static BusesProperties getBusesProperties();
|
||||
|
||||
// Model* model;
|
||||
//==============================================================================
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (JuicySFAudioProcessor)
|
||||
};
|
||||
|
|
|
@ -1,21 +0,0 @@
|
|||
//
|
||||
// Created by Alex Birch on 17/09/2017.
|
||||
//
|
||||
|
||||
#include "Preset.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
Preset::Preset(
|
||||
int preset,
|
||||
string name
|
||||
) : preset(preset),
|
||||
name(name) {}
|
||||
|
||||
int Preset::getPreset() {
|
||||
return preset;
|
||||
}
|
||||
|
||||
string Preset::getName() {
|
||||
return name;
|
||||
}
|
|
@ -1,24 +0,0 @@
|
|||
//
|
||||
// Created by Alex Birch on 17/09/2017.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../JuceLibraryCode/JuceHeader.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
class Preset {
|
||||
public:
|
||||
Preset(
|
||||
int preset,
|
||||
string name
|
||||
);
|
||||
|
||||
int getPreset();
|
||||
string getName();
|
||||
|
||||
private:
|
||||
int preset;
|
||||
string name;
|
||||
};
|
|
@ -1,10 +0,0 @@
|
|||
//
|
||||
// Created by Alex Birch on 17/09/2017.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Preset.h"
|
||||
#include <map>
|
||||
|
||||
typedef std::multimap<int, Preset> BanksToPresets;
|
|
@ -1,23 +0,0 @@
|
|||
//
|
||||
// Created by Alex Birch on 10/04/2018.
|
||||
// Copyright (c) 2018 Birchlabs. All rights reserved.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../JuceLibraryCode/JuceHeader.h"
|
||||
|
||||
class SharesParams {
|
||||
public:
|
||||
virtual ~SharesParams() {}
|
||||
|
||||
virtual void setSoundFontPath(const String& value) = 0;
|
||||
virtual String& getSoundFontPath() = 0;
|
||||
virtual int getPreset() = 0;
|
||||
virtual void setPreset(int preset) = 0;
|
||||
virtual int getBank() = 0;
|
||||
virtual void setBank(int bank) = 0;
|
||||
};
|
||||
|
||||
|
||||
|
|
@ -9,11 +9,21 @@
|
|||
#include "SlidersComponent.h"
|
||||
#include "FluidSynthModel.h"
|
||||
#include "MidiConstants.h"
|
||||
#include "Util.h"
|
||||
using SliderAttachment = AudioProcessorValueTreeState::SliderAttachment;
|
||||
|
||||
std::function<void()> SlidersComponent::makeSliderListener(Slider& slider, int controller) {
|
||||
return [this, controller, &slider]{
|
||||
// RangedAudioParameter *param{valueTreeState.getParameter("release")};
|
||||
// jassert(dynamic_cast<AudioParameterInt*>(param) != nullptr);
|
||||
// AudioParameterInt* castParam{dynamic_cast<AudioParameterInt*>(param)};
|
||||
// int value{castParam->get()};
|
||||
|
||||
// String s{"slider "};
|
||||
// s << slider.getComponentID() << ", controller " << controller << ", value " << slider.getValue() << ", xmlReleaseValue " << value;
|
||||
// DEBUG_PRINT(s);
|
||||
// slider.setValue(slider.getValue(), NotificationType::dontSendNotification);
|
||||
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<fluid_midi_control_change>(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<int>(SOUND_CTRL4));
|
||||
attackSlider.onDragEnd = makeSliderListener(attackSlider, static_cast<int>(SOUND_CTRL4));
|
||||
attackSlider.setTextBoxStyle(Slider::TextBoxBelow, true, attackSlider.getTextBoxWidth(), attackSlider.getTextBoxHeight());
|
||||
attackSliderAttachment = make_unique<SliderAttachment>(valueTreeState, "attack", attackSlider);
|
||||
|
||||
decaySlider.setSliderStyle(style);
|
||||
decaySlider.setRange(rangeMin, rangeMax, rangeStep);
|
||||
decaySlider.onValueChange = makeSliderListener(decaySlider, static_cast<int>(SOUND_CTRL6));
|
||||
decaySlider.onDragEnd = makeSliderListener(decaySlider, static_cast<int>(SOUND_CTRL6));
|
||||
decaySlider.setTextBoxStyle(Slider::TextBoxBelow, true, decaySlider.getTextBoxWidth(), decaySlider.getTextBoxHeight());
|
||||
decaySliderAttachment = make_unique<SliderAttachment>(valueTreeState, "decay", decaySlider);
|
||||
|
||||
sustainSlider.setSliderStyle(style);
|
||||
sustainSlider.setRange(rangeMin, rangeMax, rangeStep);
|
||||
sustainSlider.onValueChange = makeSliderListener(sustainSlider, static_cast<int>(SOUND_CTRL10));
|
||||
sustainSlider.onDragEnd = makeSliderListener(sustainSlider, static_cast<int>(SOUND_CTRL10));
|
||||
sustainSlider.setTextBoxStyle(Slider::TextBoxBelow, true, sustainSlider.getTextBoxWidth(), sustainSlider.getTextBoxHeight());
|
||||
sustainSliderAttachment = make_unique<SliderAttachment>(valueTreeState, "sustain", sustainSlider);
|
||||
|
||||
releaseSlider.setSliderStyle(style);
|
||||
releaseSlider.setRange(rangeMin, rangeMax, rangeStep);
|
||||
releaseSlider.onValueChange = makeSliderListener(releaseSlider, static_cast<int>(SOUND_CTRL3));
|
||||
releaseSlider.onDragEnd = makeSliderListener(releaseSlider, static_cast<int>(SOUND_CTRL3));
|
||||
releaseSlider.setTextBoxStyle(Slider::TextBoxBelow, true, releaseSlider.getTextBoxWidth(), releaseSlider.getTextBoxHeight());
|
||||
releaseSliderAttachment = make_unique<SliderAttachment>(valueTreeState, "release", releaseSlider);
|
||||
|
||||
filterCutOffSlider.setSliderStyle(style);
|
||||
filterCutOffSlider.setRange(rangeMin, rangeMax, rangeStep);
|
||||
filterCutOffSlider.onValueChange = makeSliderListener(filterCutOffSlider, static_cast<int>(SOUND_CTRL5));
|
||||
filterCutOffSlider.onDragEnd = makeSliderListener(filterCutOffSlider, static_cast<int>(SOUND_CTRL5));
|
||||
filterCutOffSlider.setTextBoxStyle(Slider::TextBoxBelow, true, filterCutOffSlider.getTextBoxWidth(), filterCutOffSlider.getTextBoxHeight());
|
||||
filterCutOffSliderAttachment = make_unique<SliderAttachment>(valueTreeState, "filterCutOff", filterCutOffSlider);
|
||||
|
||||
filterResonanceSlider.setSliderStyle(style);
|
||||
filterResonanceSlider.setRange(rangeMin, rangeMax, rangeStep);
|
||||
filterResonanceSlider.onValueChange = makeSliderListener(filterResonanceSlider, static_cast<int>(SOUND_CTRL2));
|
||||
filterResonanceSlider.onDragEnd = makeSliderListener(filterResonanceSlider, static_cast<int>(SOUND_CTRL2));
|
||||
filterResonanceSlider.setTextBoxStyle(Slider::TextBoxBelow, true, filterResonanceSlider.getTextBoxWidth(), filterResonanceSlider.getTextBoxHeight());
|
||||
filterResonanceSliderAttachment = make_unique<SliderAttachment>(valueTreeState, "filterResonance", filterResonanceSlider);
|
||||
|
||||
addAndMakeVisible(attackSlider);
|
||||
addAndMakeVisible(decaySlider);
|
||||
|
|
|
@ -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<void()> makeSliderListener(Slider& slider, int controller);
|
||||
|
||||
FluidSynthModel* fluidSynthModel;
|
||||
AudioProcessorValueTreeState& valueTreeState;
|
||||
FluidSynthModel& fluidSynthModel;
|
||||
|
||||
GroupComponent envelopeGroup;
|
||||
|
||||
Slider attackSlider;
|
||||
Label attackLabel;
|
||||
unique_ptr<SliderAttachment> attackSliderAttachment;
|
||||
|
||||
Slider decaySlider;
|
||||
Label decayLabel;
|
||||
unique_ptr<SliderAttachment> decaySliderAttachment;
|
||||
|
||||
Slider sustainSlider;
|
||||
Label sustainLabel;
|
||||
unique_ptr<SliderAttachment> sustainSliderAttachment;
|
||||
|
||||
Slider releaseSlider;
|
||||
Label releaseLabel;
|
||||
unique_ptr<SliderAttachment> releaseSliderAttachment;
|
||||
|
||||
GroupComponent filterGroup;
|
||||
|
||||
Slider filterCutOffSlider;
|
||||
Label filterCutOffLabel;
|
||||
unique_ptr<SliderAttachment> filterCutOffSliderAttachment;
|
||||
|
||||
Slider filterResonanceSlider;
|
||||
Label filterResonanceLabel;
|
||||
unique_ptr<SliderAttachment> filterResonanceSliderAttachment;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (SlidersComponent)
|
||||
};
|
||||
|
|
|
@ -1,13 +0,0 @@
|
|||
//
|
||||
// Created by Alex Birch on 07/09/2017.
|
||||
//
|
||||
|
||||
#include "SoundfontSynthSound.h"
|
||||
|
||||
bool SoundfontSynthSound::appliesToChannel(int) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SoundfontSynthSound::appliesToNote(int) {
|
||||
return true;
|
||||
}
|
|
@ -1,13 +0,0 @@
|
|||
//
|
||||
// Created by Alex Birch on 07/09/2017.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../JuceLibraryCode/JuceHeader.h"
|
||||
|
||||
class SoundfontSynthSound : public SynthesiserSound {
|
||||
public:
|
||||
bool appliesToNote (int /*midiNoteNumber*/) override;
|
||||
bool appliesToChannel (int /*midiChannel*/) override;
|
||||
};
|
|
@ -1,109 +0,0 @@
|
|||
//
|
||||
// Created by Alex Birch on 07/09/2017.
|
||||
//
|
||||
|
||||
#include "SoundfontSynthVoice.h"
|
||||
#include "SoundfontSynthSound.h"
|
||||
#include "Util.h"
|
||||
|
||||
SoundfontSynthVoice::SoundfontSynthVoice(fluid_synth_t* synth)
|
||||
: tailOff (0.0),
|
||||
level(0.0),
|
||||
currentAngle(0.0),
|
||||
angleDelta(0.0),
|
||||
midiNoteNumber(0),
|
||||
synth(synth)
|
||||
{
|
||||
}
|
||||
|
||||
bool SoundfontSynthVoice::canPlaySound(SynthesiserSound* sound) {
|
||||
return dynamic_cast<SoundfontSynthSound*> (sound) != nullptr;
|
||||
}
|
||||
void SoundfontSynthVoice::startNote(
|
||||
int midiNoteNumber,
|
||||
float velocity,
|
||||
SynthesiserSound* sound,
|
||||
int /*currentPitchWheelPosition*/) {
|
||||
this->midiNoteNumber = midiNoteNumber;
|
||||
DEBUG_PRINT ( juce::String::formatted("JUCE noteon: %d, %d\n", midiNoteNumber, velocity) );
|
||||
fluid_synth_noteon(synth, 0, midiNoteNumber, static_cast<int>(velocity * 127));
|
||||
|
||||
// currentAngle = 0.0;
|
||||
// level = velocity * 0.15;
|
||||
// tailOff = 0.0;
|
||||
//
|
||||
// double cyclesPerSecond = MidiMessage::getMidiNoteInHertz (midiNoteNumber);
|
||||
// double cyclesPerSample = cyclesPerSecond / getSampleRate();
|
||||
//
|
||||
// angleDelta = cyclesPerSample * 2.0 * double_Pi;
|
||||
|
||||
// jassert(dynamic_cast<SoundfontSynthSound*> (sound) != nullptr);
|
||||
// SoundfontSynthSound* sfsynth = dynamic_cast<SoundfontSynthSound*> (sound);
|
||||
}
|
||||
|
||||
void SoundfontSynthVoice::stopNote (float /*velocity*/, bool allowTailOff) {
|
||||
// if (allowTailOff) {
|
||||
// // start a tail-off by setting this flag. The render callback will pick up on
|
||||
// // this and do a fade out, calling clearCurrentNote() when it's finished.
|
||||
//
|
||||
// // we only need to begin a tail-off if it's not already doing so - the
|
||||
// if (tailOff == 0.0) {
|
||||
// // stopNote method could be called more than once.
|
||||
// tailOff = 1.0;
|
||||
// }
|
||||
// } else {
|
||||
// // we're being told to stop playing immediately, so reset everything..
|
||||
//
|
||||
// clearCurrentNote();
|
||||
// angleDelta = 0.0;
|
||||
// }
|
||||
DEBUG_PRINT ( juce::String("JUCE noteoff\n") );
|
||||
clearCurrentNote();
|
||||
fluid_synth_noteoff(synth, 0, this->midiNoteNumber);
|
||||
}
|
||||
|
||||
// receives input as MIDI 0 to 16383, with 8192 being center
|
||||
// this is also exactly the input fluidsynth requires
|
||||
void SoundfontSynthVoice::pitchWheelMoved (int newValue) {
|
||||
// fluid_synth_pitch_bend(synth, 0, newValue);
|
||||
// int ppitch_bend;
|
||||
// fluid_synth_get_pitch_bend(synth, 0, &ppitch_bend);
|
||||
// int ppitch_bend_sens;
|
||||
// fluid_synth_get_pitch_wheel_sens(synth, 0, &ppitch_bend_sens);
|
||||
// Logger::outputDebugString ( juce::String::formatted("Pitch wheel: %d %d %d\n", newValue, ppitch_bend, ppitch_bend_sens) );
|
||||
}
|
||||
|
||||
void SoundfontSynthVoice::controllerMoved (int controllerNumber, int newValue) {
|
||||
// this seems to be "program change" event
|
||||
DEBUG_PRINT ( juce::String::formatted("Controller moved: %d, %d\n", controllerNumber, newValue) );
|
||||
}
|
||||
|
||||
void SoundfontSynthVoice::renderNextBlock (AudioBuffer<float>& outputBuffer, int startSample, int numSamples) {
|
||||
//fluid_synth_process(synth.get(), numSamples, 1, nullptr, outputBuffer.getNumChannels(), outputBuffer.getArrayOfWritePointers());
|
||||
}
|
||||
|
||||
//void SoundfontSynthVoice::renderBlock (AudioBuffer<float>& outputBuffer, int startSample, int numSamples) {
|
||||
// fluid_synth_process(synth.get(), numSamples, 1, nullptr, outputBuffer.getNumChannels(), outputBuffer.getArrayOfWritePointers());
|
||||
// if (angleDelta == 0.0) {
|
||||
// return;
|
||||
// }
|
||||
// while (--numSamples >= 0) {
|
||||
// double qualifiedTailOff = tailOff > 0 ? tailOff : 1.0;
|
||||
// auto currentSample = static_cast<FloatType> (std::sin (currentAngle) * level * qualifiedTailOff);
|
||||
// for (int i = outputBuffer.getNumChannels(); --i >= 0;)
|
||||
// outputBuffer.addSample (i, startSample, currentSample);
|
||||
//
|
||||
// currentAngle += angleDelta;
|
||||
// ++startSample;
|
||||
//
|
||||
// if (tailOff > 0) {
|
||||
// tailOff *= 0.99;
|
||||
//
|
||||
// if (tailOff <= 0.005) {
|
||||
// clearCurrentNote();
|
||||
// angleDelta = 0.0;
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//}
|
|
@ -1,39 +0,0 @@
|
|||
//
|
||||
// Created by Alex Birch on 07/09/2017.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include<memory>
|
||||
#include<fluidsynth.h>
|
||||
#include "../JuceLibraryCode/JuceHeader.h"
|
||||
|
||||
using std::shared_ptr;
|
||||
|
||||
class SoundfontSynthVoice : public SynthesiserVoice {
|
||||
public:
|
||||
SoundfontSynthVoice(fluid_synth_t* synth);
|
||||
|
||||
bool canPlaySound (SynthesiserSound* sound) override;
|
||||
void startNote (
|
||||
int midiNoteNumber,
|
||||
float velocity,
|
||||
SynthesiserSound* /*sound*/,
|
||||
int /*currentPitchWheelPosition*/) override;
|
||||
|
||||
void stopNote (float /*velocity*/, bool allowTailOff) override;
|
||||
void pitchWheelMoved (int /*newValue*/) override;
|
||||
|
||||
void controllerMoved (int /*controllerNumber*/, int /*newValue*/) override;
|
||||
|
||||
void renderNextBlock (AudioBuffer<float>& outputBuffer, int startSample, int numSamples) override;
|
||||
|
||||
private:
|
||||
double tailOff;
|
||||
double level;
|
||||
double currentAngle;
|
||||
double angleDelta;
|
||||
int midiNoteNumber;
|
||||
|
||||
fluid_synth_t* synth;
|
||||
};
|
|
@ -1,15 +0,0 @@
|
|||
//
|
||||
// Created by Alex Birch on 18/03/2018.
|
||||
// Copyright (c) 2018 Birchlabs. All rights reserved.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../JuceLibraryCode/JuceHeader.h"
|
||||
|
||||
class StateChangeSubscriber {
|
||||
public:
|
||||
virtual ~StateChangeSubscriber() {} // pass pointer ownership to another party without exposing the concrete derived class
|
||||
virtual void getStateInformation (XmlElement& xml) = 0;
|
||||
virtual void setStateInformation (XmlElement* xmlState) = 0;
|
||||
};
|
|
@ -7,25 +7,23 @@
|
|||
//
|
||||
|
||||
#include "TableComponent.h"
|
||||
#include "Util.h"
|
||||
#include <functional>
|
||||
#include <iterator>
|
||||
#include <map>
|
||||
|
||||
using namespace std;
|
||||
using namespace Util;
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
This class shows how to implement a TableListBoxModel to show in a TableListBox.
|
||||
*/
|
||||
TableComponent::TableComponent(
|
||||
const vector<string> &columns,
|
||||
const vector<vector<string>> &rows,
|
||||
const function<void (int)> &onRowSelected,
|
||||
const function<int (const vector<string>&)> &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),
|
||||
String("#"),
|
||||
columnIx++,
|
||||
colWidth, // column width
|
||||
30, // column width
|
||||
30, // min width
|
||||
400, // max width
|
||||
TableHeaderComponent::defaultFlags
|
||||
);
|
||||
table.getHeader().addColumn (
|
||||
String("Name"),
|
||||
columnIx++,
|
||||
200, // column width
|
||||
30, // min width
|
||||
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<vector<string>>& 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<banksChildren; bankIx++) {
|
||||
ValueTree bank{banks.getChild(bankIx)};
|
||||
int bankNum{bank.getProperty("num")};
|
||||
int bankChildren{bank.getNumChildren()};
|
||||
for(int presetIx{0}; presetIx<bankChildren; presetIx++) {
|
||||
ValueTree preset{bank.getChild(presetIx)};
|
||||
int presetNum{preset.getProperty("num")};
|
||||
String presetName{preset.getProperty("name")};
|
||||
TableRow row{presetNum, move(presetName)};
|
||||
banksToPresets.emplace(bankNum, move(row));
|
||||
}
|
||||
}
|
||||
repopulateTable();
|
||||
}
|
||||
|
||||
void TableComponent::parameterChanged(const String& parameterID, float newValue) {
|
||||
if (parameterID == "bank") {
|
||||
repopulateTable();
|
||||
} else if (parameterID == "preset") {
|
||||
selectCurrentPreset();
|
||||
}
|
||||
}
|
||||
|
||||
void TableComponent::repopulateTable() {
|
||||
rows.clear();
|
||||
RangedAudioParameter *param{valueTreeState.getParameter("bank")};
|
||||
jassert(dynamic_cast<AudioParameterInt*>(param) != nullptr);
|
||||
AudioParameterInt* castParam{dynamic_cast<AudioParameterInt*>(param)};
|
||||
int bank{castParam->get()};
|
||||
|
||||
BanksToPresets::iterator lowerBound{banksToPresets.lower_bound(bank)};
|
||||
BanksToPresets::iterator upperBound{banksToPresets.upper_bound(bank)};
|
||||
|
||||
// basic syntaxes for a lambda which return's a pair's .second
|
||||
// https://stackoverflow.com/questions/2568194/populate-a-vector-with-all-multimap-values-with-a-given-key
|
||||
// shorter syntax with mem_fn()
|
||||
// https://stackoverflow.com/a/36775400/5257399
|
||||
transform(
|
||||
lowerBound,
|
||||
upperBound,
|
||||
back_inserter(rows),
|
||||
mem_fn(&BanksToPresets::value_type::second)
|
||||
);
|
||||
table.deselectAllRows();
|
||||
table.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,43 +190,44 @@ void TableComponent::sortOrderChanged (
|
|||
bool isForwards
|
||||
) {
|
||||
if (newSortColumnId != 0) {
|
||||
int selectedRowIx = table.getSelectedRow();
|
||||
vector<string> 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<int>(std::distance(rows.begin(), it));
|
||||
void TableComponent::selectCurrentPreset() {
|
||||
table.deselectAllRows();
|
||||
RangedAudioParameter *param{valueTreeState.getParameter("preset")};
|
||||
jassert(dynamic_cast<AudioParameterInt*>(param) != nullptr);
|
||||
AudioParameterInt* castParam{dynamic_cast<AudioParameterInt*>(param)};
|
||||
int value{castParam->get()};
|
||||
|
||||
for (auto it{rows.begin()}; it != rows.end(); ++it) {
|
||||
if(it->preset == value) {
|
||||
int index{static_cast<int>(distance(rows.begin(), it))};
|
||||
table.selectRow(index);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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<string> first,
|
||||
vector<string> 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<AudioParameterInt*>(param) != nullptr);
|
||||
AudioParameterInt* castParam{dynamic_cast<AudioParameterInt*>(param)};
|
||||
*castParam = newPreset;
|
||||
}
|
||||
|
||||
bool TableComponent::keyPressed(const KeyPress &key) {
|
||||
return table.keyPressed(key);
|
||||
}
|
||||
|
||||
TableRow::TableRow(
|
||||
int preset,
|
||||
String name
|
||||
)
|
||||
: preset{preset}
|
||||
, name{name}
|
||||
{}
|
||||
|
|
|
@ -9,22 +9,38 @@
|
|||
#pragma once
|
||||
|
||||
#include "../JuceLibraryCode/JuceHeader.h"
|
||||
#include "PresetsToBanks.h"
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <map>
|
||||
|
||||
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<string> &columns,
|
||||
const vector<vector<string>> &rows,
|
||||
const function<void (int)> &onRowSelected,
|
||||
const function<int (const vector<string>&)> &rowToIDMapper,
|
||||
int initiallySelectedRow
|
||||
AudioProcessorValueTreeState& valueTreeState
|
||||
);
|
||||
~TableComponent();
|
||||
|
||||
int getNumRows() override;
|
||||
|
||||
|
@ -52,19 +68,35 @@ public:
|
|||
|
||||
void resized() override;
|
||||
|
||||
void setRows(const vector<vector<string>>& 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<string> columns;
|
||||
vector<vector<string>> rows;
|
||||
typedef multimap<int, TableRow> BanksToPresets;
|
||||
BanksToPresets banksToPresets;
|
||||
|
||||
function<void (int)> onRowSelected;
|
||||
function<int (const vector<string>&)> rowToIDMapper;
|
||||
vector<TableRow> 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<string> first,
|
||||
vector<string> second
|
||||
TableRow first,
|
||||
TableRow second
|
||||
);
|
||||
|
||||
private:
|
||||
|
|
|
@ -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<string> &row) {
|
||||
return stoi(row[0]);
|
||||
};
|
||||
auto itemToBankMapper = [](const string &item) {
|
||||
return stoi(item);
|
||||
};
|
||||
|
||||
presetTable = new TableComponent(
|
||||
{"#", "Name"},
|
||||
mapPresets(
|
||||
banksToPresets,
|
||||
selectedBank
|
||||
),
|
||||
[this](int preset){
|
||||
this->onPresetSelected(preset);
|
||||
},
|
||||
rowToPresetMapper,
|
||||
presetToIndexMapper(selectedPreset)
|
||||
);
|
||||
banks = new Pills(
|
||||
"Banks",
|
||||
mapBanks(banksToPresets),
|
||||
[this](int bank){
|
||||
this->onBankSelected(bank);
|
||||
},
|
||||
itemToBankMapper,
|
||||
selectedBank
|
||||
);
|
||||
|
||||
presetTable->setWantsKeyboardFocus(false);
|
||||
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<BanksToPresets::const_iterator, BanksToPresets::const_iterator> iterators = banksToPresets.equal_range(bank);
|
||||
BanksToPresets::const_iterator it = iterators.first;
|
||||
return it->second;
|
||||
}
|
||||
|
||||
void TablesComponent::onBankSelected(int bank) {
|
||||
if (!initialised || bank == -1) {
|
||||
return;
|
||||
}
|
||||
cout << "Bank " << bank << endl;
|
||||
selectedBank = bank;
|
||||
Preset firstPresetInBank = getFirstPresetInBank(bank);
|
||||
presetTable->setRows(
|
||||
mapPresets(
|
||||
banksToPresets,
|
||||
bank
|
||||
),
|
||||
presetToIndexMapper(firstPresetInBank.getPreset())
|
||||
);
|
||||
}
|
||||
|
||||
int TablesComponent::presetToIndexMapper(int preset) {
|
||||
int ix = 0;
|
||||
pair<BanksToPresets::const_iterator, BanksToPresets::const_iterator> iterators = this->banksToPresets.equal_range(this->selectedBank);
|
||||
for (auto it = iterators.first; it != iterators.second; ++it, ix++) {
|
||||
Preset b = it->second;
|
||||
if (preset == b.getPreset()) {
|
||||
return ix;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void TablesComponent::onPresetSelected(int preset) {
|
||||
if (!initialised || preset == -1) {
|
||||
return;
|
||||
}
|
||||
cout << "Preset " << preset << endl;
|
||||
// selectedPreset = preset;
|
||||
fluidSynthModel->changePreset(selectedBank, preset);
|
||||
}
|
||||
|
||||
TablesComponent::~TablesComponent() {
|
||||
delete presetTable;
|
||||
delete banks;
|
||||
fluidSynthModel->removeListener(this);
|
||||
}
|
||||
|
||||
vector<string> TablesComponent::mapBanks(const BanksToPresets &banksToPresets) {
|
||||
vector<string> rows;
|
||||
|
||||
const auto compareKey = [](const BanksToPresets::value_type& lhs, const BanksToPresets::value_type& rhs) {
|
||||
return lhs.first < rhs.first;
|
||||
};
|
||||
|
||||
for(auto i = banksToPresets.begin(); i != banksToPresets.end(); i = std::upper_bound(i, banksToPresets.end(), *i, compareKey)) {
|
||||
rows.push_back(to_string(i->first));
|
||||
}
|
||||
|
||||
return rows;
|
||||
}
|
||||
|
||||
|
||||
vector<vector<string>> TablesComponent::mapPresets(const BanksToPresets &banksToPresets, int bank) {
|
||||
vector<vector<string>> rows;
|
||||
|
||||
pair<BanksToPresets::const_iterator, BanksToPresets::const_iterator> iterators = banksToPresets.equal_range(bank);
|
||||
for (auto it = iterators.first; it != iterators.second; ++it) {
|
||||
Preset b = it->second;
|
||||
vector<string> row;
|
||||
row.push_back(to_string(b.getPreset()));
|
||||
row.push_back(b.getName());
|
||||
|
||||
rows.push_back(row);
|
||||
}
|
||||
|
||||
return rows;
|
||||
}
|
||||
|
||||
void TablesComponent::resized() {
|
||||
Rectangle<int> r (getLocalBounds());
|
||||
banks->setBounds (r.removeFromTop(27).reduced(5,0));
|
||||
banks.setBounds (r.removeFromTop(27).reduced(5,0));
|
||||
|
||||
presetTable->setBounds (r);
|
||||
presetTable.setBounds (r);
|
||||
}
|
||||
|
||||
bool TablesComponent::keyPressed(const KeyPress &key) {
|
||||
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);
|
||||
}
|
||||
|
|
|
@ -7,48 +7,28 @@
|
|||
#include "../JuceLibraryCode/JuceHeader.h"
|
||||
#include "Pills.h"
|
||||
#include "TableComponent.h"
|
||||
#include "Preset.h"
|
||||
#include "PresetsToBanks.h"
|
||||
#include "FluidSynthModel.h"
|
||||
#include <memory>
|
||||
#include <fluidsynth.h>
|
||||
|
||||
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<vector<string>> mapPresets(const BanksToPresets &banksToPresets, int bank);
|
||||
static vector<string> mapBanks(const BanksToPresets &banksToPresets);
|
||||
|
||||
void onBankSelected(int bank);
|
||||
void onPresetSelected(int preset);
|
||||
int presetToIndexMapper(int preset);
|
||||
|
||||
fluid_preset_t* getCurrentPreset();
|
||||
Preset getFirstPresetInBank(int bank);
|
||||
|
||||
bool initialised;
|
||||
Pills banks;
|
||||
TableComponent presetTable;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TablesComponent)
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user