From dc16a231fead322609cbff93ab5b7bdab0d4dfec Mon Sep 17 00:00:00 2001 From: Alex Birch Date: Tue, 27 Feb 2018 00:25:20 +0000 Subject: [PATCH] add lazarus source --- Builds/CLion/CMakeLists.txt | 38 + .../juicysfplugin.xcodeproj/project.pbxproj | 88 +- Source/FilePicker.cpp | 47 + Source/FilePicker.h | 29 + Source/FluidSynthModel.cpp | 138 +++ Source/FluidSynthModel.h | 76 ++ Source/MyColours.cpp | 12 + Source/MyColours.h | 13 + Source/Pill.cpp | 17 + Source/Pill.h | 19 + Source/Pills.cpp | 99 ++ Source/Pills.h | 46 + Source/PluginEditor.cpp | 107 +- Source/PluginEditor.h | 23 +- Source/PluginProcessor.cpp | 209 ++-- Source/PluginProcessor.h | 36 +- Source/Preset.cpp | 21 + Source/Preset.h | 24 + Source/PresetsToBanks.h | 10 + Source/SoundfontSynthSound.cpp | 13 + Source/SoundfontSynthSound.h | 13 + Source/SoundfontSynthVoice.cpp | 97 ++ Source/SoundfontSynthVoice.h | 39 + Source/SurjectiveMidiKeyboardComponent.cpp | 984 ++++++++++++++++++ Source/SurjectiveMidiKeyboardComponent.h | 437 ++++++++ Source/TableComponent.cpp | 204 ++++ Source/TableComponent.h | 88 ++ Source/TableModel.cpp | 88 ++ Source/TableModel.h | 61 ++ Source/TablesComponent.cpp | 183 ++++ Source/TablesComponent.h | 54 + juicysfplugin.jucer | 38 + 32 files changed, 3242 insertions(+), 109 deletions(-) create mode 100644 Source/FilePicker.cpp create mode 100644 Source/FilePicker.h create mode 100644 Source/FluidSynthModel.cpp create mode 100644 Source/FluidSynthModel.h create mode 100644 Source/MyColours.cpp create mode 100644 Source/MyColours.h create mode 100644 Source/Pill.cpp create mode 100644 Source/Pill.h create mode 100644 Source/Pills.cpp create mode 100644 Source/Pills.h create mode 100644 Source/Preset.cpp create mode 100644 Source/Preset.h create mode 100644 Source/PresetsToBanks.h create mode 100644 Source/SoundfontSynthSound.cpp create mode 100644 Source/SoundfontSynthSound.h create mode 100644 Source/SoundfontSynthVoice.cpp create mode 100644 Source/SoundfontSynthVoice.h create mode 100644 Source/SurjectiveMidiKeyboardComponent.cpp create mode 100644 Source/SurjectiveMidiKeyboardComponent.h create mode 100644 Source/TableComponent.cpp create mode 100644 Source/TableComponent.h create mode 100644 Source/TableModel.cpp create mode 100644 Source/TableModel.h create mode 100644 Source/TablesComponent.cpp create mode 100644 Source/TablesComponent.h diff --git a/Builds/CLion/CMakeLists.txt b/Builds/CLion/CMakeLists.txt index 5f33e64..64ae6db 100644 --- a/Builds/CLion/CMakeLists.txt +++ b/Builds/CLion/CMakeLists.txt @@ -54,6 +54,31 @@ add_executable (STANDALONE_PLUGIN set_source_files_properties ("../../../../../../Applications/JUCE/modules/juce_audio_plugin_client/juce_audio_plugin_client_Standalone.cpp" PROPERTIES HEADER_FILE_ONLY TRUE) add_library (SHARED_CODE STATIC + "../../Source/FilePicker.cpp" + "../../Source/FilePicker.h" + "../../Source/FluidSynthModel.cpp" + "../../Source/FluidSynthModel.h" + "../../Source/MyColours.cpp" + "../../Source/MyColours.h" + "../../Source/Pill.cpp" + "../../Source/Pill.h" + "../../Source/Pills.cpp" + "../../Source/Pills.h" + "../../Source/Preset.cpp" + "../../Source/Preset.h" + "../../Source/PresetsToBanks.h" + "../../Source/SoundfontSynthSound.cpp" + "../../Source/SoundfontSynthSound.h" + "../../Source/SoundfontSynthVoice.cpp" + "../../Source/SoundfontSynthVoice.h" + "../../Source/SurjectiveMidiKeyboardComponent.cpp" + "../../Source/SurjectiveMidiKeyboardComponent.h" + "../../Source/TableComponent.cpp" + "../../Source/TableComponent.h" + "../../Source/TableModel.cpp" + "../../Source/TableModel.h" + "../../Source/TablesComponent.cpp" + "../../Source/TablesComponent.h" "../../Source/PluginProcessor.cpp" "../../Source/PluginProcessor.h" "../../Source/PluginEditor.cpp" @@ -1352,6 +1377,19 @@ add_library (SHARED_CODE STATIC "../../JuceLibraryCode/JuceHeader.h" ) +set_source_files_properties ("../../Source/FilePicker.h" PROPERTIES HEADER_FILE_ONLY TRUE) +set_source_files_properties ("../../Source/FluidSynthModel.h" PROPERTIES HEADER_FILE_ONLY TRUE) +set_source_files_properties ("../../Source/MyColours.h" PROPERTIES HEADER_FILE_ONLY TRUE) +set_source_files_properties ("../../Source/Pill.h" PROPERTIES HEADER_FILE_ONLY TRUE) +set_source_files_properties ("../../Source/Pills.h" PROPERTIES HEADER_FILE_ONLY TRUE) +set_source_files_properties ("../../Source/Preset.h" PROPERTIES HEADER_FILE_ONLY TRUE) +set_source_files_properties ("../../Source/PresetsToBanks.h" PROPERTIES HEADER_FILE_ONLY TRUE) +set_source_files_properties ("../../Source/SoundfontSynthSound.h" PROPERTIES HEADER_FILE_ONLY TRUE) +set_source_files_properties ("../../Source/SoundfontSynthVoice.h" PROPERTIES HEADER_FILE_ONLY TRUE) +set_source_files_properties ("../../Source/SurjectiveMidiKeyboardComponent.h" PROPERTIES HEADER_FILE_ONLY TRUE) +set_source_files_properties ("../../Source/TableComponent.h" PROPERTIES HEADER_FILE_ONLY TRUE) +set_source_files_properties ("../../Source/TableModel.h" PROPERTIES HEADER_FILE_ONLY TRUE) +set_source_files_properties ("../../Source/TablesComponent.h" PROPERTIES HEADER_FILE_ONLY TRUE) set_source_files_properties ("../../Source/PluginProcessor.h" PROPERTIES HEADER_FILE_ONLY TRUE) set_source_files_properties ("../../Source/PluginEditor.h" PROPERTIES HEADER_FILE_ONLY TRUE) set_source_files_properties ("../../../../../../Applications/JUCE/modules/juce_audio_basics/audio_play_head/juce_AudioPlayHead.h" PROPERTIES HEADER_FILE_ONLY TRUE) diff --git a/Builds/MacOSX/juicysfplugin.xcodeproj/project.pbxproj b/Builds/MacOSX/juicysfplugin.xcodeproj/project.pbxproj index ada6172..17ca4a1 100644 --- a/Builds/MacOSX/juicysfplugin.xcodeproj/project.pbxproj +++ b/Builds/MacOSX/juicysfplugin.xcodeproj/project.pbxproj @@ -29,6 +29,18 @@ F6C481BB44ECD934C583F8DE = {isa = PBXBuildFile; fileRef = 2FB62D371AA3AB416FCB73CE; }; 4E15A26240490B6186AF5814 = {isa = PBXBuildFile; fileRef = 4FD107636B29A49B998F03CA; }; C00686DADD52BA7D05F5509C = {isa = PBXBuildFile; fileRef = 414563833DC377B6107201FD; }; + D7EDE9D1D3EB839E23B30138 = {isa = PBXBuildFile; fileRef = 63942F8053F1E4E72C1BE98C; }; + F74CC5452717B67C0B388588 = {isa = PBXBuildFile; fileRef = 86224165AC264294A5EC5D98; }; + 6CFEB62A3D350429A770FC6C = {isa = PBXBuildFile; fileRef = AF417398F43EA6221E80C5CD; }; + 7239ADCE2A4B2D0B6B532B4F = {isa = PBXBuildFile; fileRef = 16D350530F5AC08BBA372685; }; + E886D809EB324E3A9DF877C4 = {isa = PBXBuildFile; fileRef = B98EB4DDD27035E23CB56C06; }; + B0EEDCDF37720AE014344897 = {isa = PBXBuildFile; fileRef = 83CBB2BD8C28A055DF2200DA; }; + 340927BEB265E20CFA2FC43C = {isa = PBXBuildFile; fileRef = 9B0C59050B1829A5A118F652; }; + FB8CA3A0A9B7C681F105556B = {isa = PBXBuildFile; fileRef = 9C0E9C73E7079FCAB66498C4; }; + 1CC9C1994887BB28C5841428 = {isa = PBXBuildFile; fileRef = 9B56ACC16CEF4E77D5F953F4; }; + ED089881750EB9643689BA92 = {isa = PBXBuildFile; fileRef = 1630B10A5CCE5F19D6D9DED6; }; + F8DF7CA78D33D9A7EC380CC7 = {isa = PBXBuildFile; fileRef = B69B5C0D575E1538E48DB0BC; }; + BAC1351E9F5BE8EF42572A23 = {isa = PBXBuildFile; fileRef = CE9C9C10847F9625D99EA0C5; }; 581AADF14FC0294CD5AE416C = {isa = PBXBuildFile; fileRef = E15CB0CA601DF22E4DA35DCF; }; 8624BF275D1ECBB9C12BE9C8 = {isa = PBXBuildFile; fileRef = 457D4946B07CC4A74EB0FAE1; }; 11EB2EB69F20083150F99E78 = {isa = PBXBuildFile; fileRef = ADA40386504651113647CE71; }; @@ -55,16 +67,21 @@ 906232DF8DDD023678AB78A3 = {isa = PBXBuildFile; fileRef = 0C492ABD41089E16644BE612; }; B2B7F4D38157F527D17E0B44 = {isa = PBXBuildFile; fileRef = E097366DCF5B3FA61B3BD878; }; 00595E54810E3F6E3F1774E1 = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; name = "include_juce_events.mm"; path = "../../JuceLibraryCode/include_juce_events.mm"; sourceTree = "SOURCE_ROOT"; }; + 015713B103D25BEC3F6BFDAF = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SurjectiveMidiKeyboardComponent.h; path = ../../Source/SurjectiveMidiKeyboardComponent.h; sourceTree = "SOURCE_ROOT"; }; 065BC23C916D238CBB74C6EE = {isa = PBXFileReference; lastKnownFileType = file; name = "juce_graphics"; path = "/Applications/JUCE/modules/juce_graphics"; sourceTree = ""; }; 0C492ABD41089E16644BE612 = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; name = "include_juce_opengl.mm"; path = "../../JuceLibraryCode/include_juce_opengl.mm"; sourceTree = "SOURCE_ROOT"; }; 0D8D06B7105FD68639DD4A2C = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; name = "include_juce_audio_plugin_client_AUv3.mm"; path = "../../JuceLibraryCode/include_juce_audio_plugin_client_AUv3.mm"; sourceTree = "SOURCE_ROOT"; }; 132B1211E0AF52A3265FFF59 = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = "include_juce_audio_plugin_client_Standalone.cpp"; path = "../../JuceLibraryCode/include_juce_audio_plugin_client_Standalone.cpp"; sourceTree = "SOURCE_ROOT"; }; + 1630B10A5CCE5F19D6D9DED6 = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = TableComponent.cpp; path = ../../Source/TableComponent.cpp; sourceTree = "SOURCE_ROOT"; }; + 16D350530F5AC08BBA372685 = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = Pill.cpp; path = ../../Source/Pill.cpp; sourceTree = "SOURCE_ROOT"; }; 1987A2252A5209E2C84CA3AE = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = "Info-VST.plist"; path = "Info-VST.plist"; sourceTree = "SOURCE_ROOT"; }; 1DE691CFDBE3AA935369A0D9 = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AVKit.framework; path = System/Library/Frameworks/AVKit.framework; sourceTree = SDKROOT; }; + 211587D8CDC501F1BACF8DD4 = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SoundfontSynthSound.h; path = ../../Source/SoundfontSynthSound.h; sourceTree = "SOURCE_ROOT"; }; 2465FB46F4FBE888A5D785D5 = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = "include_juce_audio_plugin_client_utils.cpp"; path = "../../JuceLibraryCode/include_juce_audio_plugin_client_utils.cpp"; sourceTree = "SOURCE_ROOT"; }; 2FB62D371AA3AB416FCB73CE = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = System/Library/Frameworks/QuartzCore.framework; sourceTree = SDKROOT; }; 379C84D5A55C1857A91CBC40 = {isa = PBXFileReference; lastKnownFileType = file; name = "juce_audio_plugin_client"; path = "/Applications/JUCE/modules/juce_audio_plugin_client"; sourceTree = ""; }; 3B0BE48E8B34519259BA931D = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AudioUnit.framework; path = System/Library/Frameworks/AudioUnit.framework; sourceTree = SDKROOT; }; + 3B1AE50061C46417C2C4E30D = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = FluidSynthModel.h; path = ../../Source/FluidSynthModel.h; sourceTree = "SOURCE_ROOT"; }; 3BF01ACCE2847C5084FC6EE6 = {isa = PBXFileReference; lastKnownFileType = file; name = "juce_audio_formats"; path = "/Applications/JUCE/modules/juce_audio_formats"; sourceTree = ""; }; 3C86CF996DA9683D8BC69C07 = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = "Info-VST3.plist"; path = "Info-VST3.plist"; sourceTree = "SOURCE_ROOT"; }; 3F9038E23CFD1962440D79A0 = {isa = PBXFileReference; lastKnownFileType = file; name = "juce_audio_devices"; path = "/Applications/JUCE/modules/juce_audio_devices"; sourceTree = ""; }; @@ -75,21 +92,28 @@ 4C95500A577A3EB04B67B88A = {isa = PBXFileReference; lastKnownFileType = file; name = "juce_opengl"; path = "/Applications/JUCE/modules/juce_opengl"; sourceTree = ""; }; 4FD107636B29A49B998F03CA = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WebKit.framework; path = System/Library/Frameworks/WebKit.framework; sourceTree = SDKROOT; }; 4FF86BF92DE08D8847D303E4 = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; name = "include_juce_core.mm"; path = "../../JuceLibraryCode/include_juce_core.mm"; sourceTree = "SOURCE_ROOT"; }; + 526AF36E1188174AB300DA7B = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = Pills.h; path = ../../Source/Pills.h; sourceTree = "SOURCE_ROOT"; }; + 560D40E30164CA9D05C6AC3B = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = MyColours.h; path = ../../Source/MyColours.h; sourceTree = "SOURCE_ROOT"; }; 571BC08FE42BABE3BAF364C8 = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = "Info-AUv3_AppExtension.plist"; path = "Info-AUv3_AppExtension.plist"; sourceTree = "SOURCE_ROOT"; }; 5B3CBC48DAB08EDF53CEE609 = {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"; }; - 6C0D1AFF0E1B107C54560D35 = {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"; }; 6FC6B8171FA703EB0D26D931 = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = juicysfplugin.vst; sourceTree = "BUILT_PRODUCTS_DIR"; }; 4700F54A29B0F8757E9E3AC0 = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = juicysfplugin.vst3; sourceTree = "BUILT_PRODUCTS_DIR"; }; + 6C0D1AFF0E1B107C54560D35 = {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"; }; + 7525A1BA309F3F9A2C84B0D0 = {isa = PBXFileReference; lastKnownFileType = file; name = "juce_audio_processors"; path = "/Applications/JUCE/modules/juce_audio_processors"; sourceTree = ""; }; + 75A66C3558F366D3A8477EFE = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; name = "include_juce_graphics.mm"; path = "../../JuceLibraryCode/include_juce_graphics.mm"; sourceTree = "SOURCE_ROOT"; }; + 7E486C828309A453850FE84B = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = TableComponent.h; path = ../../Source/TableComponent.h; sourceTree = "SOURCE_ROOT"; }; + 7EE782A780F66B4A55AFA7D6 = {isa = PBXFileReference; lastKnownFileType = file; name = "juce_gui_extra"; path = "/Applications/JUCE/modules/juce_gui_extra"; sourceTree = ""; }; 94060BC2E3F4083F53CDDDCA = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = juicysfplugin.component; sourceTree = "BUILT_PRODUCTS_DIR"; }; 33B56FA30952FB33928A557F = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = juicysfplugin.appex; sourceTree = "BUILT_PRODUCTS_DIR"; }; 5BC90F629770BCF4193FABDD = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AudioToolbox.framework; path = System/Library/Frameworks/AudioToolbox.framework; sourceTree = SDKROOT; }; + 63942F8053F1E4E72C1BE98C = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = FilePicker.cpp; path = ../../Source/FilePicker.cpp; sourceTree = "SOURCE_ROOT"; }; 674E72377F8F5CAB9A844A6A = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; }; 710102EC39EC0F8E952234C6 = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = "Info-AU.plist"; path = "Info-AU.plist"; sourceTree = "SOURCE_ROOT"; }; - 7525A1BA309F3F9A2C84B0D0 = {isa = PBXFileReference; lastKnownFileType = file; name = "juce_audio_processors"; path = "/Applications/JUCE/modules/juce_audio_processors"; sourceTree = ""; }; - 75A66C3558F366D3A8477EFE = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; name = "include_juce_graphics.mm"; path = "../../JuceLibraryCode/include_juce_graphics.mm"; sourceTree = "SOURCE_ROOT"; }; - 7EE782A780F66B4A55AFA7D6 = {isa = PBXFileReference; lastKnownFileType = file; name = "juce_gui_extra"; path = "/Applications/JUCE/modules/juce_gui_extra"; sourceTree = ""; }; 829055EE1D344045DA0CAA05 = {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"; }; + 83CBB2BD8C28A055DF2200DA = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = Preset.cpp; path = ../../Source/Preset.cpp; sourceTree = "SOURCE_ROOT"; }; + 847F052415DCD1CDC6B21CCF = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = PresetsToBanks.h; path = ../../Source/PresetsToBanks.h; sourceTree = "SOURCE_ROOT"; }; 86183C1237DA7A5F2CF41CC7 = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreAudio.framework; path = System/Library/Frameworks/CoreAudio.framework; sourceTree = SDKROOT; }; + 86224165AC264294A5EC5D98 = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = FluidSynthModel.cpp; path = ../../Source/FluidSynthModel.cpp; sourceTree = "SOURCE_ROOT"; }; 877D9F7DB97DBD969D0EF8CA = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Accelerate.framework; path = System/Library/Frameworks/Accelerate.framework; sourceTree = SDKROOT; }; 8BB9990C94156A68C5E9752E = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = AppConfig.h; path = ../../JuceLibraryCode/AppConfig.h; sourceTree = "SOURCE_ROOT"; }; 8D5359C5826FB54D34DD16E5 = {isa = PBXFileReference; lastKnownFileType = file; name = "juce_gui_basics"; path = "/Applications/JUCE/modules/juce_gui_basics"; sourceTree = ""; }; @@ -101,21 +125,31 @@ 956F86CAE587B3ABC34C69A9 = {isa = PBXFileReference; lastKnownFileType = file; name = "juce_data_structures"; path = "/Applications/JUCE/modules/juce_data_structures"; sourceTree = ""; }; 965A4878123BD53E138EBAF4 = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; name = "include_juce_cryptography.mm"; path = "../../JuceLibraryCode/include_juce_cryptography.mm"; sourceTree = "SOURCE_ROOT"; }; 99900FF0BB5E405DDEB97C76 = {isa = PBXFileReference; lastKnownFileType = file; name = "juce_audio_basics"; path = "/Applications/JUCE/modules/juce_audio_basics"; sourceTree = ""; }; + 9B0C59050B1829A5A118F652 = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = SoundfontSynthSound.cpp; path = ../../Source/SoundfontSynthSound.cpp; sourceTree = "SOURCE_ROOT"; }; + 9B56ACC16CEF4E77D5F953F4 = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = SurjectiveMidiKeyboardComponent.cpp; path = ../../Source/SurjectiveMidiKeyboardComponent.cpp; sourceTree = "SOURCE_ROOT"; }; + 9B832FDC7670EB6794D32F2E = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = TablesComponent.h; path = ../../Source/TablesComponent.h; sourceTree = "SOURCE_ROOT"; }; + 9C0E9C73E7079FCAB66498C4 = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = SoundfontSynthVoice.cpp; path = ../../Source/SoundfontSynthVoice.cpp; sourceTree = "SOURCE_ROOT"; }; + 9CDABFEBADFFA0A85C7C6321 = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SoundfontSynthVoice.h; path = ../../Source/SoundfontSynthVoice.h; sourceTree = "SOURCE_ROOT"; }; A147762BC87F26ED856D2F55 = {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"; }; A28FA9AB4E04B85000B5117D = {isa = PBXFileReference; lastKnownFileType = file; name = "juce_video"; path = "/Applications/JUCE/modules/juce_video"; sourceTree = ""; }; A6E1E5CF34685FECF77A432A = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; name = "include_juce_data_structures.mm"; path = "../../JuceLibraryCode/include_juce_data_structures.mm"; sourceTree = "SOURCE_ROOT"; }; ADA40386504651113647CE71 = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; name = "include_juce_audio_basics.mm"; path = "../../JuceLibraryCode/include_juce_audio_basics.mm"; sourceTree = "SOURCE_ROOT"; }; + AF417398F43EA6221E80C5CD = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = MyColours.cpp; path = ../../Source/MyColours.cpp; sourceTree = "SOURCE_ROOT"; }; B05DD9A42AA47641F4DE2368 = {isa = PBXFileReference; lastKnownFileType = file; name = "juce_events"; path = "/Applications/JUCE/modules/juce_events"; sourceTree = ""; }; B26B2A9A2233349B6579E60D = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = JuceHeader.h; path = ../../JuceLibraryCode/JuceHeader.h; sourceTree = "SOURCE_ROOT"; }; B3E8D1BE528BBA1B6004672E = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = PluginEditor.h; path = ../../Source/PluginEditor.h; sourceTree = "SOURCE_ROOT"; }; + B69B5C0D575E1538E48DB0BC = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = TableModel.cpp; path = ../../Source/TableModel.cpp; sourceTree = "SOURCE_ROOT"; }; + B98EB4DDD27035E23CB56C06 = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = Pills.cpp; path = ../../Source/Pills.cpp; sourceTree = "SOURCE_ROOT"; }; C59CC905C8A1A22FD4B6D4F3 = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; name = "include_juce_audio_formats.mm"; path = "../../JuceLibraryCode/include_juce_audio_formats.mm"; sourceTree = "SOURCE_ROOT"; }; C80F88F3BE2DA4CE5296D346 = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = "Info-Standalone_Plugin.plist"; path = "Info-Standalone_Plugin.plist"; sourceTree = "SOURCE_ROOT"; }; - CB5DC270A942A41EFC26AC64 = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; name = "include_juce_gui_extra.mm"; path = "../../JuceLibraryCode/include_juce_gui_extra.mm"; sourceTree = "SOURCE_ROOT"; }; - CBABA15F78A71EE58A0B721A = {isa = PBXFileReference; lastKnownFileType = file; name = "juce_audio_utils"; path = "/Applications/JUCE/modules/juce_audio_utils"; sourceTree = ""; }; - CDEEF07090FA0F9AC13D71D4 = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; name = "include_juce_audio_devices.mm"; path = "../../JuceLibraryCode/include_juce_audio_devices.mm"; sourceTree = "SOURCE_ROOT"; }; D7DD2AAFDD58DAB35733C236 = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = juicysfplugin.app; sourceTree = "BUILT_PRODUCTS_DIR"; }; C38E2C13EAAE0DF111831089 = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libjuicysfplugin.a; sourceTree = "BUILT_PRODUCTS_DIR"; }; + C9BF599C9715D12F9FAE7664 = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = Pill.h; path = ../../Source/Pill.h; sourceTree = "SOURCE_ROOT"; }; + CB5DC270A942A41EFC26AC64 = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; name = "include_juce_gui_extra.mm"; path = "../../JuceLibraryCode/include_juce_gui_extra.mm"; sourceTree = "SOURCE_ROOT"; }; + CBABA15F78A71EE58A0B721A = {isa = PBXFileReference; lastKnownFileType = file; name = "juce_audio_utils"; path = "/Applications/JUCE/modules/juce_audio_utils"; sourceTree = ""; }; CC620AB9A3158E481982505A = {isa = PBXFileReference; lastKnownFileType = file; name = "juce_cryptography"; path = "/Applications/JUCE/modules/juce_cryptography"; sourceTree = ""; }; + CDEEF07090FA0F9AC13D71D4 = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; name = "include_juce_audio_devices.mm"; path = "../../JuceLibraryCode/include_juce_audio_devices.mm"; sourceTree = "SOURCE_ROOT"; }; + CE9C9C10847F9625D99EA0C5 = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = TablesComponent.cpp; path = ../../Source/TablesComponent.cpp; sourceTree = "SOURCE_ROOT"; }; D000F312B9132388ED1431BF = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = IOKit.framework; path = System/Library/Frameworks/IOKit.framework; sourceTree = SDKROOT; }; D45370C4C04A476A5B316761 = {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"; }; D83BE45225B0E49E077E5B87 = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; name = "include_juce_audio_processors.mm"; path = "../../JuceLibraryCode/include_juce_audio_processors.mm"; sourceTree = "SOURCE_ROOT"; }; @@ -124,10 +158,38 @@ E15CB0CA601DF22E4DA35DCF = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = PluginProcessor.cpp; path = ../../Source/PluginProcessor.cpp; sourceTree = "SOURCE_ROOT"; }; E33003EEC78BECD6944E9755 = {isa = PBXFileReference; lastKnownFileType = file; name = "juce_core"; path = "/Applications/JUCE/modules/juce_core"; sourceTree = ""; }; E36561722259E962C1EB89BD = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Carbon.framework; path = System/Library/Frameworks/Carbon.framework; sourceTree = SDKROOT; }; + E365B9925F815EB2C229831F = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = TableModel.h; path = ../../Source/TableModel.h; sourceTree = "SOURCE_ROOT"; }; F079F935113E4B832844A824 = {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"; }; + F376CFD96DB24F385F4278C5 = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = FilePicker.h; path = ../../Source/FilePicker.h; sourceTree = "SOURCE_ROOT"; }; F79C25232BB02BA6BAEB155A = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AVFoundation.framework; path = System/Library/Frameworks/AVFoundation.framework; sourceTree = SDKROOT; }; FB9DC0F378DBE6110579ABEB = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreAudioKit.framework; path = System/Library/Frameworks/CoreAudioKit.framework; sourceTree = SDKROOT; }; + FD8ABA8CD051ABB29C96EF0B = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = Preset.h; path = ../../Source/Preset.h; sourceTree = "SOURCE_ROOT"; }; D1F59E5B10A55A42B51DCF81 = {isa = PBXGroup; children = ( + 63942F8053F1E4E72C1BE98C, + F376CFD96DB24F385F4278C5, + 86224165AC264294A5EC5D98, + 3B1AE50061C46417C2C4E30D, + AF417398F43EA6221E80C5CD, + 560D40E30164CA9D05C6AC3B, + 16D350530F5AC08BBA372685, + C9BF599C9715D12F9FAE7664, + B98EB4DDD27035E23CB56C06, + 526AF36E1188174AB300DA7B, + 83CBB2BD8C28A055DF2200DA, + FD8ABA8CD051ABB29C96EF0B, + 847F052415DCD1CDC6B21CCF, + 9B0C59050B1829A5A118F652, + 211587D8CDC501F1BACF8DD4, + 9C0E9C73E7079FCAB66498C4, + 9CDABFEBADFFA0A85C7C6321, + 9B56ACC16CEF4E77D5F953F4, + 015713B103D25BEC3F6BFDAF, + 1630B10A5CCE5F19D6D9DED6, + 7E486C828309A453850FE84B, + B69B5C0D575E1538E48DB0BC, + E365B9925F815EB2C229831F, + CE9C9C10847F9625D99EA0C5, + 9B832FDC7670EB6794D32F2E, E15CB0CA601DF22E4DA35DCF, 4C295CB748DFF39857513207, 457D4946B07CC4A74EB0FAE1, @@ -886,6 +948,18 @@ 1AAB501451B3A6B2188833CA, A20BA522ED3B37FC993BC005, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Debug; }; 6DE3DBD02F8BB43E5621DB38 = {isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + D7EDE9D1D3EB839E23B30138, + F74CC5452717B67C0B388588, + 6CFEB62A3D350429A770FC6C, + 7239ADCE2A4B2D0B6B532B4F, + E886D809EB324E3A9DF877C4, + B0EEDCDF37720AE014344897, + 340927BEB265E20CFA2FC43C, + FB8CA3A0A9B7C681F105556B, + 1CC9C1994887BB28C5841428, + ED089881750EB9643689BA92, + F8DF7CA78D33D9A7EC380CC7, + BAC1351E9F5BE8EF42572A23, 581AADF14FC0294CD5AE416C, 8624BF275D1ECBB9C12BE9C8, 11EB2EB69F20083150F99E78, diff --git a/Source/FilePicker.cpp b/Source/FilePicker.cpp new file mode 100644 index 0000000..32647f8 --- /dev/null +++ b/Source/FilePicker.cpp @@ -0,0 +1,47 @@ +// +// Created by Alex Birch on 03/10/2017. +// + +#include "FilePicker.h" +#include "MyColours.h" + +FilePicker::FilePicker( + FluidSynthModel* fluidSynthModel +) +: fileChooser( + "File", + File(), + true, + false, + false, + "*.sf2;*.sf3", + String(), + "Choose a Soundfont file to load into the synthesizer" +), + fluidSynthModel(fluidSynthModel) { + // faster (rounded edges introduce transparency) + setOpaque (true); + + addAndMakeVisible (fileChooser); + fileChooser.addListener (this); +} +FilePicker::~FilePicker() { + fileChooser.removeListener (this); +} + +void FilePicker::resized() { + Rectangle r (getLocalBounds()); + fileChooser.setBounds (r); +} + +/** + * This is required to support setOpaque(true) + */ +void FilePicker::paint(Graphics& g) +{ + g.fillAll(MyColours::getUIColourIfAvailable(LookAndFeel_V4::ColourScheme::UIColour::windowBackground, Colours::lightgrey)); +} + +void FilePicker::filenameComponentChanged (FilenameComponent*) { + fluidSynthModel->onFileNameChanged(fileChooser.getCurrentFile().getFullPathName().toStdString()); +} \ No newline at end of file diff --git a/Source/FilePicker.h b/Source/FilePicker.h new file mode 100644 index 0000000..6090a7f --- /dev/null +++ b/Source/FilePicker.h @@ -0,0 +1,29 @@ +// +// Created by Alex Birch on 03/10/2017. +// + +#pragma once + +#include "../JuceLibraryCode/JuceHeader.h" +#include "FluidSynthModel.h" + +class FilePicker: public Component, + private FilenameComponentListener +{ +public: + FilePicker( + FluidSynthModel* fluidSynthModel + ); + ~FilePicker(); + + void resized() override; + void paint (Graphics& g) override; +private: + FilenameComponent fileChooser; + + FluidSynthModel* fluidSynthModel; + + void filenameComponentChanged (FilenameComponent*) override; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (FilePicker) +}; \ No newline at end of file diff --git a/Source/FluidSynthModel.cpp b/Source/FluidSynthModel.cpp new file mode 100644 index 0000000..bab6c34 --- /dev/null +++ b/Source/FluidSynthModel.cpp @@ -0,0 +1,138 @@ +// +// Created by Alex Birch on 10/09/2017. +// + +#include +#include "FluidSynthModel.h" + +using namespace std; + +FluidSynthModel::FluidSynthModel() {} + +FluidSynthModel::~FluidSynthModel() { + if (initialised) { +// delete_fluid_audio_driver(driver); + delete_fluid_synth(synth); + delete_fluid_settings(settings); +// delete driver; +// delete settings; + } +} + +void FluidSynthModel::initialise() { + settings = new_fluid_settings(); + // https://sourceforge.net/p/fluidsynth/wiki/FluidSettings/ + fluid_settings_setstr(settings, "synth.verbose", "yes"); + + synth = new_fluid_synth(settings); + + sfont_id = 0; + loadFont("/Users/birch/Documents/soundfont/EarthBound.sf2"); + + + fluid_synth_set_gain(synth, 2.0); + +// fluid_synth_bank_select(synth, 0, 3); + +// fluid_handle_inst + +// driver = new_fluid_audio_driver(settings, synth); + +// changePreset(128, 13); + + initialised = true; +} + +int FluidSynthModel::getChannel() { + return channel; +} + +void FluidSynthModel::changePreset(int bank, int preset) { + fluid_synth_program_select(synth, channel, sfont_id, static_cast(bank), static_cast(preset)); +} + +const fluid_preset_t FluidSynthModel::getFirstPreset() { + fluid_sfont_t* sfont = fluid_synth_get_sfont_by_id(synth, sfont_id); + + jassert(sfont != nullptr); + sfont->iteration_start(sfont); + + fluid_preset_t preset; + + sfont->iteration_next(sfont, &preset); + + return preset; +} + +void FluidSynthModel::selectFirstPreset() { + fluid_preset_t preset = getFirstPreset(); + + int offset = fluid_synth_get_bank_offset(synth, sfont_id); + + changePreset(preset.get_banknum(&preset) + offset, preset.get_num(&preset)); +} + +BanksToPresets FluidSynthModel::getBanks() { + BanksToPresets banksToPresets; + + fluid_sfont_t* sfont = fluid_synth_get_sfont_by_id(synth, sfont_id); + + int offset = fluid_synth_get_bank_offset(synth, sfont_id); + + jassert(sfont != nullptr); + sfont->iteration_start(sfont); + + fluid_preset_t preset; + + while(sfont->iteration_next(sfont, &preset)) { + banksToPresets.insert(BanksToPresets::value_type( + preset.get_banknum(&preset) + offset, + *new Preset( + preset.get_num(&preset), + 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) { + unloadAndLoadFont(absPath); + eventListeners.call(&FluidSynthModel::Listener::fontChanged, this); +} + +void FluidSynthModel::unloadAndLoadFont(const string &absPath) { + fluid_synth_sfunload(synth, sfont_id, 1); + loadFont(absPath); +} + +void FluidSynthModel::loadFont(const string &absPath) { + sfont_id++; + fluid_synth_sfload(synth, absPath.c_str(), 1); + selectFirstPreset(); +} + +FluidSynthModel::Listener::~Listener() { +} + +void FluidSynthModel::Listener::fontChanged(FluidSynthModel *) { +} + +//============================================================================== +void FluidSynthModel::addListener (FluidSynthModel::Listener* const newListener) +{ + eventListeners.add(newListener); +} + +void FluidSynthModel::removeListener (FluidSynthModel::Listener* const listener) +{ + eventListeners.remove(listener); +} \ No newline at end of file diff --git a/Source/FluidSynthModel.h b/Source/FluidSynthModel.h new file mode 100644 index 0000000..0d96c8d --- /dev/null +++ b/Source/FluidSynthModel.h @@ -0,0 +1,76 @@ +// +// Created by Alex Birch on 10/09/2017. +// + +#pragma once + +#include "../JuceLibraryCode/JuceHeader.h" +#include +#include +#include "PresetsToBanks.h" + + +// https://stackoverflow.com/a/13446565/5257399 +using std::shared_ptr; + +class FluidSynthModel { +public: + FluidSynthModel(); + ~FluidSynthModel(); + + fluid_synth_t* getSynth(); + void initialise(); + + BanksToPresets getBanks(); + + void changePreset(int bank, int preset); + int getChannel(); + + void onFileNameChanged(const string &absPath); + + //============================================================================== + /** + Used to receive callbacks when a button is clicked. + + @see Button::addListener, Button::removeListener + */ + class Listener + { + public: + /** Destructor. */ + virtual ~Listener(); + + /** Called when the button is clicked. */ + virtual void fontChanged (FluidSynthModel*); + }; + + /** 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); + +private: + fluid_synth_t* synth; + fluid_settings_t* settings; +// fluid_audio_driver_t* driver; + + const fluid_preset_t getFirstPreset(); + void selectFirstPreset(); + + void unloadAndLoadFont(const string &absPath); + void loadFont(const string &absPath); + + bool initialised; + unsigned int sfont_id; + unsigned int channel; + + ListenerList eventListeners; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (FluidSynthModel) +}; \ No newline at end of file diff --git a/Source/MyColours.cpp b/Source/MyColours.cpp new file mode 100644 index 0000000..144c630 --- /dev/null +++ b/Source/MyColours.cpp @@ -0,0 +1,12 @@ +// +// Created by Alex Birch on 03/10/2017. +// + +#include "MyColours.h" + +Colour MyColours::getUIColourIfAvailable (LookAndFeel_V4::ColourScheme::UIColour uiColour, Colour fallback = Colour (0xff4d4d4d)) { + if (auto* v4 = dynamic_cast (&LookAndFeel::getDefaultLookAndFeel())) + return v4->getCurrentColourScheme().getUIColour (uiColour); + + return fallback; +} \ No newline at end of file diff --git a/Source/MyColours.h b/Source/MyColours.h new file mode 100644 index 0000000..2fd1be3 --- /dev/null +++ b/Source/MyColours.h @@ -0,0 +1,13 @@ +// +// Created by Alex Birch on 03/10/2017. +// + +#pragma once + +#include "../JuceLibraryCode/JuceHeader.h" + +class MyColours { +public: + static Colour getUIColourIfAvailable (LookAndFeel_V4::ColourScheme::UIColour uiColour, Colour fallback); + +}; \ No newline at end of file diff --git a/Source/Pill.cpp b/Source/Pill.cpp new file mode 100644 index 0000000..873e6f8 --- /dev/null +++ b/Source/Pill.cpp @@ -0,0 +1,17 @@ +// +// Created by Alex Birch on 01/10/2017. +// + +#include "Pill.h" + +Pill::Pill( + const String& buttonName, + const int index, + const int lastIx +) : TextButton(buttonName) { + setBounds(20 + index * 55, 260, 55, 24); + setConnectedEdges ( + (index == 0 ? 0 : Button::ConnectedOnLeft) + | (index == lastIx ? 0 : Button::ConnectedOnRight) + ); +} \ No newline at end of file diff --git a/Source/Pill.h b/Source/Pill.h new file mode 100644 index 0000000..f44317f --- /dev/null +++ b/Source/Pill.h @@ -0,0 +1,19 @@ +// +// Created by Alex Birch on 01/10/2017. +// + +#pragma once + +#include "../JuceLibraryCode/JuceHeader.h" + +class Pill : public TextButton { +public: + explicit Pill( + const String& buttonName, + const int index, + const int lastIx + ); + +private: + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Pill) +}; diff --git a/Source/Pills.cpp b/Source/Pills.cpp new file mode 100644 index 0000000..6e5afc5 --- /dev/null +++ b/Source/Pills.cpp @@ -0,0 +1,99 @@ +// +// Created by Alex Birch on 01/10/2017. +// + +#include "Pills.h" +#include "MyColours.h" + +using namespace std; + +Pills::Pills( + string label, + const vector &items, + const function &onItemSelected, + const function &itemToIDMapper, + int initiallySelectedItem +) : label(label), + items(items), + onItemSelected(onItemSelected), + itemToIDMapper(itemToIDMapper) +{ + // faster (rounded edges introduce transparency) + setOpaque (true); + + populate(initiallySelectedItem); +} + +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++; + } +} + +void Pills::setItems( + const vector &items, + int initiallySelectedItem +) { + this->items = items; + for(TextButton* t : buttons) { + t->removeListener(this); + } + buttons.clear(true); + populate(initiallySelectedItem); + resized(); +} + +void Pills::buttonClicked (Button* button) { + selected = button; + onItemSelected(itemToIDMapper(button->getName().toStdString())); +} + +TextButton* Pills::addToList (TextButton* newButton) { + buttons.add (newButton); + addAndMakeVisible (newButton); + return newButton; +} + +void Pills::cycle(bool right) { + int currentIx = static_cast(distance(buttons.begin(), find(buttons.begin(), buttons.end(), selected))); + currentIx += right ? 1 : buttons.size()-1; + buttons[currentIx % buttons.size()]->triggerClick(); +} + +void Pills::resized() { + int index = 0; + Rectangle r (getLocalBounds()); + const int equalWidth = r.proportionOfWidth(buttons.size() <= 0 ? 1.0 : 1.0f/buttons.size()); + for(TextButton* t : buttons) { + Rectangle r2 (getLocalBounds()); + r2.removeFromLeft(equalWidth * index); + r2.removeFromRight(equalWidth * (buttons.size()-index-1)); + t->setBounds (r2); + index++; + } +} + +/** + * This is required to support setOpaque(true) + */ +void Pills::paint(Graphics& g) +{ + g.fillAll(MyColours::getUIColourIfAvailable(LookAndFeel_V4::ColourScheme::UIColour::windowBackground, Colours::lightgrey)); +} \ No newline at end of file diff --git a/Source/Pills.h b/Source/Pills.h new file mode 100644 index 0000000..530a0eb --- /dev/null +++ b/Source/Pills.h @@ -0,0 +1,46 @@ +// +// Created by Alex Birch on 01/10/2017. +// + +#pragma once + +#include "../JuceLibraryCode/JuceHeader.h" + +using namespace std; + +class Pills : public Component, + public Button::Listener { +public: + Pills( + string label, + const vector &items, + const function &onItemSelected, + const function &itemToIDMapper, + int initiallySelectedItem + ); + + void setItems( + const vector &items, + int initiallySelectedItem + ); + + void buttonClicked (Button* button) override; + void cycle(bool right); + +private: + string label; + vector items; + function onItemSelected; + function itemToIDMapper; + + OwnedArray buttons; + Button *selected; + + TextButton* addToList (TextButton* newButton); + + void populate(int initiallySelectedItem); + void resized() override; + void paint(Graphics& g) override; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Pills) +}; \ No newline at end of file diff --git a/Source/PluginEditor.cpp b/Source/PluginEditor.cpp index 2e50d9c..9531a40 100644 --- a/Source/PluginEditor.cpp +++ b/Source/PluginEditor.cpp @@ -11,33 +11,112 @@ #include "PluginProcessor.h" #include "PluginEditor.h" - //============================================================================== -JuicysfpluginAudioProcessorEditor::JuicysfpluginAudioProcessorEditor (JuicysfpluginAudioProcessor& p) - : AudioProcessorEditor (&p), processor (p) +LazarusAudioProcessorEditor::LazarusAudioProcessorEditor (LazarusAudioProcessor& p) + : AudioProcessorEditor (&p), + processor (p), + midiKeyboard (p.keyboardState, SurjectiveMidiKeyboardComponent::horizontalKeyboard), + tablesComponent(p.getFluidSynthModel()), + filePicker(p.getFluidSynthModel()) { - // Make sure that before the constructor has finished, you've set the - // editor's size to whatever you need it to be. - setSize (400, 300); + // set resize limits for this plug-in + setResizeLimits (400, 300, 800, 600); + + setSize (p.getLastUIWidth(), + p.getLastUIHeight()); + + midiKeyboard.setName ("MIDI Keyboard"); + + midiKeyboard.setWantsKeyboardFocus(false); + tablesComponent.setWantsKeyboardFocus(false); + + setWantsKeyboardFocus(true); + addAndMakeVisible (midiKeyboard); + + addAndMakeVisible(tablesComponent); + addAndMakeVisible(filePicker); } -JuicysfpluginAudioProcessorEditor::~JuicysfpluginAudioProcessorEditor() +LazarusAudioProcessorEditor::~LazarusAudioProcessorEditor() { } //============================================================================== -void JuicysfpluginAudioProcessorEditor::paint (Graphics& g) +void LazarusAudioProcessorEditor::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); +// g.setColour (Colours::white); +// g.setFont (15.0f); +// g.drawFittedText ("Hello World!", getLocalBounds(), Justification::centred, 1); + + if (!focusInitialized) { + if (!hasKeyboardFocus(false) && isVisible()) { + grabKeyboardFocus(); + } + if (getCurrentlyFocusedComponent() == this) { + focusInitialized = true; + } + } } -void JuicysfpluginAudioProcessorEditor::resized() +void LazarusAudioProcessorEditor::resized() { - // This is generally where you'll want to lay out the positions of any - // subcomponents in your editor.. + const int padding = 8; + const int pianoHeight = 70; + const int filePickerHeight = 25; + Rectangle r (getLocalBounds()); + filePicker.setBounds(r.removeFromTop(filePickerHeight + padding).reduced(padding, 0).withTrimmedTop(padding)); + midiKeyboard.setBounds (r.removeFromBottom (pianoHeight).reduced(padding, 0)); + tablesComponent.setBounds(r.reduced(0, padding)); + +// Rectangle r2 (getLocalBounds()); +// r2.reduce(0, padding); +// r2.removeFromBottom(pianoHeight); +// r2.removeFromTop(filePickerHeight); +// tablesComponent.setBounds (r2); +// +// Rectangle r3 (getLocalBounds()); +// r3.removeFromTop(filePickerHeight); +// +// filePicker.setBounds(r3); + + processor.setLastUIWidth(getWidth()); + processor.setLastUIHeight(getHeight()); } + +bool LazarusAudioProcessorEditor::keyPressed(const KeyPress &key) { +// if (!hasKeyboardFocus(false)) +// return false; +// if (key.getKeyCode() == KeyPress::upKey){ +// } +// cout << "hey\n"; + const int cursorKeys[] = { + KeyPress::leftKey, + KeyPress::rightKey, + KeyPress::upKey, + KeyPress::downKey + }; + if (any_of( + begin(cursorKeys), + end(cursorKeys), + [&](int i) { return i == key.getKeyCode(); } + )) { + return tablesComponent.keyPressed(key); + } else { + return midiKeyboard.keyPressed(key); + } +// for(auto childComponent : getChildren()) { +// if (childComponent->keyPressed(key)) return true; +// } + return false; +} + +bool LazarusAudioProcessorEditor::keyStateChanged (bool isKeyDown) { + return midiKeyboard.keyStateChanged(isKeyDown); +// for(auto childComponent : getChildren()) { +// if (childComponent->keyStateChanged(isKeyDown)) return true; +// } +// return false; +} \ No newline at end of file diff --git a/Source/PluginEditor.h b/Source/PluginEditor.h index 8221186..9a21c6c 100644 --- a/Source/PluginEditor.h +++ b/Source/PluginEditor.h @@ -12,25 +12,36 @@ #include "../JuceLibraryCode/JuceHeader.h" #include "PluginProcessor.h" - +#include "TablesComponent.h" +#include "SurjectiveMidiKeyboardComponent.h" +#include "FilePicker.h" //============================================================================== /** */ -class JuicysfpluginAudioProcessorEditor : public AudioProcessorEditor +class LazarusAudioProcessorEditor : public AudioProcessorEditor { public: - JuicysfpluginAudioProcessorEditor (JuicysfpluginAudioProcessor&); - ~JuicysfpluginAudioProcessorEditor(); + LazarusAudioProcessorEditor (LazarusAudioProcessor&); + ~LazarusAudioProcessorEditor(); //============================================================================== void paint (Graphics&) override; void resized() override; + bool keyPressed(const KeyPress &key) override; + bool keyStateChanged (bool isKeyDown) override; + private: // This reference is provided as a quick way for your editor to // access the processor object that created it. - JuicysfpluginAudioProcessor& processor; + LazarusAudioProcessor& processor; - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (JuicysfpluginAudioProcessorEditor) + SurjectiveMidiKeyboardComponent midiKeyboard; + TablesComponent tablesComponent; + FilePicker filePicker; + + bool focusInitialized; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (LazarusAudioProcessorEditor) }; diff --git a/Source/PluginProcessor.cpp b/Source/PluginProcessor.cpp index b59553b..a2ccff9 100644 --- a/Source/PluginProcessor.cpp +++ b/Source/PluginProcessor.cpp @@ -10,34 +10,49 @@ #include "PluginProcessor.h" #include "PluginEditor.h" +#include "SoundfontSynthVoice.h" +#include "SoundfontSynthSound.h" + +AudioProcessor* JUCE_CALLTYPE createPluginFilter(); //============================================================================== -JuicysfpluginAudioProcessor::JuicysfpluginAudioProcessor() -#ifndef JucePlugin_PreferredChannelConfigurations - : AudioProcessor (BusesProperties() - #if ! JucePlugin_IsMidiEffect - #if ! JucePlugin_IsSynth - .withInput ("Input", AudioChannelSet::stereo(), true) - #endif - .withOutput ("Output", AudioChannelSet::stereo(), true) - #endif - ) -#endif +LazarusAudioProcessor::LazarusAudioProcessor() + : AudioProcessor (getBusesProperties()), + fluidSynthModel(), + lastUIWidth(400), + lastUIHeight(300) { + initialiseSynth(); } -JuicysfpluginAudioProcessor::~JuicysfpluginAudioProcessor() +LazarusAudioProcessor::~LazarusAudioProcessor() { +// delete fluidSynthModel; +} + +void LazarusAudioProcessor::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()); } //============================================================================== -const String JuicysfpluginAudioProcessor::getName() const +const String LazarusAudioProcessor::getName() const { return JucePlugin_Name; } -bool JuicysfpluginAudioProcessor::acceptsMidi() const +bool LazarusAudioProcessor::acceptsMidi() const { #if JucePlugin_WantsMidiInput return true; @@ -46,7 +61,7 @@ bool JuicysfpluginAudioProcessor::acceptsMidi() const #endif } -bool JuicysfpluginAudioProcessor::producesMidi() const +bool LazarusAudioProcessor::producesMidi() const { #if JucePlugin_ProducesMidiOutput return true; @@ -55,86 +70,87 @@ bool JuicysfpluginAudioProcessor::producesMidi() const #endif } -bool JuicysfpluginAudioProcessor::isMidiEffect() const -{ - #if JucePlugin_IsMidiEffect - return true; - #else - return false; - #endif -} - -double JuicysfpluginAudioProcessor::getTailLengthSeconds() const +double LazarusAudioProcessor::getTailLengthSeconds() const { return 0.0; } -int JuicysfpluginAudioProcessor::getNumPrograms() +int LazarusAudioProcessor::getNumPrograms() { return 1; // 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 JuicysfpluginAudioProcessor::getCurrentProgram() +int LazarusAudioProcessor::getCurrentProgram() { return 0; } -void JuicysfpluginAudioProcessor::setCurrentProgram (int index) +void LazarusAudioProcessor::setCurrentProgram (int index) { } -const String JuicysfpluginAudioProcessor::getProgramName (int index) +const String LazarusAudioProcessor::getProgramName (int index) { return {}; } -void JuicysfpluginAudioProcessor::changeProgramName (int index, const String& newName) +void LazarusAudioProcessor::changeProgramName (int index, const String& newName) { } //============================================================================== -void JuicysfpluginAudioProcessor::prepareToPlay (double sampleRate, int samplesPerBlock) +void LazarusAudioProcessor::prepareToPlay (double sampleRate, int /*samplesPerBlock*/) { // Use this method as the place to do any pre-playback // initialisation that you need.. + synth.setCurrentPlaybackSampleRate (sampleRate); + keyboardState.reset(); + + reset(); } -void JuicysfpluginAudioProcessor::releaseResources() +void LazarusAudioProcessor::releaseResources() { // When playback stops, you can use this as an opportunity to free up any // spare memory, etc. + keyboardState.reset(); } -#ifndef JucePlugin_PreferredChannelConfigurations -bool JuicysfpluginAudioProcessor::isBusesLayoutSupported (const BusesLayout& layouts) const +bool LazarusAudioProcessor::isBusesLayoutSupported (const BusesLayout& layouts) const { - #if JucePlugin_IsMidiEffect - ignoreUnused (layouts); - return true; - #else - // This is the place where you check if the layout is supported. - // In this template code we only support mono or stereo. - if (layouts.getMainOutputChannelSet() != AudioChannelSet::mono() - && layouts.getMainOutputChannelSet() != AudioChannelSet::stereo()) + // Only mono/stereo and input/output must have same layout + const AudioChannelSet& mainOutput = layouts.getMainOutputChannelSet(); + const AudioChannelSet& mainInput = layouts.getMainInputChannelSet(); + + // input and output layout must either be the same or the input must be disabled altogether + if (! mainInput.isDisabled() && mainInput != mainOutput) return false; - // This checks if the input layout matches the output layout - #if ! JucePlugin_IsSynth - if (layouts.getMainOutputChannelSet() != layouts.getMainInputChannelSet()) + // do not allow disabling the main buses + if (mainOutput.isDisabled()) return false; - #endif - return true; - #endif + // only allow stereo and mono + return mainOutput.size() <= 2; } -#endif -void JuicysfpluginAudioProcessor::processBlock (AudioBuffer& buffer, MidiBuffer& midiMessages) -{ - ScopedNoDenormals noDenormals; - auto totalNumInputChannels = getTotalNumInputChannels(); - auto totalNumOutputChannels = getTotalNumOutputChannels(); +AudioProcessor::BusesProperties LazarusAudioProcessor::getBusesProperties() { + return BusesProperties().withInput ("Input", AudioChannelSet::stereo(), true) + .withOutput ("Output", AudioChannelSet::stereo(), true); +} + +void LazarusAudioProcessor::processBlock (AudioBuffer& buffer, MidiBuffer& midiMessages) { + jassert (!isUsingDoublePrecision()); + const int numSamples = buffer.getNumSamples(); + + // Now pass any incoming midi messages to our keyboard state object, and let it + // add messages to the buffer if the user is clicking on the on-screen keys + keyboardState.processNextMidiBuffer (midiMessages, 0, numSamples, true); + + // 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, 1, nullptr, buffer.getNumChannels(), buffer.getArrayOfWritePointers()); // In case we have more outputs than inputs, this code clears any output // channels that didn't contain input data, (because these aren't @@ -142,51 +158,94 @@ void JuicysfpluginAudioProcessor::processBlock (AudioBuffer& buffer, Midi // This is here to avoid people getting screaming feedback // when they first compile a plugin, but obviously you don't need to keep // this code if your algorithm always overwrites all the output channels. - for (auto i = totalNumInputChannels; i < totalNumOutputChannels; ++i) - buffer.clear (i, 0, buffer.getNumSamples()); - - // This is the place where you'd normally do the guts of your plugin's - // audio processing... - // Make sure to reset the state if your inner loop is processing - // the samples and the outer loop is handling the channels. - // Alternatively, you can process the samples with the channels - // interleaved by keeping the same state. - for (int channel = 0; channel < totalNumInputChannels; ++channel) - { - auto* channelData = buffer.getWritePointer (channel); - - // ..do something to the data... - } +// for (int i = getTotalNumInputChannels(); i < getTotalNumOutputChannels(); ++i) +// buffer.clear (i, 0, numSamples); } //============================================================================== -bool JuicysfpluginAudioProcessor::hasEditor() const +bool LazarusAudioProcessor::hasEditor() const { return true; // (change this to false if you choose to not supply an editor) } -AudioProcessorEditor* JuicysfpluginAudioProcessor::createEditor() +AudioProcessorEditor* LazarusAudioProcessor::createEditor() { - return new JuicysfpluginAudioProcessorEditor (*this); + return new LazarusAudioProcessorEditor (*this); } //============================================================================== -void JuicysfpluginAudioProcessor::getStateInformation (MemoryBlock& destData) +void LazarusAudioProcessor::getStateInformation (MemoryBlock& destData) { // You should use this method to store your parameters in the memory block. // You could do that either as raw data, or use the XML or ValueTree classes // 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); + + // Store the values of all our parameters, using their param ID as the XML attribute + for (auto* param : getParameters()) + if (auto* p = dynamic_cast (param)) + xml.setAttribute (p->paramID, p->getValue()); + + // then use this helper function to stuff it into the binary blob and return it.. + copyXmlToBinary (xml, destData); } -void JuicysfpluginAudioProcessor::setStateInformation (const void* data, int sizeInBytes) +void LazarusAudioProcessor::setStateInformation (const void* data, int sizeInBytes) { // You should use this method to restore your parameters from this memory block, // whose contents will have been created by the getStateInformation() call. + // This getXmlFromBinary() helper function retrieves our XML from the binary blob.. + ScopedPointer xmlState (getXmlFromBinary (data, sizeInBytes)); + + if (xmlState != nullptr) + { + // make sure that it's actually our type of XML object.. + if (xmlState->hasTagName ("MYPLUGINSETTINGS")) + { + // ok, now pull out our last window size.. + lastUIWidth = jmax (xmlState->getIntAttribute ("uiWidth", lastUIWidth), 400); + lastUIHeight = jmax (xmlState->getIntAttribute ("uiHeight", lastUIHeight), 300); + + // Now reload our parameters.. + for (auto* param : getParameters()) + if (auto* p = dynamic_cast (param)) + p->setValue ((float) xmlState->getDoubleAttribute (p->paramID, p->getValue())); + } + } +} + +// FluidSynth only supports float in its process function, so that's all we can support. +bool LazarusAudioProcessor::supportsDoublePrecisionProcessing() const { + return false; +} + +FluidSynthModel* LazarusAudioProcessor::getFluidSynthModel() { + return &fluidSynthModel; } //============================================================================== // This creates new instances of the plugin.. AudioProcessor* JUCE_CALLTYPE createPluginFilter() { - return new JuicysfpluginAudioProcessor(); + return new LazarusAudioProcessor(); } + +void LazarusAudioProcessor::setLastUIWidth(int width) { + this->lastUIWidth = width; +} +void LazarusAudioProcessor::setLastUIHeight(int height) { + this->lastUIHeight = height; +} + +int LazarusAudioProcessor::getLastUIWidth() { + return lastUIWidth; +} +int LazarusAudioProcessor::getLastUIHeight() { + return lastUIHeight; +} \ No newline at end of file diff --git a/Source/PluginProcessor.h b/Source/PluginProcessor.h index 43d348d..fa7c201 100644 --- a/Source/PluginProcessor.h +++ b/Source/PluginProcessor.h @@ -11,17 +11,17 @@ #pragma once #include "../JuceLibraryCode/JuceHeader.h" - +#include "FluidSynthModel.h" //============================================================================== /** */ -class JuicysfpluginAudioProcessor : public AudioProcessor +class LazarusAudioProcessor : public AudioProcessor { public: //============================================================================== - JuicysfpluginAudioProcessor(); - ~JuicysfpluginAudioProcessor(); + LazarusAudioProcessor(); + ~LazarusAudioProcessor(); //============================================================================== void prepareToPlay (double sampleRate, int samplesPerBlock) override; @@ -31,7 +31,7 @@ public: bool isBusesLayoutSupported (const BusesLayout& layouts) const override; #endif - void processBlock (AudioBuffer&, MidiBuffer&) override; + void processBlock (AudioBuffer& buffer, MidiBuffer& midiMessages) override; //============================================================================== AudioProcessorEditor* createEditor() override; @@ -42,7 +42,6 @@ public: bool acceptsMidi() const override; bool producesMidi() const override; - bool isMidiEffect() const override; double getTailLengthSeconds() const override; //============================================================================== @@ -56,7 +55,30 @@ public: void getStateInformation (MemoryBlock& destData) override; void setStateInformation (const void* data, int sizeInBytes) override; + bool supportsDoublePrecisionProcessing() const override; + + FluidSynthModel* getFluidSynthModel(); + + MidiKeyboardState keyboardState; + + int getLastUIWidth(); + int getLastUIHeight(); + + void setLastUIWidth(int width); + void setLastUIHeight(int height); + private: + void initialiseSynth(); + + FluidSynthModel fluidSynthModel; + fluid_synth_t* fluidSynth; + Synthesiser synth; + + static BusesProperties getBusesProperties(); + + int lastUIWidth, lastUIHeight; + +// Model* model; //============================================================================== - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (JuicysfpluginAudioProcessor) + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (LazarusAudioProcessor) }; diff --git a/Source/Preset.cpp b/Source/Preset.cpp new file mode 100644 index 0000000..aac7a84 --- /dev/null +++ b/Source/Preset.cpp @@ -0,0 +1,21 @@ +// +// Created by Alex Birch on 17/09/2017. +// + +#include "Preset.h" + +using namespace std; + +Preset::Preset( + int preset, + string name +) : preset(preset), + name(name) {} + +int Preset::getPreset() { + return preset; +} + +string Preset::getName() { + return name; +} \ No newline at end of file diff --git a/Source/Preset.h b/Source/Preset.h new file mode 100644 index 0000000..03b46b5 --- /dev/null +++ b/Source/Preset.h @@ -0,0 +1,24 @@ +// +// Created by Alex Birch on 17/09/2017. +// + +#pragma once + +#include "../JuceLibraryCode/JuceHeader.h" + +using namespace std; + +class Preset { +public: + Preset( + int preset, + string name + ); + + int getPreset(); + string getName(); + +private: + int preset; + string name; +}; \ No newline at end of file diff --git a/Source/PresetsToBanks.h b/Source/PresetsToBanks.h new file mode 100644 index 0000000..ea70353 --- /dev/null +++ b/Source/PresetsToBanks.h @@ -0,0 +1,10 @@ +// +// Created by Alex Birch on 17/09/2017. +// + +#pragma once + +#include "Preset.h" +#include + +typedef std::multimap BanksToPresets; \ No newline at end of file diff --git a/Source/SoundfontSynthSound.cpp b/Source/SoundfontSynthSound.cpp new file mode 100644 index 0000000..110fa33 --- /dev/null +++ b/Source/SoundfontSynthSound.cpp @@ -0,0 +1,13 @@ +// +// Created by Alex Birch on 07/09/2017. +// + +#include "SoundfontSynthSound.h" + +bool SoundfontSynthSound::appliesToChannel(int) { + return true; +} + +bool SoundfontSynthSound::appliesToNote(int) { + return true; +} \ No newline at end of file diff --git a/Source/SoundfontSynthSound.h b/Source/SoundfontSynthSound.h new file mode 100644 index 0000000..52e0764 --- /dev/null +++ b/Source/SoundfontSynthSound.h @@ -0,0 +1,13 @@ +// +// Created by Alex Birch on 07/09/2017. +// + +#pragma once + +#include "../JuceLibraryCode/JuceHeader.h" + +class SoundfontSynthSound : public SynthesiserSound { +public: + bool appliesToNote (int /*midiNoteNumber*/) override; + bool appliesToChannel (int /*midiChannel*/) override; +}; \ No newline at end of file diff --git a/Source/SoundfontSynthVoice.cpp b/Source/SoundfontSynthVoice.cpp new file mode 100644 index 0000000..bcf8dc6 --- /dev/null +++ b/Source/SoundfontSynthVoice.cpp @@ -0,0 +1,97 @@ +// +// Created by Alex Birch on 07/09/2017. +// + +#include "SoundfontSynthVoice.h" +#include "SoundfontSynthSound.h" + +SoundfontSynthVoice::SoundfontSynthVoice(fluid_synth_t* synth) +: tailOff (0.0), +level(0.0), +currentAngle(0.0), +angleDelta(0.0), +midiNoteNumber(0), +synth(synth) +{ +} + +bool SoundfontSynthVoice::canPlaySound(SynthesiserSound* sound) { + return dynamic_cast (sound) != nullptr; +} +void SoundfontSynthVoice::startNote( + int midiNoteNumber, + float velocity, + SynthesiserSound* sound, + int /*currentPitchWheelPosition*/) { + this->midiNoteNumber = midiNoteNumber; + fluid_synth_noteon(synth, 0, midiNoteNumber, static_cast(velocity * 127)); + +// currentAngle = 0.0; +// level = velocity * 0.15; +// tailOff = 0.0; +// +// double cyclesPerSecond = MidiMessage::getMidiNoteInHertz (midiNoteNumber); +// double cyclesPerSample = cyclesPerSecond / getSampleRate(); +// +// angleDelta = cyclesPerSample * 2.0 * double_Pi; + +// jassert(dynamic_cast (sound) != nullptr); +// SoundfontSynthSound* sfsynth = dynamic_cast (sound); +} + +void SoundfontSynthVoice::stopNote (float /*velocity*/, bool allowTailOff) { +// if (allowTailOff) { +// // start a tail-off by setting this flag. The render callback will pick up on +// // this and do a fade out, calling clearCurrentNote() when it's finished. +// +// // we only need to begin a tail-off if it's not already doing so - the +// if (tailOff == 0.0) { +// // stopNote method could be called more than once. +// tailOff = 1.0; +// } +// } else { +// // we're being told to stop playing immediately, so reset everything.. +// +// clearCurrentNote(); +// angleDelta = 0.0; +// } + clearCurrentNote(); + fluid_synth_noteoff(synth, 0, this->midiNoteNumber); +} +void SoundfontSynthVoice::pitchWheelMoved (int /*newValue*/) { + // who cares? +} + +void SoundfontSynthVoice::controllerMoved (int /*controllerNumber*/, int /*newValue*/) { + // what's a controller? +} + +void SoundfontSynthVoice::renderNextBlock (AudioBuffer& outputBuffer, int startSample, int numSamples) { + //fluid_synth_process(synth.get(), numSamples, 1, nullptr, outputBuffer.getNumChannels(), outputBuffer.getArrayOfWritePointers()); +} + +//void SoundfontSynthVoice::renderBlock (AudioBuffer& outputBuffer, int startSample, int numSamples) { +// fluid_synth_process(synth.get(), numSamples, 1, nullptr, outputBuffer.getNumChannels(), outputBuffer.getArrayOfWritePointers()); +// if (angleDelta == 0.0) { +// return; +// } +// while (--numSamples >= 0) { +// double qualifiedTailOff = tailOff > 0 ? tailOff : 1.0; +// auto currentSample = static_cast (std::sin (currentAngle) * level * qualifiedTailOff); +// for (int i = outputBuffer.getNumChannels(); --i >= 0;) +// outputBuffer.addSample (i, startSample, currentSample); +// +// currentAngle += angleDelta; +// ++startSample; +// +// if (tailOff > 0) { +// tailOff *= 0.99; +// +// if (tailOff <= 0.005) { +// clearCurrentNote(); +// angleDelta = 0.0; +// break; +// } +// } +// } +//} \ No newline at end of file diff --git a/Source/SoundfontSynthVoice.h b/Source/SoundfontSynthVoice.h new file mode 100644 index 0000000..e920882 --- /dev/null +++ b/Source/SoundfontSynthVoice.h @@ -0,0 +1,39 @@ +// +// Created by Alex Birch on 07/09/2017. +// + +#pragma once + +#include +#include +#include "../JuceLibraryCode/JuceHeader.h" + +using std::shared_ptr; + +class SoundfontSynthVoice : public SynthesiserVoice { +public: + SoundfontSynthVoice(fluid_synth_t* synth); + + bool canPlaySound (SynthesiserSound* sound) override; + void startNote ( + int midiNoteNumber, + float velocity, + SynthesiserSound* /*sound*/, + int /*currentPitchWheelPosition*/) override; + + void stopNote (float /*velocity*/, bool allowTailOff) override; + void pitchWheelMoved (int /*newValue*/) override; + + void controllerMoved (int /*controllerNumber*/, int /*newValue*/) override; + + void renderNextBlock (AudioBuffer& outputBuffer, int startSample, int numSamples) override; + +private: + double tailOff; + double level; + double currentAngle; + double angleDelta; + int midiNoteNumber; + + fluid_synth_t* synth; +}; diff --git a/Source/SurjectiveMidiKeyboardComponent.cpp b/Source/SurjectiveMidiKeyboardComponent.cpp new file mode 100644 index 0000000..b01078c --- /dev/null +++ b/Source/SurjectiveMidiKeyboardComponent.cpp @@ -0,0 +1,984 @@ +// +// Created by Alex Birch on 27/09/2017. +// + +#include "SurjectiveMidiKeyboardComponent.h" + +/* + * Forked from JUCE/modules/juce_audio_utils/gui/juce_MidiKeyboardComponent.cpp, + * which had the following license: + */ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2017 - ROLI Ltd. + + JUCE is an open source library subject to commercial or open-source + licensing. + + By using JUCE, you agree to the terms of both the JUCE 5 End-User License + Agreement and JUCE 5 Privacy Policy (both updated and effective as of the + 27th April 2017). + + End User License Agreement: www.juce.com/juce-5-licence + Privacy Policy: www.juce.com/juce-5-privacy-policy + + Or: You may also use this code under the terms of the GPL v3 (see + www.gnu.org/licenses). + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ +class SurjectiveMidiKeyboardUpDownButton : public Button +{ +public: + SurjectiveMidiKeyboardUpDownButton (SurjectiveMidiKeyboardComponent& comp, const int d) + : Button (String()), owner (comp), delta (d) + { + } + + void clicked() override + { + int note = owner.getLowestVisibleKey(); + + if (delta < 0) + note = (note - 1) / 12; + else + note = note / 12 + 1; + + owner.setLowestVisibleKey (note * 12); + } + + void paintButton (Graphics& g, bool isMouseOverButton, bool isButtonDown) override + { + owner.drawUpDownButton (g, getWidth(), getHeight(), + isMouseOverButton, isButtonDown, + delta > 0); + } + +private: + SurjectiveMidiKeyboardComponent& owner; + const int delta; + + JUCE_DECLARE_NON_COPYABLE (SurjectiveMidiKeyboardUpDownButton) +}; + +//============================================================================== +SurjectiveMidiKeyboardComponent::SurjectiveMidiKeyboardComponent (MidiKeyboardState& s, Orientation o) + : state (s), + blackNoteLengthRatio (0.7f), + xOffset (0), + keyWidth (16.0f), + orientation (o), + midiChannel (1), + midiInChannelMask (0xffff), + velocity (1.0f), + shouldCheckState (false), + rangeStart (0), + rangeEnd (127), + firstKey (12 * 4.0f), + canScroll (true), + useMousePositionForVelocity (true), + shouldCheckMousePos (false), + keyMappingOctave (5), + octaveNumForMiddleC (3) +{ + addChildComponent (scrollDown = new SurjectiveMidiKeyboardUpDownButton (*this, -1)); + addChildComponent (scrollUp = new SurjectiveMidiKeyboardUpDownButton (*this, 1)); + + bindKeysToMidiKeyboard(); + + mouseOverNotes.insertMultiple (0, -1, 32); + mouseDownNotes.insertMultiple (0, -1, 32); + + colourChanged(); + setWantsKeyboardFocus (true); + + state.addListener (this); + + startTimerHz (20); +} + +SurjectiveMidiKeyboardComponent::~SurjectiveMidiKeyboardComponent() +{ + state.removeListener (this); +} + +void SurjectiveMidiKeyboardComponent::bindKeysToMidiKeyboard() { + // FL studio key chart: + // http://s3.amazonaws.com/fl_resource/flkeychart.png + + int ix, degree; + const int whiteJumps[] = {2,2,1,2,2,2,1}; + const int blackJumps[] = {2,3,2}; + const int whiteJumpsC = sizeof(whiteJumps)/sizeof(whiteJumps[0]); + const int blackJumpsC = sizeof(blackJumps)/sizeof(blackJumps[0]); + + ix = degree = 0; + for (const char keyCode : "ZXCVBNM,./") { + setKeyPressForNote(KeyPress(keyCode), degree); + degree += whiteJumps[ix++ % whiteJumpsC]; + } + + ix = 0; + degree = 1; + for (const char keyCode : "SDGHJL;") { + setKeyPressForNote(KeyPress(keyCode), degree); + degree += blackJumps[ix++ % blackJumpsC]; + } + + ix = 0; + degree = 12; + for (const char keyCode : "QWERTYUIOP") { + setKeyPressForNote(KeyPress(keyCode), degree); + degree += whiteJumps[ix++ % whiteJumpsC]; + } + + ix = 0; + degree = 13; + for (const char keyCode : "2356790") { + setKeyPressForNote(KeyPress(keyCode), degree); + degree += blackJumps[ix++ % blackJumpsC]; + } +} + +//============================================================================== +void SurjectiveMidiKeyboardComponent::setKeyWidth (const float widthInPixels) +{ + jassert (widthInPixels > 0); + + if (keyWidth != widthInPixels) // Prevent infinite recursion if the width is being computed in a 'resized()' call-back + { + keyWidth = widthInPixels; + resized(); + } +} + +void SurjectiveMidiKeyboardComponent::setOrientation (const Orientation newOrientation) +{ + if (orientation != newOrientation) + { + orientation = newOrientation; + resized(); + } +} + +void SurjectiveMidiKeyboardComponent::setAvailableRange (const int lowestNote, + const int highestNote) +{ + jassert (lowestNote >= 0 && lowestNote <= 127); + jassert (highestNote >= 0 && highestNote <= 127); + jassert (lowestNote <= highestNote); + + if (rangeStart != lowestNote || rangeEnd != highestNote) + { + rangeStart = jlimit (0, 127, lowestNote); + rangeEnd = jlimit (0, 127, highestNote); + firstKey = jlimit ((float) rangeStart, (float) rangeEnd, firstKey); + resized(); + } +} + +void SurjectiveMidiKeyboardComponent::setLowestVisibleKey (int noteNumber) +{ + setLowestVisibleKeyFloat ((float) noteNumber); +} + +void SurjectiveMidiKeyboardComponent::setLowestVisibleKeyFloat (float noteNumber) +{ + noteNumber = jlimit ((float) rangeStart, (float) rangeEnd, noteNumber); + + if (noteNumber != firstKey) + { + const bool hasMoved = (((int) firstKey) != (int) noteNumber); + firstKey = noteNumber; + + if (hasMoved) + sendChangeMessage(); + + resized(); + } +} + +void SurjectiveMidiKeyboardComponent::setScrollButtonsVisible (const bool newCanScroll) +{ + if (canScroll != newCanScroll) + { + canScroll = newCanScroll; + resized(); + } +} + +void SurjectiveMidiKeyboardComponent::colourChanged() +{ + setOpaque (findColour (whiteNoteColourId).isOpaque()); + repaint(); +} + +//============================================================================== +void SurjectiveMidiKeyboardComponent::setMidiChannel (const int midiChannelNumber) +{ + jassert (midiChannelNumber > 0 && midiChannelNumber <= 16); + + if (midiChannel != midiChannelNumber) + { + resetAnyKeysInUse(); + midiChannel = jlimit (1, 16, midiChannelNumber); + } +} + +void SurjectiveMidiKeyboardComponent::setMidiChannelsToDisplay (const int midiChannelMask) +{ + midiInChannelMask = midiChannelMask; + shouldCheckState = true; +} + +void SurjectiveMidiKeyboardComponent::setVelocity (const float v, const bool useMousePosition) +{ + velocity = jlimit (0.0f, 1.0f, v); + useMousePositionForVelocity = useMousePosition; +} + +//============================================================================== +void SurjectiveMidiKeyboardComponent::getKeyPosition (int midiNoteNumber, const float keyWidth_, int& x, int& w) const +{ + jassert (midiNoteNumber >= 0 && midiNoteNumber < 128); + + static const float blackNoteWidth = 0.7f; + + static const float notePos[] = { 0.0f, 1 - blackNoteWidth * 0.6f, + 1.0f, 2 - blackNoteWidth * 0.4f, + 2.0f, + 3.0f, 4 - blackNoteWidth * 0.7f, + 4.0f, 5 - blackNoteWidth * 0.5f, + 5.0f, 6 - blackNoteWidth * 0.3f, + 6.0f }; + + const int octave = midiNoteNumber / 12; + const int note = midiNoteNumber % 12; + + x = roundToInt (octave * 7.0f * keyWidth_ + notePos [note] * keyWidth_); + w = roundToInt (MidiMessage::isMidiNoteBlack (note) ? blackNoteWidth * keyWidth_ : keyWidth_); +} + +void SurjectiveMidiKeyboardComponent::getKeyPos (int midiNoteNumber, int& x, int& w) const +{ + getKeyPosition (midiNoteNumber, keyWidth, x, w); + + int rx, rw; + getKeyPosition (rangeStart, keyWidth, rx, rw); + + x -= xOffset + rx; +} + +Rectangle SurjectiveMidiKeyboardComponent::getRectangleForKey (const int note) const +{ + jassert (note >= rangeStart && note <= rangeEnd); + + int x, w; + getKeyPos (note, x, w); + + if (MidiMessage::isMidiNoteBlack (note)) + { + const int blackNoteLength = getBlackNoteLength(); + + switch (orientation) + { + case horizontalKeyboard: return Rectangle (x, 0, w, blackNoteLength); + case verticalKeyboardFacingLeft: return Rectangle (getWidth() - blackNoteLength, x, blackNoteLength, w); + case verticalKeyboardFacingRight: return Rectangle (0, getHeight() - x - w, blackNoteLength, w); + default: jassertfalse; break; + } + } + else + { + switch (orientation) + { + case horizontalKeyboard: return Rectangle (x, 0, w, getHeight()); + case verticalKeyboardFacingLeft: return Rectangle (0, x, getWidth(), w); + case verticalKeyboardFacingRight: return Rectangle (0, getHeight() - x - w, getWidth(), w); + default: jassertfalse; break; + } + } + + return Rectangle(); +} + +int SurjectiveMidiKeyboardComponent::getKeyStartPosition (const int midiNoteNumber) const +{ + int x, w; + getKeyPos (midiNoteNumber, x, w); + return x; +} + +int SurjectiveMidiKeyboardComponent::getTotalKeyboardWidth() const noexcept +{ + int x, w; + getKeyPos (rangeEnd, x, w); + return x + w; +} + +int SurjectiveMidiKeyboardComponent::getNoteAtPosition (Point p) +{ + float v; + return xyToNote (p, v); +} + +const uint8 SurjectiveMidiKeyboardComponent::whiteNotes[] = { 0, 2, 4, 5, 7, 9, 11 }; +const uint8 SurjectiveMidiKeyboardComponent::blackNotes[] = { 1, 3, 6, 8, 10 }; + +int SurjectiveMidiKeyboardComponent::xyToNote (Point pos, float& mousePositionVelocity) +{ + if (! reallyContains (pos, false)) + return -1; + + Point p (pos); + + if (orientation != horizontalKeyboard) + { + p = Point (p.y, p.x); + + if (orientation == verticalKeyboardFacingLeft) + p = Point (p.x, getWidth() - p.y); + else + p = Point (getHeight() - p.x, p.y); + } + + return remappedXYToNote (p + Point (xOffset, 0), mousePositionVelocity); +} + +int SurjectiveMidiKeyboardComponent::remappedXYToNote (Point pos, float& mousePositionVelocity) const +{ + const int blackNoteLength = getBlackNoteLength(); + + if (pos.getY() < blackNoteLength) + { + for (int octaveStart = 12 * (rangeStart / 12); octaveStart <= rangeEnd; octaveStart += 12) + { + for (int i = 0; i < 5; ++i) + { + const int note = octaveStart + blackNotes [i]; + + if (note >= rangeStart && note <= rangeEnd) + { + int kx, kw; + getKeyPos (note, kx, kw); + kx += xOffset; + + if (pos.x >= kx && pos.x < kx + kw) + { + mousePositionVelocity = pos.y / (float) blackNoteLength; + return note; + } + } + } + } + } + + for (int octaveStart = 12 * (rangeStart / 12); octaveStart <= rangeEnd; octaveStart += 12) + { + for (int i = 0; i < 7; ++i) + { + const int note = octaveStart + whiteNotes [i]; + + if (note >= rangeStart && note <= rangeEnd) + { + int kx, kw; + getKeyPos (note, kx, kw); + kx += xOffset; + + if (pos.x >= kx && pos.x < kx + kw) + { + const int whiteNoteLength = (orientation == horizontalKeyboard) ? getHeight() : getWidth(); + mousePositionVelocity = pos.y / (float) whiteNoteLength; + return note; + } + } + } + } + + mousePositionVelocity = 0; + return -1; +} + +//============================================================================== +void SurjectiveMidiKeyboardComponent::repaintNote (const int noteNum) +{ + if (noteNum >= rangeStart && noteNum <= rangeEnd) + repaint (getRectangleForKey (noteNum)); +} + +void SurjectiveMidiKeyboardComponent::paint (Graphics& g) +{ + g.fillAll (findColour (whiteNoteColourId)); + + const Colour lineColour (findColour (keySeparatorLineColourId)); + const Colour textColour (findColour (textLabelColourId)); + + for (int octave = 0; octave < 128; octave += 12) + { + for (int white = 0; white < 7; ++white) + { + const int noteNum = octave + whiteNotes [white]; + + if (noteNum >= rangeStart && noteNum <= rangeEnd) + { + Rectangle pos = getRectangleForKey (noteNum); + + drawWhiteNote (noteNum, g, pos.getX(), pos.getY(), pos.getWidth(), pos.getHeight(), + state.isNoteOnForChannels (midiInChannelMask, noteNum), + mouseOverNotes.contains (noteNum), lineColour, textColour); + } + } + } + + float x1 = 0.0f, y1 = 0.0f, x2 = 0.0f, y2 = 0.0f; + const int width = getWidth(); + const int height = getHeight(); + + if (orientation == verticalKeyboardFacingLeft) + { + x1 = width - 1.0f; + x2 = width - 5.0f; + } + else if (orientation == verticalKeyboardFacingRight) + x2 = 5.0f; + else + y2 = 5.0f; + + int x, w; + getKeyPos (rangeEnd, x, w); + x += w; + + const Colour shadowCol (findColour (shadowColourId)); + + if (! shadowCol.isTransparent()) + { + g.setGradientFill (ColourGradient (shadowCol, x1, y1, shadowCol.withAlpha (0.0f), x2, y2, false)); + + switch (orientation) + { + case horizontalKeyboard: g.fillRect (0, 0, x, 5); break; + case verticalKeyboardFacingLeft: g.fillRect (width - 5, 0, 5, x); break; + case verticalKeyboardFacingRight: g.fillRect (0, 0, 5, x); break; + default: break; + } + } + + if (! lineColour.isTransparent()) + { + g.setColour (lineColour); + + switch (orientation) + { + case horizontalKeyboard: g.fillRect (0, height - 1, x, 1); break; + case verticalKeyboardFacingLeft: g.fillRect (0, 0, 1, x); break; + case verticalKeyboardFacingRight: g.fillRect (width - 1, 0, 1, x); break; + default: break; + } + } + + const Colour blackNoteColour (findColour (blackNoteColourId)); + + for (int octave = 0; octave < 128; octave += 12) + { + for (int black = 0; black < 5; ++black) + { + const int noteNum = octave + blackNotes [black]; + + if (noteNum >= rangeStart && noteNum <= rangeEnd) + { + Rectangle pos = getRectangleForKey (noteNum); + + drawBlackNote (noteNum, g, pos.getX(), pos.getY(), pos.getWidth(), pos.getHeight(), + state.isNoteOnForChannels (midiInChannelMask, noteNum), + mouseOverNotes.contains (noteNum), blackNoteColour); + } + } + } +} + +void SurjectiveMidiKeyboardComponent::drawWhiteNote (int midiNoteNumber, + Graphics& g, int x, int y, int w, int h, + bool isDown, bool isOver, + const Colour& lineColour, + const Colour& textColour) +{ + Colour c (Colours::transparentWhite); + + if (isDown) c = findColour (keyDownOverlayColourId); + if (isOver) c = c.overlaidWith (findColour (mouseOverKeyOverlayColourId)); + + g.setColour (c); + g.fillRect (x, y, w, h); + + const String text (getWhiteNoteText (midiNoteNumber)); + + if (text.isNotEmpty()) + { + const float fontHeight = jmin (12.0f, keyWidth * 0.9f); + + g.setColour (textColour); + g.setFont (Font (fontHeight).withHorizontalScale (0.8f)); + + switch (orientation) + { + case horizontalKeyboard: g.drawText (text, x + 1, y, w - 1, h - 2, Justification::centredBottom, false); break; + case verticalKeyboardFacingLeft: g.drawText (text, x + 2, y + 2, w - 4, h - 4, Justification::centredLeft, false); break; + case verticalKeyboardFacingRight: g.drawText (text, x + 2, y + 2, w - 4, h - 4, Justification::centredRight, false); break; + default: break; + } + } + + if (! lineColour.isTransparent()) + { + g.setColour (lineColour); + + switch (orientation) + { + case horizontalKeyboard: g.fillRect (x, y, 1, h); break; + case verticalKeyboardFacingLeft: g.fillRect (x, y, w, 1); break; + case verticalKeyboardFacingRight: g.fillRect (x, y + h - 1, w, 1); break; + default: break; + } + + if (midiNoteNumber == rangeEnd) + { + switch (orientation) + { + case horizontalKeyboard: g.fillRect (x + w, y, 1, h); break; + case verticalKeyboardFacingLeft: g.fillRect (x, y + h, w, 1); break; + case verticalKeyboardFacingRight: g.fillRect (x, y - 1, w, 1); break; + default: break; + } + } + } +} + +void SurjectiveMidiKeyboardComponent::drawBlackNote (int /*midiNoteNumber*/, + Graphics& g, int x, int y, int w, int h, + bool isDown, bool isOver, + const Colour& noteFillColour) +{ + Colour c (noteFillColour); + + if (isDown) c = c.overlaidWith (findColour (keyDownOverlayColourId)); + if (isOver) c = c.overlaidWith (findColour (mouseOverKeyOverlayColourId)); + + g.setColour (c); + g.fillRect (x, y, w, h); + + if (isDown) + { + g.setColour (noteFillColour); + g.drawRect (x, y, w, h); + } + else + { + g.setColour (c.brighter()); + const int xIndent = jmax (1, jmin (w, h) / 8); + + switch (orientation) + { + case horizontalKeyboard: g.fillRect (x + xIndent, y, w - xIndent * 2, 7 * h / 8); break; + case verticalKeyboardFacingLeft: g.fillRect (x + w / 8, y + xIndent, w - w / 8, h - xIndent * 2); break; + case verticalKeyboardFacingRight: g.fillRect (x, y + xIndent, 7 * w / 8, h - xIndent * 2); break; + default: break; + } + } +} + +void SurjectiveMidiKeyboardComponent::setOctaveForMiddleC (const int octaveNum) +{ + octaveNumForMiddleC = octaveNum; + repaint(); +} + +String SurjectiveMidiKeyboardComponent::getWhiteNoteText (const int midiNoteNumber) +{ + if (midiNoteNumber % 12 == 0) + return MidiMessage::getMidiNoteName (midiNoteNumber, true, true, octaveNumForMiddleC); + + return {}; +} + +void SurjectiveMidiKeyboardComponent::drawUpDownButton (Graphics& g, int w, int h, + const bool mouseOver, + const bool buttonDown, + const bool movesOctavesUp) +{ + g.fillAll (findColour (upDownButtonBackgroundColourId)); + + float angle; + + switch (orientation) + { + case horizontalKeyboard: angle = movesOctavesUp ? 0.0f : 0.5f; break; + case verticalKeyboardFacingLeft: angle = movesOctavesUp ? 0.25f : 0.75f; break; + case verticalKeyboardFacingRight: angle = movesOctavesUp ? 0.75f : 0.25f; break; + default: jassertfalse; angle = 0; break; + } + + Path path; + path.addTriangle (0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.5f); + path.applyTransform (AffineTransform::rotation (float_Pi * 2.0f * angle, 0.5f, 0.5f)); + + g.setColour (findColour (upDownButtonArrowColourId) + .withAlpha (buttonDown ? 1.0f : (mouseOver ? 0.6f : 0.4f))); + + g.fillPath (path, path.getTransformToScaleToFit (1.0f, 1.0f, w - 2.0f, h - 2.0f, true)); +} + +void SurjectiveMidiKeyboardComponent::setBlackNoteLengthProportion (float ratio) noexcept +{ + jassert (ratio >= 0.0f && ratio <= 1.0f); + if (blackNoteLengthRatio != ratio) + { + blackNoteLengthRatio = ratio; + resized(); + } +} + +int SurjectiveMidiKeyboardComponent::getBlackNoteLength() const noexcept +{ + const int whiteNoteLength = orientation == horizontalKeyboard ? getHeight() : getWidth(); + + return roundToInt (whiteNoteLength * blackNoteLengthRatio); +} + +void SurjectiveMidiKeyboardComponent::resized() +{ + int w = getWidth(); + int h = getHeight(); + + if (w > 0 && h > 0) + { + if (orientation != horizontalKeyboard) + std::swap (w, h); + + int kx2, kw2; + getKeyPos (rangeEnd, kx2, kw2); + + kx2 += kw2; + + if ((int) firstKey != rangeStart) + { + int kx1, kw1; + getKeyPos (rangeStart, kx1, kw1); + + if (kx2 - kx1 <= w) + { + firstKey = (float) rangeStart; + sendChangeMessage(); + repaint(); + } + } + + scrollDown->setVisible (canScroll && firstKey > (float) rangeStart); + + xOffset = 0; + + if (canScroll) + { + const int scrollButtonW = jmin (12, w / 2); + Rectangle r (getLocalBounds()); + + if (orientation == horizontalKeyboard) + { + scrollDown->setBounds (r.removeFromLeft (scrollButtonW)); + scrollUp ->setBounds (r.removeFromRight (scrollButtonW)); + } + else if (orientation == verticalKeyboardFacingLeft) + { + scrollDown->setBounds (r.removeFromTop (scrollButtonW)); + scrollUp ->setBounds (r.removeFromBottom (scrollButtonW)); + } + else + { + scrollDown->setBounds (r.removeFromBottom (scrollButtonW)); + scrollUp ->setBounds (r.removeFromTop (scrollButtonW)); + } + + int endOfLastKey, kw; + getKeyPos (rangeEnd, endOfLastKey, kw); + endOfLastKey += kw; + + float mousePositionVelocity; + const int spaceAvailable = w; + const int lastStartKey = remappedXYToNote (Point (endOfLastKey - spaceAvailable, 0), mousePositionVelocity) + 1; + + if (lastStartKey >= 0 && ((int) firstKey) > lastStartKey) + { + firstKey = (float) jlimit (rangeStart, rangeEnd, lastStartKey); + sendChangeMessage(); + } + + int newOffset = 0; + getKeyPos ((int) firstKey, newOffset, kw); + xOffset = newOffset; + } + else + { + firstKey = (float) rangeStart; + } + + getKeyPos (rangeEnd, kx2, kw2); + scrollUp->setVisible (canScroll && kx2 > w); + repaint(); + } +} + +//============================================================================== +void SurjectiveMidiKeyboardComponent::handleNoteOn (MidiKeyboardState*, int /*midiChannel*/, int /*midiNoteNumber*/, float /*velocity*/) +{ + shouldCheckState = true; // (probably being called from the audio thread, so avoid blocking in here) +} + +void SurjectiveMidiKeyboardComponent::handleNoteOff (MidiKeyboardState*, int /*midiChannel*/, int /*midiNoteNumber*/, float /*velocity*/) +{ + shouldCheckState = true; // (probably being called from the audio thread, so avoid blocking in here) +} + +//============================================================================== +void SurjectiveMidiKeyboardComponent::resetAnyKeysInUse() +{ + if (! keysPressed.isZero()) + { + for (int i = 128; --i >= 0;) + if (keysPressed[i]) + state.noteOff (midiChannel, i, 0.0f); + + keysPressed.clear(); + } + + for (int i = mouseDownNotes.size(); --i >= 0;) + { + const int noteDown = mouseDownNotes.getUnchecked(i); + + if (noteDown >= 0) + { + state.noteOff (midiChannel, noteDown, 0.0f); + mouseDownNotes.set (i, -1); + } + + mouseOverNotes.set (i, -1); + } +} + +void SurjectiveMidiKeyboardComponent::updateNoteUnderMouse (const MouseEvent& e, bool isDown) +{ + updateNoteUnderMouse (e.getEventRelativeTo (this).getPosition(), isDown, e.source.getIndex()); +} + +void SurjectiveMidiKeyboardComponent::updateNoteUnderMouse (Point pos, bool isDown, int fingerNum) +{ + float mousePositionVelocity = 0.0f; + const int newNote = xyToNote (pos, mousePositionVelocity); + const int oldNote = mouseOverNotes.getUnchecked (fingerNum); + const int oldNoteDown = mouseDownNotes.getUnchecked (fingerNum); + const float eventVelocity = useMousePositionForVelocity ? mousePositionVelocity * velocity : 1.0f; + + if (oldNote != newNote) + { + repaintNote (oldNote); + repaintNote (newNote); + mouseOverNotes.set (fingerNum, newNote); + } + + if (isDown) + { + if (newNote != oldNoteDown) + { + if (oldNoteDown >= 0) + { + mouseDownNotes.set (fingerNum, -1); + + if (! mouseDownNotes.contains (oldNoteDown)) + state.noteOff (midiChannel, oldNoteDown, eventVelocity); + } + + if (newNote >= 0 && ! mouseDownNotes.contains (newNote)) + { + state.noteOn (midiChannel, newNote, eventVelocity); + mouseDownNotes.set (fingerNum, newNote); + } + } + } + else if (oldNoteDown >= 0) + { + mouseDownNotes.set (fingerNum, -1); + + if (! mouseDownNotes.contains (oldNoteDown)) + state.noteOff (midiChannel, oldNoteDown, eventVelocity); + } +} + +void SurjectiveMidiKeyboardComponent::mouseMove (const MouseEvent& e) +{ + updateNoteUnderMouse (e, false); + shouldCheckMousePos = false; +} + +void SurjectiveMidiKeyboardComponent::mouseDrag (const MouseEvent& e) +{ + float mousePositionVelocity; + const int newNote = xyToNote (e.getPosition(), mousePositionVelocity); + + if (newNote >= 0) + mouseDraggedToKey (newNote, e); + + updateNoteUnderMouse (e, true); +} + +bool SurjectiveMidiKeyboardComponent::mouseDownOnKey (int, const MouseEvent&) { return true; } +void SurjectiveMidiKeyboardComponent::mouseDraggedToKey (int, const MouseEvent&) {} +void SurjectiveMidiKeyboardComponent::mouseUpOnKey (int, const MouseEvent&) {} + +void SurjectiveMidiKeyboardComponent::mouseDown (const MouseEvent& e) +{ + float mousePositionVelocity; + const int newNote = xyToNote (e.getPosition(), mousePositionVelocity); + + if (newNote >= 0 && mouseDownOnKey (newNote, e)) + { + updateNoteUnderMouse (e, true); + shouldCheckMousePos = true; + } +} + +void SurjectiveMidiKeyboardComponent::mouseUp (const MouseEvent& e) +{ + updateNoteUnderMouse (e, false); + shouldCheckMousePos = false; + + float mousePositionVelocity; + const int note = xyToNote (e.getPosition(), mousePositionVelocity); + if (note >= 0) + mouseUpOnKey (note, e); +} + +void SurjectiveMidiKeyboardComponent::mouseEnter (const MouseEvent& e) +{ + updateNoteUnderMouse (e, false); +} + +void SurjectiveMidiKeyboardComponent::mouseExit (const MouseEvent& e) +{ + updateNoteUnderMouse (e, false); +} + +void SurjectiveMidiKeyboardComponent::mouseWheelMove (const MouseEvent&, const MouseWheelDetails& wheel) +{ + const float amount = (orientation == horizontalKeyboard && wheel.deltaX != 0) + ? wheel.deltaX : (orientation == verticalKeyboardFacingLeft ? wheel.deltaY + : -wheel.deltaY); + + setLowestVisibleKeyFloat (firstKey - amount * keyWidth); +} + +void SurjectiveMidiKeyboardComponent::timerCallback() +{ + if (shouldCheckState) + { + shouldCheckState = false; + + for (int i = rangeStart; i <= rangeEnd; ++i) + { + if (keysCurrentlyDrawnDown[i] != state.isNoteOnForChannels (midiInChannelMask, i)) + { + keysCurrentlyDrawnDown.setBit (i, state.isNoteOnForChannels (midiInChannelMask, i)); + repaintNote (i); + } + } + } + + if (shouldCheckMousePos) + { + for (auto& ms : Desktop::getInstance().getMouseSources()) + if (ms.getComponentUnderMouse() == this || isParentOf (ms.getComponentUnderMouse())) + updateNoteUnderMouse (getLocalPoint (nullptr, ms.getScreenPosition()).roundToInt(), ms.isDragging(), ms.getIndex()); + } +} + +//============================================================================== +void SurjectiveMidiKeyboardComponent::clearKeyMappings() +{ + resetAnyKeysInUse(); + degreeToAsciis.clear(); +} + +void SurjectiveMidiKeyboardComponent::setKeyPressForNote (const KeyPress& key, int midiNoteOffsetFromC) +{ + degreeToAsciis.insert(DegreeToAscii::value_type(midiNoteOffsetFromC, key)); +} + +void SurjectiveMidiKeyboardComponent::removeKeyPressForNote (const int midiNoteOffsetFromC) +{ + degreeToAsciis.erase(midiNoteOffsetFromC); +} + +void SurjectiveMidiKeyboardComponent::setKeyPressBaseOctave (const int newOctaveNumber) +{ + jassert (newOctaveNumber >= 0 && newOctaveNumber <= 10); + + keyMappingOctave = newOctaveNumber; +} + +bool SurjectiveMidiKeyboardComponent::keyStateChanged (const bool /*isKeyDown*/) +{ + bool keyPressUsed = false; + + bool keyDepressedForCurrentNote = false; + int currentNote = -1; + for (auto it = degreeToAsciis.begin(); it != degreeToAsciis.end(); it++){ + const int proposedNote = 12 * keyMappingOctave + it->first; + if (proposedNote != currentNote) { + if (currentNote != -1 + && !keyDepressedForCurrentNote + && keysPressed[currentNote]) { + keysPressed.clearBit(currentNote); + state.noteOff(midiChannel, currentNote, velocity); + keyPressUsed = true; + } + keyDepressedForCurrentNote = false; + currentNote = proposedNote; + } + if (!keyDepressedForCurrentNote + && it->second.isCurrentlyDown()) { + keyDepressedForCurrentNote = true; + if (!keysPressed[currentNote]) { + keysPressed.setBit(currentNote); + state.noteOn(midiChannel, currentNote, velocity); + keyPressUsed = true; + } + } + } + + if (currentNote != -1 + && !keyDepressedForCurrentNote + && keysPressed[currentNote]) { + keysPressed.clearBit(currentNote); + state.noteOff(midiChannel, currentNote, velocity); + } + + return keyPressUsed; +} + +bool SurjectiveMidiKeyboardComponent::keyPressed (const KeyPress& key) +{ + for (auto it = degreeToAsciis.begin(); it != degreeToAsciis.end(); it++){ + if (it->second == key) { + return true; + } + } + return false; +} + +void SurjectiveMidiKeyboardComponent::focusLost (FocusChangeType) +{ + resetAnyKeysInUse(); +} diff --git a/Source/SurjectiveMidiKeyboardComponent.h b/Source/SurjectiveMidiKeyboardComponent.h new file mode 100644 index 0000000..6d43943 --- /dev/null +++ b/Source/SurjectiveMidiKeyboardComponent.h @@ -0,0 +1,437 @@ +// +// Created by Alex Birch on 27/09/2017. +// + +#pragma once + +#include "../JuceLibraryCode/JuceHeader.h" +#include + +using namespace std; + +/* + * Forked from JUCE/modules/juce_audio_utils/gui/juce_SurjectiveMidiKeyboardComponent.h, + * which had the following license: + */ +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2017 - ROLI Ltd. + + JUCE is an open source library subject to commercial or open-source + licensing. + + By using JUCE, you agree to the terms of both the JUCE 5 End-User License + Agreement and JUCE 5 Privacy Policy (both updated and effective as of the + 27th April 2017). + + End User License Agreement: www.juce.com/juce-5-licence + Privacy Policy: www.juce.com/juce-5-privacy-policy + + Or: You may also use this code under the terms of the GPL v3 (see + www.gnu.org/licenses). + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ +//============================================================================== +/** + * This fork aims to allow multiple QWERTY keys to map to the same MIDI keyboard key + * + A component that displays a piano keyboard, whose notes can be clicked on. + + This component will mimic a physical midi keyboard, showing the current state of + a MidiKeyboardState object. When the on-screen keys are clicked on, it will play these + notes by calling the noteOn() and noteOff() methods of its MidiKeyboardState object. + + Another feature is that the computer keyboard can also be used to play notes. By + default it maps the top two rows of a standard qwerty keyboard to the notes, but + these can be remapped if needed. It will only respond to keypresses when it has + the keyboard focus, so to disable this feature you can call setWantsKeyboardFocus (false). + + The component is also a ChangeBroadcaster, so if you want to be informed when the + keyboard is scrolled, you can register a ChangeListener for callbacks. + + @see MidiKeyboardState +*/ +class SurjectiveMidiKeyboardComponent : public Component, + public MidiKeyboardStateListener, + public ChangeBroadcaster, + private Timer +{ +public: + //============================================================================== + /** The direction of the keyboard. + @see setOrientation + */ + enum Orientation + { + horizontalKeyboard, + verticalKeyboardFacingLeft, + verticalKeyboardFacingRight, + }; + + /** Creates a SurjectiveMidiKeyboardComponent. + + @param state the midi keyboard model that this component will represent + @param orientation whether the keyboard is horizonal or vertical + */ + SurjectiveMidiKeyboardComponent (MidiKeyboardState& state, + Orientation orientation); + + /** Destructor. */ + ~SurjectiveMidiKeyboardComponent(); + + //============================================================================== + /** Changes the velocity used in midi note-on messages that are triggered by clicking + on the component. + + Values are 0 to 1.0, where 1.0 is the heaviest. + + @see setMidiChannel + */ + void setVelocity (float velocity, bool useMousePositionForVelocity); + + /** Changes the midi channel number that will be used for events triggered by clicking + on the component. + + The channel must be between 1 and 16 (inclusive). This is the channel that will be + passed on to the MidiKeyboardState::noteOn() method when the user clicks the component. + + Although this is the channel used for outgoing events, the component can display + incoming events from more than one channel - see setMidiChannelsToDisplay() + + @see setVelocity + */ + void setMidiChannel (int midiChannelNumber); + + /** Returns the midi channel that the keyboard is using for midi messages. + @see setMidiChannel + */ + int getMidiChannel() const noexcept { return midiChannel; } + + /** Sets a mask to indicate which incoming midi channels should be represented by + key movements. + + The mask is a set of bits, where bit 0 = midi channel 1, bit 1 = midi channel 2, etc. + + If the MidiKeyboardState has a key down for any of the channels whose bits are set + in this mask, the on-screen keys will also go down. + + By default, this mask is set to 0xffff (all channels displayed). + + @see setMidiChannel + */ + void setMidiChannelsToDisplay (int midiChannelMask); + + /** Returns the current set of midi channels represented by the component. + This is the value that was set with setMidiChannelsToDisplay(). + */ + int getMidiChannelsToDisplay() const noexcept { return midiInChannelMask; } + + //============================================================================== + /** Changes the width used to draw the white keys. */ + void setKeyWidth (float widthInPixels); + + /** Returns the width that was set by setKeyWidth(). */ + float getKeyWidth() const noexcept { return keyWidth; } + + /** Changes the keyboard's current direction. */ + void setOrientation (Orientation newOrientation); + + /** Returns the keyboard's current direction. */ + Orientation getOrientation() const noexcept { return orientation; } + + /** Sets the range of midi notes that the keyboard will be limited to. + + By default the range is 0 to 127 (inclusive), but you can limit this if you + only want a restricted set of the keys to be shown. + + Note that the values here are inclusive and must be between 0 and 127. + */ + void setAvailableRange (int lowestNote, + int highestNote); + + /** Returns the first note in the available range. + @see setAvailableRange + */ + int getRangeStart() const noexcept { return rangeStart; } + + /** Returns the last note in the available range. + @see setAvailableRange + */ + int getRangeEnd() const noexcept { return rangeEnd; } + + /** If the keyboard extends beyond the size of the component, this will scroll + it to show the given key at the start. + + Whenever the keyboard's position is changed, this will use the ChangeBroadcaster + base class to send a callback to any ChangeListeners that have been registered. + */ + void setLowestVisibleKey (int noteNumber); + + /** Returns the number of the first key shown in the component. + @see setLowestVisibleKey + */ + int getLowestVisibleKey() const noexcept { return (int) firstKey; } + + /** Sets the length of the black notes as a proportion of the white note length. */ + void setBlackNoteLengthProportion (float ratio) noexcept; + + /** Returns the length of the black notes as a proportion of the white note length. */ + float getBlackNoteLengthProportion() const noexcept { return blackNoteLengthRatio; } + + /** Returns the absolute length of the black notes. + This will be their vertical or horizontal length, depending on the keyboard's orientation. + */ + int getBlackNoteLength() const noexcept; + + /** If set to true, then scroll buttons will appear at either end of the keyboard + if there are too many notes to fit them all in the component at once. + */ + void setScrollButtonsVisible (bool canScroll); + + //============================================================================== + /** A set of colour IDs to use to change the colour of various aspects of the keyboard. + + These constants can be used either via the Component::setColour(), or LookAndFeel::setColour() + methods. + + @see Component::setColour, Component::findColour, LookAndFeel::setColour, LookAndFeel::findColour + */ + enum ColourIds + { + whiteNoteColourId = 0x1005000, + blackNoteColourId = 0x1005001, + keySeparatorLineColourId = 0x1005002, + mouseOverKeyOverlayColourId = 0x1005003, /**< This colour will be overlaid on the normal note colour. */ + keyDownOverlayColourId = 0x1005004, /**< This colour will be overlaid on the normal note colour. */ + textLabelColourId = 0x1005005, + upDownButtonBackgroundColourId = 0x1005006, + upDownButtonArrowColourId = 0x1005007, + shadowColourId = 0x1005008 + }; + + /** Returns the position within the component of the left-hand edge of a key. + + Depending on the keyboard's orientation, this may be a horizontal or vertical + distance, in either direction. + */ + int getKeyStartPosition (int midiNoteNumber) const; + + /** Returns the total width needed to fit all the keys in the available range. */ + int getTotalKeyboardWidth() const noexcept; + + /** Returns the key at a given coordinate. */ + int getNoteAtPosition (Point position); + + //============================================================================== + /** Deletes all key-mappings. + @see setKeyPressForNote + */ + void clearKeyMappings(); + + /** Maps a key-press to a given note. + + @param key the key that should trigger the note + @param midiNoteOffsetFromC how many semitones above C the triggered note should + be. The actual midi note that gets played will be + this value + (12 * the current base octave). To change + the base octave, see setKeyPressBaseOctave() + */ + void setKeyPressForNote (const KeyPress& key, + int midiNoteOffsetFromC); + + /** Removes any key-mappings for a given note. + For a description of what the note number means, see setKeyPressForNote(). + */ + void removeKeyPressForNote (int midiNoteOffsetFromC); + + /** Changes the base note above which key-press-triggered notes are played. + + The set of key-mappings that trigger notes can be moved up and down to cover + the entire scale using this method. + + The value passed in is an octave number between 0 and 10 (inclusive), and + indicates which C is the base note to which the key-mapped notes are + relative. + */ + void setKeyPressBaseOctave (int newOctaveNumber); + + /** This sets the octave number which is shown as the octave number for middle C. + + This affects only the default implementation of getWhiteNoteText(), which + passes this octave number to MidiMessage::getMidiNoteName() in order to + get the note text. See MidiMessage::getMidiNoteName() for more info about + the parameter. + + By default this value is set to 3. + + @see getOctaveForMiddleC + */ + void setOctaveForMiddleC (int octaveNumForMiddleC); + + /** This returns the value set by setOctaveForMiddleC(). + @see setOctaveForMiddleC + */ + int getOctaveForMiddleC() const noexcept { return octaveNumForMiddleC; } + + //============================================================================== + /** @internal */ + void paint (Graphics&) override; + /** @internal */ + void resized() override; + /** @internal */ + void mouseMove (const MouseEvent&) override; + /** @internal */ + void mouseDrag (const MouseEvent&) override; + /** @internal */ + void mouseDown (const MouseEvent&) override; + /** @internal */ + void mouseUp (const MouseEvent&) override; + /** @internal */ + void mouseEnter (const MouseEvent&) override; + /** @internal */ + void mouseExit (const MouseEvent&) override; + /** @internal */ + void mouseWheelMove (const MouseEvent&, const MouseWheelDetails&) override; + /** @internal */ + void timerCallback() override; + /** @internal */ + bool keyStateChanged (bool isKeyDown) override; + /** @internal */ + bool keyPressed (const KeyPress&) override; + /** @internal */ + void focusLost (FocusChangeType) override; + /** @internal */ + void handleNoteOn (MidiKeyboardState*, int midiChannel, int midiNoteNumber, float velocity) override; + /** @internal */ + void handleNoteOff (MidiKeyboardState*, int midiChannel, int midiNoteNumber, float velocity) override; + /** @internal */ + void colourChanged() override; + +protected: + //============================================================================== + /** Draws a white note in the given rectangle. + + isOver indicates whether the mouse is over the key, isDown indicates whether the key is + currently pressed down. + + When doing this, be sure to note the keyboard's orientation. + */ + virtual void drawWhiteNote (int midiNoteNumber, + Graphics& g, + int x, int y, int w, int h, + bool isDown, bool isOver, + const Colour& lineColour, + const Colour& textColour); + + /** Draws a black note in the given rectangle. + + isOver indicates whether the mouse is over the key, isDown indicates whether the key is + currently pressed down. + + When doing this, be sure to note the keyboard's orientation. + */ + virtual void drawBlackNote (int midiNoteNumber, + Graphics& g, + int x, int y, int w, int h, + bool isDown, bool isOver, + const Colour& noteFillColour); + + /** Allows text to be drawn on the white notes. + By default this is used to label the C in each octave, but could be used for other things. + @see setOctaveForMiddleC + */ + virtual String getWhiteNoteText (const int midiNoteNumber); + + /** Draws the up and down buttons that change the base note. */ + virtual void drawUpDownButton (Graphics& g, int w, int h, + const bool isMouseOver, + const bool isButtonPressed, + const bool movesOctavesUp); + + /** Callback when the mouse is clicked on a key. + + You could use this to do things like handle right-clicks on keys, etc. + + Return true if you want the click to trigger the note, or false if you + want to handle it yourself and not have the note played. + + @see mouseDraggedToKey + */ + virtual bool mouseDownOnKey (int midiNoteNumber, const MouseEvent& e); + + /** Callback when the mouse is dragged from one key onto another. + @see mouseDownOnKey + */ + virtual void mouseDraggedToKey (int midiNoteNumber, const MouseEvent& e); + + /** Callback when the mouse is released from a key. + @see mouseDownOnKey + */ + virtual void mouseUpOnKey (int midiNoteNumber, const MouseEvent& e); + + /** Calculates the position of a given midi-note. + + This can be overridden to create layouts with custom key-widths. + + @param midiNoteNumber the note to find + @param keyWidth the desired width in pixels of one key - see setKeyWidth() + @param x the x position of the left-hand edge of the key (this method + always works in terms of a horizontal keyboard) + @param w the width of the key + */ + virtual void getKeyPosition (int midiNoteNumber, float keyWidth, + int& x, int& w) const; + + /** Returns the rectangle for a given key if within the displayable range */ + Rectangle getRectangleForKey (int midiNoteNumber) const; + + +private: + //============================================================================== + friend class SurjectiveMidiKeyboardUpDownButton; + + MidiKeyboardState& state; + float blackNoteLengthRatio; + int xOffset; + float keyWidth; + Orientation orientation; + + int midiChannel, midiInChannelMask; + float velocity; + + Array mouseOverNotes, mouseDownNotes; + BigInteger keysPressed, keysCurrentlyDrawnDown; + bool shouldCheckState; + + int rangeStart, rangeEnd; + float firstKey; + bool canScroll, useMousePositionForVelocity, shouldCheckMousePos; + ScopedPointer