├── .gitignore ├── JuceLibraryCode ├── AppConfig.h ├── JuceHeader.h ├── JucePluginDefines.h ├── ReadMe.txt ├── include_juce_audio_basics.cpp ├── include_juce_audio_basics.mm ├── include_juce_audio_devices.cpp ├── include_juce_audio_devices.mm ├── include_juce_audio_formats.cpp ├── include_juce_audio_formats.mm ├── include_juce_audio_plugin_client_AAX.cpp ├── include_juce_audio_plugin_client_AAX.mm ├── include_juce_audio_plugin_client_AAX_utils.cpp ├── include_juce_audio_plugin_client_ARA.cpp ├── include_juce_audio_plugin_client_AU_1.mm ├── include_juce_audio_plugin_client_AU_2.mm ├── include_juce_audio_plugin_client_AUv3.mm ├── include_juce_audio_plugin_client_LV2.cpp ├── include_juce_audio_plugin_client_LV2.mm ├── include_juce_audio_plugin_client_Standalone.cpp ├── include_juce_audio_plugin_client_Unity.cpp ├── include_juce_audio_plugin_client_VST2.cpp ├── include_juce_audio_plugin_client_VST2.mm ├── include_juce_audio_plugin_client_VST3.cpp ├── include_juce_audio_plugin_client_VST3.mm ├── include_juce_audio_processors.cpp ├── include_juce_audio_processors.mm ├── include_juce_audio_processors_ara.cpp ├── include_juce_audio_processors_lv2_libs.cpp ├── include_juce_audio_utils.cpp ├── include_juce_audio_utils.mm ├── include_juce_core.cpp ├── include_juce_core.mm ├── include_juce_data_structures.cpp ├── include_juce_data_structures.mm ├── include_juce_dsp.cpp ├── include_juce_dsp.mm ├── include_juce_events.cpp ├── include_juce_events.mm ├── include_juce_graphics.cpp ├── include_juce_graphics.mm ├── include_juce_gui_basics.cpp ├── include_juce_gui_basics.mm ├── include_juce_gui_extra.cpp └── include_juce_gui_extra.mm ├── LICENSE ├── README.md ├── Sampler.jucer └── Source ├── CommandFifo.h ├── Components ├── LoopPointMarker.h ├── LoopPointsOverlay.h ├── MPELegacySettingsComponent.h ├── MPENewSettingsComponent.h ├── MPESettingsComponent.h ├── MainSamplerView.h ├── PlaybackPositionOverlay.h ├── Ruler.h ├── WaveformEditor.h └── WaveformView.h ├── DataModels ├── DataModel.cpp ├── DataModel.h ├── MPESettingsDataModel.cpp ├── MPESettingsDataModel.h └── VisibleRangeDataModel.h ├── DemoUtilities.h ├── FileAudioFormatReaderFactory.h ├── MPESamplerSound.h ├── MPESamplerVoice.h ├── Main.cpp ├── MemoryAudioFormatReaderFactory.h ├── Misc.h ├── ProcessorState.h ├── Sample.h ├── SamplerAudioProcessor.cpp ├── SamplerAudioProcessor.h ├── SamplerAudioProcessorEditor.cpp ├── SamplerAudioProcessorEditor.h └── SamplerPluginDemo.h /.gitignore: -------------------------------------------------------------------------------- 1 | Builds/ 2 | 3 | # Prerequisites 4 | *.d 5 | 6 | # Compiled Object files 7 | *.slo 8 | *.lo 9 | *.o 10 | *.obj 11 | 12 | # Precompiled Headers 13 | *.gch 14 | *.pch 15 | 16 | # Compiled Dynamic libraries 17 | *.so 18 | *.dylib 19 | *.dll 20 | 21 | # Fortran module files 22 | *.mod 23 | *.smod 24 | 25 | # Compiled Static libraries 26 | *.lai 27 | *.la 28 | *.a 29 | *.lib 30 | 31 | # Executables 32 | *.exe 33 | *.out 34 | *.app 35 | -------------------------------------------------------------------------------- /JuceLibraryCode/AppConfig.h: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | IMPORTANT! This file is auto-generated each time you save your 4 | project - if you alter its contents, your changes may be overwritten! 5 | 6 | There's a section below where you can add your own custom code safely, and the 7 | Projucer will preserve the contents of that block, but the best way to change 8 | any of these definitions is by using the Projucer's project settings. 9 | 10 | Any commented-out settings will assume their default values. 11 | 12 | */ 13 | 14 | #pragma once 15 | 16 | //============================================================================== 17 | // [BEGIN_USER_CODE_SECTION] 18 | 19 | // (You can add your own code in this section, and the Projucer will not overwrite it) 20 | 21 | // [END_USER_CODE_SECTION] 22 | 23 | #include "JucePluginDefines.h" 24 | 25 | /* 26 | ============================================================================== 27 | 28 | In accordance with the terms of the JUCE 7 End-Use License Agreement, the 29 | JUCE Code in SECTION A cannot be removed, changed or otherwise rendered 30 | ineffective unless you have a JUCE Indie or Pro license, or are using JUCE 31 | under the GPL v3 license. 32 | 33 | End User License Agreement: www.juce.com/juce-7-licence 34 | 35 | ============================================================================== 36 | */ 37 | 38 | // BEGIN SECTION A 39 | 40 | #ifndef JUCE_DISPLAY_SPLASH_SCREEN 41 | #define JUCE_DISPLAY_SPLASH_SCREEN 1 42 | #endif 43 | 44 | // END SECTION A 45 | 46 | #define JUCE_USE_DARK_SPLASH_SCREEN 1 47 | 48 | #define JUCE_PROJUCER_VERSION 0x70007 49 | 50 | //============================================================================== 51 | #define JUCE_MODULE_AVAILABLE_juce_audio_basics 1 52 | #define JUCE_MODULE_AVAILABLE_juce_audio_devices 1 53 | #define JUCE_MODULE_AVAILABLE_juce_audio_formats 1 54 | #define JUCE_MODULE_AVAILABLE_juce_audio_plugin_client 1 55 | #define JUCE_MODULE_AVAILABLE_juce_audio_processors 1 56 | #define JUCE_MODULE_AVAILABLE_juce_audio_utils 1 57 | #define JUCE_MODULE_AVAILABLE_juce_core 1 58 | #define JUCE_MODULE_AVAILABLE_juce_data_structures 1 59 | #define JUCE_MODULE_AVAILABLE_juce_dsp 1 60 | #define JUCE_MODULE_AVAILABLE_juce_events 1 61 | #define JUCE_MODULE_AVAILABLE_juce_graphics 1 62 | #define JUCE_MODULE_AVAILABLE_juce_gui_basics 1 63 | #define JUCE_MODULE_AVAILABLE_juce_gui_extra 1 64 | 65 | #define JUCE_GLOBAL_MODULE_SETTINGS_INCLUDED 1 66 | 67 | //============================================================================== 68 | // juce_audio_devices flags: 69 | 70 | #ifndef JUCE_USE_WINRT_MIDI 71 | //#define JUCE_USE_WINRT_MIDI 0 72 | #endif 73 | 74 | #ifndef JUCE_ASIO 75 | //#define JUCE_ASIO 0 76 | #endif 77 | 78 | #ifndef JUCE_WASAPI 79 | //#define JUCE_WASAPI 1 80 | #endif 81 | 82 | #ifndef JUCE_DIRECTSOUND 83 | //#define JUCE_DIRECTSOUND 1 84 | #endif 85 | 86 | #ifndef JUCE_ALSA 87 | //#define JUCE_ALSA 1 88 | #endif 89 | 90 | #ifndef JUCE_JACK 91 | //#define JUCE_JACK 0 92 | #endif 93 | 94 | #ifndef JUCE_BELA 95 | //#define JUCE_BELA 0 96 | #endif 97 | 98 | #ifndef JUCE_USE_ANDROID_OBOE 99 | //#define JUCE_USE_ANDROID_OBOE 1 100 | #endif 101 | 102 | #ifndef JUCE_USE_OBOE_STABILIZED_CALLBACK 103 | //#define JUCE_USE_OBOE_STABILIZED_CALLBACK 0 104 | #endif 105 | 106 | #ifndef JUCE_USE_ANDROID_OPENSLES 107 | //#define JUCE_USE_ANDROID_OPENSLES 0 108 | #endif 109 | 110 | #ifndef JUCE_DISABLE_AUDIO_MIXING_WITH_OTHER_APPS 111 | //#define JUCE_DISABLE_AUDIO_MIXING_WITH_OTHER_APPS 0 112 | #endif 113 | 114 | //============================================================================== 115 | // juce_audio_formats flags: 116 | 117 | #ifndef JUCE_USE_FLAC 118 | //#define JUCE_USE_FLAC 1 119 | #endif 120 | 121 | #ifndef JUCE_USE_OGGVORBIS 122 | //#define JUCE_USE_OGGVORBIS 1 123 | #endif 124 | 125 | #ifndef JUCE_USE_MP3AUDIOFORMAT 126 | //#define JUCE_USE_MP3AUDIOFORMAT 0 127 | #endif 128 | 129 | #ifndef JUCE_USE_LAME_AUDIO_FORMAT 130 | //#define JUCE_USE_LAME_AUDIO_FORMAT 0 131 | #endif 132 | 133 | #ifndef JUCE_USE_WINDOWS_MEDIA_FORMAT 134 | //#define JUCE_USE_WINDOWS_MEDIA_FORMAT 1 135 | #endif 136 | 137 | //============================================================================== 138 | // juce_audio_plugin_client flags: 139 | 140 | #ifndef JUCE_VST3_CAN_REPLACE_VST2 141 | #define JUCE_VST3_CAN_REPLACE_VST2 0 142 | #endif 143 | 144 | #ifndef JUCE_FORCE_USE_LEGACY_PARAM_IDS 145 | //#define JUCE_FORCE_USE_LEGACY_PARAM_IDS 0 146 | #endif 147 | 148 | #ifndef JUCE_FORCE_LEGACY_PARAMETER_AUTOMATION_TYPE 149 | //#define JUCE_FORCE_LEGACY_PARAMETER_AUTOMATION_TYPE 0 150 | #endif 151 | 152 | #ifndef JUCE_USE_STUDIO_ONE_COMPATIBLE_PARAMETERS 153 | //#define JUCE_USE_STUDIO_ONE_COMPATIBLE_PARAMETERS 1 154 | #endif 155 | 156 | #ifndef JUCE_AU_WRAPPERS_SAVE_PROGRAM_STATES 157 | //#define JUCE_AU_WRAPPERS_SAVE_PROGRAM_STATES 0 158 | #endif 159 | 160 | #ifndef JUCE_STANDALONE_FILTER_WINDOW_USE_KIOSK_MODE 161 | //#define JUCE_STANDALONE_FILTER_WINDOW_USE_KIOSK_MODE 0 162 | #endif 163 | 164 | //============================================================================== 165 | // juce_audio_processors flags: 166 | 167 | #ifndef JUCE_PLUGINHOST_VST 168 | //#define JUCE_PLUGINHOST_VST 0 169 | #endif 170 | 171 | #ifndef JUCE_PLUGINHOST_VST3 172 | //#define JUCE_PLUGINHOST_VST3 0 173 | #endif 174 | 175 | #ifndef JUCE_PLUGINHOST_AU 176 | //#define JUCE_PLUGINHOST_AU 0 177 | #endif 178 | 179 | #ifndef JUCE_PLUGINHOST_LADSPA 180 | //#define JUCE_PLUGINHOST_LADSPA 0 181 | #endif 182 | 183 | #ifndef JUCE_PLUGINHOST_LV2 184 | //#define JUCE_PLUGINHOST_LV2 0 185 | #endif 186 | 187 | #ifndef JUCE_PLUGINHOST_ARA 188 | //#define JUCE_PLUGINHOST_ARA 0 189 | #endif 190 | 191 | #ifndef JUCE_CUSTOM_VST3_SDK 192 | //#define JUCE_CUSTOM_VST3_SDK 0 193 | #endif 194 | 195 | //============================================================================== 196 | // juce_audio_utils flags: 197 | 198 | #ifndef JUCE_USE_CDREADER 199 | //#define JUCE_USE_CDREADER 0 200 | #endif 201 | 202 | #ifndef JUCE_USE_CDBURNER 203 | //#define JUCE_USE_CDBURNER 0 204 | #endif 205 | 206 | //============================================================================== 207 | // juce_core flags: 208 | 209 | #ifndef JUCE_FORCE_DEBUG 210 | //#define JUCE_FORCE_DEBUG 0 211 | #endif 212 | 213 | #ifndef JUCE_LOG_ASSERTIONS 214 | //#define JUCE_LOG_ASSERTIONS 0 215 | #endif 216 | 217 | #ifndef JUCE_CHECK_MEMORY_LEAKS 218 | //#define JUCE_CHECK_MEMORY_LEAKS 1 219 | #endif 220 | 221 | #ifndef JUCE_DONT_AUTOLINK_TO_WIN32_LIBRARIES 222 | //#define JUCE_DONT_AUTOLINK_TO_WIN32_LIBRARIES 0 223 | #endif 224 | 225 | #ifndef JUCE_INCLUDE_ZLIB_CODE 226 | //#define JUCE_INCLUDE_ZLIB_CODE 1 227 | #endif 228 | 229 | #ifndef JUCE_USE_CURL 230 | //#define JUCE_USE_CURL 1 231 | #endif 232 | 233 | #ifndef JUCE_LOAD_CURL_SYMBOLS_LAZILY 234 | //#define JUCE_LOAD_CURL_SYMBOLS_LAZILY 0 235 | #endif 236 | 237 | #ifndef JUCE_CATCH_UNHANDLED_EXCEPTIONS 238 | //#define JUCE_CATCH_UNHANDLED_EXCEPTIONS 0 239 | #endif 240 | 241 | #ifndef JUCE_ALLOW_STATIC_NULL_VARIABLES 242 | //#define JUCE_ALLOW_STATIC_NULL_VARIABLES 0 243 | #endif 244 | 245 | #ifndef JUCE_STRICT_REFCOUNTEDPOINTER 246 | #define JUCE_STRICT_REFCOUNTEDPOINTER 1 247 | #endif 248 | 249 | #ifndef JUCE_ENABLE_ALLOCATION_HOOKS 250 | //#define JUCE_ENABLE_ALLOCATION_HOOKS 0 251 | #endif 252 | 253 | //============================================================================== 254 | // juce_dsp flags: 255 | 256 | #ifndef JUCE_ASSERTION_FIRFILTER 257 | //#define JUCE_ASSERTION_FIRFILTER 1 258 | #endif 259 | 260 | #ifndef JUCE_DSP_USE_INTEL_MKL 261 | //#define JUCE_DSP_USE_INTEL_MKL 0 262 | #endif 263 | 264 | #ifndef JUCE_DSP_USE_SHARED_FFTW 265 | //#define JUCE_DSP_USE_SHARED_FFTW 0 266 | #endif 267 | 268 | #ifndef JUCE_DSP_USE_STATIC_FFTW 269 | //#define JUCE_DSP_USE_STATIC_FFTW 0 270 | #endif 271 | 272 | #ifndef JUCE_DSP_ENABLE_SNAP_TO_ZERO 273 | //#define JUCE_DSP_ENABLE_SNAP_TO_ZERO 1 274 | #endif 275 | 276 | //============================================================================== 277 | // juce_events flags: 278 | 279 | #ifndef JUCE_EXECUTE_APP_SUSPEND_ON_BACKGROUND_TASK 280 | //#define JUCE_EXECUTE_APP_SUSPEND_ON_BACKGROUND_TASK 0 281 | #endif 282 | 283 | //============================================================================== 284 | // juce_graphics flags: 285 | 286 | #ifndef JUCE_USE_COREIMAGE_LOADER 287 | //#define JUCE_USE_COREIMAGE_LOADER 1 288 | #endif 289 | 290 | #ifndef JUCE_USE_DIRECTWRITE 291 | //#define JUCE_USE_DIRECTWRITE 1 292 | #endif 293 | 294 | #ifndef JUCE_DISABLE_COREGRAPHICS_FONT_SMOOTHING 295 | //#define JUCE_DISABLE_COREGRAPHICS_FONT_SMOOTHING 0 296 | #endif 297 | 298 | //============================================================================== 299 | // juce_gui_basics flags: 300 | 301 | #ifndef JUCE_ENABLE_REPAINT_DEBUGGING 302 | //#define JUCE_ENABLE_REPAINT_DEBUGGING 0 303 | #endif 304 | 305 | #ifndef JUCE_USE_XRANDR 306 | //#define JUCE_USE_XRANDR 1 307 | #endif 308 | 309 | #ifndef JUCE_USE_XINERAMA 310 | //#define JUCE_USE_XINERAMA 1 311 | #endif 312 | 313 | #ifndef JUCE_USE_XSHM 314 | //#define JUCE_USE_XSHM 1 315 | #endif 316 | 317 | #ifndef JUCE_USE_XRENDER 318 | //#define JUCE_USE_XRENDER 0 319 | #endif 320 | 321 | #ifndef JUCE_USE_XCURSOR 322 | //#define JUCE_USE_XCURSOR 1 323 | #endif 324 | 325 | #ifndef JUCE_WIN_PER_MONITOR_DPI_AWARE 326 | //#define JUCE_WIN_PER_MONITOR_DPI_AWARE 1 327 | #endif 328 | 329 | //============================================================================== 330 | // juce_gui_extra flags: 331 | 332 | #ifndef JUCE_WEB_BROWSER 333 | //#define JUCE_WEB_BROWSER 1 334 | #endif 335 | 336 | #ifndef JUCE_USE_WIN_WEBVIEW2 337 | //#define JUCE_USE_WIN_WEBVIEW2 0 338 | #endif 339 | 340 | #ifndef JUCE_ENABLE_LIVE_CONSTANT_EDITOR 341 | //#define JUCE_ENABLE_LIVE_CONSTANT_EDITOR 0 342 | #endif 343 | 344 | //============================================================================== 345 | #ifndef JUCE_STANDALONE_APPLICATION 346 | #if defined(JucePlugin_Name) && defined(JucePlugin_Build_Standalone) 347 | #define JUCE_STANDALONE_APPLICATION JucePlugin_Build_Standalone 348 | #else 349 | #define JUCE_STANDALONE_APPLICATION 0 350 | #endif 351 | #endif 352 | -------------------------------------------------------------------------------- /JuceLibraryCode/JuceHeader.h: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | IMPORTANT! This file is auto-generated each time you save your 4 | project - if you alter its contents, your changes may be overwritten! 5 | 6 | This is the header file that your files should include in order to get all the 7 | JUCE library headers. You should avoid including the JUCE headers directly in 8 | your own source files, because that wouldn't pick up the correct configuration 9 | options for your app. 10 | 11 | */ 12 | 13 | #pragma once 14 | 15 | #include "AppConfig.h" 16 | 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | 31 | 32 | #if defined (JUCE_PROJUCER_VERSION) && JUCE_PROJUCER_VERSION < JUCE_VERSION 33 | /** If you've hit this error then the version of the Projucer that was used to generate this project is 34 | older than the version of the JUCE modules being included. To fix this error, re-save your project 35 | using the latest version of the Projucer or, if you aren't using the Projucer to manage your project, 36 | remove the JUCE_PROJUCER_VERSION define. 37 | */ 38 | #error "This project was last saved using an outdated version of the Projucer! Re-save this project with the latest version to fix this error." 39 | #endif 40 | 41 | 42 | #if ! JUCE_DONT_DECLARE_PROJECTINFO 43 | namespace ProjectInfo 44 | { 45 | const char* const projectName = "Sampler"; 46 | const char* const companyName = "DIRT Design"; 47 | const char* const versionString = "0.1.1"; 48 | const int versionNumber = 0x101; 49 | } 50 | #endif 51 | -------------------------------------------------------------------------------- /JuceLibraryCode/JucePluginDefines.h: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | IMPORTANT! This file is auto-generated each time you save your 4 | project - if you alter its contents, your changes may be overwritten! 5 | 6 | */ 7 | 8 | #pragma once 9 | 10 | //============================================================================== 11 | // Audio plugin settings.. 12 | 13 | #ifndef JucePlugin_Build_VST 14 | #define JucePlugin_Build_VST 0 15 | #endif 16 | #ifndef JucePlugin_Build_VST3 17 | #define JucePlugin_Build_VST3 1 18 | #endif 19 | #ifndef JucePlugin_Build_AU 20 | #define JucePlugin_Build_AU 1 21 | #endif 22 | #ifndef JucePlugin_Build_AUv3 23 | #define JucePlugin_Build_AUv3 0 24 | #endif 25 | #ifndef JucePlugin_Build_AAX 26 | #define JucePlugin_Build_AAX 0 27 | #endif 28 | #ifndef JucePlugin_Build_Standalone 29 | #define JucePlugin_Build_Standalone 1 30 | #endif 31 | #ifndef JucePlugin_Build_Unity 32 | #define JucePlugin_Build_Unity 0 33 | #endif 34 | #ifndef JucePlugin_Build_LV2 35 | #define JucePlugin_Build_LV2 0 36 | #endif 37 | #ifndef JucePlugin_Enable_IAA 38 | #define JucePlugin_Enable_IAA 0 39 | #endif 40 | #ifndef JucePlugin_Enable_ARA 41 | #define JucePlugin_Enable_ARA 0 42 | #endif 43 | #ifndef JucePlugin_Name 44 | #define JucePlugin_Name "Sampler" 45 | #endif 46 | #ifndef JucePlugin_Desc 47 | #define JucePlugin_Desc "Sampler" 48 | #endif 49 | #ifndef JucePlugin_Manufacturer 50 | #define JucePlugin_Manufacturer "DIRT Design" 51 | #endif 52 | #ifndef JucePlugin_ManufacturerWebsite 53 | #define JucePlugin_ManufacturerWebsite "https://ccrma.stanford.edu/~braun/" 54 | #endif 55 | #ifndef JucePlugin_ManufacturerEmail 56 | #define JucePlugin_ManufacturerEmail "" 57 | #endif 58 | #ifndef JucePlugin_ManufacturerCode 59 | #define JucePlugin_ManufacturerCode 0x44495254 60 | #endif 61 | #ifndef JucePlugin_PluginCode 62 | #define JucePlugin_PluginCode 0x44303031 63 | #endif 64 | #ifndef JucePlugin_IsSynth 65 | #define JucePlugin_IsSynth 1 66 | #endif 67 | #ifndef JucePlugin_WantsMidiInput 68 | #define JucePlugin_WantsMidiInput 1 69 | #endif 70 | #ifndef JucePlugin_ProducesMidiOutput 71 | #define JucePlugin_ProducesMidiOutput 0 72 | #endif 73 | #ifndef JucePlugin_IsMidiEffect 74 | #define JucePlugin_IsMidiEffect 0 75 | #endif 76 | #ifndef JucePlugin_EditorRequiresKeyboardFocus 77 | #define JucePlugin_EditorRequiresKeyboardFocus 0 78 | #endif 79 | #ifndef JucePlugin_Version 80 | #define JucePlugin_Version 0.1.1 81 | #endif 82 | #ifndef JucePlugin_VersionCode 83 | #define JucePlugin_VersionCode 0x101 84 | #endif 85 | #ifndef JucePlugin_VersionString 86 | #define JucePlugin_VersionString "0.1.1" 87 | #endif 88 | #ifndef JucePlugin_VSTUniqueID 89 | #define JucePlugin_VSTUniqueID JucePlugin_PluginCode 90 | #endif 91 | #ifndef JucePlugin_VSTCategory 92 | #define JucePlugin_VSTCategory kPlugCategSynth 93 | #endif 94 | #ifndef JucePlugin_Vst3Category 95 | #define JucePlugin_Vst3Category "Instrument|Synth" 96 | #endif 97 | #ifndef JucePlugin_AUMainType 98 | #define JucePlugin_AUMainType 'aumu' 99 | #endif 100 | #ifndef JucePlugin_AUSubType 101 | #define JucePlugin_AUSubType JucePlugin_PluginCode 102 | #endif 103 | #ifndef JucePlugin_AUExportPrefix 104 | #define JucePlugin_AUExportPrefix SamplerAU 105 | #endif 106 | #ifndef JucePlugin_AUExportPrefixQuoted 107 | #define JucePlugin_AUExportPrefixQuoted "SamplerAU" 108 | #endif 109 | #ifndef JucePlugin_AUManufacturerCode 110 | #define JucePlugin_AUManufacturerCode JucePlugin_ManufacturerCode 111 | #endif 112 | #ifndef JucePlugin_CFBundleIdentifier 113 | #define JucePlugin_CFBundleIdentifier com.DIRTDESIGN.Sampler 114 | #endif 115 | #ifndef JucePlugin_AAXIdentifier 116 | #define JucePlugin_AAXIdentifier com.DIRTDesign.Sampler 117 | #endif 118 | #ifndef JucePlugin_AAXManufacturerCode 119 | #define JucePlugin_AAXManufacturerCode JucePlugin_ManufacturerCode 120 | #endif 121 | #ifndef JucePlugin_AAXProductId 122 | #define JucePlugin_AAXProductId JucePlugin_PluginCode 123 | #endif 124 | #ifndef JucePlugin_AAXCategory 125 | #define JucePlugin_AAXCategory 2048 126 | #endif 127 | #ifndef JucePlugin_AAXDisableBypass 128 | #define JucePlugin_AAXDisableBypass 0 129 | #endif 130 | #ifndef JucePlugin_AAXDisableMultiMono 131 | #define JucePlugin_AAXDisableMultiMono 0 132 | #endif 133 | #ifndef JucePlugin_IAAType 134 | #define JucePlugin_IAAType 0x61757269 135 | #endif 136 | #ifndef JucePlugin_IAASubType 137 | #define JucePlugin_IAASubType JucePlugin_PluginCode 138 | #endif 139 | #ifndef JucePlugin_IAAName 140 | #define JucePlugin_IAAName "DIRT Design: Sampler" 141 | #endif 142 | #ifndef JucePlugin_VSTNumMidiInputs 143 | #define JucePlugin_VSTNumMidiInputs 16 144 | #endif 145 | #ifndef JucePlugin_VSTNumMidiOutputs 146 | #define JucePlugin_VSTNumMidiOutputs 16 147 | #endif 148 | #ifndef JucePlugin_ARAContentTypes 149 | #define JucePlugin_ARAContentTypes 0 150 | #endif 151 | #ifndef JucePlugin_ARATransformationFlags 152 | #define JucePlugin_ARATransformationFlags 0 153 | #endif 154 | #ifndef JucePlugin_ARAFactoryID 155 | #define JucePlugin_ARAFactoryID "com.DIRTDesign.Sampler.factory" 156 | #endif 157 | #ifndef JucePlugin_ARADocumentArchiveID 158 | #define JucePlugin_ARADocumentArchiveID "com.DIRTDesign.Sampler.aradocumentarchive.0.1.0" 159 | #endif 160 | #ifndef JucePlugin_ARACompatibleArchiveIDs 161 | #define JucePlugin_ARACompatibleArchiveIDs "" 162 | #endif 163 | -------------------------------------------------------------------------------- /JuceLibraryCode/ReadMe.txt: -------------------------------------------------------------------------------- 1 | 2 | Important Note!! 3 | ================ 4 | 5 | The purpose of this folder is to contain files that are auto-generated by the Projucer, 6 | and ALL files in this folder will be mercilessly DELETED and completely re-written whenever 7 | the Projucer saves your project. 8 | 9 | Therefore, it's a bad idea to make any manual changes to the files in here, or to 10 | put any of your own files in here if you don't want to lose them. (Of course you may choose 11 | to add the folder's contents to your version-control system so that you can re-merge your own 12 | modifications after the Projucer has saved its changes). 13 | -------------------------------------------------------------------------------- /JuceLibraryCode/include_juce_audio_basics.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | IMPORTANT! This file is auto-generated each time you save your 4 | project - if you alter its contents, your changes may be overwritten! 5 | 6 | */ 7 | 8 | #include "AppConfig.h" 9 | #include 10 | -------------------------------------------------------------------------------- /JuceLibraryCode/include_juce_audio_basics.mm: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | IMPORTANT! This file is auto-generated each time you save your 4 | project - if you alter its contents, your changes may be overwritten! 5 | 6 | */ 7 | 8 | #include "AppConfig.h" 9 | #include 10 | -------------------------------------------------------------------------------- /JuceLibraryCode/include_juce_audio_devices.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | IMPORTANT! This file is auto-generated each time you save your 4 | project - if you alter its contents, your changes may be overwritten! 5 | 6 | */ 7 | 8 | #include "AppConfig.h" 9 | #include 10 | -------------------------------------------------------------------------------- /JuceLibraryCode/include_juce_audio_devices.mm: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | IMPORTANT! This file is auto-generated each time you save your 4 | project - if you alter its contents, your changes may be overwritten! 5 | 6 | */ 7 | 8 | #include "AppConfig.h" 9 | #include 10 | -------------------------------------------------------------------------------- /JuceLibraryCode/include_juce_audio_formats.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | IMPORTANT! This file is auto-generated each time you save your 4 | project - if you alter its contents, your changes may be overwritten! 5 | 6 | */ 7 | 8 | #include "AppConfig.h" 9 | #include 10 | -------------------------------------------------------------------------------- /JuceLibraryCode/include_juce_audio_formats.mm: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | IMPORTANT! This file is auto-generated each time you save your 4 | project - if you alter its contents, your changes may be overwritten! 5 | 6 | */ 7 | 8 | #include "AppConfig.h" 9 | #include 10 | -------------------------------------------------------------------------------- /JuceLibraryCode/include_juce_audio_plugin_client_AAX.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | IMPORTANT! This file is auto-generated each time you save your 4 | project - if you alter its contents, your changes may be overwritten! 5 | 6 | */ 7 | 8 | #include "AppConfig.h" 9 | #include 10 | -------------------------------------------------------------------------------- /JuceLibraryCode/include_juce_audio_plugin_client_AAX.mm: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | IMPORTANT! This file is auto-generated each time you save your 4 | project - if you alter its contents, your changes may be overwritten! 5 | 6 | */ 7 | 8 | #include "AppConfig.h" 9 | #include 10 | -------------------------------------------------------------------------------- /JuceLibraryCode/include_juce_audio_plugin_client_AAX_utils.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | IMPORTANT! This file is auto-generated each time you save your 4 | project - if you alter its contents, your changes may be overwritten! 5 | 6 | */ 7 | 8 | #include "AppConfig.h" 9 | #include 10 | -------------------------------------------------------------------------------- /JuceLibraryCode/include_juce_audio_plugin_client_ARA.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | IMPORTANT! This file is auto-generated each time you save your 4 | project - if you alter its contents, your changes may be overwritten! 5 | 6 | */ 7 | 8 | #include "AppConfig.h" 9 | #include 10 | -------------------------------------------------------------------------------- /JuceLibraryCode/include_juce_audio_plugin_client_AU_1.mm: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | IMPORTANT! This file is auto-generated each time you save your 4 | project - if you alter its contents, your changes may be overwritten! 5 | 6 | */ 7 | 8 | #include "AppConfig.h" 9 | #include 10 | -------------------------------------------------------------------------------- /JuceLibraryCode/include_juce_audio_plugin_client_AU_2.mm: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | IMPORTANT! This file is auto-generated each time you save your 4 | project - if you alter its contents, your changes may be overwritten! 5 | 6 | */ 7 | 8 | #include "AppConfig.h" 9 | #include 10 | -------------------------------------------------------------------------------- /JuceLibraryCode/include_juce_audio_plugin_client_AUv3.mm: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | IMPORTANT! This file is auto-generated each time you save your 4 | project - if you alter its contents, your changes may be overwritten! 5 | 6 | */ 7 | 8 | #include "AppConfig.h" 9 | #include 10 | -------------------------------------------------------------------------------- /JuceLibraryCode/include_juce_audio_plugin_client_LV2.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | IMPORTANT! This file is auto-generated each time you save your 4 | project - if you alter its contents, your changes may be overwritten! 5 | 6 | */ 7 | 8 | #include "AppConfig.h" 9 | #include 10 | -------------------------------------------------------------------------------- /JuceLibraryCode/include_juce_audio_plugin_client_LV2.mm: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | IMPORTANT! This file is auto-generated each time you save your 4 | project - if you alter its contents, your changes may be overwritten! 5 | 6 | */ 7 | 8 | #include "AppConfig.h" 9 | #include 10 | -------------------------------------------------------------------------------- /JuceLibraryCode/include_juce_audio_plugin_client_Standalone.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | IMPORTANT! This file is auto-generated each time you save your 4 | project - if you alter its contents, your changes may be overwritten! 5 | 6 | */ 7 | 8 | #include "AppConfig.h" 9 | #include 10 | -------------------------------------------------------------------------------- /JuceLibraryCode/include_juce_audio_plugin_client_Unity.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | IMPORTANT! This file is auto-generated each time you save your 4 | project - if you alter its contents, your changes may be overwritten! 5 | 6 | */ 7 | 8 | #include "AppConfig.h" 9 | #include 10 | -------------------------------------------------------------------------------- /JuceLibraryCode/include_juce_audio_plugin_client_VST2.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | IMPORTANT! This file is auto-generated each time you save your 4 | project - if you alter its contents, your changes may be overwritten! 5 | 6 | */ 7 | 8 | #include "AppConfig.h" 9 | #include 10 | -------------------------------------------------------------------------------- /JuceLibraryCode/include_juce_audio_plugin_client_VST2.mm: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | IMPORTANT! This file is auto-generated each time you save your 4 | project - if you alter its contents, your changes may be overwritten! 5 | 6 | */ 7 | 8 | #include "AppConfig.h" 9 | #include 10 | -------------------------------------------------------------------------------- /JuceLibraryCode/include_juce_audio_plugin_client_VST3.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | IMPORTANT! This file is auto-generated each time you save your 4 | project - if you alter its contents, your changes may be overwritten! 5 | 6 | */ 7 | 8 | #include "AppConfig.h" 9 | #include 10 | -------------------------------------------------------------------------------- /JuceLibraryCode/include_juce_audio_plugin_client_VST3.mm: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | IMPORTANT! This file is auto-generated each time you save your 4 | project - if you alter its contents, your changes may be overwritten! 5 | 6 | */ 7 | 8 | #include "AppConfig.h" 9 | #include 10 | -------------------------------------------------------------------------------- /JuceLibraryCode/include_juce_audio_processors.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | IMPORTANT! This file is auto-generated each time you save your 4 | project - if you alter its contents, your changes may be overwritten! 5 | 6 | */ 7 | 8 | #include "AppConfig.h" 9 | #include 10 | -------------------------------------------------------------------------------- /JuceLibraryCode/include_juce_audio_processors.mm: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | IMPORTANT! This file is auto-generated each time you save your 4 | project - if you alter its contents, your changes may be overwritten! 5 | 6 | */ 7 | 8 | #include "AppConfig.h" 9 | #include 10 | -------------------------------------------------------------------------------- /JuceLibraryCode/include_juce_audio_processors_ara.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | IMPORTANT! This file is auto-generated each time you save your 4 | project - if you alter its contents, your changes may be overwritten! 5 | 6 | */ 7 | 8 | #include "AppConfig.h" 9 | #include 10 | -------------------------------------------------------------------------------- /JuceLibraryCode/include_juce_audio_processors_lv2_libs.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | IMPORTANT! This file is auto-generated each time you save your 4 | project - if you alter its contents, your changes may be overwritten! 5 | 6 | */ 7 | 8 | #include "AppConfig.h" 9 | #include 10 | -------------------------------------------------------------------------------- /JuceLibraryCode/include_juce_audio_utils.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | IMPORTANT! This file is auto-generated each time you save your 4 | project - if you alter its contents, your changes may be overwritten! 5 | 6 | */ 7 | 8 | #include "AppConfig.h" 9 | #include 10 | -------------------------------------------------------------------------------- /JuceLibraryCode/include_juce_audio_utils.mm: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | IMPORTANT! This file is auto-generated each time you save your 4 | project - if you alter its contents, your changes may be overwritten! 5 | 6 | */ 7 | 8 | #include "AppConfig.h" 9 | #include 10 | -------------------------------------------------------------------------------- /JuceLibraryCode/include_juce_core.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | IMPORTANT! This file is auto-generated each time you save your 4 | project - if you alter its contents, your changes may be overwritten! 5 | 6 | */ 7 | 8 | #include "AppConfig.h" 9 | #include 10 | -------------------------------------------------------------------------------- /JuceLibraryCode/include_juce_core.mm: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | IMPORTANT! This file is auto-generated each time you save your 4 | project - if you alter its contents, your changes may be overwritten! 5 | 6 | */ 7 | 8 | #include "AppConfig.h" 9 | #include 10 | -------------------------------------------------------------------------------- /JuceLibraryCode/include_juce_data_structures.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | IMPORTANT! This file is auto-generated each time you save your 4 | project - if you alter its contents, your changes may be overwritten! 5 | 6 | */ 7 | 8 | #include "AppConfig.h" 9 | #include 10 | -------------------------------------------------------------------------------- /JuceLibraryCode/include_juce_data_structures.mm: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | IMPORTANT! This file is auto-generated each time you save your 4 | project - if you alter its contents, your changes may be overwritten! 5 | 6 | */ 7 | 8 | #include "AppConfig.h" 9 | #include 10 | -------------------------------------------------------------------------------- /JuceLibraryCode/include_juce_dsp.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | IMPORTANT! This file is auto-generated each time you save your 4 | project - if you alter its contents, your changes may be overwritten! 5 | 6 | */ 7 | 8 | #include "AppConfig.h" 9 | #include 10 | -------------------------------------------------------------------------------- /JuceLibraryCode/include_juce_dsp.mm: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | IMPORTANT! This file is auto-generated each time you save your 4 | project - if you alter its contents, your changes may be overwritten! 5 | 6 | */ 7 | 8 | #include "AppConfig.h" 9 | #include 10 | -------------------------------------------------------------------------------- /JuceLibraryCode/include_juce_events.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | IMPORTANT! This file is auto-generated each time you save your 4 | project - if you alter its contents, your changes may be overwritten! 5 | 6 | */ 7 | 8 | #include "AppConfig.h" 9 | #include 10 | -------------------------------------------------------------------------------- /JuceLibraryCode/include_juce_events.mm: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | IMPORTANT! This file is auto-generated each time you save your 4 | project - if you alter its contents, your changes may be overwritten! 5 | 6 | */ 7 | 8 | #include "AppConfig.h" 9 | #include 10 | -------------------------------------------------------------------------------- /JuceLibraryCode/include_juce_graphics.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | IMPORTANT! This file is auto-generated each time you save your 4 | project - if you alter its contents, your changes may be overwritten! 5 | 6 | */ 7 | 8 | #include "AppConfig.h" 9 | #include 10 | -------------------------------------------------------------------------------- /JuceLibraryCode/include_juce_graphics.mm: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | IMPORTANT! This file is auto-generated each time you save your 4 | project - if you alter its contents, your changes may be overwritten! 5 | 6 | */ 7 | 8 | #include "AppConfig.h" 9 | #include 10 | -------------------------------------------------------------------------------- /JuceLibraryCode/include_juce_gui_basics.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | IMPORTANT! This file is auto-generated each time you save your 4 | project - if you alter its contents, your changes may be overwritten! 5 | 6 | */ 7 | 8 | #include "AppConfig.h" 9 | #include 10 | -------------------------------------------------------------------------------- /JuceLibraryCode/include_juce_gui_basics.mm: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | IMPORTANT! This file is auto-generated each time you save your 4 | project - if you alter its contents, your changes may be overwritten! 5 | 6 | */ 7 | 8 | #include "AppConfig.h" 9 | #include 10 | -------------------------------------------------------------------------------- /JuceLibraryCode/include_juce_gui_extra.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | IMPORTANT! This file is auto-generated each time you save your 4 | project - if you alter its contents, your changes may be overwritten! 5 | 6 | */ 7 | 8 | #include "AppConfig.h" 9 | #include 10 | -------------------------------------------------------------------------------- /JuceLibraryCode/include_juce_gui_extra.mm: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | IMPORTANT! This file is auto-generated each time you save your 4 | project - if you alter its contents, your changes may be overwritten! 5 | 6 | */ 7 | 8 | #include "AppConfig.h" 9 | #include 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 David Braun 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Sampler 2 | 3 | This is a **work-in-progress** improvement of JUCE's [SamplerPluginDemo](https://github.com/juce-framework/JUCE/blob/28414a6af81ccb8bca32b1854cb35aca50d905da/examples/Plugins/SamplerPluginDemo.h). 4 | 5 | ## License 6 | 7 | All files that copy or modify code from the original demo include this license disclaimer: 8 | 9 | ``` 10 | ============================================================================== 11 | This file is part of the JUCE examples. 12 | Copyright (c) 2020 - Raw Material Software Limited 13 | The code included in this file is provided under the terms of the ISC license 14 | http://www.isc.org/downloads/software-support-policy/isc-license. Permission 15 | To use, copy, modify, and/or distribute this software for any purpose with or 16 | without fee is hereby granted provided that the above copyright notice and 17 | this permission notice appear in all copies. 18 | THE SOFTWARE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, 19 | WHETHER EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR 20 | PURPOSE, ARE DISCLAIMED. 21 | ============================================================================== 22 | ``` 23 | 24 | All other files are distributed under the `LICENSE` next to this `README`. -------------------------------------------------------------------------------- /Sampler.jucer: -------------------------------------------------------------------------------- 1 | 2 | 3 | 11 | 12 | 13 | 14 | 15 | 16 | 18 | 20 | 22 | 23 | 24 | 26 | 28 | 30 | 32 | 34 | 36 | 38 | 39 | 41 | 42 | 43 | 44 | 46 | 47 | 49 | 50 | 52 | 54 | 56 | 57 | 59 | 61 | 63 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | -------------------------------------------------------------------------------- /Source/CommandFifo.h: -------------------------------------------------------------------------------- 1 | /* 2 | ============================================================================== 3 | This file is part of the JUCE examples. 4 | Copyright (c) 2020 - Raw Material Software Limited 5 | The code included in this file is provided under the terms of the ISC license 6 | http://www.isc.org/downloads/software-support-policy/isc-license. Permission 7 | To use, copy, modify, and/or distribute this software for any purpose with or 8 | without fee is hereby granted provided that the above copyright notice and 9 | this permission notice appear in all copies. 10 | THE SOFTWARE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, 11 | WHETHER EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR 12 | PURPOSE, ARE DISCLAIMED. 13 | ============================================================================== 14 | */ 15 | 16 | #pragma once 17 | 18 | template 19 | class CommandFifo final 20 | { 21 | public: 22 | explicit CommandFifo(int size) 23 | : buffer((size_t)size), 24 | abstractFifo(size) 25 | {} 26 | 27 | CommandFifo() 28 | : CommandFifo(1024) 29 | {} 30 | 31 | template 32 | void push(Item&& item) noexcept 33 | { 34 | auto command = makeCommand(std::forward(item)); 35 | 36 | abstractFifo.write(1).forEach([&](int index) 37 | { 38 | buffer[size_t(index)] = std::move(command); 39 | }); 40 | } 41 | 42 | void call(Proc& proc) noexcept 43 | { 44 | abstractFifo.read(abstractFifo.getNumReady()).forEach([&](int index) 45 | { 46 | buffer[size_t(index)]->run(proc); 47 | }); 48 | } 49 | 50 | private: 51 | template 52 | static std::unique_ptr> makeCommand(Func&& func) 53 | { 54 | using Decayed = std::decay_t; 55 | return std::make_unique>(std::forward(func)); 56 | } 57 | 58 | std::vector>> buffer; 59 | AbstractFifo abstractFifo; 60 | }; 61 | -------------------------------------------------------------------------------- /Source/Components/LoopPointMarker.h: -------------------------------------------------------------------------------- 1 | /* 2 | ============================================================================== 3 | This file is part of the JUCE examples. 4 | Copyright (c) 2020 - Raw Material Software Limited 5 | The code included in this file is provided under the terms of the ISC license 6 | http://www.isc.org/downloads/software-support-policy/isc-license. Permission 7 | To use, copy, modify, and/or distribute this software for any purpose with or 8 | without fee is hereby granted provided that the above copyright notice and 9 | this permission notice appear in all copies. 10 | THE SOFTWARE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, 11 | WHETHER EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR 12 | PURPOSE, ARE DISCLAIMED. 13 | ============================================================================== 14 | */ 15 | 16 | #pragma once 17 | 18 | class LoopPointMarker : public juce::Component 19 | { 20 | public: 21 | using MouseCallback = std::function; 22 | 23 | LoopPointMarker(String marker, 24 | MouseCallback onMouseDownIn, 25 | MouseCallback onMouseDragIn, 26 | MouseCallback onMouseUpIn) 27 | : text(std::move(marker)), 28 | onMouseDown(std::move(onMouseDownIn)), 29 | onMouseDrag(std::move(onMouseDragIn)), 30 | onMouseUp(std::move(onMouseUpIn)) 31 | { 32 | setMouseCursor(MouseCursor::LeftRightResizeCursor); 33 | } 34 | 35 | private: 36 | void resized() override 37 | { 38 | auto height = 20; 39 | auto triHeight = 6; 40 | 41 | auto bounds = getLocalBounds(); 42 | Path newPath; 43 | newPath.addRectangle(bounds.removeFromBottom(height)); 44 | 45 | newPath.startNewSubPath(bounds.getBottomLeft().toFloat()); 46 | newPath.lineTo(bounds.getBottomRight().toFloat()); 47 | juce::Point apex(static_cast (bounds.getX() + (bounds.getWidth() / 2)), 48 | static_cast (bounds.getBottom() - triHeight)); 49 | newPath.lineTo(apex); 50 | newPath.closeSubPath(); 51 | 52 | newPath.addLineSegment(Line(apex, juce::Point(apex.getX(), 0)), 1); 53 | 54 | path = newPath; 55 | } 56 | 57 | void paint(Graphics& g) override 58 | { 59 | g.setColour(juce::Colours::deepskyblue); 60 | g.fillPath(path); 61 | 62 | auto height = 20; 63 | g.setColour(juce::Colours::white); 64 | g.drawText(text, getLocalBounds().removeFromBottom(height), Justification::centred); 65 | } 66 | 67 | bool hitTest(int x, int y) override 68 | { 69 | return path.contains((float)x, (float)y); 70 | } 71 | 72 | void mouseDown(const MouseEvent& e) override 73 | { 74 | onMouseDown(*this, e); 75 | } 76 | 77 | void mouseDrag(const MouseEvent& e) override 78 | { 79 | onMouseDrag(*this, e); 80 | } 81 | 82 | void mouseUp(const MouseEvent& e) override 83 | { 84 | onMouseUp(*this, e); 85 | } 86 | 87 | String text; 88 | Path path; 89 | MouseCallback onMouseDown; 90 | MouseCallback onMouseDrag; 91 | MouseCallback onMouseUp; 92 | }; 93 | -------------------------------------------------------------------------------- /Source/Components/LoopPointsOverlay.h: -------------------------------------------------------------------------------- 1 | /* 2 | ============================================================================== 3 | This file is part of the JUCE examples. 4 | Copyright (c) 2020 - Raw Material Software Limited 5 | The code included in this file is provided under the terms of the ISC license 6 | http://www.isc.org/downloads/software-support-policy/isc-license. Permission 7 | To use, copy, modify, and/or distribute this software for any purpose with or 8 | without fee is hereby granted provided that the above copyright notice and 9 | this permission notice appear in all copies. 10 | THE SOFTWARE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, 11 | WHETHER EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR 12 | PURPOSE, ARE DISCLAIMED. 13 | ============================================================================== 14 | */ 15 | 16 | #pragma once 17 | 18 | #include "LoopPointMarker.h" 19 | 20 | class LoopPointsOverlay : public juce::Component, 21 | private DataModel::Listener, 22 | private VisibleRangeDataModel::Listener 23 | { 24 | public: 25 | LoopPointsOverlay(const DataModel& dModel, 26 | const VisibleRangeDataModel& vModel, 27 | UndoManager& undoManagerIn) 28 | : dataModel(dModel), 29 | visibleRange(vModel), 30 | beginMarker("B", 31 | [this](LoopPointMarker& m, const MouseEvent& e) { this->loopPointMouseDown(m, e); }, 32 | [this](LoopPointMarker& m, const MouseEvent& e) { this->loopPointDragged(m, e); }, 33 | [this](LoopPointMarker& m, const MouseEvent& e) { this->loopPointMouseUp(m, e); }), 34 | endMarker("E", 35 | [this](LoopPointMarker& m, const MouseEvent& e) { this->loopPointMouseDown(m, e); }, 36 | [this](LoopPointMarker& m, const MouseEvent& e) { this->loopPointDragged(m, e); }, 37 | [this](LoopPointMarker& m, const MouseEvent& e) { this->loopPointMouseUp(m, e); }), 38 | undoManager(&undoManagerIn) 39 | { 40 | dataModel.addListener(*this); 41 | visibleRange.addListener(*this); 42 | 43 | for (auto ptr : { &beginMarker, &endMarker }) 44 | addAndMakeVisible(ptr); 45 | } 46 | 47 | private: 48 | void resized() override 49 | { 50 | positionLoopPointMarkers(); 51 | } 52 | 53 | void loopPointMouseDown(LoopPointMarker&, const MouseEvent&) 54 | { 55 | loopPointsOnMouseDown = dataModel.getLoopPointsSeconds(); 56 | undoManager->beginNewTransaction(); 57 | } 58 | 59 | void loopPointDragged(LoopPointMarker& marker, const MouseEvent& e) 60 | { 61 | auto x = xPositionToTime(e.getEventRelativeTo(this).position.x); 62 | const Range newLoopRange(&marker == &beginMarker ? x : loopPointsOnMouseDown.getStart(), 63 | &marker == &endMarker ? x : loopPointsOnMouseDown.getEnd()); 64 | 65 | dataModel.setLoopPointsSeconds(newLoopRange, undoManager); 66 | } 67 | 68 | void loopPointMouseUp(LoopPointMarker& marker, const MouseEvent& e) 69 | { 70 | auto x = xPositionToTime(e.getEventRelativeTo(this).position.x); 71 | const Range newLoopRange(&marker == &beginMarker ? x : loopPointsOnMouseDown.getStart(), 72 | &marker == &endMarker ? x : loopPointsOnMouseDown.getEnd()); 73 | 74 | dataModel.setLoopPointsSeconds(newLoopRange, undoManager); 75 | } 76 | 77 | void loopPointsSecondsChanged(Range) override 78 | { 79 | positionLoopPointMarkers(); 80 | } 81 | 82 | void visibleRangeChanged(Range) override 83 | { 84 | positionLoopPointMarkers(); 85 | } 86 | 87 | double timeToXPosition(double time) const 88 | { 89 | return (time - visibleRange.getVisibleRange().getStart()) * getWidth() 90 | / visibleRange.getVisibleRange().getLength(); 91 | } 92 | 93 | double xPositionToTime(double xPosition) const 94 | { 95 | return ((xPosition * visibleRange.getVisibleRange().getLength()) / getWidth()) 96 | + visibleRange.getVisibleRange().getStart(); 97 | } 98 | 99 | void positionLoopPointMarkers() 100 | { 101 | auto halfMarkerWidth = 7; 102 | 103 | for (auto tup : { std::make_tuple(&beginMarker, dataModel.getLoopPointsSeconds().getStart()), 104 | std::make_tuple(&endMarker, dataModel.getLoopPointsSeconds().getEnd()) }) 105 | { 106 | auto ptr = std::get<0>(tup); 107 | auto time = std::get<1>(tup); 108 | ptr->setSize(halfMarkerWidth * 2, getHeight()); 109 | ptr->setTopLeftPosition(roundToInt(timeToXPosition(time) - halfMarkerWidth), 0); 110 | } 111 | } 112 | 113 | DataModel dataModel; 114 | VisibleRangeDataModel visibleRange; 115 | Range loopPointsOnMouseDown; 116 | LoopPointMarker beginMarker, endMarker; 117 | UndoManager* undoManager; 118 | }; 119 | -------------------------------------------------------------------------------- /Source/Components/MPELegacySettingsComponent.h: -------------------------------------------------------------------------------- 1 | /* 2 | ============================================================================== 3 | This file is part of the JUCE examples. 4 | Copyright (c) 2020 - Raw Material Software Limited 5 | The code included in this file is provided under the terms of the ISC license 6 | http://www.isc.org/downloads/software-support-policy/isc-license. Permission 7 | To use, copy, modify, and/or distribute this software for any purpose with or 8 | without fee is hereby granted provided that the above copyright notice and 9 | this permission notice appear in all copies. 10 | THE SOFTWARE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, 11 | WHETHER EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR 12 | PURPOSE, ARE DISCLAIMED. 13 | ============================================================================== 14 | */ 15 | 16 | #pragma once 17 | 18 | #include "../DataModels/VisibleRangeDataModel.h" 19 | 20 | class MPELegacySettingsComponent final : public juce::Component, 21 | private MPESettingsDataModel::Listener 22 | { 23 | public: 24 | explicit MPELegacySettingsComponent(const MPESettingsDataModel& model, 25 | UndoManager& um) 26 | : dataModel(model), 27 | undoManager(&um) 28 | { 29 | dataModel.addListener(*this); 30 | 31 | initialiseComboBoxWithConsecutiveIntegers(*this, legacyStartChannel, legacyStartChannelLabel, 1, 16, 1); 32 | initialiseComboBoxWithConsecutiveIntegers(*this, legacyEndChannel, legacyEndChannelLabel, 1, 16, 16); 33 | initialiseComboBoxWithConsecutiveIntegers(*this, legacyPitchbendRange, legacyPitchbendRangeLabel, 0, 96, 2); 34 | 35 | legacyStartChannel.onChange = [this] 36 | { 37 | if (isLegacyModeValid()) 38 | { 39 | undoManager->beginNewTransaction(); 40 | dataModel.setLegacyFirstChannel(getFirstChannel(), undoManager); 41 | } 42 | }; 43 | 44 | legacyEndChannel.onChange = [this] 45 | { 46 | if (isLegacyModeValid()) 47 | { 48 | undoManager->beginNewTransaction(); 49 | dataModel.setLegacyLastChannel(getLastChannel(), undoManager); 50 | } 51 | }; 52 | 53 | legacyPitchbendRange.onChange = [this] 54 | { 55 | if (isLegacyModeValid()) 56 | { 57 | undoManager->beginNewTransaction(); 58 | dataModel.setLegacyPitchbendRange(legacyPitchbendRange.getText().getIntValue(), undoManager); 59 | } 60 | }; 61 | } 62 | 63 | int getMinHeight() const 64 | { 65 | return (controlHeight * 3) + (controlSeparation * 2); 66 | } 67 | 68 | private: 69 | void resized() override 70 | { 71 | juce::Rectangle r(proportionOfWidth(0.65f), 0, proportionOfWidth(0.25f), getHeight()); 72 | 73 | for (auto& comboBox : { &legacyStartChannel, &legacyEndChannel, &legacyPitchbendRange }) 74 | { 75 | comboBox->setBounds(r.removeFromTop(controlHeight)); 76 | r.removeFromTop(controlSeparation); 77 | } 78 | } 79 | 80 | bool isLegacyModeValid() 81 | { 82 | if (!areLegacyModeParametersValid()) 83 | { 84 | handleInvalidLegacyModeParameters(); 85 | return false; 86 | } 87 | 88 | return true; 89 | } 90 | 91 | void legacyFirstChannelChanged(int value) override 92 | { 93 | legacyStartChannel.setSelectedId(value, dontSendNotification); 94 | } 95 | 96 | void legacyLastChannelChanged(int value) override 97 | { 98 | legacyEndChannel.setSelectedId(value, dontSendNotification); 99 | } 100 | 101 | void legacyPitchbendRangeChanged(int value) override 102 | { 103 | legacyPitchbendRange.setSelectedId(value + 1, dontSendNotification); 104 | } 105 | 106 | int getFirstChannel() const 107 | { 108 | return legacyStartChannel.getText().getIntValue(); 109 | } 110 | 111 | int getLastChannel() const 112 | { 113 | return legacyEndChannel.getText().getIntValue(); 114 | } 115 | 116 | bool areLegacyModeParametersValid() const 117 | { 118 | return getFirstChannel() <= getLastChannel(); 119 | } 120 | 121 | void handleInvalidLegacyModeParameters() 122 | { 123 | auto options = juce::MessageBoxOptions::makeOptionsOk (AlertWindow::WarningIcon, 124 | "Invalid legacy mode channel layout", 125 | "Cannot set legacy mode start/end channel:\n" 126 | "The end channel must not be less than the start channel!", 127 | "Got it"); 128 | messageBox = AlertWindow::showScopedAsync (options, nullptr); 129 | } 130 | 131 | MPESettingsDataModel dataModel; 132 | 133 | ComboBox legacyStartChannel, legacyEndChannel, legacyPitchbendRange; 134 | 135 | Label legacyStartChannelLabel{ {}, "First channel" }, 136 | legacyEndChannelLabel{ {}, "Last channel" }, 137 | legacyPitchbendRangeLabel{ {}, "Pitchbend range (semitones)" }; 138 | 139 | UndoManager* undoManager; 140 | juce::ScopedMessageBox messageBox; 141 | }; 142 | -------------------------------------------------------------------------------- /Source/Components/MPENewSettingsComponent.h: -------------------------------------------------------------------------------- 1 | /* 2 | ============================================================================== 3 | This file is part of the JUCE examples. 4 | Copyright (c) 2020 - Raw Material Software Limited 5 | The code included in this file is provided under the terms of the ISC license 6 | http://www.isc.org/downloads/software-support-policy/isc-license. Permission 7 | To use, copy, modify, and/or distribute this software for any purpose with or 8 | without fee is hereby granted provided that the above copyright notice and 9 | this permission notice appear in all copies. 10 | THE SOFTWARE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, 11 | WHETHER EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR 12 | PURPOSE, ARE DISCLAIMED. 13 | ============================================================================== 14 | */ 15 | 16 | #pragma once 17 | 18 | #include "../DataModels/VisibleRangeDataModel.h" 19 | 20 | class MPENewSettingsComponent final : public juce::Component, 21 | private MPESettingsDataModel::Listener 22 | { 23 | public: 24 | MPENewSettingsComponent(const MPESettingsDataModel& model, 25 | UndoManager& um) 26 | : dataModel(model), 27 | undoManager(&um) 28 | { 29 | dataModel.addListener(*this); 30 | 31 | addAndMakeVisible(isLowerZoneButton); 32 | isLowerZoneButton.setToggleState(true, NotificationType::dontSendNotification); 33 | 34 | initialiseComboBoxWithConsecutiveIntegers(*this, memberChannels, memberChannelsLabel, 0, 16, 15); 35 | initialiseComboBoxWithConsecutiveIntegers(*this, masterPitchbendRange, masterPitchbendRangeLabel, 0, 96, 2); 36 | initialiseComboBoxWithConsecutiveIntegers(*this, notePitchbendRange, notePitchbendRangeLabel, 0, 96, 48); 37 | 38 | for (auto& button : { &setZoneButton, &clearAllZonesButton }) 39 | addAndMakeVisible(button); 40 | 41 | setZoneButton.onClick = [this] 42 | { 43 | auto isLowerZone = isLowerZoneButton.getToggleState(); 44 | auto numMemberChannels = memberChannels.getText().getIntValue(); 45 | auto perNotePb = notePitchbendRange.getText().getIntValue(); 46 | auto masterPb = masterPitchbendRange.getText().getIntValue(); 47 | 48 | if (isLowerZone) 49 | zoneLayout.setLowerZone(numMemberChannels, perNotePb, masterPb); 50 | else 51 | zoneLayout.setUpperZone(numMemberChannels, perNotePb, masterPb); 52 | 53 | undoManager->beginNewTransaction(); 54 | dataModel.setMPEZoneLayout(zoneLayout, undoManager); 55 | }; 56 | 57 | clearAllZonesButton.onClick = [this] 58 | { 59 | zoneLayout.clearAllZones(); 60 | undoManager->beginNewTransaction(); 61 | dataModel.setMPEZoneLayout(zoneLayout, undoManager); 62 | }; 63 | } 64 | 65 | int getMinHeight() const 66 | { 67 | return (controlHeight * 6) + (controlSeparation * 6); 68 | } 69 | 70 | private: 71 | void resized() override 72 | { 73 | juce::Rectangle r(proportionOfWidth(0.65f), 0, proportionOfWidth(0.25f), getHeight()); 74 | 75 | isLowerZoneButton.setBounds(r.removeFromTop(controlHeight)); 76 | r.removeFromTop(controlSeparation); 77 | 78 | for (auto& comboBox : { &memberChannels, &masterPitchbendRange, ¬ePitchbendRange }) 79 | { 80 | comboBox->setBounds(r.removeFromTop(controlHeight)); 81 | r.removeFromTop(controlSeparation); 82 | } 83 | 84 | r.removeFromTop(controlSeparation); 85 | 86 | auto buttonLeft = proportionOfWidth(0.5f); 87 | 88 | setZoneButton.setBounds(r.removeFromTop(controlHeight).withLeft(buttonLeft)); 89 | r.removeFromTop(controlSeparation); 90 | clearAllZonesButton.setBounds(r.removeFromTop(controlHeight).withLeft(buttonLeft)); 91 | } 92 | 93 | void mpeZoneLayoutChanged(const MPEZoneLayout& value) override 94 | { 95 | zoneLayout = value; 96 | } 97 | 98 | MPESettingsDataModel dataModel; 99 | MPEZoneLayout zoneLayout; 100 | 101 | ComboBox memberChannels, masterPitchbendRange, notePitchbendRange; 102 | 103 | ToggleButton isLowerZoneButton{ "Lower zone" }; 104 | 105 | Label memberChannelsLabel{ {}, "Nr. of member channels" }, 106 | masterPitchbendRangeLabel{ {}, "Master pitchbend range (semitones)" }, 107 | notePitchbendRangeLabel{ {}, "Note pitchbend range (semitones)" }; 108 | 109 | TextButton setZoneButton{ "Set zone" }, 110 | clearAllZonesButton{ "Clear all zones" }; 111 | 112 | UndoManager* undoManager; 113 | }; 114 | -------------------------------------------------------------------------------- /Source/Components/MPESettingsComponent.h: -------------------------------------------------------------------------------- 1 | /* 2 | ============================================================================== 3 | This file is part of the JUCE examples. 4 | Copyright (c) 2020 - Raw Material Software Limited 5 | The code included in this file is provided under the terms of the ISC license 6 | http://www.isc.org/downloads/software-support-policy/isc-license. Permission 7 | To use, copy, modify, and/or distribute this software for any purpose with or 8 | without fee is hereby granted provided that the above copyright notice and 9 | this permission notice appear in all copies. 10 | THE SOFTWARE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, 11 | WHETHER EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR 12 | PURPOSE, ARE DISCLAIMED. 13 | ============================================================================== 14 | */ 15 | 16 | #pragma once 17 | 18 | #include "MPELegacySettingsComponent.h" 19 | #include "MPENewSettingsComponent.h" 20 | 21 | //============================================================================== 22 | class MPESettingsComponent final : public juce::Component, 23 | private MPESettingsDataModel::Listener 24 | { 25 | public: 26 | MPESettingsComponent(const MPESettingsDataModel& model, 27 | UndoManager& um) 28 | : dataModel(model), 29 | legacySettings(dataModel, um), 30 | newSettings(dataModel, um), 31 | undoManager(&um) 32 | { 33 | dataModel.addListener(*this); 34 | 35 | addAndMakeVisible(newSettings); 36 | addChildComponent(legacySettings); 37 | 38 | initialiseComboBoxWithConsecutiveIntegers(*this, numberOfVoices, numberOfVoicesLabel, 1, 20, 15); 39 | numberOfVoices.onChange = [this] 40 | { 41 | undoManager->beginNewTransaction(); 42 | dataModel.setSynthVoices(numberOfVoices.getText().getIntValue(), undoManager); 43 | }; 44 | 45 | for (auto& button : { &legacyModeEnabledToggle, &voiceStealingEnabledToggle }) 46 | { 47 | addAndMakeVisible(button); 48 | } 49 | 50 | legacyModeEnabledToggle.onClick = [this] 51 | { 52 | undoManager->beginNewTransaction(); 53 | dataModel.setLegacyModeEnabled(legacyModeEnabledToggle.getToggleState(), undoManager); 54 | }; 55 | 56 | voiceStealingEnabledToggle.onClick = [this] 57 | { 58 | undoManager->beginNewTransaction(); 59 | dataModel.setVoiceStealingEnabled(voiceStealingEnabledToggle.getToggleState(), undoManager); 60 | }; 61 | } 62 | 63 | private: 64 | void resized() override 65 | { 66 | auto topHeight = jmax(legacySettings.getMinHeight(), newSettings.getMinHeight()); 67 | auto r = getLocalBounds(); 68 | r.removeFromTop(15); 69 | auto top = r.removeFromTop(topHeight); 70 | legacySettings.setBounds(top); 71 | newSettings.setBounds(top); 72 | 73 | r.removeFromLeft(proportionOfWidth(0.65f)); 74 | r = r.removeFromLeft(proportionOfWidth(0.25f)); 75 | 76 | auto toggleLeft = proportionOfWidth(0.25f); 77 | 78 | legacyModeEnabledToggle.setBounds(r.removeFromTop(controlHeight).withLeft(toggleLeft)); 79 | r.removeFromTop(controlSeparation); 80 | voiceStealingEnabledToggle.setBounds(r.removeFromTop(controlHeight).withLeft(toggleLeft)); 81 | r.removeFromTop(controlSeparation); 82 | numberOfVoices.setBounds(r.removeFromTop(controlHeight)); 83 | } 84 | 85 | void legacyModeEnabledChanged(bool value) override 86 | { 87 | legacySettings.setVisible(value); 88 | newSettings.setVisible(!value); 89 | legacyModeEnabledToggle.setToggleState(value, dontSendNotification); 90 | } 91 | 92 | void voiceStealingEnabledChanged(bool value) override 93 | { 94 | voiceStealingEnabledToggle.setToggleState(value, dontSendNotification); 95 | } 96 | 97 | void synthVoicesChanged(int value) override 98 | { 99 | numberOfVoices.setSelectedId(value, dontSendNotification); 100 | } 101 | 102 | MPESettingsDataModel dataModel; 103 | MPELegacySettingsComponent legacySettings; 104 | MPENewSettingsComponent newSettings; 105 | 106 | ToggleButton legacyModeEnabledToggle{ "Enable Legacy Mode" }, 107 | voiceStealingEnabledToggle{ "Enable synth voice stealing" }; 108 | 109 | ComboBox numberOfVoices; 110 | Label numberOfVoicesLabel{ {}, "Number of synth voices" }; 111 | 112 | UndoManager* undoManager; 113 | }; 114 | -------------------------------------------------------------------------------- /Source/Components/MainSamplerView.h: -------------------------------------------------------------------------------- 1 | /* 2 | ============================================================================== 3 | This file is part of the JUCE examples. 4 | Copyright (c) 2020 - Raw Material Software Limited 5 | The code included in this file is provided under the terms of the ISC license 6 | http://www.isc.org/downloads/software-support-policy/isc-license. Permission 7 | To use, copy, modify, and/or distribute this software for any purpose with or 8 | without fee is hereby granted provided that the above copyright notice and 9 | this permission notice appear in all copies. 10 | THE SOFTWARE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, 11 | WHETHER EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR 12 | PURPOSE, ARE DISCLAIMED. 13 | ============================================================================== 14 | */ 15 | 16 | #pragma once 17 | 18 | #include "PlaybackPositionOverlay.h" 19 | #include "WaveformEditor.h" 20 | 21 | typedef juce::AudioProcessorValueTreeState::SliderAttachment SliderAttachment; 22 | typedef juce::AudioProcessorValueTreeState::ButtonAttachment ButtonAttachment; 23 | 24 | 25 | class MainSamplerView : public juce::Component, 26 | private DataModel::Listener, 27 | private ChangeListener, 28 | public ValueTree::Listener 29 | { 30 | public: 31 | MainSamplerView(const DataModel& model, 32 | PlaybackPositionOverlay::Provider provider, 33 | UndoManager& um, 34 | AudioProcessorValueTreeState& vts) 35 | : dataModel(model), 36 | waveformEditor(dataModel, std::move(provider), um), 37 | undoManager(um), 38 | valueTreeState(vts) 39 | { 40 | valueTreeState.state.addListener(this); 41 | 42 | dataModel.addListener(*this); 43 | 44 | int WIDTH = 48; 45 | 46 | addAndMakeVisible(waveformEditor); 47 | addAndMakeVisible(loadNewSampleButton); 48 | addAndMakeVisible(undoButton); 49 | addAndMakeVisible(redoButton); 50 | 51 | ampEnvAttackSlider.setSliderStyle(Slider::LinearVertical); 52 | ampEnvAttackSlider.setTextBoxStyle(Slider::NoTextBox, false, 40, 20); 53 | ampEnvAttackSlider.setBounds(0*WIDTH, 120, 40, 136); 54 | ampEnvAttackSlider.setRange(0.0f, 500.0f); 55 | ampEnvAttackSlider.setValue(0.0f); 56 | addAndMakeVisible(ampEnvAttackSlider); 57 | 58 | ampEnvDecaySlider.setSliderStyle(Slider::LinearVertical); 59 | ampEnvDecaySlider.setTextBoxStyle(Slider::NoTextBox, false, 40, 20); 60 | ampEnvDecaySlider.setBounds(1 * WIDTH, 120, 40, 136); 61 | ampEnvDecaySlider.setRange(0.0f, 1000.0f); 62 | ampEnvDecaySlider.setValue(0.0f); 63 | addAndMakeVisible(ampEnvDecaySlider); 64 | 65 | ampEnvSustainSlider.setSliderStyle(Slider::LinearVertical); 66 | ampEnvSustainSlider.setTextBoxStyle(Slider::NoTextBox, false, 40, 20); 67 | ampEnvSustainSlider.setBounds(2 * WIDTH, 120, 40, 136); 68 | ampEnvSustainSlider.setRange(0.0f, 1.0f); 69 | ampEnvSustainSlider.setValue(1.0f); 70 | addAndMakeVisible(ampEnvSustainSlider); 71 | 72 | ampEnvReleaseSlider.setSliderStyle(Slider::LinearVertical); 73 | ampEnvReleaseSlider.setTextBoxStyle(Slider::NoTextBox, false, 40, 20); 74 | ampEnvReleaseSlider.setBounds(3 * WIDTH, 120, 40, 136); 75 | ampEnvReleaseSlider.setRange(0.0f, 5000.0f); 76 | ampEnvReleaseSlider.setValue(1000.0f); 77 | addAndMakeVisible(ampEnvReleaseSlider); 78 | 79 | ampEnvModAmtSlider.setSliderStyle(Slider::LinearVertical); 80 | ampEnvModAmtSlider.setTextBoxStyle(Slider::NoTextBox, false, 40, 20); 81 | ampEnvModAmtSlider.setBounds(4 * WIDTH, 120, 40, 136); 82 | ampEnvModAmtSlider.setRange(-20000., 20000.0f); 83 | ampEnvModAmtSlider.setValue(0.0f); 84 | addAndMakeVisible(ampEnvModAmtSlider); 85 | 86 | 87 | 88 | filterEnvAttackSlider.setSliderStyle(Slider::LinearVertical); 89 | filterEnvAttackSlider.setTextBoxStyle(Slider::NoTextBox, false, 40, 20); 90 | filterEnvAttackSlider.setBounds(5 * WIDTH, 120, 40, 136); 91 | filterEnvAttackSlider.setRange(0.0f, 500.0f); 92 | filterEnvAttackSlider.setValue(0.0f); 93 | addAndMakeVisible(filterEnvAttackSlider); 94 | 95 | filterEnvDecaySlider.setSliderStyle(Slider::LinearVertical); 96 | filterEnvDecaySlider.setTextBoxStyle(Slider::NoTextBox, false, 40, 20); 97 | filterEnvDecaySlider.setBounds(6 * WIDTH, 120, 40, 136); 98 | filterEnvDecaySlider.setRange(0.0f, 1000.0f); 99 | filterEnvDecaySlider.setValue(0.0f); 100 | addAndMakeVisible(filterEnvDecaySlider); 101 | 102 | filterEnvSustainSlider.setSliderStyle(Slider::LinearVertical); 103 | filterEnvSustainSlider.setTextBoxStyle(Slider::NoTextBox, false, 40, 20); 104 | filterEnvSustainSlider.setBounds(7 * WIDTH, 120, 40, 136); 105 | filterEnvSustainSlider.setRange(0.0f, 1.0f); 106 | filterEnvSustainSlider.setValue(1.0f); 107 | addAndMakeVisible(filterEnvSustainSlider); 108 | 109 | filterEnvReleaseSlider.setSliderStyle(Slider::LinearVertical); 110 | filterEnvReleaseSlider.setTextBoxStyle(Slider::NoTextBox, false, 40, 20); 111 | filterEnvReleaseSlider.setBounds(8 * WIDTH, 120, 40, 136); 112 | filterEnvReleaseSlider.setRange(.0f, 5000.0f); 113 | filterEnvReleaseSlider.setValue(1000.0f); 114 | addAndMakeVisible(filterEnvReleaseSlider); 115 | 116 | filterEnvModAmtSlider.setSliderStyle(Slider::LinearVertical); 117 | filterEnvModAmtSlider.setTextBoxStyle(Slider::NoTextBox, false, 40, 20); 118 | filterEnvModAmtSlider.setBounds(9 * WIDTH, 120, 40, 136); 119 | filterEnvModAmtSlider.setRange(-20000., 20000.0f); 120 | filterEnvModAmtSlider.setValue(0.0f); 121 | addAndMakeVisible(filterEnvModAmtSlider); 122 | 123 | filterCutoffSlider.setSliderStyle(Slider::LinearVertical); 124 | filterCutoffSlider.setTextBoxStyle(Slider::NoTextBox, false, 40, 20); 125 | filterCutoffSlider.setBounds(10 * WIDTH, 120, 40, 136); 126 | filterCutoffSlider.setRange(-20000., 20000.0f); 127 | filterCutoffSlider.setValue(0.0f); 128 | addAndMakeVisible(filterCutoffSlider); 129 | 130 | filterEnvAttackAttachment.reset(new SliderAttachment(valueTreeState, "filterEnvAttack", filterEnvAttackSlider)); 131 | filterEnvDecayAttachment.reset(new SliderAttachment(valueTreeState, "filterEnvDecay", filterEnvDecaySlider)); 132 | filterEnvSustainAttachment.reset(new SliderAttachment(valueTreeState, "filterEnvSustain", filterEnvSustainSlider)); 133 | filterEnvReleaseAttachment.reset(new SliderAttachment(valueTreeState, "filterEnvRelease", filterEnvReleaseSlider)); 134 | filterEnvModAmtAttachment.reset(new SliderAttachment(valueTreeState, "filterEnvModAmt", filterEnvModAmtSlider)); 135 | filterCutoffAttachment.reset(new SliderAttachment(valueTreeState, "filterCutoff", filterCutoffSlider)); 136 | 137 | ampEnvAttackAttachment.reset(new SliderAttachment(valueTreeState, "ampEnvAttack", ampEnvAttackSlider)); 138 | ampEnvDecayAttachment.reset(new SliderAttachment(valueTreeState, "ampEnvDecay", ampEnvDecaySlider)); 139 | ampEnvSustainAttachment.reset(new SliderAttachment(valueTreeState, "ampEnvSustain", ampEnvSustainSlider)); 140 | ampEnvReleaseAttachment.reset(new SliderAttachment(valueTreeState, "ampEnvRelease", ampEnvReleaseSlider)); 141 | ampEnvModAmtAttachment.reset(new SliderAttachment(valueTreeState, "ampEnvModAmt", ampEnvModAmtSlider)); 142 | 143 | auto setReader = [this](const FileChooser& fc) 144 | { 145 | const auto result = fc.getResult(); 146 | 147 | if (result != File()) 148 | { 149 | undoManager.beginNewTransaction(); 150 | auto readerFactory = new FileAudioFormatReaderFactory(result); 151 | dataModel.setSampleReader(std::unique_ptr(readerFactory), 152 | &undoManager); 153 | } 154 | }; 155 | 156 | loadNewSampleButton.onClick = [this, setReader] 157 | { 158 | fileChooser.launchAsync(FileBrowserComponent::FileChooserFlags::openMode | 159 | FileBrowserComponent::FileChooserFlags::canSelectFiles, 160 | setReader); 161 | }; 162 | 163 | addAndMakeVisible(centreFrequency); 164 | centreFrequency.onValueChange = [this] 165 | { 166 | undoManager.beginNewTransaction(); 167 | dataModel.setCentreFrequencyHz(centreFrequency.getValue(), 168 | centreFrequency.isMouseButtonDown() ? nullptr : &undoManager); 169 | }; 170 | 171 | centreFrequency.setRange(2, 20000, 1); 172 | centreFrequency.setSliderStyle(Slider::SliderStyle::IncDecButtons); 173 | centreFrequency.setIncDecButtonsMode(Slider::IncDecButtonMode::incDecButtonsDraggable_Vertical); 174 | 175 | auto radioGroupId = 1; 176 | 177 | for (auto buttonPtr : { &loopKindNone, &loopKindForward, &loopKindPingpong }) 178 | { 179 | addAndMakeVisible(buttonPtr); 180 | buttonPtr->setRadioGroupId(radioGroupId, dontSendNotification); 181 | buttonPtr->setClickingTogglesState(true); 182 | } 183 | 184 | loopKindNone.onClick = [this] 185 | { 186 | if (loopKindNone.getToggleState()) 187 | { 188 | undoManager.beginNewTransaction(); 189 | dataModel.setLoopMode(LoopMode::none, &undoManager); 190 | } 191 | }; 192 | 193 | loopKindForward.onClick = [this] 194 | { 195 | if (loopKindForward.getToggleState()) 196 | { 197 | undoManager.beginNewTransaction(); 198 | dataModel.setLoopMode(LoopMode::forward, &undoManager); 199 | } 200 | }; 201 | 202 | loopKindPingpong.onClick = [this] 203 | { 204 | if (loopKindPingpong.getToggleState()) 205 | { 206 | undoManager.beginNewTransaction(); 207 | dataModel.setLoopMode(LoopMode::pingpong, &undoManager); 208 | } 209 | }; 210 | 211 | undoButton.onClick = [this] { undoManager.undo(); }; 212 | redoButton.onClick = [this] { undoManager.redo(); }; 213 | 214 | addAndMakeVisible(centreFrequencyLabel); 215 | addAndMakeVisible(loopKindLabel); 216 | 217 | changeListenerCallback(&undoManager); 218 | undoManager.addChangeListener(this); 219 | } 220 | 221 | ~MainSamplerView() override 222 | { 223 | undoManager.removeChangeListener(this); 224 | } 225 | 226 | private: 227 | void changeListenerCallback(ChangeBroadcaster* source) override 228 | { 229 | if (source == &undoManager) 230 | { 231 | undoButton.setEnabled(undoManager.canUndo()); 232 | redoButton.setEnabled(undoManager.canRedo()); 233 | } 234 | } 235 | 236 | void resized() override 237 | { 238 | auto bounds = getLocalBounds(); 239 | 240 | auto topBar = bounds.removeFromTop(50); 241 | auto padding = 4; 242 | loadNewSampleButton.setBounds(topBar.removeFromRight(100).reduced(padding)); 243 | redoButton.setBounds(topBar.removeFromRight(100).reduced(padding)); 244 | undoButton.setBounds(topBar.removeFromRight(100).reduced(padding)); 245 | centreFrequencyLabel.setBounds(topBar.removeFromLeft(100).reduced(padding)); 246 | centreFrequency.setBounds(topBar.removeFromLeft(100).reduced(padding)); 247 | 248 | auto bottomBar = bounds.removeFromBottom(50); 249 | loopKindLabel.setBounds(bottomBar.removeFromLeft(100).reduced(padding)); 250 | loopKindNone.setBounds(bottomBar.removeFromLeft(80).reduced(padding)); 251 | loopKindForward.setBounds(bottomBar.removeFromLeft(80).reduced(padding)); 252 | loopKindPingpong.setBounds(bottomBar.removeFromLeft(80).reduced(padding)); 253 | 254 | waveformEditor.setBounds(bounds); 255 | } 256 | 257 | void loopModeChanged(LoopMode value) override 258 | { 259 | switch (value) 260 | { 261 | case LoopMode::none: 262 | loopKindNone.setToggleState(true, dontSendNotification); 263 | break; 264 | case LoopMode::forward: 265 | loopKindForward.setToggleState(true, dontSendNotification); 266 | break; 267 | case LoopMode::pingpong: 268 | loopKindPingpong.setToggleState(true, dontSendNotification); 269 | break; 270 | 271 | default: 272 | break; 273 | } 274 | } 275 | 276 | void centreFrequencyHzChanged(double value) override 277 | { 278 | centreFrequency.setValue(value, dontSendNotification); 279 | } 280 | 281 | AudioProcessorValueTreeState& valueTreeState; // from the SamplerAudioProcessor 282 | 283 | DataModel dataModel; 284 | WaveformEditor waveformEditor; 285 | TextButton loadNewSampleButton{ "Load New Sample" }; 286 | TextButton undoButton{ "Undo" }; 287 | TextButton redoButton{ "Redo" }; 288 | Slider centreFrequency; 289 | 290 | TextButton loopKindNone{ "None" }, 291 | loopKindForward{ "Forward" }, 292 | loopKindPingpong{ "Ping Pong" }; 293 | 294 | Label centreFrequencyLabel{ {}, "Sample Centre Freq / Hz" }, 295 | loopKindLabel{ {}, "Looping Mode" }; 296 | 297 | 298 | FileChooser fileChooser{ "Select a file to load...", File(), 299 | dataModel.getAudioFormatManager().getWildcardForAllFormats() }; 300 | 301 | Slider ampEnvAttackSlider; 302 | Slider ampEnvDecaySlider; 303 | Slider ampEnvSustainSlider; 304 | Slider ampEnvReleaseSlider; 305 | Slider ampEnvModAmtSlider; 306 | 307 | Slider filterCutoffSlider; 308 | 309 | Slider filterEnvAttackSlider; 310 | Slider filterEnvDecaySlider; 311 | Slider filterEnvSustainSlider; 312 | Slider filterEnvReleaseSlider; 313 | Slider filterEnvModAmtSlider; 314 | 315 | std::unique_ptr ampEnvAttackAttachment; 316 | std::unique_ptr ampEnvDecayAttachment; 317 | std::unique_ptr ampEnvSustainAttachment; 318 | std::unique_ptr ampEnvReleaseAttachment; 319 | std::unique_ptr ampEnvModAmtAttachment; 320 | 321 | std::unique_ptr filterEnvAttackAttachment; 322 | std::unique_ptr filterEnvDecayAttachment; 323 | std::unique_ptr filterEnvSustainAttachment; 324 | std::unique_ptr filterEnvReleaseAttachment; 325 | std::unique_ptr filterEnvModAmtAttachment; 326 | std::unique_ptr filterCutoffAttachment; 327 | 328 | UndoManager& undoManager; 329 | }; 330 | -------------------------------------------------------------------------------- /Source/Components/PlaybackPositionOverlay.h: -------------------------------------------------------------------------------- 1 | /* 2 | ============================================================================== 3 | This file is part of the JUCE examples. 4 | Copyright (c) 2020 - Raw Material Software Limited 5 | The code included in this file is provided under the terms of the ISC license 6 | http://www.isc.org/downloads/software-support-policy/isc-license. Permission 7 | To use, copy, modify, and/or distribute this software for any purpose with or 8 | without fee is hereby granted provided that the above copyright notice and 9 | this permission notice appear in all copies. 10 | THE SOFTWARE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, 11 | WHETHER EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR 12 | PURPOSE, ARE DISCLAIMED. 13 | ============================================================================== 14 | */ 15 | 16 | #pragma once 17 | 18 | #include "../DataModels/VisibleRangeDataModel.h" 19 | 20 | class PlaybackPositionOverlay : public juce::Component, 21 | private Timer, 22 | private VisibleRangeDataModel::Listener 23 | { 24 | public: 25 | using Provider = std::function()>; 26 | PlaybackPositionOverlay(const VisibleRangeDataModel& model, 27 | Provider providerIn) 28 | : visibleRange(model), 29 | provider(std::move(providerIn)) 30 | { 31 | visibleRange.addListener(*this); 32 | startTimer(16); 33 | } 34 | 35 | private: 36 | void paint(Graphics& g) override 37 | { 38 | g.setColour(juce::Colours::red); 39 | 40 | for (auto position : provider()) 41 | { 42 | g.drawVerticalLine(roundToInt(timeToXPosition(position)), 0.0f, (float)getHeight()); 43 | } 44 | } 45 | 46 | void timerCallback() override 47 | { 48 | repaint(); 49 | } 50 | 51 | void visibleRangeChanged(Range) override 52 | { 53 | repaint(); 54 | } 55 | 56 | double timeToXPosition(double time) const 57 | { 58 | return (time - visibleRange.getVisibleRange().getStart()) * getWidth() 59 | / visibleRange.getVisibleRange().getLength(); 60 | } 61 | 62 | VisibleRangeDataModel visibleRange; 63 | Provider provider; 64 | }; 65 | -------------------------------------------------------------------------------- /Source/Components/Ruler.h: -------------------------------------------------------------------------------- 1 | /* 2 | ============================================================================== 3 | This file is part of the JUCE examples. 4 | Copyright (c) 2020 - Raw Material Software Limited 5 | The code included in this file is provided under the terms of the ISC license 6 | http://www.isc.org/downloads/software-support-policy/isc-license. Permission 7 | To use, copy, modify, and/or distribute this software for any purpose with or 8 | without fee is hereby granted provided that the above copyright notice and 9 | this permission notice appear in all copies. 10 | THE SOFTWARE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, 11 | WHETHER EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR 12 | PURPOSE, ARE DISCLAIMED. 13 | ============================================================================== 14 | */ 15 | 16 | #pragma once 17 | 18 | class Ruler : public juce::Component, 19 | private VisibleRangeDataModel::Listener 20 | { 21 | public: 22 | explicit Ruler(const VisibleRangeDataModel& model) 23 | : visibleRange(model) 24 | { 25 | visibleRange.addListener(*this); 26 | setMouseCursor(MouseCursor::LeftRightResizeCursor); 27 | } 28 | 29 | private: 30 | void paint(Graphics& g) override 31 | { 32 | auto minDivisionWidth = 50.0f; 33 | auto maxDivisions = (float)getWidth() / minDivisionWidth; 34 | 35 | auto lookFeel = dynamic_cast (&getLookAndFeel()); 36 | auto bg = lookFeel->getCurrentColourScheme() 37 | .getUIColour(LookAndFeel_V4::ColourScheme::UIColour::widgetBackground); 38 | 39 | g.setGradientFill(ColourGradient(bg.brighter(), 40 | 0, 41 | 0, 42 | bg.darker(), 43 | 0, 44 | (float)getHeight(), 45 | false)); 46 | 47 | g.fillAll(); 48 | g.setColour(bg.brighter()); 49 | g.drawHorizontalLine(0, 0.0f, (float)getWidth()); 50 | g.setColour(bg.darker()); 51 | g.drawHorizontalLine(1, 0.0f, (float)getWidth()); 52 | g.setColour(juce::Colours::lightgrey); 53 | 54 | auto minLog = std::ceil(std::log10(visibleRange.getVisibleRange().getLength() / maxDivisions)); 55 | auto precision = 2 + std::abs(minLog); 56 | auto divisionMagnitude = std::pow(10, minLog); 57 | auto startingDivision = std::ceil(visibleRange.getVisibleRange().getStart() / divisionMagnitude); 58 | 59 | for (auto div = startingDivision; div * divisionMagnitude < visibleRange.getVisibleRange().getEnd(); ++div) 60 | { 61 | auto time = div * divisionMagnitude; 62 | auto xPos = (time - visibleRange.getVisibleRange().getStart()) * getWidth() 63 | / visibleRange.getVisibleRange().getLength(); 64 | 65 | std::ostringstream outStream; 66 | outStream << std::setprecision(roundToInt(precision)) << time; 67 | 68 | const auto bounds = juce::Rectangle(juce::Point(roundToInt(xPos) + 3, 0), 69 | juce::Point(roundToInt(xPos + minDivisionWidth), getHeight())); 70 | 71 | g.drawText(outStream.str(), bounds, Justification::centredLeft, false); 72 | 73 | g.drawVerticalLine(roundToInt(xPos), 2.0f, (float)getHeight()); 74 | } 75 | } 76 | 77 | void mouseDown(const MouseEvent& e) override 78 | { 79 | visibleRangeOnMouseDown = visibleRange.getVisibleRange(); 80 | timeOnMouseDown = visibleRange.getVisibleRange().getStart() 81 | + (visibleRange.getVisibleRange().getLength() * e.getMouseDownX()) / getWidth(); 82 | } 83 | 84 | void mouseDrag(const MouseEvent& e) override 85 | { 86 | // Work out the scale of the new range 87 | auto unitDistance = 100.0f; 88 | auto scaleFactor = 1.0 / std::pow(2, (float)e.getDistanceFromDragStartY() / unitDistance); 89 | 90 | // Now position it so that the mouse continues to point at the same 91 | // place on the ruler. 92 | auto visibleLength = std::max(0.12, visibleRangeOnMouseDown.getLength() * scaleFactor); 93 | auto rangeBegin = timeOnMouseDown - visibleLength * e.x / getWidth(); 94 | const Range range(rangeBegin, rangeBegin + visibleLength); 95 | visibleRange.setVisibleRange(range, nullptr); 96 | } 97 | 98 | void visibleRangeChanged(Range) override 99 | { 100 | repaint(); 101 | } 102 | 103 | VisibleRangeDataModel visibleRange; 104 | Range visibleRangeOnMouseDown; 105 | double timeOnMouseDown; 106 | }; 107 | -------------------------------------------------------------------------------- /Source/Components/WaveformEditor.h: -------------------------------------------------------------------------------- 1 | /* 2 | ============================================================================== 3 | This file is part of the JUCE examples. 4 | Copyright (c) 2020 - Raw Material Software Limited 5 | The code included in this file is provided under the terms of the ISC license 6 | http://www.isc.org/downloads/software-support-policy/isc-license. Permission 7 | To use, copy, modify, and/or distribute this software for any purpose with or 8 | without fee is hereby granted provided that the above copyright notice and 9 | this permission notice appear in all copies. 10 | THE SOFTWARE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, 11 | WHETHER EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR 12 | PURPOSE, ARE DISCLAIMED. 13 | ============================================================================== 14 | */ 15 | 16 | #pragma once 17 | 18 | #include "WaveformView.h" 19 | #include "LoopPointsOverlay.h" 20 | #include "Ruler.h" 21 | 22 | class WaveformEditor : public juce::Component, 23 | private DataModel::Listener 24 | { 25 | public: 26 | WaveformEditor(const DataModel& model, 27 | PlaybackPositionOverlay::Provider provider, 28 | UndoManager& undoManager) 29 | : dataModel(model), 30 | waveformView(model, visibleRange), 31 | playbackOverlay(visibleRange, std::move(provider)), 32 | loopPoints(dataModel, visibleRange, undoManager), 33 | ruler(visibleRange) 34 | { 35 | dataModel.addListener(*this); 36 | 37 | addAndMakeVisible(waveformView); 38 | addAndMakeVisible(playbackOverlay); 39 | addChildComponent(loopPoints); 40 | loopPoints.setAlwaysOnTop(true); 41 | 42 | waveformView.toBack(); 43 | 44 | addAndMakeVisible(ruler); 45 | } 46 | 47 | private: 48 | void resized() override 49 | { 50 | auto bounds = getLocalBounds(); 51 | ruler.setBounds(bounds.removeFromTop(25)); 52 | waveformView.setBounds(bounds); 53 | playbackOverlay.setBounds(bounds); 54 | loopPoints.setBounds(bounds); 55 | } 56 | 57 | void loopModeChanged(LoopMode value) override 58 | { 59 | loopPoints.setVisible(value != LoopMode::none); 60 | } 61 | 62 | void sampleReaderChanged(std::shared_ptr) override 63 | { 64 | auto lengthInSeconds = dataModel.getSampleLengthSeconds(); 65 | visibleRange.setTotalRange(Range(0, lengthInSeconds), nullptr); 66 | visibleRange.setVisibleRange(Range(0, lengthInSeconds), nullptr); 67 | } 68 | 69 | DataModel dataModel; 70 | VisibleRangeDataModel visibleRange; 71 | WaveformView waveformView; 72 | PlaybackPositionOverlay playbackOverlay; 73 | LoopPointsOverlay loopPoints; 74 | Ruler ruler; 75 | }; 76 | -------------------------------------------------------------------------------- /Source/Components/WaveformView.h: -------------------------------------------------------------------------------- 1 | /* 2 | ============================================================================== 3 | This file is part of the JUCE examples. 4 | Copyright (c) 2020 - Raw Material Software Limited 5 | The code included in this file is provided under the terms of the ISC license 6 | http://www.isc.org/downloads/software-support-policy/isc-license. Permission 7 | To use, copy, modify, and/or distribute this software for any purpose with or 8 | without fee is hereby granted provided that the above copyright notice and 9 | this permission notice appear in all copies. 10 | THE SOFTWARE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, 11 | WHETHER EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR 12 | PURPOSE, ARE DISCLAIMED. 13 | ============================================================================== 14 | */ 15 | 16 | #pragma once 17 | 18 | class WaveformView : public juce::Component, 19 | private ChangeListener, 20 | private DataModel::Listener, 21 | private VisibleRangeDataModel::Listener 22 | { 23 | public: 24 | WaveformView(const DataModel& model, 25 | const VisibleRangeDataModel& vr) 26 | : dataModel(model), 27 | visibleRange(vr), 28 | thumbnailCache(4), 29 | thumbnail(4, dataModel.getAudioFormatManager(), thumbnailCache) 30 | { 31 | dataModel.addListener(*this); 32 | visibleRange.addListener(*this); 33 | thumbnail.addChangeListener(this); 34 | } 35 | 36 | private: 37 | void paint(Graphics& g) override 38 | { 39 | // Draw the waveforms 40 | g.fillAll(juce::Colours::black); 41 | auto numChannels = thumbnail.getNumChannels(); 42 | 43 | if (numChannels == 0) 44 | { 45 | g.setColour(juce::Colours::white); 46 | g.drawFittedText("No File Loaded", getLocalBounds(), Justification::centred, 1); 47 | return; 48 | } 49 | 50 | auto bounds = getLocalBounds(); 51 | auto channelHeight = bounds.getHeight() / numChannels; 52 | 53 | for (auto i = 0; i != numChannels; ++i) 54 | { 55 | drawChannel(g, i, bounds.removeFromTop(channelHeight)); 56 | } 57 | } 58 | 59 | void changeListenerCallback(ChangeBroadcaster* source) override 60 | { 61 | if (source == &thumbnail) 62 | repaint(); 63 | } 64 | 65 | void sampleReaderChanged(std::shared_ptr value) override 66 | { 67 | if (value != nullptr) 68 | { 69 | if (auto reader = value->make(dataModel.getAudioFormatManager())) 70 | { 71 | thumbnail.setReader(reader.release(), currentHashCode); 72 | currentHashCode += 1; 73 | 74 | return; 75 | } 76 | } 77 | 78 | thumbnail.clear(); 79 | } 80 | 81 | void visibleRangeChanged(Range) override 82 | { 83 | repaint(); 84 | } 85 | 86 | void drawChannel(Graphics& g, int channel, juce::Rectangle bounds) 87 | { 88 | g.setGradientFill(ColourGradient(juce::Colours::lightblue, 89 | bounds.getTopLeft().toFloat(), 90 | juce::Colours::darkgrey, 91 | bounds.getBottomLeft().toFloat(), 92 | false)); 93 | thumbnail.drawChannel(g, 94 | bounds, 95 | visibleRange.getVisibleRange().getStart(), 96 | visibleRange.getVisibleRange().getEnd(), 97 | channel, 98 | 1.0f); 99 | } 100 | 101 | DataModel dataModel; 102 | VisibleRangeDataModel visibleRange; 103 | juce::AudioThumbnailCache thumbnailCache; 104 | juce::AudioThumbnail thumbnail; 105 | int64 currentHashCode = 0; 106 | }; 107 | -------------------------------------------------------------------------------- /Source/DataModels/DataModel.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | ============================================================================== 3 | This file is part of the JUCE examples. 4 | Copyright (c) 2020 - Raw Material Software Limited 5 | The code included in this file is provided under the terms of the ISC license 6 | http://www.isc.org/downloads/software-support-policy/isc-license. Permission 7 | To use, copy, modify, and/or distribute this software for any purpose with or 8 | without fee is hereby granted provided that the above copyright notice and 9 | this permission notice appear in all copies. 10 | THE SOFTWARE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, 11 | WHETHER EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR 12 | PURPOSE, ARE DISCLAIMED. 13 | ============================================================================== 14 | */ 15 | 16 | #include "DataModel.h" 17 | 18 | DataModel::DataModel(AudioFormatManager& audioFormatManagerIn) 19 | : DataModel(audioFormatManagerIn, ValueTree(IDs::DATA_MODEL)) 20 | {} 21 | 22 | DataModel::DataModel(AudioFormatManager& audioFormatManagerIn, const ValueTree& vt) 23 | : audioFormatManager(&audioFormatManagerIn), 24 | valueTree(vt), 25 | sampleReader(valueTree, IDs::sampleReader, nullptr), 26 | centreFrequencyHz(valueTree, IDs::centreFrequencyHz, nullptr), 27 | loopMode(valueTree, IDs::loopMode, nullptr, LoopMode::none), 28 | loopPointsSeconds(valueTree, IDs::loopPointsSeconds, nullptr) 29 | { 30 | jassert(valueTree.hasType(IDs::DATA_MODEL)); 31 | valueTree.addListener(this); 32 | } 33 | 34 | DataModel::DataModel(const DataModel& other) 35 | : DataModel(*other.audioFormatManager, other.valueTree) 36 | {} 37 | 38 | DataModel& DataModel::operator= (const DataModel& other) 39 | { 40 | auto copy(other); 41 | swap(copy); 42 | return *this; 43 | } 44 | 45 | std::unique_ptr DataModel::getSampleReader() const 46 | { 47 | return sampleReader != nullptr ? sampleReader.get()->make(*audioFormatManager) : nullptr; 48 | } 49 | 50 | void DataModel::setSampleReader(std::unique_ptr readerFactory, 51 | UndoManager* undoManager) 52 | { 53 | sampleReader.setValue(std::move(readerFactory), undoManager); 54 | setLoopPointsSeconds(Range(0, getSampleLengthSeconds()).constrainRange(loopPointsSeconds), 55 | undoManager); 56 | } 57 | 58 | double DataModel::getSampleLengthSeconds() const 59 | { 60 | if (auto r = getSampleReader()) 61 | return (double)r->lengthInSamples / r->sampleRate; 62 | 63 | return 1.0; 64 | } 65 | 66 | double DataModel::getCentreFrequencyHz() const 67 | { 68 | return centreFrequencyHz; 69 | } 70 | 71 | void DataModel::setCentreFrequencyHz(double value, UndoManager* undoManager) 72 | { 73 | centreFrequencyHz.setValue(Range(2, 20000).clipValue(value), 74 | undoManager); 75 | } 76 | 77 | LoopMode DataModel::getLoopMode() const 78 | { 79 | return loopMode; 80 | } 81 | 82 | void DataModel::setLoopMode(LoopMode value, UndoManager* undoManager) 83 | { 84 | loopMode.setValue(value, undoManager); 85 | } 86 | 87 | Range DataModel::getLoopPointsSeconds() const 88 | { 89 | return loopPointsSeconds; 90 | } 91 | 92 | void DataModel::setLoopPointsSeconds(Range value, UndoManager* undoManager) 93 | { 94 | loopPointsSeconds.setValue(Range(0, getSampleLengthSeconds()).constrainRange(value), 95 | undoManager); 96 | } 97 | 98 | MPESettingsDataModel DataModel::mpeSettings() 99 | { 100 | return MPESettingsDataModel(valueTree.getOrCreateChildWithName(IDs::MPE_SETTINGS, nullptr)); 101 | } 102 | 103 | void DataModel::addListener(Listener& listener) 104 | { 105 | listenerList.add(&listener); 106 | } 107 | 108 | void DataModel::removeListener(Listener& listener) 109 | { 110 | listenerList.remove(&listener); 111 | } 112 | 113 | void DataModel::swap(DataModel& other) noexcept 114 | { 115 | using std::swap; 116 | swap(other.valueTree, valueTree); 117 | } 118 | 119 | AudioFormatManager& DataModel::getAudioFormatManager() const 120 | { 121 | return *audioFormatManager; 122 | } 123 | 124 | void DataModel::valueTreePropertyChanged(ValueTree&, const Identifier& property) 125 | { 126 | if (property == IDs::sampleReader) 127 | { 128 | sampleReader.forceUpdateOfCachedValue(); 129 | listenerList.call([this](Listener& l) { l.sampleReaderChanged(sampleReader); }); 130 | } 131 | else if (property == IDs::centreFrequencyHz) 132 | { 133 | centreFrequencyHz.forceUpdateOfCachedValue(); 134 | listenerList.call([this](Listener& l) { l.centreFrequencyHzChanged(centreFrequencyHz); }); 135 | } 136 | else if (property == IDs::loopMode) 137 | { 138 | loopMode.forceUpdateOfCachedValue(); 139 | listenerList.call([this](Listener& l) { l.loopModeChanged(loopMode); }); 140 | } 141 | else if (property == IDs::loopPointsSeconds) 142 | { 143 | loopPointsSeconds.forceUpdateOfCachedValue(); 144 | listenerList.call([this](Listener& l) { l.loopPointsSecondsChanged(loopPointsSeconds); }); 145 | } 146 | } 147 | 148 | void DataModel::valueTreeChildAdded(ValueTree&, ValueTree&) {} 149 | void DataModel::valueTreeChildRemoved(ValueTree&, ValueTree&, int) { jassertfalse; } 150 | void DataModel::valueTreeChildOrderChanged(ValueTree&, int, int) { jassertfalse; } 151 | void DataModel::valueTreeParentChanged(ValueTree&) { jassertfalse; } 152 | -------------------------------------------------------------------------------- /Source/DataModels/DataModel.h: -------------------------------------------------------------------------------- 1 | /* 2 | ============================================================================== 3 | This file is part of the JUCE examples. 4 | Copyright (c) 2020 - Raw Material Software Limited 5 | The code included in this file is provided under the terms of the ISC license 6 | http://www.isc.org/downloads/software-support-policy/isc-license. Permission 7 | To use, copy, modify, and/or distribute this software for any purpose with or 8 | without fee is hereby granted provided that the above copyright notice and 9 | this permission notice appear in all copies. 10 | THE SOFTWARE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, 11 | WHETHER EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR 12 | PURPOSE, ARE DISCLAIMED. 13 | ============================================================================== 14 | */ 15 | 16 | #pragma once 17 | 18 | #include "MPESettingsDataModel.h" 19 | 20 | class DataModel : private ValueTree::Listener 21 | { 22 | public: 23 | class Listener 24 | { 25 | public: 26 | virtual ~Listener() noexcept = default; 27 | virtual void sampleReaderChanged(std::shared_ptr) {} 28 | virtual void centreFrequencyHzChanged(double) {} 29 | virtual void loopModeChanged(LoopMode) {} 30 | virtual void loopPointsSecondsChanged(Range) {} 31 | }; 32 | 33 | explicit DataModel(AudioFormatManager& audioFormatManagerIn); 34 | 35 | DataModel(AudioFormatManager& audioFormatManagerIn, const ValueTree& vt); 36 | 37 | DataModel(const DataModel& other); 38 | 39 | DataModel& operator= (const DataModel& other); 40 | 41 | std::unique_ptr getSampleReader() const; 42 | 43 | void setSampleReader(std::unique_ptr readerFactory, 44 | UndoManager* undoManager); 45 | 46 | double getSampleLengthSeconds() const; 47 | 48 | double getCentreFrequencyHz() const; 49 | 50 | void setCentreFrequencyHz(double value, UndoManager* undoManager); 51 | 52 | LoopMode getLoopMode() const; 53 | 54 | void setLoopMode(LoopMode value, UndoManager* undoManager); 55 | 56 | Range getLoopPointsSeconds() const; 57 | 58 | void setLoopPointsSeconds(Range value, UndoManager* undoManager); 59 | 60 | MPESettingsDataModel mpeSettings(); 61 | 62 | void addListener(Listener& listener); 63 | 64 | void removeListener(Listener& listener); 65 | 66 | void swap(DataModel& other) noexcept; 67 | 68 | AudioFormatManager& getAudioFormatManager() const; 69 | 70 | private: 71 | void valueTreePropertyChanged(ValueTree&, const Identifier& property) override; 72 | 73 | void valueTreeChildAdded(ValueTree&, ValueTree&) override; 74 | void valueTreeChildRemoved(ValueTree&, ValueTree&, int) override; 75 | void valueTreeChildOrderChanged(ValueTree&, int, int) override; 76 | void valueTreeParentChanged(ValueTree&) override; 77 | 78 | AudioFormatManager* audioFormatManager; 79 | 80 | ValueTree valueTree; 81 | 82 | CachedValue> sampleReader; 83 | CachedValue centreFrequencyHz; 84 | CachedValue loopMode; 85 | CachedValue> loopPointsSeconds; 86 | 87 | ListenerList listenerList; 88 | }; -------------------------------------------------------------------------------- /Source/DataModels/MPESettingsDataModel.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | ============================================================================== 3 | This file is part of the JUCE examples. 4 | Copyright (c) 2020 - Raw Material Software Limited 5 | The code included in this file is provided under the terms of the ISC license 6 | http://www.isc.org/downloads/software-support-policy/isc-license. Permission 7 | To use, copy, modify, and/or distribute this software for any purpose with or 8 | without fee is hereby granted provided that the above copyright notice and 9 | this permission notice appear in all copies. 10 | THE SOFTWARE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, 11 | WHETHER EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR 12 | PURPOSE, ARE DISCLAIMED. 13 | ============================================================================== 14 | */ 15 | 16 | #include "MPESettingsDataModel.h" 17 | 18 | MPESettingsDataModel::MPESettingsDataModel() 19 | : MPESettingsDataModel(ValueTree(IDs::MPE_SETTINGS)) 20 | {} 21 | 22 | MPESettingsDataModel::MPESettingsDataModel(const ValueTree& vt) 23 | : valueTree(vt), 24 | synthVoices(valueTree, IDs::synthVoices, nullptr, 15), 25 | voiceStealingEnabled(valueTree, IDs::voiceStealingEnabled, nullptr, false), 26 | legacyModeEnabled(valueTree, IDs::legacyModeEnabled, nullptr, true), 27 | mpeZoneLayout(valueTree, IDs::mpeZoneLayout, nullptr, {}), 28 | legacyFirstChannel(valueTree, IDs::legacyFirstChannel, nullptr, 1), 29 | legacyLastChannel(valueTree, IDs::legacyLastChannel, nullptr, 15), 30 | legacyPitchbendRange(valueTree, IDs::legacyPitchbendRange, nullptr, 48) 31 | { 32 | jassert(valueTree.hasType(IDs::MPE_SETTINGS)); 33 | valueTree.addListener(this); 34 | } 35 | 36 | MPESettingsDataModel::MPESettingsDataModel(const MPESettingsDataModel& other) 37 | : MPESettingsDataModel(other.valueTree) 38 | {} 39 | 40 | int MPESettingsDataModel::getSynthVoices() const 41 | { 42 | return synthVoices; 43 | } 44 | 45 | void MPESettingsDataModel::setSynthVoices(int value, UndoManager* undoManager) 46 | { 47 | synthVoices.setValue(Range(1, 20).clipValue(value), undoManager); 48 | } 49 | 50 | bool MPESettingsDataModel::getVoiceStealingEnabled() const 51 | { 52 | return voiceStealingEnabled; 53 | } 54 | 55 | void MPESettingsDataModel::setVoiceStealingEnabled(bool value, UndoManager* undoManager) 56 | { 57 | voiceStealingEnabled.setValue(value, undoManager); 58 | } 59 | 60 | bool MPESettingsDataModel::getLegacyModeEnabled() const 61 | { 62 | return legacyModeEnabled; 63 | } 64 | 65 | void MPESettingsDataModel::setLegacyModeEnabled(bool value, UndoManager* undoManager) 66 | { 67 | legacyModeEnabled.setValue(value, undoManager); 68 | } 69 | 70 | MPEZoneLayout MPESettingsDataModel::getMPEZoneLayout() const 71 | { 72 | return mpeZoneLayout; 73 | } 74 | 75 | void MPESettingsDataModel::setMPEZoneLayout(MPEZoneLayout value, UndoManager* undoManager) 76 | { 77 | mpeZoneLayout.setValue(value, undoManager); 78 | } 79 | 80 | int MPESettingsDataModel::getLegacyFirstChannel() const 81 | { 82 | return legacyFirstChannel; 83 | } 84 | 85 | void MPESettingsDataModel::setLegacyFirstChannel(int value, UndoManager* undoManager) 86 | { 87 | legacyFirstChannel.setValue(Range(1, legacyLastChannel).clipValue(value), undoManager); 88 | } 89 | 90 | int MPESettingsDataModel::getLegacyLastChannel() const 91 | { 92 | return legacyLastChannel; 93 | } 94 | 95 | void MPESettingsDataModel::setLegacyLastChannel(int value, UndoManager* undoManager) 96 | { 97 | legacyLastChannel.setValue(Range(legacyFirstChannel, 15).clipValue(value), undoManager); 98 | } 99 | 100 | int MPESettingsDataModel::getLegacyPitchbendRange() const 101 | { 102 | return legacyPitchbendRange; 103 | } 104 | 105 | void MPESettingsDataModel::setLegacyPitchbendRange(int value, UndoManager* undoManager) 106 | { 107 | legacyPitchbendRange.setValue(Range(0, 95).clipValue(value), undoManager); 108 | } 109 | 110 | void MPESettingsDataModel::addListener(Listener& listener) 111 | { 112 | listenerList.add(&listener); 113 | } 114 | 115 | void MPESettingsDataModel::removeListener(Listener& listener) 116 | { 117 | listenerList.remove(&listener); 118 | } 119 | 120 | void MPESettingsDataModel::swap(MPESettingsDataModel& other) noexcept 121 | { 122 | using std::swap; 123 | swap(other.valueTree, valueTree); 124 | } 125 | 126 | void MPESettingsDataModel::valueTreePropertyChanged(ValueTree&, const Identifier& property) 127 | { 128 | if (property == IDs::synthVoices) 129 | { 130 | synthVoices.forceUpdateOfCachedValue(); 131 | listenerList.call([this](Listener& l) { l.synthVoicesChanged(synthVoices); }); 132 | } 133 | else if (property == IDs::voiceStealingEnabled) 134 | { 135 | voiceStealingEnabled.forceUpdateOfCachedValue(); 136 | listenerList.call([this](Listener& l) { l.voiceStealingEnabledChanged(voiceStealingEnabled); }); 137 | } 138 | else if (property == IDs::legacyModeEnabled) 139 | { 140 | legacyModeEnabled.forceUpdateOfCachedValue(); 141 | listenerList.call([this](Listener& l) { l.legacyModeEnabledChanged(legacyModeEnabled); }); 142 | } 143 | else if (property == IDs::mpeZoneLayout) 144 | { 145 | mpeZoneLayout.forceUpdateOfCachedValue(); 146 | listenerList.call([this](Listener& l) { l.mpeZoneLayoutChanged(mpeZoneLayout); }); 147 | } 148 | else if (property == IDs::legacyFirstChannel) 149 | { 150 | legacyFirstChannel.forceUpdateOfCachedValue(); 151 | listenerList.call([this](Listener& l) { l.legacyFirstChannelChanged(legacyFirstChannel); }); 152 | } 153 | else if (property == IDs::legacyLastChannel) 154 | { 155 | legacyLastChannel.forceUpdateOfCachedValue(); 156 | listenerList.call([this](Listener& l) { l.legacyLastChannelChanged(legacyLastChannel); }); 157 | } 158 | else if (property == IDs::legacyPitchbendRange) 159 | { 160 | legacyPitchbendRange.forceUpdateOfCachedValue(); 161 | listenerList.call([this](Listener& l) { l.legacyPitchbendRangeChanged(legacyPitchbendRange); }); 162 | } 163 | } 164 | 165 | void MPESettingsDataModel::valueTreeChildAdded(ValueTree&, ValueTree&) { jassertfalse; } 166 | void MPESettingsDataModel::valueTreeChildRemoved(ValueTree&, ValueTree&, int) { jassertfalse; } 167 | void MPESettingsDataModel::valueTreeChildOrderChanged(ValueTree&, int, int) { jassertfalse; } 168 | void MPESettingsDataModel::valueTreeParentChanged(ValueTree&) { jassertfalse; } 169 | 170 | 171 | -------------------------------------------------------------------------------- /Source/DataModels/MPESettingsDataModel.h: -------------------------------------------------------------------------------- 1 | /* 2 | ============================================================================== 3 | This file is part of the JUCE examples. 4 | Copyright (c) 2020 - Raw Material Software Limited 5 | The code included in this file is provided under the terms of the ISC license 6 | http://www.isc.org/downloads/software-support-policy/isc-license. Permission 7 | To use, copy, modify, and/or distribute this software for any purpose with or 8 | without fee is hereby granted provided that the above copyright notice and 9 | this permission notice appear in all copies. 10 | THE SOFTWARE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, 11 | WHETHER EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR 12 | PURPOSE, ARE DISCLAIMED. 13 | ============================================================================== 14 | */ 15 | 16 | #pragma once 17 | 18 | #include "../Misc.h" 19 | 20 | class MPESettingsDataModel : private ValueTree::Listener 21 | { 22 | public: 23 | class Listener 24 | { 25 | public: 26 | virtual ~Listener() noexcept = default; 27 | virtual void synthVoicesChanged(int) {} 28 | virtual void voiceStealingEnabledChanged(bool) {} 29 | virtual void legacyModeEnabledChanged(bool) {} 30 | virtual void mpeZoneLayoutChanged(const MPEZoneLayout&) {} 31 | virtual void legacyFirstChannelChanged(int) {} 32 | virtual void legacyLastChannelChanged(int) {} 33 | virtual void legacyPitchbendRangeChanged(int) {} 34 | }; 35 | 36 | MPESettingsDataModel(); 37 | 38 | explicit MPESettingsDataModel(const ValueTree& vt); 39 | 40 | MPESettingsDataModel(const MPESettingsDataModel& other); 41 | 42 | int getSynthVoices() const; 43 | 44 | void setSynthVoices(int value, UndoManager* undoManager); 45 | 46 | bool getVoiceStealingEnabled() const; 47 | 48 | void setVoiceStealingEnabled(bool value, UndoManager* undoManager); 49 | 50 | bool getLegacyModeEnabled() const; 51 | 52 | void setLegacyModeEnabled(bool value, UndoManager* undoManager); 53 | 54 | MPEZoneLayout getMPEZoneLayout() const; 55 | 56 | void setMPEZoneLayout(MPEZoneLayout value, UndoManager* undoManager); 57 | 58 | int getLegacyFirstChannel() const; 59 | 60 | void setLegacyFirstChannel(int value, UndoManager* undoManager); 61 | 62 | int getLegacyLastChannel() const; 63 | 64 | void setLegacyLastChannel(int value, UndoManager* undoManager); 65 | 66 | int getLegacyPitchbendRange() const; 67 | 68 | void setLegacyPitchbendRange(int value, UndoManager* undoManager); 69 | 70 | void addListener(Listener& listener); 71 | 72 | void removeListener(Listener& listener); 73 | 74 | void swap(MPESettingsDataModel& other) noexcept; 75 | 76 | private: 77 | void valueTreePropertyChanged(ValueTree&, const Identifier& property) override; 78 | 79 | void valueTreeChildAdded(ValueTree&, ValueTree&) override; 80 | void valueTreeChildRemoved(ValueTree&, ValueTree&, int) override; 81 | void valueTreeChildOrderChanged(ValueTree&, int, int) override; 82 | void valueTreeParentChanged(ValueTree&) override; 83 | 84 | ValueTree valueTree; 85 | 86 | CachedValue synthVoices; 87 | CachedValue voiceStealingEnabled; 88 | CachedValue legacyModeEnabled; 89 | CachedValue mpeZoneLayout; 90 | CachedValue legacyFirstChannel; 91 | CachedValue legacyLastChannel; 92 | CachedValue legacyPitchbendRange; 93 | 94 | ListenerList listenerList; 95 | }; 96 | -------------------------------------------------------------------------------- /Source/DataModels/VisibleRangeDataModel.h: -------------------------------------------------------------------------------- 1 | /* 2 | ============================================================================== 3 | This file is part of the JUCE examples. 4 | Copyright (c) 2020 - Raw Material Software Limited 5 | The code included in this file is provided under the terms of the ISC license 6 | http://www.isc.org/downloads/software-support-policy/isc-license. Permission 7 | To use, copy, modify, and/or distribute this software for any purpose with or 8 | without fee is hereby granted provided that the above copyright notice and 9 | this permission notice appear in all copies. 10 | THE SOFTWARE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, 11 | WHETHER EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR 12 | PURPOSE, ARE DISCLAIMED. 13 | ============================================================================== 14 | */ 15 | 16 | #pragma once 17 | 18 | class VisibleRangeDataModel : private ValueTree::Listener 19 | { 20 | public: 21 | class Listener 22 | { 23 | public: 24 | virtual ~Listener() noexcept = default; 25 | virtual void totalRangeChanged(Range) {} 26 | virtual void visibleRangeChanged(Range) {} 27 | }; 28 | 29 | VisibleRangeDataModel() 30 | : VisibleRangeDataModel(ValueTree(IDs::VISIBLE_RANGE)) 31 | {} 32 | 33 | explicit VisibleRangeDataModel(const ValueTree& vt) 34 | : valueTree(vt), 35 | totalRange(valueTree, IDs::totalRange, nullptr), 36 | visibleRange(valueTree, IDs::visibleRange, nullptr) 37 | { 38 | jassert(valueTree.hasType(IDs::VISIBLE_RANGE)); 39 | valueTree.addListener(this); 40 | } 41 | 42 | VisibleRangeDataModel(const VisibleRangeDataModel& other) 43 | : VisibleRangeDataModel(other.valueTree) 44 | {} 45 | 46 | VisibleRangeDataModel& operator= (const VisibleRangeDataModel& other) 47 | { 48 | auto copy(other); 49 | swap(copy); 50 | return *this; 51 | } 52 | 53 | Range getTotalRange() const 54 | { 55 | return totalRange; 56 | } 57 | 58 | void setTotalRange(Range value, UndoManager* undoManager) 59 | { 60 | totalRange.setValue(value, undoManager); 61 | setVisibleRange(visibleRange, undoManager); 62 | } 63 | 64 | Range getVisibleRange() const 65 | { 66 | return visibleRange; 67 | } 68 | 69 | void setVisibleRange(Range value, UndoManager* undoManager) 70 | { 71 | visibleRange.setValue(totalRange.get().constrainRange(value), undoManager); 72 | } 73 | 74 | void addListener(Listener& listener) 75 | { 76 | listenerList.add(&listener); 77 | } 78 | 79 | void removeListener(Listener& listener) 80 | { 81 | listenerList.remove(&listener); 82 | } 83 | 84 | void swap(VisibleRangeDataModel& other) noexcept 85 | { 86 | using std::swap; 87 | swap(other.valueTree, valueTree); 88 | } 89 | 90 | private: 91 | void valueTreePropertyChanged(ValueTree&, const Identifier& property) override 92 | { 93 | if (property == IDs::totalRange) 94 | { 95 | totalRange.forceUpdateOfCachedValue(); 96 | listenerList.call([this](Listener& l) { l.totalRangeChanged(totalRange); }); 97 | } 98 | else if (property == IDs::visibleRange) 99 | { 100 | visibleRange.forceUpdateOfCachedValue(); 101 | listenerList.call([this](Listener& l) { l.visibleRangeChanged(visibleRange); }); 102 | } 103 | } 104 | 105 | void valueTreeChildAdded(ValueTree&, ValueTree&) override { jassertfalse; } 106 | void valueTreeChildRemoved(ValueTree&, ValueTree&, int) override { jassertfalse; } 107 | void valueTreeChildOrderChanged(ValueTree&, int, int) override { jassertfalse; } 108 | void valueTreeParentChanged(ValueTree&) override { jassertfalse; } 109 | 110 | ValueTree valueTree; 111 | 112 | CachedValue> totalRange; 113 | CachedValue> visibleRange; 114 | 115 | ListenerList listenerList; 116 | }; 117 | 118 | namespace 119 | { 120 | void initialiseComboBoxWithConsecutiveIntegers(juce::Component& owner, 121 | ComboBox& comboBox, 122 | Label& label, 123 | int firstValue, 124 | int numValues, 125 | int valueToSelect) 126 | { 127 | for (auto i = 0; i < numValues; ++i) 128 | comboBox.addItem(String(i + firstValue), i + 1); 129 | 130 | comboBox.setSelectedId(valueToSelect - firstValue + 1); 131 | 132 | label.attachToComponent(&comboBox, true); 133 | owner.addAndMakeVisible(comboBox); 134 | } 135 | 136 | constexpr int controlHeight = 24; 137 | constexpr int controlSeparation = 6; 138 | 139 | } // namespace 140 | -------------------------------------------------------------------------------- /Source/DemoUtilities.h: -------------------------------------------------------------------------------- 1 | /* 2 | ============================================================================== 3 | 4 | This file is part of the JUCE examples. 5 | Copyright (c) 2020 - Raw Material Software Limited 6 | 7 | The code included in this file is provided under the terms of the ISC license 8 | http://www.isc.org/downloads/software-support-policy/isc-license. Permission 9 | To use, copy, modify, and/or distribute this software for any purpose with or 10 | without fee is hereby granted provided that the above copyright notice and 11 | this permission notice appear in all copies. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, 14 | WHETHER EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR 15 | PURPOSE, ARE DISCLAIMED. 16 | 17 | ============================================================================== 18 | */ 19 | 20 | #pragma once 21 | 22 | #include 23 | 24 | using juce::ADSR; 25 | using juce::AbstractFifo; 26 | using juce::AbstractFifo; 27 | using juce::AlertWindow; 28 | using juce::Array; 29 | using juce::AudioChannelSet; 30 | using juce::AudioFormatManager; 31 | using juce::AudioFormatReader; 32 | using juce::AudioParameterBool; 33 | using juce::AudioParameterChoice; 34 | using juce::AudioParameterFloat; 35 | using juce::AudioParameterInt; 36 | using juce::AudioPlayHead; 37 | using juce::AudioPluginFormat; 38 | using juce::AudioPluginFormatManager; 39 | using juce::AudioPluginInstance; 40 | using juce::AudioProcessor; 41 | using juce::AudioProcessorEditor; 42 | using juce::AudioProcessorEditorListener; 43 | using juce::AudioProcessorGraph; 44 | using juce::AudioProcessorParameter; 45 | using juce::AudioProcessorValueTreeState; 46 | using juce::AudioSampleBuffer; 47 | using juce::Base64; 48 | using juce::Button; 49 | using juce::CachedValue; 50 | using juce::ChangeBroadcaster; 51 | using juce::ChangeListener; 52 | using juce::CodeEditorComponent; 53 | using juce::Colour; 54 | using juce::ColourGradient; 55 | using juce::ComboBox; 56 | using juce::Drawable; 57 | using juce::File; 58 | using juce::FileBrowserComponent; 59 | using juce::FileChooser; 60 | using juce::FileDragAndDropTarget; 61 | using juce::FileInputStream; 62 | using juce::GenericScopedTryLock; 63 | using juce::Graphics; 64 | using juce::Identifier; 65 | using juce::Image; 66 | using juce::ImageCache; 67 | using juce::ImageFileFormat; 68 | using juce::InputStream; 69 | using juce::InputStream; 70 | using juce::Justification; 71 | using juce::KeyPress; 72 | using juce::KnownPluginList; 73 | using juce::Label; 74 | using juce::LagrangeInterpolator; 75 | using juce::Line; 76 | using juce::ListenerList; 77 | using juce::LocalisedStrings; 78 | using juce::LookAndFeel; 79 | using juce::LookAndFeel_V4; 80 | using juce::MPESynthesiser; 81 | using juce::MPESynthesiserBase; 82 | using juce::MPESynthesiserVoice; 83 | using juce::MPEZoneLayout; 84 | using juce::MemoryBlock; 85 | using juce::MemoryInputStream; 86 | using juce::MemoryOutputStream; 87 | using juce::MidiBuffer; 88 | using juce::MidiBufferIterator; 89 | using juce::MidiFile; 90 | using juce::MidiMessage; 91 | using juce::MidiMessageSequence; 92 | using juce::ModifierKeys; 93 | using juce::MouseCursor; 94 | using juce::MouseEvent; 95 | using juce::MouseListener; 96 | using juce::MPENote; 97 | using juce::NormalisableRange; 98 | using juce::NotificationType; 99 | using juce::OwnedArray; 100 | using juce::Path; 101 | using juce::PluginDescription; 102 | using juce::Random; 103 | using juce::Range; 104 | using juce::RangedAudioParameter; 105 | using juce::RectangleList; 106 | using juce::ReferenceCountedArray; 107 | using juce::ReferenceCountedObject; 108 | using juce::ReferenceCountedObjectPtr; 109 | using juce::Slider; 110 | using juce::SmoothedValue; 111 | using juce::SpinLock; 112 | using juce::String; 113 | using juce::StringArray; 114 | using juce::TabbedButtonBar; 115 | using juce::TabbedComponent; 116 | using juce::TextButton; 117 | using juce::Time; 118 | using juce::Timer; 119 | using juce::ToggleButton; 120 | using juce::UndoManager; 121 | using juce::ValueTree; 122 | using juce::WildcardFileFilter; 123 | using juce::dontSendNotification; 124 | using juce::ignoreUnused; 125 | using juce::int64; 126 | using juce::jmax; 127 | using juce::jmin; 128 | using juce::roundFloatToInt; 129 | using juce::roundToInt; 130 | using juce::uint8; 131 | 132 | 133 | #ifndef PIP_DEMO_UTILITIES_INCLUDED 134 | #define PIP_DEMO_UTILITIES_INCLUDED 1 135 | #endif 136 | 137 | //============================================================================== 138 | /* 139 | This file contains a bunch of miscellaneous utilities that are 140 | used by the various demos. 141 | */ 142 | 143 | //============================================================================== 144 | inline juce::Colour getRandomColour (float brightness) noexcept 145 | { 146 | return Colour::fromHSV (Random::getSystemRandom().nextFloat(), 0.5f, brightness, 1.0f); 147 | } 148 | 149 | inline juce::Colour getRandomBrightColour() noexcept { return getRandomColour (0.8f); } 150 | inline juce::Colour getRandomDarkColour() noexcept { return getRandomColour (0.3f); } 151 | 152 | inline juce::Colour getUIColourIfAvailable (LookAndFeel_V4::ColourScheme::UIColour uiColour, Colour fallback = Colour (0xff4d4d4d)) noexcept 153 | { 154 | if (auto* v4 = dynamic_cast (&LookAndFeel::getDefaultLookAndFeel())) 155 | return v4->getCurrentColourScheme().getUIColour (uiColour); 156 | 157 | return fallback; 158 | } 159 | 160 | inline File getExamplesDirectory() noexcept 161 | { 162 | #ifdef PIP_JUCE_EXAMPLES_DIRECTORY 163 | MemoryOutputStream mo; 164 | 165 | auto success = Base64::convertFromBase64 (mo, JUCE_STRINGIFY (PIP_JUCE_EXAMPLES_DIRECTORY)); 166 | ignoreUnused (success); 167 | jassert (success); 168 | 169 | return mo.toString(); 170 | #elif defined PIP_JUCE_EXAMPLES_DIRECTORY_STRING 171 | return File { CharPointer_UTF8 { PIP_JUCE_EXAMPLES_DIRECTORY_STRING } }; 172 | #else 173 | auto currentFile = File::getSpecialLocation (File::SpecialLocationType::currentApplicationFile); 174 | auto exampleDir = currentFile.getParentDirectory().getChildFile ("examples"); 175 | 176 | if (exampleDir.exists()) 177 | return exampleDir; 178 | 179 | // keep track of the number of parent directories so we don't go on endlessly 180 | for (int numTries = 0; numTries < 15; ++numTries) 181 | { 182 | if (currentFile.getFileName() == "examples") 183 | return currentFile; 184 | 185 | const auto sibling = currentFile.getSiblingFile ("examples"); 186 | 187 | if (sibling.exists()) 188 | return sibling; 189 | 190 | currentFile = currentFile.getParentDirectory(); 191 | } 192 | 193 | return currentFile; 194 | #endif 195 | } 196 | 197 | inline std::unique_ptr createAssetInputStream (const char* resourcePath) 198 | { 199 | #if JUCE_ANDROID 200 | ZipFile apkZip (File::getSpecialLocation (File::invokedExecutableFile)); 201 | return std::unique_ptr (apkZip.createStreamForEntry (apkZip.getIndexOfFileName ("assets/" + String (resourcePath)))); 202 | #else 203 | #if JUCE_IOS 204 | auto assetsDir = File::getSpecialLocation (File::currentExecutableFile) 205 | .getParentDirectory().getChildFile ("Assets"); 206 | #elif JUCE_MAC 207 | auto assetsDir = File::getSpecialLocation (File::currentExecutableFile) 208 | .getParentDirectory().getParentDirectory().getChildFile ("Resources").getChildFile ("Assets"); 209 | 210 | if (! assetsDir.exists()) 211 | assetsDir = getExamplesDirectory().getChildFile ("Assets"); 212 | #else 213 | auto assetsDir = getExamplesDirectory().getChildFile ("Assets"); 214 | #endif 215 | 216 | auto resourceFile = assetsDir.getChildFile (resourcePath); 217 | jassert (resourceFile.existsAsFile()); 218 | 219 | return resourceFile.createInputStream(); 220 | #endif 221 | } 222 | 223 | inline Image getImageFromAssets (const char* assetName) 224 | { 225 | auto hashCode = (String (assetName) + "@juce_demo_assets").hashCode64(); 226 | auto img = ImageCache::getFromHashCode (hashCode); 227 | 228 | if (img.isNull()) 229 | { 230 | std::unique_ptr juceIconStream (createAssetInputStream (assetName)); 231 | 232 | if (juceIconStream == nullptr) 233 | return {}; 234 | 235 | img = ImageFileFormat::loadFrom (*juceIconStream); 236 | 237 | ImageCache::addImageToCache (img, hashCode); 238 | } 239 | 240 | return img; 241 | } 242 | 243 | inline String loadEntireAssetIntoString (const char* assetName) 244 | { 245 | std::unique_ptr input (createAssetInputStream (assetName)); 246 | 247 | if (input == nullptr) 248 | return {}; 249 | 250 | return input->readString(); 251 | } 252 | 253 | //============================================================================== 254 | inline Path getJUCELogoPath() 255 | { 256 | return Drawable::parseSVGPath ( 257 | "M250,301.3c-37.2,0-67.5-30.3-67.5-67.5s30.3-67.5,67.5-67.5s67.5,30.3,67.5,67.5S287.2,301.3,250,301.3zM250,170.8c-34.7,0-63,28.3-63,63s28.3,63,63,63s63-28.3,63-63S284.7,170.8,250,170.8z" 258 | "M247.8,180.4c0-2.3-1.8-4.1-4.1-4.1c-0.2,0-0.3,0-0.5,0c-10.6,1.2-20.6,5.4-29,12c-1,0.8-1.5,1.8-1.6,2.9c-0.1,1.2,0.4,2.3,1.3,3.2l32.5,32.5c0.5,0.5,1.4,0.1,1.4-0.6V180.4z" 259 | "M303.2,231.6c1.2,0,2.3-0.4,3.1-1.2c0.9-0.9,1.3-2.1,1.1-3.3c-1.2-10.6-5.4-20.6-12-29c-0.8-1-1.9-1.6-3.2-1.6c-1.1,0-2.1,0.5-3,1.3l-32.5,32.5c-0.5,0.5-0.1,1.4,0.6,1.4L303.2,231.6z" 260 | "M287.4,191.3c-0.1-1.1-0.6-2.2-1.6-2.9c-8.4-6.6-18.4-10.8-29-12c-0.2,0-0.3,0-0.5,0c-2.3,0-4.1,1.9-4.1,4.1v46c0,0.7,0.9,1.1,1.4,0.6l32.5-32.5C287,193.6,287.5,192.5,287.4,191.3z" 261 | "M252.2,287.2c0,2.3,1.8,4.1,4.1,4.1c0.2,0,0.3,0,0.5,0c10.6-1.2,20.6-5.4,29-12c1-0.8,1.5-1.8,1.6-2.9c0.1-1.2-0.4-2.3-1.3-3.2l-32.5-32.5c-0.5-0.5-1.4-0.1-1.4,0.6V287.2z" 262 | "M292.3,271.2L292.3,271.2c1.2,0,2.4-0.6,3.2-1.6c6.6-8.4,10.8-18.4,12-29c0.1-1.2-0.3-2.4-1.1-3.3c-0.8-0.8-1.9-1.2-3.1-1.2l-45.9,0c-0.7,0-1.1,0.9-0.6,1.4l32.5,32.5C290.2,270.8,291.2,271.2,292.3,271.2z" 263 | "M207.7,196.4c-1.2,0-2.4,0.6-3.2,1.6c-6.6,8.4-10.8,18.4-12,29c-0.1,1.2,0.3,2.4,1.1,3.3c0.8,0.8,1.9,1.2,3.1,1.2l45.9,0c0.7,0,1.1-0.9,0.6-1.4l-32.5-32.5C209.8,196.8,208.8,196.4,207.7,196.4z" 264 | "M242.6,236.1l-45.9,0c-1.2,0-2.3,0.4-3.1,1.2c-0.9,0.9-1.3,2.1-1.1,3.3c1.2,10.6,5.4,20.6,12,29c0.8,1,1.9,1.6,3.2,1.6c1.1,0,2.1-0.5,3-1.3c0,0,0,0,0,0l32.5-32.5C243.7,236.9,243.4,236.1,242.6,236.1z" 265 | "M213.8,273.1L213.8,273.1c-0.9,0.9-1.3,2-1.3,3.2c0.1,1.1,0.6,2.2,1.6,2.9c8.4,6.6,18.4,10.8,29,12c0.2,0,0.3,0,0.5,0h0c1.2,0,2.3-0.5,3.1-1.4c0.7-0.8,1-1.8,1-2.9v-45.9c0-0.7-0.9-1.1-1.4-0.6l-13.9,13.9L213.8,273.1z" 266 | "M197.2,353c-4.1,0-7.4-1.5-10.4-5.4l4-3.5c2,2.6,3.9,3.6,6.4,3.6c4.4,0,7.4-3.3,7.4-8.3v-24.7h5.6v24.7C210.2,347.5,204.8,353,197.2,353z" 267 | "M232.4,353c-8.1,0-15-6-15-15.8v-22.5h5.6v22.2c0,6.6,3.9,10.8,9.5,10.8c5.6,0,9.5-4.3,9.5-10.8v-22.2h5.6v22.5C247.5,347,240.5,353,232.4,353z" 268 | "M272,353c-10.8,0-19.5-8.6-19.5-19.3c0-10.8,8.8-19.3,19.5-19.3c4.8,0,9,1.6,12.3,4.4l-3.3,4.1c-3.4-2.4-5.7-3.2-8.9-3.2c-7.7,0-13.8,6.2-13.8,14.1c0,7.9,6.1,14.1,13.8,14.1c3.1,0,5.6-1,8.8-3.2l3.3,4.1C280.1,351.9,276.4,353,272,353z" 269 | "M290.4,352.5v-37.8h22.7v5H296v11.2h16.5v5H296v11.6h17.2v5H290.4z"); 270 | } 271 | 272 | //============================================================================== 273 | #if JUCE_MODULE_AVAILABLE_juce_gui_extra 274 | inline CodeEditorComponent::ColourScheme getDarkCodeEditorColourScheme() 275 | { 276 | struct Type 277 | { 278 | const char* name; 279 | juce::uint32 colour; 280 | }; 281 | 282 | const Type types[] = 283 | { 284 | { "Error", 0xffe60000 }, 285 | { "Comment", 0xff72d20c }, 286 | { "Keyword", 0xffee6f6f }, 287 | { "Operator", 0xffc4eb19 }, 288 | { "Identifier", 0xffcfcfcf }, 289 | { "Integer", 0xff42c8c4 }, 290 | { "Float", 0xff885500 }, 291 | { "String", 0xffbc45dd }, 292 | { "Bracket", 0xff058202 }, 293 | { "Punctuation", 0xffcfbeff }, 294 | { "Preprocessor Text", 0xfff8f631 } 295 | }; 296 | 297 | CodeEditorComponent::ColourScheme cs; 298 | 299 | for (auto& t : types) 300 | cs.set (t.name, Colour (t.colour)); 301 | 302 | return cs; 303 | } 304 | 305 | inline CodeEditorComponent::ColourScheme getLightCodeEditorColourScheme() 306 | { 307 | struct Type 308 | { 309 | const char* name; 310 | juce::uint32 colour; 311 | }; 312 | 313 | const Type types[] = 314 | { 315 | { "Error", 0xffcc0000 }, 316 | { "Comment", 0xff00aa00 }, 317 | { "Keyword", 0xff0000cc }, 318 | { "Operator", 0xff225500 }, 319 | { "Identifier", 0xff000000 }, 320 | { "Integer", 0xff880000 }, 321 | { "Float", 0xff885500 }, 322 | { "String", 0xff990099 }, 323 | { "Bracket", 0xff000055 }, 324 | { "Punctuation", 0xff004400 }, 325 | { "Preprocessor Text", 0xff660000 } 326 | }; 327 | 328 | CodeEditorComponent::ColourScheme cs; 329 | 330 | for (auto& t : types) 331 | cs.set (t.name, Colour (t.colour)); 332 | 333 | return cs; 334 | } 335 | #endif 336 | 337 | //============================================================================== 338 | // This is basically a sawtooth wave generator - maps a value that bounces between 339 | // 0.0 and 1.0 at a random speed 340 | struct BouncingNumber 341 | { 342 | BouncingNumber() 343 | : speed (0.0004 + 0.0007 * Random::getSystemRandom().nextDouble()), 344 | phase (Random::getSystemRandom().nextDouble()) 345 | { 346 | } 347 | 348 | float getValue() const 349 | { 350 | double v = fmod (phase + speed * Time::getMillisecondCounterHiRes(), 2.0); 351 | return (float) (v >= 1.0 ? (2.0 - v) : v); 352 | } 353 | 354 | protected: 355 | double speed, phase; 356 | }; 357 | 358 | struct SlowerBouncingNumber : public BouncingNumber 359 | { 360 | SlowerBouncingNumber() 361 | { 362 | speed *= 0.3; 363 | } 364 | }; 365 | -------------------------------------------------------------------------------- /Source/FileAudioFormatReaderFactory.h: -------------------------------------------------------------------------------- 1 | /* 2 | ============================================================================== 3 | This file is part of the JUCE examples. 4 | Copyright (c) 2020 - Raw Material Software Limited 5 | The code included in this file is provided under the terms of the ISC license 6 | http://www.isc.org/downloads/software-support-policy/isc-license. Permission 7 | To use, copy, modify, and/or distribute this software for any purpose with or 8 | without fee is hereby granted provided that the above copyright notice and 9 | this permission notice appear in all copies. 10 | THE SOFTWARE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, 11 | WHETHER EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR 12 | PURPOSE, ARE DISCLAIMED. 13 | ============================================================================== 14 | */ 15 | 16 | #pragma once 17 | 18 | inline std::unique_ptr makeAudioFormatReader(AudioFormatManager& manager, 19 | const void* sampleData, 20 | size_t dataSize) 21 | { 22 | return std::unique_ptr(manager.createReaderFor(std::make_unique(sampleData, 23 | dataSize, 24 | false))); 25 | } 26 | 27 | inline std::unique_ptr makeAudioFormatReader(AudioFormatManager& manager, 28 | const File& file) 29 | { 30 | return std::unique_ptr(manager.createReaderFor(file)); 31 | } 32 | 33 | 34 | //============================================================================== 35 | class FileAudioFormatReaderFactory : public AudioFormatReaderFactory 36 | { 37 | public: 38 | explicit FileAudioFormatReaderFactory(File fileIn) 39 | : file(std::move(fileIn)) 40 | {} 41 | 42 | std::unique_ptr make(AudioFormatManager& manager) const override 43 | { 44 | return makeAudioFormatReader(manager, file); 45 | } 46 | 47 | std::unique_ptr clone() const override 48 | { 49 | return std::unique_ptr(new FileAudioFormatReaderFactory(*this)); 50 | } 51 | 52 | private: 53 | File file; 54 | }; -------------------------------------------------------------------------------- /Source/MPESamplerSound.h: -------------------------------------------------------------------------------- 1 | /* 2 | ============================================================================== 3 | This file is part of the JUCE examples. 4 | Copyright (c) 2020 - Raw Material Software Limited 5 | The code included in this file is provided under the terms of the ISC license 6 | http://www.isc.org/downloads/software-support-policy/isc-license. Permission 7 | To use, copy, modify, and/or distribute this software for any purpose with or 8 | without fee is hereby granted provided that the above copyright notice and 9 | this permission notice appear in all copies. 10 | THE SOFTWARE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, 11 | WHETHER EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR 12 | PURPOSE, ARE DISCLAIMED. 13 | ============================================================================== 14 | */ 15 | 16 | #pragma once 17 | 18 | //============================================================================== 19 | // A class which contains all the information related to sample-playback, such 20 | // as sample data, loop points, and loop kind. 21 | // It is expected that multiple sampler voices will maintain pointers to a 22 | // single instance of this class, to avoid redundant duplication of sample 23 | // data in memory. 24 | class MPESamplerSound final 25 | { 26 | public: 27 | void setSample(std::unique_ptr value) 28 | { 29 | sample = std::move(value); 30 | setLoopPointsInSeconds(loopPoints); 31 | } 32 | 33 | Sample* getSample() const 34 | { 35 | return sample.get(); 36 | } 37 | 38 | void setLoopPointsInSeconds(Range value) 39 | { 40 | loopPoints = sample == nullptr ? value 41 | : Range(0, sample->getLength() / sample->getSampleRate()) 42 | .constrainRange(value); 43 | } 44 | 45 | Range getLoopPointsInSeconds() const 46 | { 47 | return loopPoints; 48 | } 49 | 50 | void setCentreFrequencyInHz(double centre) 51 | { 52 | centreFrequencyInHz = centre; 53 | } 54 | 55 | double getCentreFrequencyInHz() const 56 | { 57 | return centreFrequencyInHz; 58 | } 59 | 60 | void setLoopMode(LoopMode type) 61 | { 62 | loopMode = type; 63 | } 64 | 65 | LoopMode getLoopMode() const 66 | { 67 | return loopMode; 68 | } 69 | 70 | private: 71 | std::unique_ptr sample; 72 | double centreFrequencyInHz{ 440.0 }; 73 | Range loopPoints; 74 | LoopMode loopMode{ LoopMode::none }; 75 | }; 76 | -------------------------------------------------------------------------------- /Source/MPESamplerVoice.h: -------------------------------------------------------------------------------- 1 | /* 2 | ============================================================================== 3 | This file is part of the JUCE examples. 4 | Copyright (c) 2020 - Raw Material Software Limited 5 | The code included in this file is provided under the terms of the ISC license 6 | http://www.isc.org/downloads/software-support-policy/isc-license. Permission 7 | To use, copy, modify, and/or distribute this software for any purpose with or 8 | without fee is hereby granted provided that the above copyright notice and 9 | this permission notice appear in all copies. 10 | THE SOFTWARE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, 11 | WHETHER EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR 12 | PURPOSE, ARE DISCLAIMED. 13 | ============================================================================== 14 | */ 15 | 16 | #pragma once 17 | 18 | #include "MPESamplerSound.h" 19 | 20 | class MPESamplerVoice : public MPESynthesiserVoice 21 | { 22 | public: 23 | explicit MPESamplerVoice(std::shared_ptr sound, AudioProcessorValueTreeState& vts) 24 | : samplerSound(std::move(sound)), 25 | valueTreeState(vts) 26 | { 27 | jassert(samplerSound != nullptr); 28 | 29 | m_Buffer.setSize(2, 1, false, true, false); 30 | } 31 | 32 | void setCurrentSampleRate(double newRate) override { 33 | 34 | MPESynthesiserVoice::setCurrentSampleRate(newRate); 35 | 36 | if (newRate <= 0) { 37 | return; 38 | } 39 | 40 | ampEnv.setSampleRate(newRate); 41 | filterEnv.setSampleRate(newRate); 42 | 43 | int numChannels = 2; 44 | int samplesPerBlock = 1; 45 | juce::dsp::ProcessSpec spec{ newRate, static_cast (samplesPerBlock), static_cast (numChannels) }; 46 | m_Filter.prepare(spec); 47 | } 48 | 49 | void noteStarted() override 50 | { 51 | jassert(currentlyPlayingNote.isValid()); 52 | jassert(currentlyPlayingNote.keyState == MPENote::keyDown 53 | || currentlyPlayingNote.keyState == MPENote::keyDownAndSustained); 54 | 55 | level.setTargetValue (currentlyPlayingNote.noteOnVelocity.asUnsignedFloat()); 56 | frequency.setTargetValue(currentlyPlayingNote.getFrequencyInHertz()); 57 | 58 | auto loopPoints = samplerSound->getLoopPointsInSeconds(); 59 | loopBegin.setTargetValue(loopPoints.getStart() * samplerSound->getSample()->getSampleRate()); 60 | loopEnd.setTargetValue(loopPoints.getEnd() * samplerSound->getSample()->getSampleRate()); 61 | 62 | for (auto smoothed : { &frequency, &loopBegin, &loopEnd }) 63 | smoothed->reset(currentSampleRate, smoothingLengthInSeconds); 64 | 65 | previousPressure = currentlyPlayingNote.pressure.asUnsignedFloat(); 66 | currentSamplePos = 0.0; 67 | tailOff = 0.0; 68 | 69 | ampEnv.noteOn(); 70 | filterEnv.noteOn(); 71 | } 72 | 73 | void noteStopped(bool allowTailOff) override 74 | { 75 | jassert(currentlyPlayingNote.keyState == MPENote::off); 76 | 77 | ampEnv.noteOff(); 78 | filterEnv.noteOff(); 79 | 80 | if (allowTailOff && juce::approximatelyEqual (tailOff, 0.0)) 81 | tailOff = 1.0; 82 | else 83 | stopNote(); 84 | } 85 | 86 | void notePressureChanged() override 87 | { 88 | const auto currentPressure = static_cast (currentlyPlayingNote.pressure.asUnsignedFloat()); 89 | const auto deltaPressure = currentPressure - previousPressure; 90 | level.setTargetValue (juce::jlimit (0.0, 1.0, level.getCurrentValue() + deltaPressure)); 91 | previousPressure = currentPressure; } 92 | 93 | void notePitchbendChanged() override 94 | { 95 | frequency.setTargetValue(currentlyPlayingNote.getFrequencyInHertz()); 96 | } 97 | 98 | void noteTimbreChanged() override {} 99 | void noteKeyStateChanged() override {} 100 | 101 | void renderNextBlock(juce::AudioBuffer& outputBuffer, 102 | int startSample, 103 | int numSamples) override 104 | { 105 | render(outputBuffer, startSample, numSamples); 106 | } 107 | 108 | void renderNextBlock(juce::AudioBuffer& outputBuffer, 109 | int startSample, 110 | int numSamples) override 111 | { 112 | render(outputBuffer, startSample, numSamples); 113 | } 114 | 115 | double getCurrentSamplePosition() const 116 | { 117 | return currentSamplePos; 118 | } 119 | 120 | void sampleReaderChanged(std::shared_ptr) {} 121 | void centreFrequencyHzChanged(double) {} 122 | void loopModeChanged(LoopMode) {} 123 | void loopPointsSecondsChanged(Range) {} 124 | 125 | void updateParams() { 126 | updateAmpEnv(); 127 | updateFilter(); 128 | updateFilterEnv(); 129 | } 130 | 131 | private: 132 | 133 | void updateAmpEnv() { 134 | 135 | auto params = ampEnv.getParameters(); 136 | params.attack = *valueTreeState.getRawParameterValue(IDs::ampEnvAttack) * .001; 137 | params.decay = *valueTreeState.getRawParameterValue(IDs::ampEnvDecay) * .001; 138 | params.sustain = *valueTreeState.getRawParameterValue(IDs::ampEnvSustain); 139 | params.release = *valueTreeState.getRawParameterValue(IDs::ampEnvRelease) *.001; 140 | ampEnv.setParameters(params); 141 | 142 | // todo: update mod amount 143 | } 144 | 145 | void updateFilter() { 146 | filterCutoff = *(valueTreeState.getRawParameterValue(IDs::filterCutoff)); 147 | } 148 | 149 | void updateFilterEnv() { 150 | 151 | auto params = filterEnv.getParameters(); 152 | params.attack = *valueTreeState.getRawParameterValue(IDs::filterEnvAttack) *.001; 153 | params.decay = *valueTreeState.getRawParameterValue(IDs::filterEnvDecay) *.001; 154 | params.sustain = *valueTreeState.getRawParameterValue(IDs::filterEnvSustain); 155 | params.release = *valueTreeState.getRawParameterValue(IDs::filterEnvRelease) *.001; 156 | filterEnv.setParameters(params); 157 | 158 | filterCutoffModAmt = *valueTreeState.getRawParameterValue(IDs::filterEnvModAmt); 159 | } 160 | 161 | 162 | template 163 | void render(juce::AudioBuffer& outputBuffer, int startSample, int numSamples) 164 | { 165 | jassert(samplerSound->getSample() != nullptr); 166 | 167 | updateParams(); // NB: important line 168 | 169 | auto loopPoints = samplerSound->getLoopPointsInSeconds(); 170 | loopBegin.setTargetValue(loopPoints.getStart() * samplerSound->getSample()->getSampleRate()); 171 | loopEnd.setTargetValue(loopPoints.getEnd() * samplerSound->getSample()->getSampleRate()); 172 | 173 | auto& data = samplerSound->getSample()->getBuffer(); 174 | 175 | auto inL = data.getReadPointer(0); 176 | auto inR = data.getNumChannels() > 1 ? data.getReadPointer(1) : nullptr; 177 | 178 | auto outL = outputBuffer.getWritePointer(0, startSample); 179 | 180 | if (outL == nullptr) 181 | return; 182 | 183 | auto outR = outputBuffer.getNumChannels() > 1 ? outputBuffer.getWritePointer(1, startSample) 184 | : nullptr; 185 | 186 | size_t writePos = 0; 187 | 188 | while (--numSamples >= 0 && renderNextSample(inL, inR, outL, outR, writePos)) 189 | writePos += 1; 190 | } 191 | 192 | template 193 | bool renderNextSample(const float* inL, 194 | const float* inR, 195 | Element* outL, 196 | Element* outR, 197 | size_t writePos) 198 | { 199 | auto currentFrequency = frequency.getNextValue(); // based on note pitch 200 | auto currentLoopBegin = loopBegin.getNextValue(); 201 | auto currentLoopEnd = loopEnd.getNextValue(); 202 | 203 | bool ampActive = *valueTreeState.getRawParameterValue(IDs::ampActive); 204 | float ampEnvLast = ampEnv.getNextSample(); 205 | 206 | if (ampActive && isTailingOff()) 207 | { 208 | if (ampEnvLast < 0.001) 209 | { 210 | stopNote(); 211 | return false; 212 | } 213 | } 214 | 215 | auto pos = (int)currentSamplePos; 216 | auto nextPos = pos + 1; 217 | auto alpha = (Element)(currentSamplePos - pos); 218 | auto invAlpha = 1.0f - alpha; 219 | 220 | // Very simple linear interpolation here because the Sampler class should have already upsampled. 221 | auto l = static_cast ((inL[pos] * invAlpha + inL[nextPos] * alpha)); 222 | auto r = static_cast ((inR != nullptr) ? (inR[pos] * invAlpha + inR[nextPos] * alpha) 223 | : l); 224 | 225 | m_Buffer.setSample(0, 0, l); 226 | m_Buffer.setSample(1, 0, r); 227 | 228 | // apply velocity-> gain 229 | m_Buffer.applyGain(currentlyPlayingNote.noteOnVelocity.asUnsignedFloat()); 230 | 231 | // apply amplitude 232 | if (ampActive) { 233 | m_Buffer.applyGain(ampEnvLast); 234 | } 235 | 236 | float cutoff = filterCutoff + filterCutoffModAmt*filterEnv.getNextSample(); 237 | cutoff = fmax(40., fmin(20000., cutoff)); 238 | 239 | float q_val = 0.70710678118; 240 | *m_Filter.state = *juce::dsp::IIR::Coefficients::makeLowPass(currentSampleRate, cutoff, q_val); 241 | 242 | if (*valueTreeState.getRawParameterValue(IDs::filterActive)) { 243 | // apply low pass filter 244 | juce::dsp::AudioBlock block(m_Buffer); 245 | juce::dsp::ProcessContextReplacing context(block); 246 | m_Filter.process(context); 247 | } 248 | 249 | if (outR != nullptr) 250 | { 251 | outL[writePos] += m_Buffer.getSample(0, 0); 252 | outR[writePos] += m_Buffer.getSample(1, 0); 253 | } 254 | else 255 | { 256 | outL[writePos] += (m_Buffer.getSample(0, 0) + m_Buffer.getSample(1, 0)) * 0.5f; 257 | } 258 | 259 | std::tie(currentSamplePos, currentDirection) = getNextState(currentFrequency, 260 | currentLoopBegin, 261 | currentLoopEnd); 262 | 263 | if (currentSamplePos > samplerSound->getSample()->getLength()) 264 | { 265 | stopNote(); 266 | return false; 267 | } 268 | 269 | return true; 270 | } 271 | 272 | double getSampleValue() const; 273 | 274 | bool isTailingOff() const 275 | { 276 | return ! juce::approximatelyEqual (tailOff, 0.0); 277 | } 278 | 279 | void stopNote() 280 | { 281 | 282 | // todo: are these necessary? 283 | if (ampEnv.isActive()) { 284 | ampEnv.reset(); 285 | } 286 | if (filterEnv.isActive()) { 287 | filterEnv.reset(); 288 | } 289 | 290 | m_Filter.reset(); 291 | 292 | clearCurrentNote(); 293 | currentSamplePos = 0.0; 294 | } 295 | 296 | enum class Direction 297 | { 298 | forward, 299 | backward 300 | }; 301 | 302 | std::tuple getNextState(double freq, 303 | double begin, 304 | double end) const 305 | { 306 | auto nextPitchRatio = (freq / samplerSound->getCentreFrequencyInHz()) * samplerSound->getSample()->getSampleRate() / this->currentSampleRate; 307 | 308 | auto nextSamplePos = currentSamplePos; 309 | auto nextDirection = currentDirection; 310 | 311 | // Move the current sample pos in the correct direction 312 | switch (currentDirection) 313 | { 314 | case Direction::forward: 315 | nextSamplePos += nextPitchRatio; 316 | break; 317 | 318 | case Direction::backward: 319 | nextSamplePos -= nextPitchRatio; 320 | break; 321 | 322 | default: 323 | break; 324 | } 325 | 326 | // Update current sample position, taking loop mode into account 327 | // If the loop mode was changed while we were travelling backwards, deal 328 | // with it gracefully. 329 | if (nextDirection == Direction::backward && nextSamplePos < begin) 330 | { 331 | nextSamplePos = begin; 332 | nextDirection = Direction::forward; 333 | 334 | return std::tuple(nextSamplePos, nextDirection); 335 | } 336 | 337 | if (samplerSound->getLoopMode() == LoopMode::none) 338 | return std::tuple(nextSamplePos, nextDirection); 339 | 340 | if (nextDirection == Direction::forward && end < nextSamplePos && !isTailingOff()) 341 | { 342 | if (samplerSound->getLoopMode() == LoopMode::forward) 343 | nextSamplePos = begin; 344 | else if (samplerSound->getLoopMode() == LoopMode::pingpong) 345 | { 346 | nextSamplePos = end; 347 | nextDirection = Direction::backward; 348 | } 349 | } 350 | return std::tuple(nextSamplePos, nextDirection); 351 | } 352 | 353 | AudioProcessorValueTreeState& valueTreeState; // from the SamplerAudioProcessor 354 | 355 | std::shared_ptr samplerSound; 356 | SmoothedValue level { 0 }; 357 | SmoothedValue frequency{ 0 }; 358 | SmoothedValue loopBegin; 359 | SmoothedValue loopEnd; 360 | double previousPressure { 0 }; 361 | double currentSamplePos{ 0 }; 362 | double tailOff{ 0 }; 363 | Direction currentDirection{ Direction::forward }; 364 | double smoothingLengthInSeconds{ 0.01 }; 365 | 366 | ADSR ampEnv; 367 | 368 | ADSR filterEnv; 369 | double filterCutoff = 20000.; 370 | double filterCutoffModAmt = 0.; 371 | 372 | juce::dsp::ProcessorDuplicator, juce::dsp::IIR::Coefficients> m_Filter; 373 | juce::AudioBuffer m_Buffer; 374 | }; 375 | -------------------------------------------------------------------------------- /Source/Main.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | ============================================================================== 3 | This file is part of the JUCE examples. 4 | Copyright (c) 2020 - Raw Material Software Limited 5 | The code included in this file is provided under the terms of the ISC license 6 | http://www.isc.org/downloads/software-support-policy/isc-license. Permission 7 | To use, copy, modify, and/or distribute this software for any purpose with or 8 | without fee is hereby granted provided that the above copyright notice and 9 | this permission notice appear in all copies. 10 | THE SOFTWARE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, 11 | WHETHER EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR 12 | PURPOSE, ARE DISCLAIMED. 13 | ============================================================================== 14 | */ 15 | 16 | #include 17 | #include "SamplerAudioProcessor.h" 18 | 19 | //============================================================================== 20 | juce::AudioProcessor* JUCE_CALLTYPE createPluginFilter() 21 | { 22 | return new SamplerAudioProcessor(); 23 | } 24 | -------------------------------------------------------------------------------- /Source/MemoryAudioFormatReaderFactory.h: -------------------------------------------------------------------------------- 1 | /* 2 | ============================================================================== 3 | This file is part of the JUCE examples. 4 | Copyright (c) 2020 - Raw Material Software Limited 5 | The code included in this file is provided under the terms of the ISC license 6 | http://www.isc.org/downloads/software-support-policy/isc-license. Permission 7 | To use, copy, modify, and/or distribute this software for any purpose with or 8 | without fee is hereby granted provided that the above copyright notice and 9 | this permission notice appear in all copies. 10 | THE SOFTWARE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, 11 | WHETHER EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR 12 | PURPOSE, ARE DISCLAIMED. 13 | ============================================================================== 14 | */ 15 | 16 | #pragma once 17 | 18 | #include "Misc.h" 19 | #include "FileAudioFormatReaderFactory.h" 20 | 21 | class MemoryAudioFormatReaderFactory : public AudioFormatReaderFactory 22 | { 23 | public: 24 | MemoryAudioFormatReaderFactory(const void* sampleDataIn, size_t dataSizeIn) 25 | : sampleData(sampleDataIn), 26 | dataSize(dataSizeIn) 27 | {} 28 | 29 | std::unique_ptr make(AudioFormatManager& manager) const override 30 | { 31 | return makeAudioFormatReader(manager, sampleData, dataSize); 32 | } 33 | 34 | std::unique_ptr clone() const override 35 | { 36 | return std::unique_ptr(new MemoryAudioFormatReaderFactory(*this)); 37 | } 38 | 39 | private: 40 | const void* sampleData; 41 | size_t dataSize; 42 | }; -------------------------------------------------------------------------------- /Source/Misc.h: -------------------------------------------------------------------------------- 1 | /* 2 | ============================================================================== 3 | This file is part of the JUCE examples. 4 | Copyright (c) 2020 - Raw Material Software Limited 5 | The code included in this file is provided under the terms of the ISC license 6 | http://www.isc.org/downloads/software-support-policy/isc-license. Permission 7 | To use, copy, modify, and/or distribute this software for any purpose with or 8 | without fee is hereby granted provided that the above copyright notice and 9 | this permission notice appear in all copies. 10 | THE SOFTWARE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, 11 | WHETHER EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR 12 | PURPOSE, ARE DISCLAIMED. 13 | ============================================================================== 14 | */ 15 | 16 | #pragma once 17 | 18 | #include "DemoUtilities.h" 19 | 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | 30 | namespace IDs 31 | { 32 | 33 | #define DECLARE_ID(name) const juce::Identifier name (#name); 34 | 35 | DECLARE_ID(DATA_MODEL) 36 | DECLARE_ID(samplerVoice) 37 | DECLARE_ID(sampleReader) 38 | DECLARE_ID(centreFrequencyHz) 39 | DECLARE_ID(loopMode) 40 | DECLARE_ID(loopPointsSeconds) 41 | 42 | DECLARE_ID(centerNote) 43 | 44 | DECLARE_ID(ampActive) 45 | DECLARE_ID(ampEnvAttack) 46 | DECLARE_ID(ampEnvDecay) 47 | DECLARE_ID(ampEnvSustain) 48 | DECLARE_ID(ampEnvRelease) 49 | DECLARE_ID(ampEnvModAmt) 50 | 51 | DECLARE_ID(filterActive) 52 | DECLARE_ID(filterCutoff) 53 | 54 | DECLARE_ID(filterEnvAttack) 55 | DECLARE_ID(filterEnvDecay) 56 | DECLARE_ID(filterEnvSustain) 57 | DECLARE_ID(filterEnvRelease) 58 | DECLARE_ID(filterEnvModAmt) 59 | 60 | DECLARE_ID(MPE_SETTINGS) 61 | DECLARE_ID(synthVoices) 62 | DECLARE_ID(voiceStealingEnabled) 63 | DECLARE_ID(legacyModeEnabled) 64 | DECLARE_ID(mpeZoneLayout) 65 | DECLARE_ID(legacyFirstChannel) 66 | DECLARE_ID(legacyLastChannel) 67 | DECLARE_ID(legacyPitchbendRange) 68 | 69 | DECLARE_ID(VISIBLE_RANGE) 70 | DECLARE_ID(totalRange) 71 | DECLARE_ID(visibleRange) 72 | 73 | #undef DECLARE_ID 74 | 75 | } // namespace IDs 76 | 77 | enum class LoopMode 78 | { 79 | none, 80 | forward, 81 | pingpong 82 | }; 83 | 84 | // We want to send type-erased commands to the audio thread, but we also 85 | // want those commands to contain move-only resources, so that we can 86 | // construct resources on the gui thread, and then transfer ownership 87 | // cheaply to the audio thread. We can't do this with std::function 88 | // because it enforces that functions are copy-constructible. 89 | // Therefore, we use a very simple templated type-eraser here. 90 | template 91 | struct Command 92 | { 93 | virtual ~Command() noexcept = default; 94 | virtual void run(Proc& proc) = 0; 95 | }; 96 | 97 | template 98 | class TemplateCommand : public Command, 99 | private Func 100 | { 101 | public: 102 | template 103 | explicit TemplateCommand(FuncPrime&& funcPrime) 104 | : Func(std::forward(funcPrime)) 105 | {} 106 | 107 | void run(Proc& proc) override { (*this) (proc); } 108 | }; 109 | 110 | class AudioFormatReaderFactory 111 | { 112 | public: 113 | AudioFormatReaderFactory() = default; 114 | AudioFormatReaderFactory (const AudioFormatReaderFactory&) = default; 115 | AudioFormatReaderFactory (AudioFormatReaderFactory&&) = default; 116 | AudioFormatReaderFactory& operator= (const AudioFormatReaderFactory&) = default; 117 | AudioFormatReaderFactory& operator= (AudioFormatReaderFactory&&) = default; 118 | 119 | virtual ~AudioFormatReaderFactory() noexcept = default; 120 | 121 | virtual std::unique_ptr make(AudioFormatManager&) const = 0; 122 | virtual std::unique_ptr clone() const = 0; 123 | }; 124 | 125 | template 126 | class ReferenceCountingAdapter : public ReferenceCountedObject 127 | { 128 | public: 129 | template 130 | explicit ReferenceCountingAdapter(Args&&... args) 131 | : contents(std::forward(args)...) 132 | {} 133 | 134 | const Contents& get() const 135 | { 136 | return contents; 137 | } 138 | 139 | Contents& get() 140 | { 141 | return contents; 142 | } 143 | 144 | private: 145 | Contents contents; 146 | }; 147 | 148 | namespace juce { 149 | 150 | template 151 | std::unique_ptr> 152 | make_reference_counted(Args&&... args) 153 | { 154 | auto adapter = new ReferenceCountingAdapter(std::forward(args)...); 155 | return std::unique_ptr>(adapter); 156 | } 157 | 158 | template<> 159 | struct VariantConverter 160 | { 161 | static LoopMode fromVar(const var& v) 162 | { 163 | return static_cast (int(v)); 164 | } 165 | 166 | static var toVar(LoopMode loopMode) 167 | { 168 | return static_cast (loopMode); 169 | } 170 | }; 171 | 172 | template 173 | struct GenericVariantConverter 174 | { 175 | static Wrapped fromVar(const var& v) 176 | { 177 | auto cast = dynamic_cast*> (v.getObject()); 178 | jassert(cast != nullptr); 179 | return cast->get(); 180 | } 181 | 182 | static var toVar(Wrapped range) 183 | { 184 | return { make_reference_counted(std::move(range)).release() }; 185 | } 186 | }; 187 | 188 | template 189 | struct VariantConverter> : GenericVariantConverter> {}; 190 | 191 | template<> 192 | struct VariantConverter : GenericVariantConverter {}; 193 | 194 | template<> 195 | struct VariantConverter> 196 | : GenericVariantConverter> 197 | {}; 198 | } // namespace juce 199 | -------------------------------------------------------------------------------- /Source/ProcessorState.h: -------------------------------------------------------------------------------- 1 | /* 2 | ============================================================================== 3 | This file is part of the JUCE examples. 4 | Copyright (c) 2020 - Raw Material Software Limited 5 | The code included in this file is provided under the terms of the ISC license 6 | http://www.isc.org/downloads/software-support-policy/isc-license. Permission 7 | To use, copy, modify, and/or distribute this software for any purpose with or 8 | without fee is hereby granted provided that the above copyright notice and 9 | this permission notice appear in all copies. 10 | THE SOFTWARE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, 11 | WHETHER EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR 12 | PURPOSE, ARE DISCLAIMED. 13 | ============================================================================== 14 | */ 15 | 16 | #pragma once 17 | 18 | struct ProcessorState 19 | { 20 | int synthVoices; 21 | bool legacyModeEnabled; 22 | Range legacyChannels; 23 | int legacyPitchbendRange; 24 | bool voiceStealingEnabled; 25 | MPEZoneLayout mpeZoneLayout; 26 | std::unique_ptr readerFactory; 27 | Range loopPointsSeconds; 28 | double centreFrequencyHz; 29 | LoopMode loopMode; 30 | }; -------------------------------------------------------------------------------- /Source/Sample.h: -------------------------------------------------------------------------------- 1 | /* 2 | ============================================================================== 3 | This file is part of the JUCE examples. 4 | Copyright (c) 2020 - Raw Material Software Limited 5 | The code included in this file is provided under the terms of the ISC license 6 | http://www.isc.org/downloads/software-support-policy/isc-license. Permission 7 | To use, copy, modify, and/or distribute this software for any purpose with or 8 | without fee is hereby granted provided that the above copyright notice and 9 | this permission notice appear in all copies. 10 | THE SOFTWARE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, 11 | WHETHER EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR 12 | PURPOSE, ARE DISCLAIMED. 13 | ============================================================================== 14 | */ 15 | 16 | #pragma once 17 | 18 | //============================================================================== 19 | // Represents the constant parts of an audio sample: its name, sample rate, 20 | // length, and the audio sample data itself. 21 | // Samples might be pretty big, so we'll keep shared_ptrs to them most of the 22 | // time, to reduce duplication and copying. 23 | class Sample final 24 | { 25 | public: 26 | Sample(AudioFormatReader& source, double maxSampleLengthSecs) 27 | : m_sourceSampleRate(source.sampleRate), 28 | m_length(jmin(int(source.lengthInSamples), 29 | int(maxSampleLengthSecs* m_sourceSampleRate))), 30 | m_temp_data(jmin(2, int(source.numChannels)), m_length + 4) 31 | { 32 | if (m_length == 0) 33 | throw std::runtime_error("Unable to load sample"); 34 | 35 | source.read(&m_temp_data, 0, m_length + 4, 0, true, true); 36 | 37 | upsample(8); 38 | } 39 | 40 | Sample(std::vector> soundData, double sr) : m_sourceSampleRate{ sr }, 41 | m_length((int)soundData.at(0).size()) { 42 | 43 | int numChans = (int) soundData.size(); 44 | int numSamples = m_length; 45 | 46 | m_temp_data.setSize(numChans, numSamples, false, true, false); 47 | 48 | for (int chan = 0; chan < numChans; chan++) { 49 | m_temp_data.copyFrom(chan, 0, soundData.at(chan).data(), m_length); 50 | } 51 | 52 | upsample(8); 53 | } 54 | 55 | double getSampleRate() const { return m_sourceSampleRate; } 56 | int getLength() const { return m_length; } 57 | const juce::AudioBuffer& getBuffer() const { return m_data; } 58 | 59 | private: 60 | double m_sourceSampleRate; 61 | int m_length; 62 | juce::AudioBuffer m_temp_data; 63 | juce::AudioBuffer m_data; 64 | 65 | LagrangeInterpolator m_interpolator; 66 | 67 | // Whenever sample data is given to the Sample class, a Lagrange interpolator upsamples it in order 68 | // to be able to play it back at at different speeds with low aliasing artifacts. Therefore, upsample() 69 | // must be called in each constructor to Sample(). 70 | void upsample(int upSampleRatio) { 71 | 72 | int numInputSamples = m_temp_data.getNumSamples(); 73 | int numOutputSamples = upSampleRatio * numInputSamples; 74 | 75 | m_data.setSize(2, numOutputSamples, false, true, false); 76 | 77 | for (int outChan = 0; outChan < 2; outChan++) { 78 | int inChan = m_temp_data.getNumChannels() > 1 ? outChan : 0; 79 | m_interpolator.process(1./(double)(upSampleRatio), m_temp_data.getReadPointer(inChan), m_data.getWritePointer(outChan), numOutputSamples, numInputSamples, 0); 80 | } 81 | 82 | m_length *= upSampleRatio; 83 | m_sourceSampleRate *= upSampleRatio; 84 | 85 | m_temp_data.clear(); 86 | } 87 | }; 88 | -------------------------------------------------------------------------------- /Source/SamplerAudioProcessor.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | ============================================================================== 3 | 4 | This file is part of the JUCE examples. 5 | Copyright (c) 2020 - Raw Material Software Limited 6 | 7 | The code included in this file is provided under the terms of the ISC license 8 | http://www.isc.org/downloads/software-support-policy/isc-license. Permission 9 | To use, copy, modify, and/or distribute this software for any purpose with or 10 | without fee is hereby granted provided that the above copyright notice and 11 | this permission notice appear in all copies. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, 14 | WHETHER EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR 15 | PURPOSE, ARE DISCLAIMED. 16 | 17 | ============================================================================== 18 | */ 19 | 20 | #include "SamplerAudioProcessor.h" 21 | #include "SamplerAudioProcessorEditor.h" 22 | 23 | 24 | SamplerAudioProcessor::SamplerAudioProcessor() 25 | : AudioProcessor(BusesProperties().withOutput("Output", AudioChannelSet::stereo(), true)), 26 | parameters (*this, nullptr, juce::Identifier("SamplerAudioProcessor"), createParameters()) 27 | { 28 | parameters.addParameterListener(IDs::centerNote, this); 29 | 30 | if (auto cello = createAssetInputStream("cello.wav")) { 31 | setSample(cello.get()); 32 | } 33 | } 34 | 35 | SamplerAudioProcessor::~SamplerAudioProcessor() { 36 | parameters.removeParameterListener(IDs::centerNote, this); 37 | } 38 | 39 | float SamplerAudioProcessor::getParameterRaw(int parameterIndex) { 40 | if (auto* param = getParameters()[parameterIndex]) 41 | { 42 | float val01 = ((RangedAudioParameter*)param)->getValue(); 43 | float convertedVal = ((RangedAudioParameter*)param)->convertFrom0to1(val01); 44 | return convertedVal; 45 | } 46 | return 0.f; 47 | } 48 | 49 | void SamplerAudioProcessor::setParameterRawNotifyingHost(int parameterIndex, float newValue) { 50 | if (auto* param = getParameters()[parameterIndex]) 51 | { 52 | newValue = ((RangedAudioParameter*)param)->convertTo0to1(newValue); 53 | this->setParameterNotifyingHost(parameterIndex, newValue); 54 | } 55 | } 56 | 57 | void SamplerAudioProcessor::parameterChanged(const String& parameterID, float newValue) { 58 | 59 | //std::cout << "parameter changed: " << parameterID << " to " << newValue << std::endl; 60 | 61 | if (parameterID.equalsIgnoreCase(IDs::centerNote)) { 62 | float pitchInHz = MidiMessage::getMidiNoteInHertz((int)newValue); 63 | dataModel.setCentreFrequencyHz(pitchInHz, nullptr); 64 | this->samplerSound->setCentreFrequencyInHz(pitchInHz); 65 | } 66 | } 67 | 68 | void SamplerAudioProcessor::reset() { 69 | synthesiser.turnOffAllVoices(false); 70 | } 71 | 72 | bool SamplerAudioProcessor::setSample(juce::InputStream* inputStream) { 73 | 74 | if (inputStream) 75 | { 76 | mb.reset(); 77 | inputStream->readIntoMemoryBlock(mb); 78 | readerFactory.reset(new MemoryAudioFormatReaderFactory(mb.getData(), mb.getSize())); 79 | } 80 | else { 81 | return false; 82 | } 83 | 84 | synthesiser.clearVoices(); 85 | 86 | // Set up initial sample, which we load from a binary resource 87 | AudioFormatManager manager; 88 | manager.registerBasicFormats(); 89 | auto reader = readerFactory->make(manager); 90 | if (reader == nullptr) { 91 | return false; 92 | } 93 | jassert(reader != nullptr); // Failed to load resource! 94 | 95 | auto sound = samplerSound; 96 | auto sample = std::unique_ptr(new Sample(*reader, 10.0)); 97 | auto lengthInSeconds = sample->getLength() / sample->getSampleRate(); 98 | sound->setLoopPointsInSeconds({ lengthInSeconds * 0.1, lengthInSeconds * 0.9 }); 99 | sound->setSample(std::move(sample)); 100 | 101 | // Start with the max number of voices 102 | for (auto i = 0; i != m_numVoices; ++i) { 103 | synthesiser.addVoice(new MPESamplerVoice(sound, this->parameters)); 104 | } 105 | 106 | return true; 107 | 108 | } 109 | 110 | AudioProcessorValueTreeState::ParameterLayout SamplerAudioProcessor::createParameters() 111 | { 112 | std::vector> params; 113 | 114 | params.push_back(std::make_unique("centerNote", "Center Note", 0.0f, 127.0f, 60.0f)); 115 | 116 | params.push_back(std::make_unique("ampActive", "Amp Active", false)); 117 | params.push_back(std::make_unique("ampEnvAttack", "Amp Env Attack", 0.0f, 3000.0f, 50.0f)); 118 | params.push_back(std::make_unique("ampEnvDecay", "Amp Env Decay", 0.0f, 3000.0f, 50.0f)); 119 | params.push_back(std::make_unique("ampEnvSustain", "Amp Env Sustain", 0.0f, 1.0f, 1.0f)); 120 | params.push_back(std::make_unique("ampEnvRelease", "Amp Env Release", 0.0f, 3000.0f, 50.0f)); 121 | params.push_back(std::make_unique("ampEnvModAmt", "Amp Env Mod Amt", 0.0f, 10.0f, 1.0f)); 122 | 123 | params.push_back(std::make_unique("filterCutoff", "Filter Cutoff", 20.0f, 20000., 20000.)); 124 | params.push_back(std::make_unique("filterActive", "Filter Active", false)); 125 | 126 | params.push_back(std::make_unique("filterEnvAttack", "Filter Env Attack", 0.0f, 3000.0f, 50.0f)); 127 | params.push_back(std::make_unique("filterEnvDecay", "Filter Env Decay", 0.0f, 3000.0f, 50.0f)); 128 | params.push_back(std::make_unique("filterEnvSustain", "Filter Env Sustain", 0.0f, 1.0f, 1.0f)); 129 | params.push_back(std::make_unique("filterEnvRelease", "Filter Env Release", 0.0f, 3000.0f, 50.0f)); 130 | params.push_back(std::make_unique("filterEnvModAmt", "Filter Env Mod Amt", -20000.0f, 20000.0f, 0.0f)); 131 | 132 | return { params.begin(), params.end() }; 133 | } 134 | 135 | void SamplerAudioProcessor::prepareToPlay(double sampleRate, int) 136 | { 137 | synthesiser.setCurrentPlaybackSampleRate(sampleRate); 138 | } 139 | 140 | void SamplerAudioProcessor::releaseResources() {} 141 | 142 | bool SamplerAudioProcessor::isBusesLayoutSupported(const BusesLayout& layouts) const 143 | { 144 | return layouts.getMainOutputChannelSet() == AudioChannelSet::mono() 145 | || layouts.getMainOutputChannelSet() == AudioChannelSet::stereo(); 146 | } 147 | 148 | //============================================================================== 149 | AudioProcessorEditor* SamplerAudioProcessor::createEditor() 150 | { 151 | // This function will be called from the message thread. We lock the command 152 | // queue to ensure that no messages are processed for the duration of this 153 | // call. 154 | SpinLock::ScopedLockType lock(commandQueueMutex); 155 | 156 | ProcessorState state; 157 | state.synthVoices = synthesiser.getNumVoices(); 158 | state.legacyModeEnabled = synthesiser.isLegacyModeEnabled(); 159 | state.legacyChannels = synthesiser.getLegacyModeChannelRange(); 160 | state.legacyPitchbendRange = synthesiser.getLegacyModePitchbendRange(); 161 | state.voiceStealingEnabled = synthesiser.isVoiceStealingEnabled(); 162 | state.mpeZoneLayout = synthesiser.getZoneLayout(); 163 | state.readerFactory = readerFactory == nullptr ? nullptr : readerFactory->clone(); 164 | 165 | auto sound = samplerSound; 166 | state.loopPointsSeconds = sound->getLoopPointsInSeconds(); 167 | state.centreFrequencyHz = sound->getCentreFrequencyInHz(); 168 | state.loopMode = sound->getLoopMode(); 169 | 170 | return new SamplerAudioProcessorEditor(*this, std::move(state), this->dataModel, this->formatManager, this->parameters); 171 | } 172 | 173 | bool SamplerAudioProcessor::hasEditor() const { return true; } 174 | 175 | //============================================================================== 176 | const String SamplerAudioProcessor::getName() const { return "SamplerPlugin"; } 177 | bool SamplerAudioProcessor::acceptsMidi() const { return true; } 178 | bool SamplerAudioProcessor::producesMidi() const { return false; } 179 | bool SamplerAudioProcessor::isMidiEffect() const { return false; } 180 | double SamplerAudioProcessor::getTailLengthSeconds() const { return 0.0; } 181 | 182 | //============================================================================== 183 | int SamplerAudioProcessor::getNumPrograms() { return 1; } 184 | int SamplerAudioProcessor::getCurrentProgram() { return 0; } 185 | void SamplerAudioProcessor::setCurrentProgram(int) {} 186 | const String SamplerAudioProcessor::getProgramName(int) { return { "None" }; } 187 | void SamplerAudioProcessor::changeProgramName(int, const String&) {} 188 | 189 | //============================================================================== 190 | void SamplerAudioProcessor::getStateInformation(MemoryBlock&) {} 191 | void SamplerAudioProcessor::setStateInformation(const void*, int) {} 192 | 193 | //============================================================================== 194 | void SamplerAudioProcessor::processBlock(juce::AudioBuffer& buffer, MidiBuffer& midi) 195 | { 196 | process(buffer, midi); 197 | } 198 | 199 | void SamplerAudioProcessor::processBlock(juce::AudioBuffer& buffer, MidiBuffer& midi) 200 | { 201 | process(buffer, midi); 202 | } 203 | 204 | // These should be called from the GUI thread, and will block until the 205 | // command buffer has enough room to accept a command. 206 | void SamplerAudioProcessor::setSample(std::unique_ptr fact, AudioFormatManager& formatManager) 207 | { 208 | class SetSampleCommand 209 | { 210 | public: 211 | SetSampleCommand(std::unique_ptr r, 212 | std::unique_ptr sampleIn, 213 | std::vector> newVoicesIn) 214 | : readerFactory(std::move(r)), 215 | sample(std::move(sampleIn)), 216 | newVoices(std::move(newVoicesIn)) 217 | {} 218 | 219 | void operator() (SamplerAudioProcessor& proc) 220 | { 221 | proc.readerFactory = std::move(readerFactory); 222 | auto sound = proc.samplerSound; 223 | sound->setSample(std::move(sample)); 224 | auto numberOfVoices = proc.synthesiser.getNumVoices(); 225 | proc.synthesiser.clearVoices(); 226 | 227 | for (auto it = begin(newVoices); proc.synthesiser.getNumVoices() < numberOfVoices; ++it) 228 | { 229 | proc.synthesiser.addVoice(it->release()); 230 | } 231 | } 232 | 233 | private: 234 | std::unique_ptr readerFactory; 235 | std::unique_ptr sample; 236 | std::vector> newVoices; 237 | }; 238 | 239 | // Note that all allocation happens here, on the main message thread. Then, 240 | // we transfer ownership across to the audio thread. 241 | auto loadedSamplerSound = samplerSound; 242 | std::vector> newSamplerVoices; 243 | newSamplerVoices.reserve(m_numVoices); 244 | 245 | for (auto i = 0; i != m_numVoices; ++i) 246 | newSamplerVoices.emplace_back(new MPESamplerVoice(loadedSamplerSound, this->parameters)); 247 | 248 | if (fact == nullptr) 249 | { 250 | commands.push(SetSampleCommand(std::move(fact), 251 | nullptr, 252 | std::move(newSamplerVoices))); 253 | } 254 | else if (auto reader = fact->make(formatManager)) 255 | { 256 | commands.push(SetSampleCommand(std::move(fact), 257 | std::unique_ptr(new Sample(*reader, 10.0)), 258 | std::move(newSamplerVoices))); 259 | } 260 | } 261 | 262 | void SamplerAudioProcessor::setSample(std::vector> soundData, double sampleRate) { 263 | 264 | synthesiser.clearVoices(); 265 | 266 | auto sound = samplerSound; 267 | auto sample = std::unique_ptr(new Sample(soundData, sampleRate)); 268 | auto lengthInSeconds = sample->getLength() / sample->getSampleRate(); 269 | sound->setLoopPointsInSeconds({ lengthInSeconds * 0.1, lengthInSeconds * 0.9 }); 270 | sound->setSample(std::move(sample)); 271 | 272 | // Start with the max number of voices 273 | for (auto i = 0; i != m_numVoices; ++i) { 274 | synthesiser.addVoice(new MPESamplerVoice(sound, this->parameters)); 275 | } 276 | 277 | } 278 | 279 | // Set the sample with an absolute path to a wav file. 280 | bool SamplerAudioProcessor::setSample(const char* path) { 281 | auto theFile = juce::File(juce::String(path)); 282 | if (!theFile.existsAsFile()) { 283 | std::cerr << "No file found at path: " << path; 284 | return false; 285 | } 286 | 287 | return setSample(theFile.createInputStream().get()); 288 | } 289 | 290 | void SamplerAudioProcessor::setCentreFrequency(double centreFrequency) 291 | { 292 | commands.push([centreFrequency](SamplerAudioProcessor& proc) 293 | { 294 | auto loaded = proc.samplerSound; 295 | if (loaded != nullptr) 296 | loaded->setCentreFrequencyInHz(centreFrequency); 297 | }); 298 | } 299 | 300 | void SamplerAudioProcessor::setLoopMode(LoopMode loopMode) 301 | { 302 | commands.push([loopMode](SamplerAudioProcessor& proc) 303 | { 304 | auto loaded = proc.samplerSound; 305 | if (loaded != nullptr) 306 | loaded->setLoopMode(loopMode); 307 | }); 308 | } 309 | 310 | void SamplerAudioProcessor::setLoopPoints(Range loopPoints) 311 | { 312 | commands.push([loopPoints](SamplerAudioProcessor& proc) 313 | { 314 | auto loaded = proc.samplerSound; 315 | if (loaded != nullptr) 316 | loaded->setLoopPointsInSeconds(loopPoints); 317 | }); 318 | } 319 | 320 | void SamplerAudioProcessor::setMPEZoneLayout(MPEZoneLayout layout) 321 | { 322 | commands.push([layout](SamplerAudioProcessor& proc) 323 | { 324 | // setZoneLayout will lock internally, so we don't care too much about 325 | // ensuring that the layout doesn't get copied or destroyed on the 326 | // audio thread. If the audio glitches while updating midi settings 327 | // it doesn't matter too much. 328 | proc.synthesiser.setZoneLayout(layout); 329 | }); 330 | } 331 | 332 | void SamplerAudioProcessor::setLegacyModeEnabled(int pitchbendRange, Range channelRange) 333 | { 334 | commands.push([pitchbendRange, channelRange](SamplerAudioProcessor& proc) 335 | { 336 | proc.synthesiser.enableLegacyMode(pitchbendRange, channelRange); 337 | }); 338 | } 339 | 340 | void SamplerAudioProcessor::setVoiceStealingEnabled(bool voiceStealingEnabled) 341 | { 342 | commands.push([voiceStealingEnabled](SamplerAudioProcessor& proc) 343 | { 344 | proc.synthesiser.setVoiceStealingEnabled(voiceStealingEnabled); 345 | }); 346 | } 347 | 348 | void SamplerAudioProcessor::setNumberOfVoices(int numberOfVoices) 349 | { 350 | // We don't want to call 'new' on the audio thread. Normally, we'd 351 | // construct things here, on the GUI thread, and then move them into the 352 | // command lambda. Unfortunately, C++11 doesn't have extended lambda 353 | // capture, so we use a custom struct instead. 354 | 355 | class SetNumVoicesCommand 356 | { 357 | public: 358 | SetNumVoicesCommand(std::vector> newVoicesIn) 359 | : newVoices(std::move(newVoicesIn)) 360 | {} 361 | 362 | void operator() (SamplerAudioProcessor& proc) 363 | { 364 | if ((int)newVoices.size() < proc.synthesiser.getNumVoices()) 365 | proc.synthesiser.reduceNumVoices(int(newVoices.size())); 366 | else 367 | for (auto it = begin(newVoices); (size_t)proc.synthesiser.getNumVoices() < newVoices.size(); ++it) 368 | proc.synthesiser.addVoice(it->release()); 369 | } 370 | 371 | private: 372 | std::vector> newVoices; 373 | }; 374 | 375 | m_numVoices = min((int)maxVoices, numberOfVoices); 376 | auto loadedSamplerSound = samplerSound; 377 | std::vector> newSamplerVoices; 378 | newSamplerVoices.reserve((size_t)m_numVoices); 379 | 380 | for (auto i = 0; i != m_numVoices; ++i) 381 | newSamplerVoices.emplace_back(new MPESamplerVoice(loadedSamplerSound, this->parameters)); 382 | 383 | commands.push(SetNumVoicesCommand(std::move(newSamplerVoices))); 384 | } 385 | 386 | // These accessors are just for an 'overview' and won't give the exact 387 | // state of the audio engine at a particular point in time. 388 | // If you call getNumVoices(), get the result '10', and then call 389 | // getPlaybackPosiiton(9), there's a chance the audio engine will have 390 | // been updated to remove some voices in the meantime, so the returned 391 | // value won't correspond to an existing voice. 392 | int SamplerAudioProcessor::getNumVoices() const { return synthesiser.getNumVoices(); } 393 | float SamplerAudioProcessor::getPlaybackPosition(int voice) const { return playbackPositions.at((size_t)voice); } 394 | 395 | //============================================================================== 396 | template 397 | void SamplerAudioProcessor::process(juce::AudioBuffer& buffer, MidiBuffer& midiMessages) 398 | { 399 | // Try to acquire a lock on the command queue. 400 | // If we were successful, we pop all pending commands off the queue and 401 | // apply them to the processor. 402 | // If we weren't able to acquire the lock, it's because someone called 403 | // createEditor, which requires that the processor data model stays in 404 | // a valid state for the duration of the call. 405 | const GenericScopedTryLock lock(commandQueueMutex); 406 | 407 | if (lock.isLocked()) 408 | commands.call(*this); 409 | 410 | synthesiser.renderNextBlock(buffer, midiMessages, 0, buffer.getNumSamples()); 411 | 412 | auto loadedSamplerSound = samplerSound; 413 | 414 | if (loadedSamplerSound->getSample() == nullptr) 415 | return; 416 | 417 | auto numVoices = synthesiser.getNumVoices(); 418 | 419 | // Update the current playback positions 420 | for (auto i = 0; i < maxVoices; ++i) 421 | { 422 | auto* voicePtr = dynamic_cast (synthesiser.getVoice(i)); 423 | 424 | if (i < numVoices && voicePtr != nullptr) 425 | playbackPositions[(size_t)i] = static_cast (voicePtr->getCurrentSamplePosition() / loadedSamplerSound->getSample()->getSampleRate()); 426 | else 427 | playbackPositions[(size_t)i] = 0.0f; 428 | } 429 | 430 | } 431 | -------------------------------------------------------------------------------- /Source/SamplerAudioProcessor.h: -------------------------------------------------------------------------------- 1 | /* 2 | ============================================================================== 3 | 4 | This file is part of the JUCE examples. 5 | Copyright (c) 2020 - Raw Material Software Limited 6 | 7 | The code included in this file is provided under the terms of the ISC license 8 | http://www.isc.org/downloads/software-support-policy/isc-license. Permission 9 | To use, copy, modify, and/or distribute this software for any purpose with or 10 | without fee is hereby granted provided that the above copyright notice and 11 | this permission notice appear in all copies. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, 14 | WHETHER EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR 15 | PURPOSE, ARE DISCLAIMED. 16 | 17 | ============================================================================== 18 | */ 19 | 20 | #pragma once 21 | 22 | #include "Misc.h" 23 | #include "MemoryAudioFormatReaderFactory.h" 24 | #include "Sample.h" 25 | #include "DataModels/DataModel.h" 26 | #include "MPESamplerSound.h" 27 | #include "MPESamplerVoice.h" 28 | #include "CommandFifo.h" 29 | 30 | 31 | class SamplerAudioProcessor : public AudioProcessor, public AudioProcessorValueTreeState::Listener 32 | { 33 | 34 | public: 35 | SamplerAudioProcessor(); 36 | 37 | ~SamplerAudioProcessor(); 38 | 39 | void prepareToPlay(double sampleRate, int) override; 40 | 41 | void releaseResources() override; 42 | 43 | bool isBusesLayoutSupported(const BusesLayout& layouts) const override; 44 | 45 | //============================================================================== 46 | AudioProcessorEditor* createEditor() override; 47 | 48 | bool hasEditor() const override; 49 | 50 | //============================================================================== 51 | const String getName() const override; 52 | bool acceptsMidi() const override; 53 | bool producesMidi() const override; 54 | bool isMidiEffect() const override; 55 | double getTailLengthSeconds() const override; 56 | 57 | //============================================================================== 58 | int getNumPrograms() override; 59 | int getCurrentProgram() override; 60 | void setCurrentProgram(int) override; 61 | const String getProgramName(int) override; 62 | void changeProgramName(int, const String&) override; 63 | 64 | //============================================================================== 65 | void getStateInformation(MemoryBlock&) override; 66 | void setStateInformation(const void*, int) override; 67 | 68 | //============================================================================== 69 | void processBlock(juce::AudioBuffer& buffer, MidiBuffer& midi) override; 70 | 71 | void processBlock(juce::AudioBuffer& buffer, MidiBuffer& midi) override; 72 | 73 | // These should be called from the GUI thread, and will block until the 74 | // command buffer has enough room to accept a command. 75 | void setSample(std::unique_ptr fact, AudioFormatManager& formatManager); 76 | 77 | // This method is not thread-safe at all and is only meant to be used by DawDreamer. 78 | void setSample(std::vector> soundData, double sampleRate); 79 | 80 | // Set the sample with an absolute filepath to a wav file. Not thread-safe at all. 81 | bool setSample(const char* path); 82 | 83 | float getParameterRaw(int parameterIndex); 84 | 85 | void setParameterRawNotifyingHost(int parameterIndex, float newValue); 86 | 87 | void setCentreFrequency(double centreFrequency); 88 | 89 | void setLoopMode(LoopMode loopMode); 90 | 91 | void setLoopPoints(Range loopPoints); 92 | 93 | void setMPEZoneLayout(MPEZoneLayout layout); 94 | 95 | void setLegacyModeEnabled(int pitchbendRange, Range channelRange); 96 | 97 | void setVoiceStealingEnabled(bool voiceStealingEnabled); 98 | 99 | void setNumberOfVoices(int numberOfVoices); 100 | 101 | // These accessors are just for an 'overview' and won't give the exact 102 | // state of the audio engine at a particular point in time. 103 | // If you call getNumVoices(), get the result '10', and then call 104 | // getPlaybackPosiiton(9), there's a chance the audio engine will have 105 | // been updated to remove some voices in the meantime, so the returned 106 | // value won't correspond to an existing voice. 107 | int getNumVoices() const; 108 | float getPlaybackPosition(int voice) const; 109 | 110 | void parameterChanged(const String& parameterID, float newValue) override; 111 | void reset() override; 112 | 113 | private: 114 | //============================================================================== 115 | template 116 | void process(juce::AudioBuffer& buffer, MidiBuffer& midiMessages); 117 | 118 | bool setSample(juce::InputStream* inputStream); 119 | 120 | CommandFifo commands; 121 | 122 | MemoryBlock mb; 123 | std::unique_ptr readerFactory; 124 | std::shared_ptr samplerSound = std::make_shared(); 125 | MPESynthesiser synthesiser; 126 | 127 | AudioFormatManager formatManager; 128 | DataModel dataModel{ formatManager }; 129 | 130 | AudioProcessorValueTreeState parameters; 131 | AudioProcessorValueTreeState::ParameterLayout createParameters(); 132 | 133 | // This mutex is used to ensure we don't modify the processor state during 134 | // a call to createEditor, which would cause the UI to become desynched 135 | // with the real state of the processor. 136 | SpinLock commandQueueMutex; 137 | 138 | enum { maxVoices = 30 }; 139 | int m_numVoices = 20; // never let m_numVoices go above maxVoices; 140 | 141 | // This is used for visualising the current playback position of each voice. 142 | // It stores values in seconds units. 143 | std::array, maxVoices> playbackPositions; 144 | 145 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(SamplerAudioProcessor) 146 | }; 147 | -------------------------------------------------------------------------------- /Source/SamplerAudioProcessorEditor.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | ============================================================================== 3 | This file is part of the JUCE examples. 4 | Copyright (c) 2020 - Raw Material Software Limited 5 | The code included in this file is provided under the terms of the ISC license 6 | http://www.isc.org/downloads/software-support-policy/isc-license. Permission 7 | To use, copy, modify, and/or distribute this software for any purpose with or 8 | without fee is hereby granted provided that the above copyright notice and 9 | this permission notice appear in all copies. 10 | THE SOFTWARE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, 11 | WHETHER EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR 12 | PURPOSE, ARE DISCLAIMED. 13 | ============================================================================== 14 | */ 15 | 16 | #include "SamplerAudioProcessorEditor.h" 17 | #include "SamplerAudioProcessor.h" 18 | 19 | SamplerAudioProcessorEditor::SamplerAudioProcessorEditor(SamplerAudioProcessor& p, ProcessorState state, const DataModel& model, 20 | AudioFormatManager& afManager, AudioProcessorValueTreeState& vts) 21 | : AudioProcessorEditor(&p), 22 | dataModel(model), 23 | formatManager(afManager), 24 | samplerAudioProcessor(p) 25 | #ifndef SAMPLER_SKIP_UI 26 | ,mainSamplerView(dataModel, 27 | [&p] 28 | { 29 | std::vector ret; 30 | auto voices = p.getNumVoices(); 31 | ret.reserve((size_t)voices); 32 | 33 | for (auto i = 0; i != voices; ++i) 34 | ret.emplace_back(p.getPlaybackPosition(i)); 35 | 36 | return ret; 37 | }, 38 | undoManager, 39 | vts) 40 | #endif 41 | { 42 | dataModel.addListener(*this); 43 | mpeSettings.addListener(*this); 44 | 45 | formatManager.registerBasicFormats(); 46 | 47 | addAndMakeVisible(tabbedComponent); 48 | 49 | auto lookFeel = dynamic_cast (&getLookAndFeel()); 50 | auto bg = lookFeel->getCurrentColourScheme() 51 | .getUIColour(LookAndFeel_V4::ColourScheme::UIColour::widgetBackground); 52 | #ifndef SAMPLER_SKIP_UI 53 | tabbedComponent.addTab("Sample Editor", bg, &mainSamplerView, false); 54 | #endif 55 | tabbedComponent.addTab("MPE Settings", bg, &settingsComponent, false); 56 | 57 | mpeSettings.setSynthVoices(state.synthVoices, nullptr); 58 | mpeSettings.setLegacyModeEnabled(state.legacyModeEnabled, nullptr); 59 | mpeSettings.setLegacyFirstChannel(state.legacyChannels.getStart(), nullptr); 60 | mpeSettings.setLegacyLastChannel(state.legacyChannels.getEnd(), nullptr); 61 | mpeSettings.setLegacyPitchbendRange(state.legacyPitchbendRange, nullptr); 62 | mpeSettings.setVoiceStealingEnabled(state.voiceStealingEnabled, nullptr); 63 | mpeSettings.setMPEZoneLayout(state.mpeZoneLayout, nullptr); 64 | 65 | dataModel.setSampleReader(move(state.readerFactory), nullptr); 66 | dataModel.setLoopPointsSeconds(state.loopPointsSeconds, nullptr); 67 | dataModel.setCentreFrequencyHz(state.centreFrequencyHz, nullptr); 68 | dataModel.setLoopMode(state.loopMode, nullptr); 69 | 70 | // Make sure that before the constructor has finished, you've set the 71 | // editor's size to whatever you need it to be. 72 | setResizable(true, true); 73 | setResizeLimits(640, 480, 2560, 1440); 74 | setSize(640, 480); 75 | } 76 | 77 | void SamplerAudioProcessorEditor::resized() 78 | { 79 | tabbedComponent.setBounds(getLocalBounds()); 80 | } 81 | 82 | bool SamplerAudioProcessorEditor::keyPressed(const KeyPress& key) 83 | { 84 | if (key == KeyPress('z', ModifierKeys::commandModifier, 0)) 85 | { 86 | undoManager.undo(); 87 | return true; 88 | } 89 | 90 | if (key == KeyPress('z', ModifierKeys::commandModifier | ModifierKeys::shiftModifier, 0)) 91 | { 92 | undoManager.redo(); 93 | return true; 94 | } 95 | 96 | return Component::keyPressed(key); 97 | } 98 | 99 | bool SamplerAudioProcessorEditor::isInterestedInFileDrag(const StringArray& files) 100 | { 101 | WildcardFileFilter filter(formatManager.getWildcardForAllFormats(), {}, "Known Audio Formats"); 102 | return files.size() == 1 && filter.isFileSuitable(files[0]); 103 | } 104 | 105 | void SamplerAudioProcessorEditor::filesDropped(const StringArray& files, int, int) 106 | { 107 | jassert(files.size() == 1); 108 | undoManager.beginNewTransaction(); 109 | auto r = new FileAudioFormatReaderFactory(files[0]); 110 | dataModel.setSampleReader(std::unique_ptr(r), 111 | &undoManager); 112 | 113 | } 114 | 115 | void SamplerAudioProcessorEditor::sampleReaderChanged(std::shared_ptr value) 116 | { 117 | samplerAudioProcessor.setSample(value == nullptr ? nullptr : value->clone(), 118 | dataModel.getAudioFormatManager()); 119 | } 120 | 121 | void SamplerAudioProcessorEditor::centreFrequencyHzChanged(double value) 122 | { 123 | samplerAudioProcessor.setCentreFrequency(value); 124 | } 125 | 126 | void SamplerAudioProcessorEditor::loopPointsSecondsChanged(Range value) 127 | { 128 | samplerAudioProcessor.setLoopPoints(value); 129 | } 130 | 131 | void SamplerAudioProcessorEditor::loopModeChanged(LoopMode value) 132 | { 133 | samplerAudioProcessor.setLoopMode(value); 134 | } 135 | 136 | void SamplerAudioProcessorEditor::synthVoicesChanged(int value) 137 | { 138 | samplerAudioProcessor.setNumberOfVoices(value); 139 | } 140 | 141 | void SamplerAudioProcessorEditor::voiceStealingEnabledChanged(bool value) 142 | { 143 | samplerAudioProcessor.setVoiceStealingEnabled(value); 144 | } 145 | 146 | void SamplerAudioProcessorEditor::legacyModeEnabledChanged(bool value) 147 | { 148 | if (value) 149 | setProcessorLegacyMode(); 150 | else 151 | setProcessorMPEMode(); 152 | } 153 | 154 | void SamplerAudioProcessorEditor::mpeZoneLayoutChanged(const MPEZoneLayout&) 155 | { 156 | setProcessorMPEMode(); 157 | } 158 | 159 | void SamplerAudioProcessorEditor::legacyFirstChannelChanged(int) 160 | { 161 | setProcessorLegacyMode(); 162 | } 163 | 164 | void SamplerAudioProcessorEditor::legacyLastChannelChanged(int) 165 | { 166 | setProcessorLegacyMode(); 167 | } 168 | 169 | void SamplerAudioProcessorEditor::legacyPitchbendRangeChanged(int) 170 | { 171 | setProcessorLegacyMode(); 172 | } 173 | 174 | void SamplerAudioProcessorEditor::setProcessorLegacyMode() 175 | { 176 | samplerAudioProcessor.setLegacyModeEnabled(mpeSettings.getLegacyPitchbendRange(), 177 | Range(mpeSettings.getLegacyFirstChannel(), 178 | mpeSettings.getLegacyLastChannel())); 179 | } 180 | 181 | void SamplerAudioProcessorEditor::setProcessorMPEMode() 182 | { 183 | samplerAudioProcessor.setMPEZoneLayout(mpeSettings.getMPEZoneLayout()); 184 | } 185 | -------------------------------------------------------------------------------- /Source/SamplerAudioProcessorEditor.h: -------------------------------------------------------------------------------- 1 | /* 2 | ============================================================================== 3 | This file is part of the JUCE examples. 4 | Copyright (c) 2020 - Raw Material Software Limited 5 | The code included in this file is provided under the terms of the ISC license 6 | http://www.isc.org/downloads/software-support-policy/isc-license. Permission 7 | To use, copy, modify, and/or distribute this software for any purpose with or 8 | without fee is hereby granted provided that the above copyright notice and 9 | this permission notice appear in all copies. 10 | THE SOFTWARE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, 11 | WHETHER EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR 12 | PURPOSE, ARE DISCLAIMED. 13 | ============================================================================== 14 | */ 15 | 16 | #pragma once 17 | 18 | #include "SamplerAudioProcessor.h" 19 | #include "Components/MPESettingsComponent.h" 20 | #ifndef SAMPLER_SKIP_UI 21 | #include "Components/MainSamplerView.h" 22 | #endif 23 | #include "ProcessorState.h" 24 | 25 | using namespace std; 26 | 27 | class SamplerAudioProcessorEditor : public AudioProcessorEditor, 28 | public FileDragAndDropTarget, 29 | private DataModel::Listener, 30 | private MPESettingsDataModel::Listener 31 | { 32 | public: 33 | SamplerAudioProcessorEditor(SamplerAudioProcessor& p, ProcessorState state, const DataModel& model, AudioFormatManager& afManager, AudioProcessorValueTreeState& vts); 34 | 35 | private: 36 | void resized() override; 37 | 38 | bool keyPressed(const KeyPress& key) override; 39 | 40 | bool isInterestedInFileDrag(const StringArray& files) override; 41 | 42 | void filesDropped(const StringArray& files, int, int) override; 43 | 44 | void sampleReaderChanged(std::shared_ptr value) override; 45 | 46 | void centreFrequencyHzChanged(double value) override; 47 | 48 | void loopPointsSecondsChanged(Range value) override; 49 | 50 | void loopModeChanged(LoopMode value) override; 51 | 52 | void synthVoicesChanged(int value) override; 53 | 54 | void voiceStealingEnabledChanged(bool value) override; 55 | 56 | void legacyModeEnabledChanged(bool value) override; 57 | 58 | void mpeZoneLayoutChanged(const MPEZoneLayout&) override; 59 | 60 | void legacyFirstChannelChanged(int) override; 61 | 62 | void legacyLastChannelChanged(int) override; 63 | 64 | void legacyPitchbendRangeChanged(int) override; 65 | 66 | void setProcessorLegacyMode(); 67 | 68 | void setProcessorMPEMode(); 69 | 70 | SamplerAudioProcessor& samplerAudioProcessor; 71 | AudioFormatManager& formatManager; 72 | DataModel dataModel; 73 | UndoManager undoManager; 74 | MPESettingsDataModel mpeSettings{ dataModel.mpeSettings() }; 75 | 76 | TabbedComponent tabbedComponent{ TabbedButtonBar::Orientation::TabsAtTop }; 77 | MPESettingsComponent settingsComponent{ dataModel.mpeSettings(), undoManager }; 78 | #ifndef SAMPLER_SKIP_UI 79 | MainSamplerView mainSamplerView; 80 | #endif 81 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(SamplerAudioProcessorEditor) 82 | }; 83 | -------------------------------------------------------------------------------- /Source/SamplerPluginDemo.h: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DBraun/Sampler/9fa4b0e4aa480a521af35b6e5bc607300f047057/Source/SamplerPluginDemo.h --------------------------------------------------------------------------------