Merge pull request #7 from Birch-san/save_load_CC

Rewrite everything
This commit is contained in:
Birch-san 2019-07-30 21:57:30 +01:00 committed by GitHub
commit 5989267615
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
38 changed files with 1344 additions and 1348 deletions

View File

@ -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 */,

View File

@ -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>

View File

@ -33,7 +33,7 @@
</AdditionalOptions>
</TestAction>
<LaunchAction
buildConfiguration = "Release"
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"

View File

@ -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>

View File

@ -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>

View File

@ -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"

View File

@ -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;
}

View File

@ -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)
};

View File

@ -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;
};

View File

@ -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);
}

View File

@ -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;

View File

@ -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;
};

View File

@ -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.
}

View File

@ -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
View 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;
};

View File

@ -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;
};

View File

@ -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++;
}
}

View File

@ -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;

View File

@ -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;
}

View File

@ -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;

View File

@ -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;
}
//==============================================================================

View File

@ -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)
};

View File

@ -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;
}

View File

@ -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;
};

View File

@ -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;

View File

@ -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;
};

View File

@ -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);

View File

@ -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)
};

View File

@ -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;
}

View File

@ -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;
};

View File

@ -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;
// }
// }
// }
//}

View File

@ -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;
};

View File

@ -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;
};

View File

@ -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}
{}

View File

@ -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:

View File

@ -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);
}

View File

@ -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)
};

View File

@ -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;
}
}