add lazarus source
This commit is contained in:
parent
6efe624417
commit
dc16a231fe
|
@ -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)
|
||||
|
|
|
@ -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 = "<absolute>"; };
|
||||
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 = "<absolute>"; };
|
||||
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 = "<absolute>"; };
|
||||
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 = "<absolute>"; };
|
||||
|
@ -75,21 +92,28 @@
|
|||
4C95500A577A3EB04B67B88A = {isa = PBXFileReference; lastKnownFileType = file; name = "juce_opengl"; path = "/Applications/JUCE/modules/juce_opengl"; sourceTree = "<absolute>"; };
|
||||
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 = "<absolute>"; };
|
||||
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 = "<absolute>"; };
|
||||
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 = "<absolute>"; };
|
||||
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 = "<absolute>"; };
|
||||
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 = "<absolute>"; };
|
||||
|
@ -101,21 +125,31 @@
|
|||
956F86CAE587B3ABC34C69A9 = {isa = PBXFileReference; lastKnownFileType = file; name = "juce_data_structures"; path = "/Applications/JUCE/modules/juce_data_structures"; sourceTree = "<absolute>"; };
|
||||
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 = "<absolute>"; };
|
||||
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 = "<absolute>"; };
|
||||
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 = "<absolute>"; };
|
||||
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 = "<absolute>"; };
|
||||
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 = "<absolute>"; };
|
||||
CC620AB9A3158E481982505A = {isa = PBXFileReference; lastKnownFileType = file; name = "juce_cryptography"; path = "/Applications/JUCE/modules/juce_cryptography"; sourceTree = "<absolute>"; };
|
||||
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 = "<absolute>"; };
|
||||
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,
|
||||
|
|
47
Source/FilePicker.cpp
Normal file
47
Source/FilePicker.cpp
Normal file
|
@ -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<int> 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());
|
||||
}
|
29
Source/FilePicker.h
Normal file
29
Source/FilePicker.h
Normal file
|
@ -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)
|
||||
};
|
138
Source/FluidSynthModel.cpp
Normal file
138
Source/FluidSynthModel.cpp
Normal file
|
@ -0,0 +1,138 @@
|
|||
//
|
||||
// Created by Alex Birch on 10/09/2017.
|
||||
//
|
||||
|
||||
#include <iostream>
|
||||
#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<unsigned int>(bank), static_cast<unsigned int>(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);
|
||||
}
|
76
Source/FluidSynthModel.h
Normal file
76
Source/FluidSynthModel.h
Normal file
|
@ -0,0 +1,76 @@
|
|||
//
|
||||
// Created by Alex Birch on 10/09/2017.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../JuceLibraryCode/JuceHeader.h"
|
||||
#include <fluidsynth.h>
|
||||
#include <memory>
|
||||
#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<Listener> eventListeners;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (FluidSynthModel)
|
||||
};
|
12
Source/MyColours.cpp
Normal file
12
Source/MyColours.cpp
Normal file
|
@ -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_V4*> (&LookAndFeel::getDefaultLookAndFeel()))
|
||||
return v4->getCurrentColourScheme().getUIColour (uiColour);
|
||||
|
||||
return fallback;
|
||||
}
|
13
Source/MyColours.h
Normal file
13
Source/MyColours.h
Normal file
|
@ -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);
|
||||
|
||||
};
|
17
Source/Pill.cpp
Normal file
17
Source/Pill.cpp
Normal file
|
@ -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)
|
||||
);
|
||||
}
|
19
Source/Pill.h
Normal file
19
Source/Pill.h
Normal file
|
@ -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)
|
||||
};
|
99
Source/Pills.cpp
Normal file
99
Source/Pills.cpp
Normal file
|
@ -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<string> &items,
|
||||
const function<void (int)> &onItemSelected,
|
||||
const function<int (const string&)> &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<string> &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<const int>(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<int> r (getLocalBounds());
|
||||
const int equalWidth = r.proportionOfWidth(buttons.size() <= 0 ? 1.0 : 1.0f/buttons.size());
|
||||
for(TextButton* t : buttons) {
|
||||
Rectangle<int> 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));
|
||||
}
|
46
Source/Pills.h
Normal file
46
Source/Pills.h
Normal file
|
@ -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<string> &items,
|
||||
const function<void (int)> &onItemSelected,
|
||||
const function<int (const string&)> &itemToIDMapper,
|
||||
int initiallySelectedItem
|
||||
);
|
||||
|
||||
void setItems(
|
||||
const vector<string> &items,
|
||||
int initiallySelectedItem
|
||||
);
|
||||
|
||||
void buttonClicked (Button* button) override;
|
||||
void cycle(bool right);
|
||||
|
||||
private:
|
||||
string label;
|
||||
vector<string> items;
|
||||
function<void (int)> onItemSelected;
|
||||
function<int (const string&)> itemToIDMapper;
|
||||
|
||||
OwnedArray<TextButton> 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)
|
||||
};
|
|
@ -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<int> 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<int> r2 (getLocalBounds());
|
||||
// r2.reduce(0, padding);
|
||||
// r2.removeFromBottom(pianoHeight);
|
||||
// r2.removeFromTop(filePickerHeight);
|
||||
// tablesComponent.setBounds (r2);
|
||||
//
|
||||
// Rectangle<int> r3 (getLocalBounds());
|
||||
// r3.removeFromTop(filePickerHeight);
|
||||
//
|
||||
// filePicker.setBounds(r3);
|
||||
|
||||
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;
|
||||
}
|
|
@ -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)
|
||||
};
|
||||
|
|
|
@ -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<float>& 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<float>& buffer, MidiBuffer& midiMessages) {
|
||||
jassert (!isUsingDoublePrecision());
|
||||
const int numSamples = buffer.getNumSamples();
|
||||
|
||||
// Now pass any incoming midi messages to our keyboard state object, and let it
|
||||
// add messages to the buffer if the user is clicking on the on-screen keys
|
||||
keyboardState.processNextMidiBuffer (midiMessages, 0, numSamples, true);
|
||||
|
||||
// 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<float>& 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<AudioProcessorParameterWithID*> (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<XmlElement> 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<AudioProcessorParameterWithID*> (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;
|
||||
}
|
|
@ -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<float>&, MidiBuffer&) override;
|
||||
void processBlock (AudioBuffer<float>& 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)
|
||||
};
|
||||
|
|
21
Source/Preset.cpp
Normal file
21
Source/Preset.cpp
Normal file
|
@ -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;
|
||||
}
|
24
Source/Preset.h
Normal file
24
Source/Preset.h
Normal file
|
@ -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;
|
||||
};
|
10
Source/PresetsToBanks.h
Normal file
10
Source/PresetsToBanks.h
Normal file
|
@ -0,0 +1,10 @@
|
|||
//
|
||||
// Created by Alex Birch on 17/09/2017.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Preset.h"
|
||||
#include <map>
|
||||
|
||||
typedef std::multimap<int, Preset> BanksToPresets;
|
13
Source/SoundfontSynthSound.cpp
Normal file
13
Source/SoundfontSynthSound.cpp
Normal file
|
@ -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;
|
||||
}
|
13
Source/SoundfontSynthSound.h
Normal file
13
Source/SoundfontSynthSound.h
Normal file
|
@ -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;
|
||||
};
|
97
Source/SoundfontSynthVoice.cpp
Normal file
97
Source/SoundfontSynthVoice.cpp
Normal file
|
@ -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<SoundfontSynthSound*> (sound) != nullptr;
|
||||
}
|
||||
void SoundfontSynthVoice::startNote(
|
||||
int midiNoteNumber,
|
||||
float velocity,
|
||||
SynthesiserSound* sound,
|
||||
int /*currentPitchWheelPosition*/) {
|
||||
this->midiNoteNumber = midiNoteNumber;
|
||||
fluid_synth_noteon(synth, 0, midiNoteNumber, static_cast<int>(velocity * 127));
|
||||
|
||||
// currentAngle = 0.0;
|
||||
// level = velocity * 0.15;
|
||||
// tailOff = 0.0;
|
||||
//
|
||||
// double cyclesPerSecond = MidiMessage::getMidiNoteInHertz (midiNoteNumber);
|
||||
// double cyclesPerSample = cyclesPerSecond / getSampleRate();
|
||||
//
|
||||
// angleDelta = cyclesPerSample * 2.0 * double_Pi;
|
||||
|
||||
// jassert(dynamic_cast<SoundfontSynthSound*> (sound) != nullptr);
|
||||
// SoundfontSynthSound* sfsynth = dynamic_cast<SoundfontSynthSound*> (sound);
|
||||
}
|
||||
|
||||
void SoundfontSynthVoice::stopNote (float /*velocity*/, bool allowTailOff) {
|
||||
// if (allowTailOff) {
|
||||
// // start a tail-off by setting this flag. The render callback will pick up on
|
||||
// // this and do a fade out, calling clearCurrentNote() when it's finished.
|
||||
//
|
||||
// // we only need to begin a tail-off if it's not already doing so - the
|
||||
// if (tailOff == 0.0) {
|
||||
// // stopNote method could be called more than once.
|
||||
// tailOff = 1.0;
|
||||
// }
|
||||
// } else {
|
||||
// // we're being told to stop playing immediately, so reset everything..
|
||||
//
|
||||
// clearCurrentNote();
|
||||
// angleDelta = 0.0;
|
||||
// }
|
||||
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<float>& outputBuffer, int startSample, int numSamples) {
|
||||
//fluid_synth_process(synth.get(), numSamples, 1, nullptr, outputBuffer.getNumChannels(), outputBuffer.getArrayOfWritePointers());
|
||||
}
|
||||
|
||||
//void SoundfontSynthVoice::renderBlock (AudioBuffer<float>& outputBuffer, int startSample, int numSamples) {
|
||||
// fluid_synth_process(synth.get(), numSamples, 1, nullptr, outputBuffer.getNumChannels(), outputBuffer.getArrayOfWritePointers());
|
||||
// if (angleDelta == 0.0) {
|
||||
// return;
|
||||
// }
|
||||
// while (--numSamples >= 0) {
|
||||
// double qualifiedTailOff = tailOff > 0 ? tailOff : 1.0;
|
||||
// auto currentSample = static_cast<FloatType> (std::sin (currentAngle) * level * qualifiedTailOff);
|
||||
// for (int i = outputBuffer.getNumChannels(); --i >= 0;)
|
||||
// outputBuffer.addSample (i, startSample, currentSample);
|
||||
//
|
||||
// currentAngle += angleDelta;
|
||||
// ++startSample;
|
||||
//
|
||||
// if (tailOff > 0) {
|
||||
// tailOff *= 0.99;
|
||||
//
|
||||
// if (tailOff <= 0.005) {
|
||||
// clearCurrentNote();
|
||||
// angleDelta = 0.0;
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//}
|
39
Source/SoundfontSynthVoice.h
Normal file
39
Source/SoundfontSynthVoice.h
Normal file
|
@ -0,0 +1,39 @@
|
|||
//
|
||||
// Created by Alex Birch on 07/09/2017.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include<memory>
|
||||
#include<fluidsynth.h>
|
||||
#include "../JuceLibraryCode/JuceHeader.h"
|
||||
|
||||
using std::shared_ptr;
|
||||
|
||||
class SoundfontSynthVoice : public SynthesiserVoice {
|
||||
public:
|
||||
SoundfontSynthVoice(fluid_synth_t* synth);
|
||||
|
||||
bool canPlaySound (SynthesiserSound* sound) override;
|
||||
void startNote (
|
||||
int midiNoteNumber,
|
||||
float velocity,
|
||||
SynthesiserSound* /*sound*/,
|
||||
int /*currentPitchWheelPosition*/) override;
|
||||
|
||||
void stopNote (float /*velocity*/, bool allowTailOff) override;
|
||||
void pitchWheelMoved (int /*newValue*/) override;
|
||||
|
||||
void controllerMoved (int /*controllerNumber*/, int /*newValue*/) override;
|
||||
|
||||
void renderNextBlock (AudioBuffer<float>& outputBuffer, int startSample, int numSamples) override;
|
||||
|
||||
private:
|
||||
double tailOff;
|
||||
double level;
|
||||
double currentAngle;
|
||||
double angleDelta;
|
||||
int midiNoteNumber;
|
||||
|
||||
fluid_synth_t* synth;
|
||||
};
|
984
Source/SurjectiveMidiKeyboardComponent.cpp
Normal file
984
Source/SurjectiveMidiKeyboardComponent.cpp
Normal file
|
@ -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<int> 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<int> (x, 0, w, blackNoteLength);
|
||||
case verticalKeyboardFacingLeft: return Rectangle<int> (getWidth() - blackNoteLength, x, blackNoteLength, w);
|
||||
case verticalKeyboardFacingRight: return Rectangle<int> (0, getHeight() - x - w, blackNoteLength, w);
|
||||
default: jassertfalse; break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (orientation)
|
||||
{
|
||||
case horizontalKeyboard: return Rectangle<int> (x, 0, w, getHeight());
|
||||
case verticalKeyboardFacingLeft: return Rectangle<int> (0, x, getWidth(), w);
|
||||
case verticalKeyboardFacingRight: return Rectangle<int> (0, getHeight() - x - w, getWidth(), w);
|
||||
default: jassertfalse; break;
|
||||
}
|
||||
}
|
||||
|
||||
return Rectangle<int>();
|
||||
}
|
||||
|
||||
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<int> 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<int> pos, float& mousePositionVelocity)
|
||||
{
|
||||
if (! reallyContains (pos, false))
|
||||
return -1;
|
||||
|
||||
Point<int> p (pos);
|
||||
|
||||
if (orientation != horizontalKeyboard)
|
||||
{
|
||||
p = Point<int> (p.y, p.x);
|
||||
|
||||
if (orientation == verticalKeyboardFacingLeft)
|
||||
p = Point<int> (p.x, getWidth() - p.y);
|
||||
else
|
||||
p = Point<int> (getHeight() - p.x, p.y);
|
||||
}
|
||||
|
||||
return remappedXYToNote (p + Point<int> (xOffset, 0), mousePositionVelocity);
|
||||
}
|
||||
|
||||
int SurjectiveMidiKeyboardComponent::remappedXYToNote (Point<int> 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<int> 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<int> 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<int> 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<int> (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<int> 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();
|
||||
}
|
437
Source/SurjectiveMidiKeyboardComponent.h
Normal file
437
Source/SurjectiveMidiKeyboardComponent.h
Normal file
|
@ -0,0 +1,437 @@
|
|||
//
|
||||
// Created by Alex Birch on 27/09/2017.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../JuceLibraryCode/JuceHeader.h"
|
||||
#include <map>
|
||||
|
||||
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<int> 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<int> getRectangleForKey (int midiNoteNumber) const;
|
||||
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
friend class SurjectiveMidiKeyboardUpDownButton;
|
||||
|
||||
MidiKeyboardState& state;
|
||||
float blackNoteLengthRatio;
|
||||
int xOffset;
|
||||
float keyWidth;
|
||||
Orientation orientation;
|
||||
|
||||
int midiChannel, midiInChannelMask;
|
||||
float velocity;
|
||||
|
||||
Array<int> mouseOverNotes, mouseDownNotes;
|
||||
BigInteger keysPressed, keysCurrentlyDrawnDown;
|
||||
bool shouldCheckState;
|
||||
|
||||
int rangeStart, rangeEnd;
|
||||
float firstKey;
|
||||
bool canScroll, useMousePositionForVelocity, shouldCheckMousePos;
|
||||
ScopedPointer<Button> scrollDown, scrollUp;
|
||||
|
||||
typedef multimap<int, KeyPress> DegreeToAscii;
|
||||
DegreeToAscii degreeToAsciis;
|
||||
|
||||
int keyMappingOctave, octaveNumForMiddleC;
|
||||
|
||||
static const uint8 whiteNotes[];
|
||||
static const uint8 blackNotes[];
|
||||
|
||||
void getKeyPos (int midiNoteNumber, int& x, int& w) const;
|
||||
int xyToNote (Point<int>, float& mousePositionVelocity);
|
||||
int remappedXYToNote (Point<int>, float& mousePositionVelocity) const;
|
||||
void resetAnyKeysInUse();
|
||||
void updateNoteUnderMouse (Point<int>, bool isDown, int fingerNum);
|
||||
void updateNoteUnderMouse (const MouseEvent&, bool isDown);
|
||||
void repaintNote (int midiNoteNumber);
|
||||
void setLowestVisibleKeyFloat (float noteNumber);
|
||||
void bindKeysToMidiKeyboard();
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (SurjectiveMidiKeyboardComponent)
|
||||
};
|
204
Source/TableComponent.cpp
Normal file
204
Source/TableComponent.cpp
Normal file
|
@ -0,0 +1,204 @@
|
|||
//
|
||||
// Model.cpp
|
||||
// Lazarus
|
||||
//
|
||||
// Created by Alex Birch on 01/09/2017.
|
||||
//
|
||||
//
|
||||
|
||||
#include "TableComponent.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
This class shows how to implement a TableListBoxModel to show in a TableListBox.
|
||||
*/
|
||||
TableComponent::TableComponent(
|
||||
const vector<string> &columns,
|
||||
const vector<vector<string>> &rows,
|
||||
const function<void (int)> &onRowSelected,
|
||||
const function<int (const vector<string>&)> &rowToIDMapper,
|
||||
int initiallySelectedRow
|
||||
)
|
||||
: font (14.0f),
|
||||
columns(columns),
|
||||
rows(rows),
|
||||
onRowSelected(onRowSelected),
|
||||
rowToIDMapper(rowToIDMapper)
|
||||
{
|
||||
// Create our table component and add it to this component..
|
||||
addAndMakeVisible (table);
|
||||
table.setModel (this);
|
||||
|
||||
// give it a border
|
||||
table.setColour (ListBox::outlineColourId, Colours::grey);
|
||||
table.setOutlineThickness (1);
|
||||
|
||||
int columnIx = 1;
|
||||
|
||||
// Add some columns to the table header, based on the column list in our database..
|
||||
for (auto &column : columns) // access by reference to avoid copying
|
||||
{
|
||||
table.getHeader().addColumn (
|
||||
String(column),
|
||||
columnIx++,
|
||||
100, // column width
|
||||
50, // min width
|
||||
400, // max width
|
||||
TableHeaderComponent::defaultFlags
|
||||
);
|
||||
}
|
||||
|
||||
table.setWantsKeyboardFocus(false);
|
||||
|
||||
table.selectRow(initiallySelectedRow);
|
||||
|
||||
// we could now change some initial settings..
|
||||
table.getHeader().setSortColumnId (1, false); // sort ascending by ID column
|
||||
// table.getHeader().setColumnVisible (7, false); // hide the "length" column until the user shows it
|
||||
|
||||
// un-comment this line to have a go of stretch-to-fit mode
|
||||
// table.getHeader().setStretchToFitActive (true);
|
||||
|
||||
// table.setMultipleSelectionEnabled (false);
|
||||
}
|
||||
|
||||
void TableComponent::setRows(const vector<vector<string>>& rows, int initiallySelectedRow) {
|
||||
this->rows = rows;
|
||||
table.deselectAllRows();
|
||||
table.updateContent();
|
||||
table.getHeader().setSortColumnId(0, true);
|
||||
table.selectRow(initiallySelectedRow);
|
||||
}
|
||||
|
||||
// This is overloaded from TableListBoxModel, and must return the total number of rows in our table
|
||||
int TableComponent::getNumRows()
|
||||
{
|
||||
return static_cast<int>(rows.size());
|
||||
}
|
||||
|
||||
// This is overloaded from TableListBoxModel, and should fill in the background of the whole row
|
||||
void TableComponent::paintRowBackground (
|
||||
Graphics& g,
|
||||
int rowNumber,
|
||||
int /*width*/,
|
||||
int /*height*/,
|
||||
bool rowIsSelected
|
||||
) {
|
||||
const Colour alternateColour (getLookAndFeel().findColour (ListBox::backgroundColourId)
|
||||
.interpolatedWith (getLookAndFeel().findColour (ListBox::textColourId), 0.03f));
|
||||
if (rowIsSelected)
|
||||
g.fillAll (Colours::lightblue);
|
||||
else if (rowNumber % 2)
|
||||
g.fillAll (alternateColour);
|
||||
}
|
||||
|
||||
// This is overloaded from TableListBoxModel, and must paint any cells that aren't using custom
|
||||
// components.
|
||||
void TableComponent::paintCell (
|
||||
Graphics& g,
|
||||
int rowNumber,
|
||||
int columnId,
|
||||
int width,
|
||||
int height,
|
||||
bool /*rowIsSelected*/
|
||||
) {
|
||||
g.setColour (getLookAndFeel().findColour (ListBox::textColourId));
|
||||
g.setFont (font);
|
||||
|
||||
g.drawText (rows[rowNumber][columnId-1], 2, 0, width - 4, height, Justification::centredLeft, true);
|
||||
|
||||
g.setColour (getLookAndFeel().findColour (ListBox::backgroundColourId));
|
||||
g.fillRect (width - 1, 0, 1, height);
|
||||
}
|
||||
|
||||
// This is overloaded from TableListBoxModel, and tells us that the user has clicked a table header
|
||||
// to change the sort order.
|
||||
void TableComponent::sortOrderChanged (
|
||||
int newSortColumnId,
|
||||
bool isForwards
|
||||
) {
|
||||
if (newSortColumnId != 0) {
|
||||
int selectedRowIx = table.getSelectedRow();
|
||||
vector<string> selectedRow;
|
||||
if (selectedRowIx >= 0) {
|
||||
selectedRow = rows[selectedRowIx];
|
||||
}
|
||||
|
||||
TableComponent::DataSorter sorter (newSortColumnId, isForwards);
|
||||
sort(rows.begin(), rows.end(), sorter);
|
||||
|
||||
table.updateContent();
|
||||
|
||||
if (selectedRowIx >= 0) {
|
||||
for (auto it = rows.begin(); it != rows.end(); ++it) {
|
||||
if(*it == selectedRow) {
|
||||
int index = static_cast<int>(std::distance(rows.begin(), it));
|
||||
table.selectRow(index);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This is overloaded from TableListBoxModel, and should choose the best width for the specified
|
||||
// column.
|
||||
int TableComponent::getColumnAutoSizeWidth (int columnId) {
|
||||
if (columnId == 5)
|
||||
return 100; // (this is the ratings column, containing a custom combobox component)
|
||||
|
||||
int widest = 32;
|
||||
|
||||
// find the widest bit of text in this column..
|
||||
for (int i = getNumRows(); --i >= 0;) {
|
||||
widest = jmax (widest, font.getStringWidth (rows[i][columnId-1]));
|
||||
}
|
||||
|
||||
return widest + 8;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void TableComponent::resized() {
|
||||
// position our table with a gap around its edge
|
||||
table.setBoundsInset (BorderSize<int> (7));
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
// A comparator used to sort our data when the user clicks a column header
|
||||
|
||||
TableComponent::DataSorter::DataSorter (
|
||||
int columnByWhichToSort,
|
||||
bool forwards
|
||||
)
|
||||
: columnByWhichToSort (columnByWhichToSort),
|
||||
direction (forwards ? 1 : -1)
|
||||
{}
|
||||
|
||||
bool TableComponent::DataSorter::operator ()(
|
||||
vector<string> first,
|
||||
vector<string> second
|
||||
) {
|
||||
int result = String(first[columnByWhichToSort-1])
|
||||
.compareNatural (String(second[columnByWhichToSort-1]));
|
||||
|
||||
if (result == 0)
|
||||
result = String(first[0])
|
||||
.compareNatural (String(second[0]));
|
||||
|
||||
result *= direction;
|
||||
|
||||
return result > 0;
|
||||
}
|
||||
|
||||
void TableComponent::selectedRowsChanged (int row) {
|
||||
if (row < 0) {
|
||||
return;
|
||||
}
|
||||
onRowSelected(rowToIDMapper(rows[row]));
|
||||
}
|
||||
|
||||
bool TableComponent::keyPressed(const KeyPress &key) {
|
||||
return table.keyPressed(key);
|
||||
}
|
88
Source/TableComponent.h
Normal file
88
Source/TableComponent.h
Normal file
|
@ -0,0 +1,88 @@
|
|||
//
|
||||
// Model.hpp
|
||||
// Lazarus
|
||||
//
|
||||
// Created by Alex Birch on 01/09/2017.
|
||||
//
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../JuceLibraryCode/JuceHeader.h"
|
||||
#include "PresetsToBanks.h"
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
using namespace std;
|
||||
|
||||
class TableComponent : public Component,
|
||||
public TableListBoxModel {
|
||||
public:
|
||||
TableComponent(
|
||||
const vector<string> &columns,
|
||||
const vector<vector<string>> &rows,
|
||||
const function<void (int)> &onRowSelected,
|
||||
const function<int (const vector<string>&)> &rowToIDMapper,
|
||||
int initiallySelectedRow
|
||||
);
|
||||
|
||||
int getNumRows() override;
|
||||
|
||||
void paintRowBackground (
|
||||
Graphics& g,
|
||||
int rowNumber,
|
||||
int width,
|
||||
int height,
|
||||
bool rowIsSelected
|
||||
) override;
|
||||
void paintCell (
|
||||
Graphics& g,
|
||||
int rowNumber,
|
||||
int columnId,
|
||||
int width,
|
||||
int height,
|
||||
bool rowIsSelected
|
||||
) override;
|
||||
|
||||
void sortOrderChanged (int newSortColumnId, bool isForwards) override;
|
||||
|
||||
int getColumnAutoSizeWidth (int columnId) override;
|
||||
|
||||
void selectedRowsChanged (int row) override;
|
||||
|
||||
void resized() override;
|
||||
|
||||
void setRows(const vector<vector<string>>& rows, int initiallySelectedRow);
|
||||
|
||||
bool keyPressed(const KeyPress &key) override;
|
||||
|
||||
private:
|
||||
TableListBox table; // the table component itself
|
||||
Font font;
|
||||
|
||||
vector<string> columns;
|
||||
vector<vector<string>> rows;
|
||||
|
||||
function<void (int)> onRowSelected;
|
||||
function<int (const vector<string>&)> rowToIDMapper;
|
||||
|
||||
// A comparator used to sort our data when the user clicks a column header
|
||||
class DataSorter {
|
||||
public:
|
||||
DataSorter (
|
||||
int columnByWhichToSort,
|
||||
bool forwards
|
||||
);
|
||||
|
||||
bool operator ()(
|
||||
vector<string> first,
|
||||
vector<string> second
|
||||
);
|
||||
|
||||
private:
|
||||
int columnByWhichToSort;
|
||||
int direction;
|
||||
};
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TableComponent)
|
||||
};
|
88
Source/TableModel.cpp
Normal file
88
Source/TableModel.cpp
Normal file
|
@ -0,0 +1,88 @@
|
|||
//
|
||||
// Created by Alex Birch on 17/09/2017.
|
||||
//
|
||||
|
||||
#include "TableModel.h"
|
||||
|
||||
// This is overloaded from TableListBoxModel, and must return the total number of rows in our table
|
||||
int TableModel::getNumRows()
|
||||
{
|
||||
return static_cast<int>(dataList.size());
|
||||
}
|
||||
|
||||
// This is overloaded from TableListBoxModel, and should fill in the background of the whole row
|
||||
void TableModel::paintRowBackground (
|
||||
Graphics& g,
|
||||
int rowNumber,
|
||||
int /*width*/,
|
||||
int /*height*/,
|
||||
bool rowIsSelected
|
||||
) {
|
||||
const Colour alternateColour (getLookAndFeel().findColour (ListBox::backgroundColourId)
|
||||
.interpolatedWith (getLookAndFeel().findColour (ListBox::textColourId), 0.03f));
|
||||
if (rowIsSelected)
|
||||
g.fillAll (Colours::lightblue);
|
||||
else if (rowNumber % 2)
|
||||
g.fillAll (alternateColour);
|
||||
}
|
||||
|
||||
// This is overloaded from TableListBoxModel, and must paint any cells that aren't using custom
|
||||
// components.
|
||||
void TableModel::paintCell (
|
||||
Graphics& g,
|
||||
int rowNumber,
|
||||
int columnId,
|
||||
int width,
|
||||
int height,
|
||||
bool /*rowIsSelected*/
|
||||
) {
|
||||
g.setColour (getLookAndFeel().findColour (ListBox::textColourId));
|
||||
g.setFont (font);
|
||||
|
||||
if (const XmlElement* rowElement = dataList->getChildElement (rowNumber))
|
||||
{
|
||||
const String text (rowElement->getStringAttribute (getAttributeNameForColumnId (columnId)));
|
||||
|
||||
g.drawText (text, 2, 0, width - 4, height, Justification::centredLeft, true);
|
||||
}
|
||||
|
||||
g.setColour (getLookAndFeel().findColour (ListBox::backgroundColourId));
|
||||
g.fillRect (width - 1, 0, 1, height);
|
||||
}
|
||||
|
||||
// This is overloaded from TableListBoxModel, and tells us that the user has clicked a table header
|
||||
// to change the sort order.
|
||||
void TableModel::sortOrderChanged (
|
||||
int newSortColumnId,
|
||||
bool isForwards
|
||||
) {
|
||||
if (newSortColumnId != 0)
|
||||
{
|
||||
TableModel::DataSorter sorter (getAttributeNameForColumnId (newSortColumnId), isForwards);
|
||||
dataList->sortChildElements (sorter);
|
||||
|
||||
table.updateContent();
|
||||
}
|
||||
}
|
||||
|
||||
// This is overloaded from TableListBoxModel, and should choose the best width for the specified
|
||||
// column.
|
||||
int TableModel::getColumnAutoSizeWidth (int columnId) {
|
||||
if (columnId == 5)
|
||||
return 100; // (this is the ratings column, containing a custom combobox component)
|
||||
|
||||
int widest = 32;
|
||||
|
||||
// find the widest bit of text in this column..
|
||||
for (int i = getNumRows(); --i >= 0;)
|
||||
{
|
||||
if (const XmlElement* rowElement = dataList->getChildElement (i))
|
||||
{
|
||||
const String text (rowElement->getStringAttribute (getAttributeNameForColumnId (columnId)));
|
||||
|
||||
widest = jmax (widest, font.getStringWidth (text));
|
||||
}
|
||||
}
|
||||
|
||||
return widest + 8;
|
||||
}
|
61
Source/TableModel.h
Normal file
61
Source/TableModel.h
Normal file
|
@ -0,0 +1,61 @@
|
|||
//
|
||||
// Created by Alex Birch on 17/09/2017.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../JuceLibraryCode/JuceHeader.h"
|
||||
|
||||
|
||||
class TableModel: public TableListBoxModel {
|
||||
public:
|
||||
int getNumRows() override;
|
||||
|
||||
void paintRowBackground (
|
||||
Graphics& g,
|
||||
int rowNumber,
|
||||
int width,
|
||||
int height,
|
||||
bool rowIsSelected
|
||||
) override;
|
||||
void paintCell (
|
||||
Graphics& g,
|
||||
int rowNumber,
|
||||
int columnId,
|
||||
int width,
|
||||
int height,
|
||||
bool rowIsSelected
|
||||
) override;
|
||||
|
||||
void sortOrderChanged (int newSortColumnId, bool isForwards) override;
|
||||
|
||||
int getColumnAutoSizeWidth (int columnId) override;
|
||||
|
||||
private:
|
||||
ScopedPointer<XmlElement> demoData; // This is the XML document loaded from the embedded file "demo table data.xml"
|
||||
std::vector<String> columnList;
|
||||
std::vector<std::vector<String>> dataList;
|
||||
|
||||
void loadData();
|
||||
String getAttributeNameForColumnId (const int columnId) const;
|
||||
|
||||
// A comparator used to sort our data when the user clicks a column header
|
||||
class DataSorter {
|
||||
public:
|
||||
DataSorter (
|
||||
const String& attributeToSortBy,
|
||||
bool forwards
|
||||
);
|
||||
|
||||
int compareElements (
|
||||
XmlElement* first,
|
||||
XmlElement* second
|
||||
) const;
|
||||
|
||||
private:
|
||||
String attributeToSort;
|
||||
int direction;
|
||||
};
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TableModel)
|
||||
};
|
183
Source/TablesComponent.cpp
Normal file
183
Source/TablesComponent.cpp
Normal file
|
@ -0,0 +1,183 @@
|
|||
//
|
||||
// Created by Alex Birch on 17/09/2017.
|
||||
//
|
||||
|
||||
#include "TablesComponent.h"
|
||||
|
||||
using namespace std;
|
||||
using namespace placeholders;
|
||||
|
||||
TablesComponent::TablesComponent(
|
||||
FluidSynthModel* fluidSynthModel
|
||||
) : fluidSynthModel(fluidSynthModel),
|
||||
banksToPresets(fluidSynthModel->getBanks()),
|
||||
initialised(false)
|
||||
{
|
||||
fluid_preset_t* currentPreset = getCurrentPreset();
|
||||
|
||||
selectedBank = currentPreset->get_banknum(currentPreset);
|
||||
int selectedPreset = currentPreset->get_num(currentPreset);
|
||||
|
||||
auto rowToPresetMapper = [this](const vector<string> &row) {
|
||||
return stoi(row[0]);
|
||||
};
|
||||
auto itemToBankMapper = [](const string &item) {
|
||||
return stoi(item);
|
||||
};
|
||||
|
||||
presetTable = new TableComponent(
|
||||
{"Preset", "Name"},
|
||||
mapPresets(
|
||||
banksToPresets,
|
||||
currentPreset->get_banknum(currentPreset)
|
||||
),
|
||||
[this](int preset){
|
||||
this->onPresetSelected(preset);
|
||||
},
|
||||
rowToPresetMapper,
|
||||
presetToIndexMapper(selectedPreset)
|
||||
);
|
||||
banks = new Pills(
|
||||
"Banks",
|
||||
mapBanks(banksToPresets),
|
||||
[this](int bank){
|
||||
this->onBankSelected(bank);
|
||||
},
|
||||
itemToBankMapper,
|
||||
selectedBank
|
||||
);
|
||||
|
||||
presetTable->setWantsKeyboardFocus(false);
|
||||
|
||||
addAndMakeVisible(presetTable);
|
||||
|
||||
addAndMakeVisible(banks);
|
||||
|
||||
initialised = true;
|
||||
|
||||
fluidSynthModel->addListener(this);
|
||||
}
|
||||
|
||||
fluid_preset_t* TablesComponent::getCurrentPreset() {
|
||||
fluid_synth_t* synth = fluidSynthModel->getSynth();
|
||||
|
||||
return fluid_synth_get_channel_preset(synth, fluidSynthModel->getChannel());
|
||||
}
|
||||
|
||||
Preset TablesComponent::getFirstPresetInBank(int bank) {
|
||||
pair<BanksToPresets::const_iterator, BanksToPresets::const_iterator> iterators = banksToPresets.equal_range(bank);
|
||||
BanksToPresets::const_iterator it = iterators.first;
|
||||
return it->second;
|
||||
}
|
||||
|
||||
void TablesComponent::onBankSelected(int bank) {
|
||||
if (!initialised) {
|
||||
return;
|
||||
}
|
||||
cout << "Bank " << bank << endl;
|
||||
selectedBank = bank;
|
||||
Preset firstPresetInBank = getFirstPresetInBank(bank);
|
||||
presetTable->setRows(
|
||||
mapPresets(
|
||||
banksToPresets,
|
||||
bank
|
||||
),
|
||||
presetToIndexMapper(firstPresetInBank.getPreset())
|
||||
);
|
||||
}
|
||||
|
||||
int TablesComponent::presetToIndexMapper(int preset) {
|
||||
int ix = 0;
|
||||
pair<BanksToPresets::const_iterator, BanksToPresets::const_iterator> iterators = this->banksToPresets.equal_range(this->selectedBank);
|
||||
for (auto it = iterators.first; it != iterators.second; ++it, ix++) {
|
||||
Preset b = it->second;
|
||||
if (preset == b.getPreset()) {
|
||||
return ix;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void TablesComponent::onPresetSelected(int preset) {
|
||||
if (!initialised) {
|
||||
return;
|
||||
}
|
||||
cout << "Preset " << preset << endl;
|
||||
// selectedPreset = preset;
|
||||
fluidSynthModel->changePreset(selectedBank, preset);
|
||||
}
|
||||
|
||||
TablesComponent::~TablesComponent() {
|
||||
delete presetTable;
|
||||
delete banks;
|
||||
fluidSynthModel->removeListener(this);
|
||||
}
|
||||
|
||||
vector<string> TablesComponent::mapBanks(const BanksToPresets &banksToPresets) {
|
||||
vector<string> rows;
|
||||
|
||||
const auto compareKey = [](const BanksToPresets::value_type& lhs, const BanksToPresets::value_type& rhs) {
|
||||
return lhs.first < rhs.first;
|
||||
};
|
||||
|
||||
for(auto i = banksToPresets.begin(); i != banksToPresets.end(); i = std::upper_bound(i, banksToPresets.end(), *i, compareKey)) {
|
||||
rows.push_back(to_string(i->first));
|
||||
}
|
||||
|
||||
return rows;
|
||||
}
|
||||
|
||||
|
||||
vector<vector<string>> TablesComponent::mapPresets(const BanksToPresets &banksToPresets, int bank) {
|
||||
vector<vector<string>> rows;
|
||||
|
||||
pair<BanksToPresets::const_iterator, BanksToPresets::const_iterator> iterators = banksToPresets.equal_range(bank);
|
||||
for (auto it = iterators.first; it != iterators.second; ++it) {
|
||||
Preset b = it->second;
|
||||
vector<string> row;
|
||||
row.push_back(to_string(b.getPreset()));
|
||||
row.push_back(b.getName());
|
||||
|
||||
rows.push_back(row);
|
||||
}
|
||||
|
||||
return rows;
|
||||
}
|
||||
|
||||
void TablesComponent::resized() {
|
||||
Rectangle<int> r (getLocalBounds());
|
||||
banks->setBounds (r.removeFromTop(27).reduced(5,0));
|
||||
|
||||
presetTable->setBounds (r);
|
||||
}
|
||||
|
||||
bool TablesComponent::keyPressed(const KeyPress &key) {
|
||||
if (key.getKeyCode() == KeyPress::leftKey
|
||||
|| key.getKeyCode() == KeyPress::rightKey) {
|
||||
banks->cycle(key.getKeyCode() == KeyPress::rightKey);
|
||||
return true;
|
||||
}
|
||||
return presetTable->keyPressed(key);
|
||||
}
|
||||
|
||||
void TablesComponent::fontChanged(FluidSynthModel *) {
|
||||
banksToPresets = fluidSynthModel->getBanks();
|
||||
|
||||
fluid_preset_t* currentPreset = getCurrentPreset();
|
||||
|
||||
selectedBank = currentPreset->get_banknum(currentPreset);
|
||||
int selectedPreset = currentPreset->get_num(currentPreset);
|
||||
|
||||
presetTable->setRows(
|
||||
mapPresets(
|
||||
banksToPresets,
|
||||
selectedBank
|
||||
),
|
||||
presetToIndexMapper(selectedPreset)
|
||||
);
|
||||
|
||||
banks->setItems(
|
||||
mapBanks(banksToPresets),
|
||||
selectedBank
|
||||
);
|
||||
}
|
54
Source/TablesComponent.h
Normal file
54
Source/TablesComponent.h
Normal file
|
@ -0,0 +1,54 @@
|
|||
//
|
||||
// Created by Alex Birch on 17/09/2017.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../JuceLibraryCode/JuceHeader.h"
|
||||
#include "Pills.h"
|
||||
#include "TableComponent.h"
|
||||
#include "Preset.h"
|
||||
#include "PresetsToBanks.h"
|
||||
#include "FluidSynthModel.h"
|
||||
#include <memory>
|
||||
#include <fluidsynth.h>
|
||||
|
||||
using namespace std;
|
||||
|
||||
class TablesComponent : public Component,
|
||||
public FluidSynthModel::Listener
|
||||
{
|
||||
public:
|
||||
TablesComponent(
|
||||
FluidSynthModel* fluidSynthModel
|
||||
);
|
||||
~TablesComponent();
|
||||
|
||||
void resized() override;
|
||||
|
||||
bool keyPressed(const KeyPress &key) override;
|
||||
void fontChanged(FluidSynthModel *) override;
|
||||
|
||||
private:
|
||||
FluidSynthModel* fluidSynthModel;
|
||||
int selectedBank;
|
||||
|
||||
Pills* banks;
|
||||
TableComponent* presetTable;
|
||||
|
||||
BanksToPresets banksToPresets;
|
||||
|
||||
static vector<vector<string>> mapPresets(const BanksToPresets &banksToPresets, int bank);
|
||||
static vector<string> mapBanks(const BanksToPresets &banksToPresets);
|
||||
|
||||
void onBankSelected(int bank);
|
||||
void onPresetSelected(int preset);
|
||||
int presetToIndexMapper(int preset);
|
||||
|
||||
fluid_preset_t* getCurrentPreset();
|
||||
Preset getFirstPresetInBank(int bank);
|
||||
|
||||
bool initialised;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TablesComponent)
|
||||
};
|
|
@ -8,6 +8,44 @@
|
|||
pluginCode="Jspf" pluginManufacturerCode="Blbs">
|
||||
<MAINGROUP id="rCqBG3" name="juicysfplugin">
|
||||
<GROUP id="{ED7E27E3-B67D-383C-F819-F05289E08A33}" name="Source">
|
||||
<FILE id="jhSsxG" name="FilePicker.cpp" compile="1" resource="0" file="Source/FilePicker.cpp"/>
|
||||
<FILE id="kDg5Qi" name="FilePicker.h" compile="0" resource="0" file="Source/FilePicker.h"/>
|
||||
<FILE id="xg2XPM" name="FluidSynthModel.cpp" compile="1" resource="0"
|
||||
file="Source/FluidSynthModel.cpp"/>
|
||||
<FILE id="kOSlkW" name="FluidSynthModel.h" compile="0" resource="0"
|
||||
file="Source/FluidSynthModel.h"/>
|
||||
<FILE id="olazZO" name="MyColours.cpp" compile="1" resource="0" file="Source/MyColours.cpp"/>
|
||||
<FILE id="RbaYg9" name="MyColours.h" compile="0" resource="0" file="Source/MyColours.h"/>
|
||||
<FILE id="ZFYfhD" name="Pill.cpp" compile="1" resource="0" file="Source/Pill.cpp"/>
|
||||
<FILE id="CBKk98" name="Pill.h" compile="0" resource="0" file="Source/Pill.h"/>
|
||||
<FILE id="esyv7j" name="Pills.cpp" compile="1" resource="0" file="Source/Pills.cpp"/>
|
||||
<FILE id="qlJw1C" name="Pills.h" compile="0" resource="0" file="Source/Pills.h"/>
|
||||
<FILE id="Q7V7pC" name="Preset.cpp" compile="1" resource="0" file="Source/Preset.cpp"/>
|
||||
<FILE id="bPOtSV" name="Preset.h" compile="0" resource="0" file="Source/Preset.h"/>
|
||||
<FILE id="tlzfXV" name="PresetsToBanks.h" compile="0" resource="0"
|
||||
file="Source/PresetsToBanks.h"/>
|
||||
<FILE id="SEcFsz" name="SoundfontSynthSound.cpp" compile="1" resource="0"
|
||||
file="Source/SoundfontSynthSound.cpp"/>
|
||||
<FILE id="kCI8aL" name="SoundfontSynthSound.h" compile="0" resource="0"
|
||||
file="Source/SoundfontSynthSound.h"/>
|
||||
<FILE id="FKMDFe" name="SoundfontSynthVoice.cpp" compile="1" resource="0"
|
||||
file="Source/SoundfontSynthVoice.cpp"/>
|
||||
<FILE id="t75qEY" name="SoundfontSynthVoice.h" compile="0" resource="0"
|
||||
file="Source/SoundfontSynthVoice.h"/>
|
||||
<FILE id="uAVIjl" name="SurjectiveMidiKeyboardComponent.cpp" compile="1"
|
||||
resource="0" file="Source/SurjectiveMidiKeyboardComponent.cpp"/>
|
||||
<FILE id="m3NMU5" name="SurjectiveMidiKeyboardComponent.h" compile="0"
|
||||
resource="0" file="Source/SurjectiveMidiKeyboardComponent.h"/>
|
||||
<FILE id="lKz1Zz" name="TableComponent.cpp" compile="1" resource="0"
|
||||
file="Source/TableComponent.cpp"/>
|
||||
<FILE id="D2v6G2" name="TableComponent.h" compile="0" resource="0"
|
||||
file="Source/TableComponent.h"/>
|
||||
<FILE id="DFpwRJ" name="TableModel.cpp" compile="1" resource="0" file="Source/TableModel.cpp"/>
|
||||
<FILE id="iaUXIc" name="TableModel.h" compile="0" resource="0" file="Source/TableModel.h"/>
|
||||
<FILE id="XSCMOI" name="TablesComponent.cpp" compile="1" resource="0"
|
||||
file="Source/TablesComponent.cpp"/>
|
||||
<FILE id="m0tqZa" name="TablesComponent.h" compile="0" resource="0"
|
||||
file="Source/TablesComponent.h"/>
|
||||
<FILE id="wm4EZl" name="PluginProcessor.cpp" compile="1" resource="0"
|
||||
file="Source/PluginProcessor.cpp"/>
|
||||
<FILE id="ABJlXP" name="PluginProcessor.h" compile="0" resource="0"
|
||||
|
|
Loading…
Reference in New Issue
Block a user