add lazarus source

This commit is contained in:
Alex Birch 2018-02-27 00:25:20 +00:00
parent 6efe624417
commit dc16a231fe
No known key found for this signature in database
GPG Key ID: 305EB1F98D44ACBA
32 changed files with 3242 additions and 109 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

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

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

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

View 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();
}

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

View File

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