From f0f524906d1f0f38e8b59a75a8a2d82eaa851681 Mon Sep 17 00:00:00 2001 From: Frank Dana Date: Tue, 13 Apr 2021 12:20:35 +0000 Subject: [PATCH] Normalize repo to LF (Unix) line endings, enforce (#117) --- .gitattributes | 2 + .../audio_play_head/juce_AudioPlayHead.h | 314 +- .../buffers/juce_AudioChannelSet.cpp | 1312 +- .../buffers/juce_AudioChannelSet.h | 968 +- .../buffers/juce_AudioDataConverters.cpp | 1192 +- .../buffers/juce_AudioDataConverters.h | 1432 +- .../buffers/juce_AudioProcessLoadMeasurer.cpp | 158 +- .../buffers/juce_AudioProcessLoadMeasurer.h | 200 +- .../buffers/juce_AudioSampleBuffer.h | 2290 +- .../buffers/juce_FloatVectorOperations.cpp | 2610 +- .../buffers/juce_FloatVectorOperations.h | 514 +- .../juce_audio_basics/juce_audio_basics.cpp | 174 +- .../juce_audio_basics/juce_audio_basics.h | 244 +- .../juce_audio_basics/juce_audio_basics.mm | 46 +- .../midi/juce_MidiBuffer.cpp | 464 +- .../juce_audio_basics/midi/juce_MidiBuffer.h | 468 +- .../juce_audio_basics/midi/juce_MidiFile.cpp | 892 +- .../juce_audio_basics/midi/juce_MidiFile.h | 394 +- .../midi/juce_MidiKeyboardState.cpp | 372 +- .../midi/juce_MidiKeyboardState.h | 412 +- .../midi/juce_MidiMessage.cpp | 2288 +- .../juce_audio_basics/midi/juce_MidiMessage.h | 1904 +- .../midi/juce_MidiMessageSequence.cpp | 824 +- .../midi/juce_MidiMessageSequence.h | 612 +- .../juce_audio_basics/midi/juce_MidiRPN.cpp | 762 +- .../juce_audio_basics/midi/juce_MidiRPN.h | 308 +- .../mpe/juce_MPEInstrument.cpp | 4584 +-- .../mpe/juce_MPEInstrument.h | 826 +- .../mpe/juce_MPEMessages.cpp | 482 +- .../juce_audio_basics/mpe/juce_MPEMessages.h | 232 +- .../juce_audio_basics/mpe/juce_MPENote.cpp | 254 +- .../juce_audio_basics/mpe/juce_MPENote.h | 368 +- .../mpe/juce_MPESynthesiser.cpp | 676 +- .../mpe/juce_MPESynthesiser.h | 624 +- .../mpe/juce_MPESynthesiserBase.cpp | 360 +- .../mpe/juce_MPESynthesiserBase.h | 420 +- .../mpe/juce_MPESynthesiserVoice.cpp | 100 +- .../mpe/juce_MPESynthesiserVoice.h | 382 +- .../juce_audio_basics/mpe/juce_MPEUtils.cpp | 988 +- .../juce_audio_basics/mpe/juce_MPEUtils.h | 306 +- .../juce_audio_basics/mpe/juce_MPEValue.cpp | 346 +- .../juce_audio_basics/mpe/juce_MPEValue.h | 194 +- .../mpe/juce_MPEZoneLayout.cpp | 780 +- .../mpe/juce_MPEZoneLayout.h | 450 +- .../native/juce_mac_CoreAudioLayouts.h | 660 +- .../sources/juce_AudioSource.h | 358 +- .../sources/juce_BufferingAudioSource.cpp | 630 +- .../sources/juce_BufferingAudioSource.h | 238 +- .../juce_ChannelRemappingAudioSource.cpp | 374 +- .../juce_ChannelRemappingAudioSource.h | 282 +- .../sources/juce_IIRFilterAudioSource.cpp | 160 +- .../sources/juce_IIRFilterAudioSource.h | 136 +- .../sources/juce_MemoryAudioSource.cpp | 146 +- .../sources/juce_MemoryAudioSource.h | 130 +- .../sources/juce_MixerAudioSource.cpp | 316 +- .../sources/juce_MixerAudioSource.h | 198 +- .../sources/juce_PositionableAudioSource.h | 152 +- .../sources/juce_ResamplingAudioSource.cpp | 530 +- .../sources/juce_ResamplingAudioSource.h | 212 +- .../sources/juce_ReverbAudioSource.cpp | 166 +- .../sources/juce_ReverbAudioSource.h | 148 +- .../sources/juce_ToneGeneratorAudioSource.cpp | 156 +- .../sources/juce_ToneGeneratorAudioSource.h | 142 +- .../synthesisers/juce_Synthesiser.cpp | 1172 +- .../synthesisers/juce_Synthesiser.h | 1290 +- .../juce_audio_basics/utilities/juce_ADSR.h | 496 +- .../utilities/juce_CatmullRomInterpolator.cpp | 150 +- .../utilities/juce_CatmullRomInterpolator.h | 292 +- .../utilities/juce_Decibels.h | 224 +- .../utilities/juce_IIRFilter.cpp | 672 +- .../utilities/juce_IIRFilter.h | 434 +- .../utilities/juce_LagrangeInterpolator.cpp | 934 +- .../utilities/juce_LagrangeInterpolator.h | 292 +- .../juce_audio_basics/utilities/juce_Reverb.h | 626 +- .../utilities/juce_SmoothedValue.cpp | 184 +- .../utilities/juce_SmoothedValue.h | 1260 +- .../audio_io/juce_AudioDeviceManager.cpp | 2318 +- .../audio_io/juce_AudioDeviceManager.h | 1104 +- .../audio_io/juce_AudioIODevice.cpp | 90 +- .../audio_io/juce_AudioIODevice.h | 650 +- .../audio_io/juce_AudioIODeviceType.cpp | 178 +- .../audio_io/juce_AudioIODeviceType.h | 368 +- .../audio_io/juce_SystemAudioVolume.h | 118 +- .../juce_audio_devices/juce_audio_devices.cpp | 504 +- .../juce_audio_devices/juce_audio_devices.h | 372 +- .../juce_audio_devices/juce_audio_devices.mm | 46 +- .../midi_io/juce_MidiDevices.cpp | 320 +- .../midi_io/juce_MidiDevices.h | 746 +- .../midi_io/juce_MidiMessageCollector.cpp | 320 +- .../midi_io/juce_MidiMessageCollector.h | 210 +- .../app/com/roli/juce/JuceMidiSupport.java | 2144 +- .../native/juce_MidiDataConcatenator.h | 376 +- .../native/juce_android_Audio.cpp | 992 +- .../native/juce_android_Midi.cpp | 1414 +- .../native/juce_android_Oboe.cpp | 2922 +- .../native/juce_android_OpenSL.cpp | 2788 +- .../native/juce_ios_Audio.cpp | 2918 +- .../native/juce_ios_Audio.h | 186 +- .../native/juce_linux_ALSA.cpp | 2614 +- .../native/juce_linux_Bela.cpp | 1200 +- .../native/juce_linux_JackAudio.cpp | 1256 +- .../native/juce_linux_Midi.cpp | 1332 +- .../native/juce_mac_CoreAudio.cpp | 4500 +-- .../native/juce_mac_CoreMidi.cpp | 1472 +- .../native/juce_win32_ASIO.cpp | 3296 +- .../native/juce_win32_DirectSound.cpp | 2582 +- .../native/juce_win32_Midi.cpp | 3968 +- .../native/juce_win32_WASAPI.cpp | 3504 +- .../sources/juce_AudioSourcePlayer.cpp | 360 +- .../sources/juce_AudioSourcePlayer.h | 228 +- .../sources/juce_AudioTransportSource.cpp | 566 +- .../sources/juce_AudioTransportSource.h | 360 +- .../juce_audio_formats/codecs/flac/all.h | 742 +- .../juce_audio_formats/codecs/flac/alloc.h | 424 +- .../juce_audio_formats/codecs/flac/assert.h | 98 +- .../juce_audio_formats/codecs/flac/callback.h | 376 +- .../juce_audio_formats/codecs/flac/compat.h | 334 +- .../juce_audio_formats/codecs/flac/endswap.h | 160 +- .../juce_audio_formats/codecs/flac/export.h | 194 +- .../juce_audio_formats/codecs/flac/format.h | 2050 +- .../codecs/flac/libFLAC/bitmath.c | 218 +- .../codecs/flac/libFLAC/bitreader.c | 2116 +- .../codecs/flac/libFLAC/bitwriter.c | 1684 +- .../codecs/flac/libFLAC/cpu.c | 988 +- .../codecs/flac/libFLAC/crc.c | 286 +- .../codecs/flac/libFLAC/fixed.c | 836 +- .../codecs/flac/libFLAC/float.c | 604 +- .../codecs/flac/libFLAC/format.c | 1168 +- .../codecs/flac/libFLAC/include/private/all.h | 100 +- .../flac/libFLAC/include/private/bitmath.h | 372 +- .../flac/libFLAC/include/private/bitreader.h | 182 +- .../flac/libFLAC/include/private/bitwriter.h | 208 +- .../codecs/flac/libFLAC/include/private/cpu.h | 198 +- .../codecs/flac/libFLAC/include/private/crc.h | 124 +- .../flac/libFLAC/include/private/fixed.h | 214 +- .../flac/libFLAC/include/private/float.h | 196 +- .../flac/libFLAC/include/private/format.h | 90 +- .../codecs/flac/libFLAC/include/private/lpc.h | 492 +- .../codecs/flac/libFLAC/include/private/md5.h | 100 +- .../flac/libFLAC/include/private/memory.h | 116 +- .../flac/libFLAC/include/private/metadata.h | 92 +- .../libFLAC/include/private/stream_encoder.h | 134 +- .../include/private/stream_encoder_framing.h | 92 +- .../flac/libFLAC/include/private/window.h | 148 +- .../flac/libFLAC/include/protected/all.h | 78 +- .../include/protected/stream_decoder.h | 120 +- .../include/protected/stream_encoder.h | 236 +- .../codecs/flac/libFLAC/lpc_flac.c | 2712 +- .../codecs/flac/libFLAC/md5.c | 1036 +- .../codecs/flac/libFLAC/memory.c | 436 +- .../codecs/flac/libFLAC/stream_decoder.c | 6790 ++-- .../codecs/flac/libFLAC/stream_encoder.c | 9054 ++--- .../flac/libFLAC/stream_encoder_framing.c | 1098 +- .../codecs/flac/libFLAC/window_flac.c | 562 +- .../juce_audio_formats/codecs/flac/metadata.h | 4362 +-- .../juce_audio_formats/codecs/flac/ordinals.h | 172 +- .../codecs/flac/stream_decoder.h | 3118 +- .../codecs/flac/stream_encoder.h | 3578 +- .../codecs/flac/win_utf8_io.h | 128 +- .../codecs/juce_AiffAudioFormat.cpp | 2016 +- .../codecs/juce_AiffAudioFormat.h | 190 +- .../codecs/juce_CoreAudioFormat.cpp | 1728 +- .../codecs/juce_CoreAudioFormat.h | 174 +- .../codecs/juce_FlacAudioFormat.cpp | 1226 +- .../codecs/juce_FlacAudioFormat.h | 152 +- .../codecs/juce_LAMEEncoderAudioFormat.cpp | 458 +- .../codecs/juce_LAMEEncoderAudioFormat.h | 162 +- .../codecs/juce_MP3AudioFormat.cpp | 6314 ++-- .../codecs/juce_MP3AudioFormat.h | 148 +- .../codecs/juce_OggVorbisAudioFormat.cpp | 1048 +- .../codecs/juce_OggVorbisAudioFormat.h | 206 +- .../codecs/juce_WavAudioFormat.cpp | 3820 +- .../codecs/juce_WavAudioFormat.h | 456 +- .../codecs/juce_WindowsMediaAudioFormat.cpp | 722 +- .../codecs/juce_WindowsMediaAudioFormat.h | 126 +- .../codecs/oggvorbis/bitwise.c | 1576 +- .../codecs/oggvorbis/codec.h | 484 +- .../codecs/oggvorbis/config_types.h | 20 +- .../codecs/oggvorbis/framing.c | 3592 +- .../oggvorbis/libvorbis-1.3.2/lib/analysis.c | 218 +- .../oggvorbis/libvorbis-1.3.2/lib/backends.h | 288 +- .../oggvorbis/libvorbis-1.3.2/lib/bitrate.c | 506 +- .../oggvorbis/libvorbis-1.3.2/lib/bitrate.h | 118 +- .../oggvorbis/libvorbis-1.3.2/lib/block.c | 2066 +- .../lib/books/coupled/res_books_51.h | 24512 ++++++------ .../lib/books/coupled/res_books_stereo.h | 31564 ++++++++-------- .../lib/books/floor/floor_books.h | 3092 +- .../lib/books/uncoupled/res_books_uncoupled.h | 15514 ++++---- .../oggvorbis/libvorbis-1.3.2/lib/codebook.c | 902 +- .../oggvorbis/libvorbis-1.3.2/lib/codebook.h | 238 +- .../libvorbis-1.3.2/lib/codec_internal.h | 374 +- .../oggvorbis/libvorbis-1.3.2/lib/envelope.c | 750 +- .../oggvorbis/libvorbis-1.3.2/lib/envelope.h | 160 +- .../oggvorbis/libvorbis-1.3.2/lib/floor0.c | 446 +- .../oggvorbis/libvorbis-1.3.2/lib/floor1.c | 2168 +- .../oggvorbis/libvorbis-1.3.2/lib/highlevel.h | 116 +- .../oggvorbis/libvorbis-1.3.2/lib/info.c | 1320 +- .../oggvorbis/libvorbis-1.3.2/lib/lookup.c | 188 +- .../oggvorbis/libvorbis-1.3.2/lib/lookup.h | 64 +- .../libvorbis-1.3.2/lib/lookup_data.h | 384 +- .../oggvorbis/libvorbis-1.3.2/lib/lpc.c | 320 +- .../oggvorbis/libvorbis-1.3.2/lib/lpc.h | 58 +- .../oggvorbis/libvorbis-1.3.2/lib/lsp.c | 908 +- .../oggvorbis/libvorbis-1.3.2/lib/lsp.h | 56 +- .../oggvorbis/libvorbis-1.3.2/lib/mapping0.c | 1632 +- .../oggvorbis/libvorbis-1.3.2/lib/masking.h | 1570 +- .../oggvorbis/libvorbis-1.3.2/lib/mdct.c | 1126 +- .../oggvorbis/libvorbis-1.3.2/lib/mdct.h | 142 +- .../oggvorbis/libvorbis-1.3.2/lib/misc.h | 106 +- .../libvorbis-1.3.2/lib/modes/floor_all.h | 520 +- .../libvorbis-1.3.2/lib/modes/psych_11.h | 100 +- .../libvorbis-1.3.2/lib/modes/psych_16.h | 266 +- .../libvorbis-1.3.2/lib/modes/psych_44.h | 1284 +- .../libvorbis-1.3.2/lib/modes/psych_8.h | 202 +- .../libvorbis-1.3.2/lib/modes/residue_16.h | 326 +- .../libvorbis-1.3.2/lib/modes/residue_44.h | 584 +- .../libvorbis-1.3.2/lib/modes/residue_44p51.h | 902 +- .../libvorbis-1.3.2/lib/modes/residue_44u.h | 636 +- .../libvorbis-1.3.2/lib/modes/residue_8.h | 218 +- .../libvorbis-1.3.2/lib/modes/setup_11.h | 286 +- .../libvorbis-1.3.2/lib/modes/setup_16.h | 306 +- .../libvorbis-1.3.2/lib/modes/setup_22.h | 256 +- .../libvorbis-1.3.2/lib/modes/setup_32.h | 264 +- .../libvorbis-1.3.2/lib/modes/setup_44.h | 234 +- .../libvorbis-1.3.2/lib/modes/setup_44p51.h | 148 +- .../libvorbis-1.3.2/lib/modes/setup_44u.h | 148 +- .../libvorbis-1.3.2/lib/modes/setup_8.h | 298 +- .../libvorbis-1.3.2/lib/modes/setup_X.h | 450 +- .../codecs/oggvorbis/libvorbis-1.3.2/lib/os.h | 372 +- .../oggvorbis/libvorbis-1.3.2/lib/psy.c | 2410 +- .../oggvorbis/libvorbis-1.3.2/lib/psy.h | 308 +- .../oggvorbis/libvorbis-1.3.2/lib/registry.c | 90 +- .../oggvorbis/libvorbis-1.3.2/lib/registry.h | 64 +- .../oggvorbis/libvorbis-1.3.2/lib/res0.c | 1782 +- .../oggvorbis/libvorbis-1.3.2/lib/scales.h | 180 +- .../libvorbis-1.3.2/lib/sharedbook.c | 1170 +- .../oggvorbis/libvorbis-1.3.2/lib/smallft.c | 2510 +- .../oggvorbis/libvorbis-1.3.2/lib/smallft.h | 68 +- .../oggvorbis/libvorbis-1.3.2/lib/synthesis.c | 368 +- .../oggvorbis/libvorbis-1.3.2/lib/vorbisenc.c | 2430 +- .../libvorbis-1.3.2/lib/vorbisfile.c | 4716 +-- .../oggvorbis/libvorbis-1.3.2/lib/window.c | 4270 +-- .../oggvorbis/libvorbis-1.3.2/lib/window.h | 52 +- .../juce_audio_formats/codecs/oggvorbis/ogg.h | 412 +- .../codecs/oggvorbis/os_types.h | 254 +- .../codecs/oggvorbis/vorbisenc.h | 872 +- .../codecs/oggvorbis/vorbisfile.h | 410 +- .../format/juce_AudioFormat.cpp | 184 +- .../format/juce_AudioFormat.h | 434 +- .../format/juce_AudioFormatManager.cpp | 332 +- .../format/juce_AudioFormatManager.h | 302 +- .../format/juce_AudioFormatReader.cpp | 884 +- .../format/juce_AudioFormatReader.h | 658 +- .../format/juce_AudioFormatReaderSource.cpp | 184 +- .../format/juce_AudioFormatReaderSource.h | 204 +- .../format/juce_AudioFormatWriter.cpp | 722 +- .../format/juce_AudioFormatWriter.h | 600 +- .../format/juce_AudioSubsectionReader.cpp | 144 +- .../format/juce_AudioSubsectionReader.h | 174 +- .../juce_BufferingAudioFormatReader.cpp | 348 +- .../format/juce_BufferingAudioFormatReader.h | 192 +- .../juce_MemoryMappedAudioFormatReader.h | 228 +- .../juce_audio_formats/juce_audio_formats.cpp | 148 +- .../juce_audio_formats/juce_audio_formats.h | 260 +- .../juce_audio_formats/juce_audio_formats.mm | 54 +- .../sampler/juce_Sampler.cpp | 332 +- .../juce_audio_formats/sampler/juce_Sampler.h | 308 +- .../containers/juce_AbstractFifo.cpp | 534 +- .../juce_core/containers/juce_AbstractFifo.h | 684 +- .../modules/juce_core/containers/juce_Array.h | 2300 +- .../containers/juce_ArrayAllocationBase.h | 242 +- .../juce_core/containers/juce_ArrayBase.cpp | 1200 +- .../juce_core/containers/juce_ArrayBase.h | 1236 +- .../containers/juce_DynamicObject.cpp | 264 +- .../juce_core/containers/juce_DynamicObject.h | 264 +- .../containers/juce_ElementComparator.h | 394 +- .../juce_core/containers/juce_HashMap.h | 1012 +- .../containers/juce_HashMap_test.cpp | 556 +- .../containers/juce_LinkedListPointer.h | 726 +- .../juce_core/containers/juce_ListenerList.h | 626 +- .../containers/juce_NamedValueSet.cpp | 612 +- .../juce_core/containers/juce_NamedValueSet.h | 370 +- .../juce_core/containers/juce_OwnedArray.cpp | 256 +- .../juce_core/containers/juce_OwnedArray.h | 1746 +- .../juce_core/containers/juce_PropertySet.cpp | 434 +- .../juce_core/containers/juce_PropertySet.h | 410 +- .../containers/juce_ReferenceCountedArray.cpp | 354 +- .../containers/juce_ReferenceCountedArray.h | 1814 +- .../containers/juce_ScopedValueSetter.h | 186 +- .../juce_core/containers/juce_SortedSet.h | 988 +- .../juce_core/containers/juce_SparseSet.cpp | 412 +- .../juce_core/containers/juce_SparseSet.h | 536 +- .../juce_core/containers/juce_Variant.cpp | 1660 +- .../juce_core/containers/juce_Variant.h | 730 +- .../files/juce_DirectoryIterator.cpp | 324 +- .../juce_core/files/juce_DirectoryIterator.h | 306 +- .../modules/juce_core/files/juce_File.cpp | 2498 +- .../modules/juce_core/files/juce_File.h | 2230 +- .../juce_core/files/juce_FileFilter.cpp | 80 +- .../modules/juce_core/files/juce_FileFilter.h | 142 +- .../juce_core/files/juce_FileInputStream.cpp | 312 +- .../juce_core/files/juce_FileInputStream.h | 180 +- .../juce_core/files/juce_FileOutputStream.cpp | 264 +- .../juce_core/files/juce_FileOutputStream.h | 252 +- .../juce_core/files/juce_FileSearchPath.cpp | 346 +- .../juce_core/files/juce_FileSearchPath.h | 342 +- .../juce_core/files/juce_MemoryMappedFile.h | 230 +- .../juce_core/files/juce_TemporaryFile.cpp | 234 +- .../juce_core/files/juce_TemporaryFile.h | 324 +- .../files/juce_WildcardFileFilter.cpp | 148 +- .../juce_core/files/juce_WildcardFileFilter.h | 152 +- .../juce_core/javascript/juce_JSON.cpp | 1382 +- .../modules/juce_core/javascript/juce_JSON.h | 272 +- .../juce_core/javascript/juce_Javascript.cpp | 3840 +- .../juce_core/javascript/juce_Javascript.h | 244 +- .../modules/juce_core/juce_core.cpp | 526 +- JuceLibraryCode/modules/juce_core/juce_core.h | 770 +- .../modules/juce_core/juce_core.mm | 46 +- .../juce_core/logging/juce_FileLogger.cpp | 266 +- .../juce_core/logging/juce_FileLogger.h | 266 +- .../modules/juce_core/logging/juce_Logger.cpp | 124 +- .../modules/juce_core/logging/juce_Logger.h | 182 +- .../juce_core/maths/juce_BigInteger.cpp | 2742 +- .../modules/juce_core/maths/juce_BigInteger.h | 684 +- .../juce_core/maths/juce_Expression.cpp | 2342 +- .../modules/juce_core/maths/juce_Expression.h | 502 +- .../juce_core/maths/juce_MathsFunctions.h | 1386 +- .../juce_core/maths/juce_NormalisableRange.h | 522 +- .../modules/juce_core/maths/juce_Random.cpp | 404 +- .../modules/juce_core/maths/juce_Random.h | 274 +- .../modules/juce_core/maths/juce_Range.h | 594 +- .../maths/juce_StatisticsAccumulator.h | 272 +- .../modules/juce_core/memory/juce_Atomic.h | 292 +- .../modules/juce_core/memory/juce_ByteOrder.h | 440 +- .../memory/juce_ContainerDeletePolicy.h | 116 +- .../modules/juce_core/memory/juce_HeapBlock.h | 700 +- .../juce_HeavyweightLeakedObjectDetector.h | 288 +- .../memory/juce_LeakedObjectDetector.h | 280 +- .../modules/juce_core/memory/juce_Memory.h | 292 +- .../juce_core/memory/juce_MemoryBlock.cpp | 820 +- .../juce_core/memory/juce_MemoryBlock.h | 556 +- .../memory/juce_OptionalScopedPointer.h | 366 +- .../memory/juce_ReferenceCountedObject.h | 936 +- .../juce_core/memory/juce_ScopedPointer.h | 434 +- .../memory/juce_SharedResourcePointer.h | 328 +- .../modules/juce_core/memory/juce_Singleton.h | 560 +- .../juce_core/memory/juce_WeakReference.h | 480 +- .../misc/juce_ConsoleApplication.cpp | 878 +- .../juce_core/misc/juce_ConsoleApplication.h | 706 +- .../modules/juce_core/misc/juce_Result.cpp | 160 +- .../modules/juce_core/misc/juce_Result.h | 232 +- .../misc/juce_RuntimePermissions.cpp | 72 +- .../juce_core/misc/juce_RuntimePermissions.h | 262 +- .../modules/juce_core/misc/juce_Uuid.cpp | 294 +- .../modules/juce_core/misc/juce_Uuid.h | 294 +- .../juce_core/misc/juce_WindowsRegistry.h | 276 +- .../app/com/roli/juce/FragmentOverlay.java | 116 +- .../app/com/roli/juce/JuceHTTPStream.java | 814 +- .../javacore/app/com/roli/juce/JuceApp.java | 30 +- .../javacore/init/com/roli/juce/Java.java | 24 +- .../native/juce_BasicNativeHeaders.h | 614 +- .../juce_core/native/juce_android_Files.cpp | 1384 +- .../native/juce_android_JNIHelpers.cpp | 1402 +- .../native/juce_android_JNIHelpers.h | 1984 +- .../juce_core/native/juce_android_Misc.cpp | 62 +- .../juce_core/native/juce_android_Network.cpp | 1308 +- .../juce_android_RuntimePermissions.cpp | 522 +- .../native/juce_android_SystemStats.cpp | 482 +- .../juce_core/native/juce_android_Threads.cpp | 790 +- .../juce_core/native/juce_curl_Network.cpp | 1300 +- .../native/juce_linux_CommonFile.cpp | 284 +- .../juce_core/native/juce_linux_Files.cpp | 468 +- .../juce_core/native/juce_linux_Network.cpp | 1170 +- .../native/juce_linux_SystemStats.cpp | 466 +- .../juce_core/native/juce_linux_Threads.cpp | 124 +- .../native/juce_mac_ClangBugWorkaround.h | 82 +- .../juce_core/native/juce_mac_Files.mm | 994 +- .../juce_core/native/juce_mac_Network.mm | 2294 +- .../juce_core/native/juce_mac_Strings.mm | 210 +- .../juce_core/native/juce_mac_SystemStats.mm | 724 +- .../juce_core/native/juce_mac_Threads.mm | 166 +- .../juce_core/native/juce_osx_ObjCHelpers.h | 860 +- .../juce_core/native/juce_posix_IPAddress.h | 266 +- .../juce_core/native/juce_posix_NamedPipe.cpp | 476 +- .../juce_core/native/juce_posix_SharedCode.h | 2904 +- .../juce_core/native/juce_win32_ComSmartPtr.h | 396 +- .../juce_core/native/juce_win32_Files.cpp | 2430 +- .../juce_core/native/juce_win32_Network.cpp | 1288 +- .../juce_core/native/juce_win32_Registry.cpp | 502 +- .../native/juce_win32_SystemStats.cpp | 1088 +- .../juce_core/native/juce_win32_Threads.cpp | 1140 +- .../juce_core/network/juce_IPAddress.cpp | 932 +- .../juce_core/network/juce_IPAddress.h | 284 +- .../juce_core/network/juce_MACAddress.cpp | 200 +- .../juce_core/network/juce_MACAddress.h | 176 +- .../juce_core/network/juce_NamedPipe.cpp | 526 +- .../juce_core/network/juce_NamedPipe.h | 200 +- .../modules/juce_core/network/juce_Socket.cpp | 1734 +- .../modules/juce_core/network/juce_Socket.h | 734 +- .../modules/juce_core/network/juce_URL.cpp | 1818 +- .../modules/juce_core/network/juce_URL.h | 1168 +- .../juce_core/network/juce_WebInputStream.cpp | 174 +- .../juce_core/network/juce_WebInputStream.h | 432 +- .../streams/juce_BufferedInputStream.cpp | 556 +- .../streams/juce_BufferedInputStream.h | 180 +- .../streams/juce_FileInputSource.cpp | 110 +- .../juce_core/streams/juce_FileInputSource.h | 120 +- .../juce_core/streams/juce_InputSource.h | 144 +- .../juce_core/streams/juce_InputStream.cpp | 498 +- .../juce_core/streams/juce_InputStream.h | 528 +- .../streams/juce_MemoryInputStream.cpp | 452 +- .../streams/juce_MemoryInputStream.h | 188 +- .../streams/juce_MemoryOutputStream.cpp | 420 +- .../streams/juce_MemoryOutputStream.h | 266 +- .../juce_core/streams/juce_OutputStream.cpp | 788 +- .../juce_core/streams/juce_OutputStream.h | 548 +- .../streams/juce_SubregionStream.cpp | 320 +- .../juce_core/streams/juce_SubregionStream.h | 166 +- .../juce_core/streams/juce_URLInputSource.cpp | 118 +- .../juce_core/streams/juce_URLInputSource.h | 122 +- .../juce_core/system/juce_CompilerSupport.h | 264 +- .../juce_core/system/juce_PlatformDefs.h | 650 +- .../juce_core/system/juce_StandardHeader.h | 326 +- .../juce_core/system/juce_SystemStats.cpp | 504 +- .../juce_core/system/juce_SystemStats.h | 488 +- .../juce_core/system/juce_TargetPlatform.h | 394 +- .../modules/juce_core/text/juce_Base64.cpp | 332 +- .../modules/juce_core/text/juce_Base64.h | 106 +- .../juce_core/text/juce_CharPointer_ASCII.h | 754 +- .../juce_core/text/juce_CharPointer_UTF16.h | 1038 +- .../juce_core/text/juce_CharPointer_UTF32.h | 744 +- .../juce_core/text/juce_CharPointer_UTF8.h | 1128 +- .../text/juce_CharacterFunctions.cpp | 606 +- .../juce_core/text/juce_CharacterFunctions.h | 1596 +- .../juce_core/text/juce_Identifier.cpp | 148 +- .../modules/juce_core/text/juce_Identifier.h | 264 +- .../juce_core/text/juce_LocalisedStrings.cpp | 412 +- .../juce_core/text/juce_LocalisedStrings.h | 480 +- .../modules/juce_core/text/juce_NewLine.h | 164 +- .../modules/juce_core/text/juce_String.cpp | 5890 +-- .../modules/juce_core/text/juce_String.h | 3110 +- .../juce_core/text/juce_StringArray.cpp | 946 +- .../modules/juce_core/text/juce_StringArray.h | 940 +- .../juce_core/text/juce_StringPairArray.cpp | 338 +- .../juce_core/text/juce_StringPairArray.h | 302 +- .../juce_core/text/juce_StringPool.cpp | 330 +- .../modules/juce_core/text/juce_StringPool.h | 180 +- .../modules/juce_core/text/juce_StringRef.h | 316 +- .../modules/juce_core/text/juce_TextDiff.cpp | 572 +- .../modules/juce_core/text/juce_TextDiff.h | 148 +- .../juce_core/threads/juce_ChildProcess.cpp | 236 +- .../juce_core/threads/juce_ChildProcess.h | 224 +- .../juce_core/threads/juce_CriticalSection.h | 524 +- .../juce_core/threads/juce_DynamicLibrary.h | 172 +- .../threads/juce_HighResolutionTimer.cpp | 70 +- .../threads/juce_HighResolutionTimer.h | 204 +- .../juce_core/threads/juce_InterProcessLock.h | 242 +- .../modules/juce_core/threads/juce_Process.h | 312 +- .../juce_core/threads/juce_ReadWriteLock.cpp | 294 +- .../juce_core/threads/juce_ReadWriteLock.h | 292 +- .../juce_core/threads/juce_ScopedLock.h | 488 +- .../juce_core/threads/juce_ScopedReadLock.h | 168 +- .../juce_core/threads/juce_ScopedWriteLock.h | 168 +- .../modules/juce_core/threads/juce_SpinLock.h | 170 +- .../modules/juce_core/threads/juce_Thread.cpp | 1080 +- .../modules/juce_core/threads/juce_Thread.h | 828 +- .../juce_core/threads/juce_ThreadLocalValue.h | 296 +- .../juce_core/threads/juce_ThreadPool.cpp | 864 +- .../juce_core/threads/juce_ThreadPool.h | 686 +- .../threads/juce_TimeSliceThread.cpp | 368 +- .../juce_core/threads/juce_TimeSliceThread.h | 300 +- .../juce_core/threads/juce_WaitableEvent.cpp | 140 +- .../juce_core/threads/juce_WaitableEvent.h | 198 +- .../time/juce_PerformanceCounter.cpp | 264 +- .../juce_core/time/juce_PerformanceCounter.h | 330 +- .../juce_core/time/juce_RelativeTime.cpp | 314 +- .../juce_core/time/juce_RelativeTime.h | 368 +- .../modules/juce_core/time/juce_Time.cpp | 1352 +- .../modules/juce_core/time/juce_Time.h | 802 +- .../juce_core/unit_tests/juce_UnitTest.cpp | 566 +- .../juce_core/unit_tests/juce_UnitTest.h | 878 +- .../unit_tests/juce_UnitTestCategories.h | 106 +- .../juce_core/xml/juce_XmlDocument.cpp | 1784 +- .../modules/juce_core/xml/juce_XmlDocument.h | 420 +- .../modules/juce_core/xml/juce_XmlElement.cpp | 2080 +- .../modules/juce_core/xml/juce_XmlElement.h | 1522 +- .../zip/juce_GZIPCompressorOutputStream.cpp | 428 +- .../zip/juce_GZIPCompressorOutputStream.h | 216 +- .../zip/juce_GZIPDecompressorInputStream.cpp | 786 +- .../zip/juce_GZIPDecompressorInputStream.h | 206 +- .../modules/juce_core/zip/juce_ZipFile.cpp | 1382 +- .../modules/juce_core/zip/juce_ZipFile.h | 532 +- .../modules/juce_core/zip/zlib/adler32.c | 286 +- .../modules/juce_core/zip/zlib/compress.c | 140 +- .../modules/juce_core/zip/zlib/crc32.c | 814 +- .../modules/juce_core/zip/zlib/crc32.h | 882 +- .../modules/juce_core/zip/zlib/deflate.c | 3358 +- .../modules/juce_core/zip/zlib/deflate.h | 666 +- .../modules/juce_core/zip/zlib/infback.c | 1222 +- .../modules/juce_core/zip/zlib/inffast.c | 632 +- .../modules/juce_core/zip/zlib/inffast.h | 22 +- .../modules/juce_core/zip/zlib/inffixed.h | 188 +- .../modules/juce_core/zip/zlib/inflate.c | 2678 +- .../modules/juce_core/zip/zlib/inflate.h | 242 +- .../modules/juce_core/zip/zlib/inftrees.c | 656 +- .../modules/juce_core/zip/zlib/inftrees.h | 122 +- .../modules/juce_core/zip/zlib/trees.c | 2382 +- .../modules/juce_core/zip/zlib/trees.h | 254 +- .../modules/juce_core/zip/zlib/uncompr.c | 120 +- .../modules/juce_core/zip/zlib/zconf.h | 690 +- .../modules/juce_core/zip/zlib/zconf.in.h | 664 +- .../modules/juce_core/zip/zlib/zlib.h | 2716 +- .../modules/juce_core/zip/zlib/zutil.c | 622 +- .../modules/juce_core/zip/zlib/zutil.h | 542 +- .../juce_ApplicationProperties.cpp | 216 +- .../juce_ApplicationProperties.h | 268 +- .../app_properties/juce_PropertiesFile.cpp | 718 +- .../app_properties/juce_PropertiesFile.h | 512 +- .../juce_data_structures.cpp | 90 +- .../juce_data_structures.h | 132 +- .../juce_data_structures.mm | 54 +- .../undomanager/juce_UndoManager.cpp | 740 +- .../undomanager/juce_UndoManager.h | 530 +- .../undomanager/juce_UndoableAction.h | 202 +- .../values/juce_CachedValue.cpp | 312 +- .../values/juce_CachedValue.h | 630 +- .../values/juce_Value.cpp | 484 +- .../juce_data_structures/values/juce_Value.h | 486 +- .../values/juce_ValueTree.cpp | 2490 +- .../values/juce_ValueTree.h | 1292 +- .../values/juce_ValueTreeSynchroniser.cpp | 480 +- .../values/juce_ValueTreeSynchroniser.h | 196 +- .../values/juce_ValueWithDefault.cpp | 204 +- .../values/juce_ValueWithDefault.h | 490 +- .../broadcasters/juce_ActionBroadcaster.cpp | 184 +- .../broadcasters/juce_ActionBroadcaster.h | 158 +- .../broadcasters/juce_ActionListener.h | 96 +- .../broadcasters/juce_AsyncUpdater.cpp | 186 +- .../broadcasters/juce_AsyncUpdater.h | 220 +- .../broadcasters/juce_ChangeBroadcaster.cpp | 204 +- .../broadcasters/juce_ChangeBroadcaster.h | 210 +- .../broadcasters/juce_ChangeListener.h | 112 +- .../juce_ConnectedChildProcess.cpp | 534 +- .../interprocess/juce_ConnectedChildProcess.h | 400 +- .../juce_InterprocessConnection.cpp | 756 +- .../juce_InterprocessConnection.h | 420 +- .../juce_InterprocessConnectionServer.cpp | 160 +- .../juce_InterprocessConnectionServer.h | 212 +- .../juce_NetworkServiceDiscovery.cpp | 406 +- .../juce_NetworkServiceDiscovery.h | 276 +- .../modules/juce_events/juce_events.cpp | 206 +- .../modules/juce_events/juce_events.h | 208 +- .../modules/juce_events/juce_events.mm | 46 +- .../messages/juce_ApplicationBase.cpp | 664 +- .../messages/juce_ApplicationBase.h | 666 +- .../messages/juce_CallbackMessage.h | 148 +- .../messages/juce_DeletedAtShutdown.cpp | 188 +- .../messages/juce_DeletedAtShutdown.h | 130 +- .../messages/juce_Initialisation.h | 408 +- .../juce_events/messages/juce_Message.h | 128 +- .../messages/juce_MessageListener.cpp | 104 +- .../messages/juce_MessageListener.h | 140 +- .../messages/juce_MessageManager.cpp | 960 +- .../messages/juce_MessageManager.h | 978 +- .../juce_MountedVolumeListChangeDetector.h | 116 +- .../messages/juce_NotificationType.h | 78 +- .../native/juce_android_Messaging.cpp | 620 +- .../native/juce_ios_MessageManager.mm | 206 +- .../juce_events/native/juce_linux_EventLoop.h | 100 +- .../native/juce_linux_Messaging.cpp | 572 +- .../native/juce_mac_MessageManager.mm | 1048 +- .../native/juce_osx_MessageQueue.h | 210 +- .../native/juce_win32_HiddenMessageWindow.h | 270 +- .../native/juce_win32_Messaging.cpp | 656 +- .../native/juce_win32_WinRTWrapper.cpp | 162 +- .../native/juce_win32_WinRTWrapper.h | 294 +- .../juce_events/timers/juce_MultiTimer.cpp | 216 +- .../juce_events/timers/juce_MultiTimer.h | 250 +- .../modules/juce_events/timers/juce_Timer.cpp | 778 +- .../modules/juce_events/timers/juce_Timer.h | 274 +- 580 files changed, 243800 insertions(+), 243798 deletions(-) create mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..032c1bd --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +* text=auto + diff --git a/JuceLibraryCode/modules/juce_audio_basics/audio_play_head/juce_AudioPlayHead.h b/JuceLibraryCode/modules/juce_audio_basics/audio_play_head/juce_AudioPlayHead.h index 70f3a2b..25ebba1 100644 --- a/JuceLibraryCode/modules/juce_audio_basics/audio_play_head/juce_AudioPlayHead.h +++ b/JuceLibraryCode/modules/juce_audio_basics/audio_play_head/juce_AudioPlayHead.h @@ -1,157 +1,157 @@ -/* - ============================================================================== - - This file is part of the JUCE library. - Copyright (c) 2017 - ROLI Ltd. - - JUCE is an open source library subject to commercial or open-source - licensing. - - The code included in this file is provided under the terms of the ISC license - http://www.isc.org/downloads/software-support-policy/isc-license. Permission - To use, copy, modify, and/or distribute this software for any purpose with or - without fee is hereby granted provided that the above copyright notice and - this permission notice appear in all copies. - - JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER - EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE - DISCLAIMED. - - ============================================================================== -*/ - -namespace juce -{ - -//============================================================================== -/** - A subclass of AudioPlayHead can supply information about the position and - status of a moving play head during audio playback. - - One of these can be supplied to an AudioProcessor object so that it can find - out about the position of the audio that it is rendering. - - @see AudioProcessor::setPlayHead, AudioProcessor::getPlayHead - - @tags{Audio} -*/ -class JUCE_API AudioPlayHead -{ -protected: - //============================================================================== - AudioPlayHead() = default; - -public: - virtual ~AudioPlayHead() = default; - - //============================================================================== - /** Frame rate types. */ - enum FrameRateType - { - fps23976 = 0, - fps24 = 1, - fps25 = 2, - fps2997 = 3, - fps30 = 4, - fps2997drop = 5, - fps30drop = 6, - fps60 = 7, - fps60drop = 8, - fpsUnknown = 99 - }; - - //============================================================================== - /** This structure is filled-in by the AudioPlayHead::getCurrentPosition() method. - */ - struct JUCE_API CurrentPositionInfo - { - /** The tempo in BPM */ - double bpm; - - /** Time signature numerator, e.g. the 3 of a 3/4 time sig */ - int timeSigNumerator; - /** Time signature denominator, e.g. the 4 of a 3/4 time sig */ - int timeSigDenominator; - - /** The current play position, in samples from the start of the timeline. */ - int64 timeInSamples; - /** The current play position, in seconds from the start of the timeline. */ - double timeInSeconds; - - /** For timecode, the position of the start of the timeline, in seconds from 00:00:00:00. */ - double editOriginTime; - - /** The current play position, in units of quarter-notes. */ - double ppqPosition; - - /** The position of the start of the last bar, in units of quarter-notes. - - This is the time from the start of the timeline to the start of the current - bar, in ppq units. - - Note - this value may be unavailable on some hosts, e.g. Pro-Tools. If - it's not available, the value will be 0. - */ - double ppqPositionOfLastBarStart; - - /** The video frame rate, if applicable. */ - FrameRateType frameRate; - - /** True if the transport is currently playing. */ - bool isPlaying; - - /** True if the transport is currently recording. - - (When isRecording is true, then isPlaying will also be true). - */ - bool isRecording; - - /** The current cycle start position in units of quarter-notes. - Note that not all hosts or plugin formats may provide this value. - @see isLooping - */ - double ppqLoopStart; - - /** The current cycle end position in units of quarter-notes. - Note that not all hosts or plugin formats may provide this value. - @see isLooping - */ - double ppqLoopEnd; - - /** True if the transport is currently looping. */ - bool isLooping; - - //============================================================================== - bool operator== (const CurrentPositionInfo& other) const noexcept; - bool operator!= (const CurrentPositionInfo& other) const noexcept; - - void resetToDefault(); - }; - - //============================================================================== - /** Fills-in the given structure with details about the transport's - position at the start of the current processing block. If this method returns - false then the current play head position is not available and the given - structure will be undefined. - - You can ONLY call this from your processBlock() method! Calling it at other - times will produce undefined behaviour, as the host may not have any context - in which a time would make sense, and some hosts will almost certainly have - multithreading issues if it's not called on the audio thread. - */ - virtual bool getCurrentPosition (CurrentPositionInfo& result) = 0; - - /** Returns true if this object can control the transport. */ - virtual bool canControlTransport() { return false; } - - /** Starts or stops the audio. */ - virtual void transportPlay (bool shouldStartPlaying) { ignoreUnused (shouldStartPlaying); } - - /** Starts or stops recording the audio. */ - virtual void transportRecord (bool shouldStartRecording) { ignoreUnused (shouldStartRecording); } - - /** Rewinds the audio. */ - virtual void transportRewind() {} -}; - -} // namespace juce +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2017 - ROLI Ltd. + + JUCE is an open source library subject to commercial or open-source + licensing. + + The code included in this file is provided under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license. Permission + To use, copy, modify, and/or distribute this software for any purpose with or + without fee is hereby granted provided that the above copyright notice and + this permission notice appear in all copies. + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +namespace juce +{ + +//============================================================================== +/** + A subclass of AudioPlayHead can supply information about the position and + status of a moving play head during audio playback. + + One of these can be supplied to an AudioProcessor object so that it can find + out about the position of the audio that it is rendering. + + @see AudioProcessor::setPlayHead, AudioProcessor::getPlayHead + + @tags{Audio} +*/ +class JUCE_API AudioPlayHead +{ +protected: + //============================================================================== + AudioPlayHead() = default; + +public: + virtual ~AudioPlayHead() = default; + + //============================================================================== + /** Frame rate types. */ + enum FrameRateType + { + fps23976 = 0, + fps24 = 1, + fps25 = 2, + fps2997 = 3, + fps30 = 4, + fps2997drop = 5, + fps30drop = 6, + fps60 = 7, + fps60drop = 8, + fpsUnknown = 99 + }; + + //============================================================================== + /** This structure is filled-in by the AudioPlayHead::getCurrentPosition() method. + */ + struct JUCE_API CurrentPositionInfo + { + /** The tempo in BPM */ + double bpm; + + /** Time signature numerator, e.g. the 3 of a 3/4 time sig */ + int timeSigNumerator; + /** Time signature denominator, e.g. the 4 of a 3/4 time sig */ + int timeSigDenominator; + + /** The current play position, in samples from the start of the timeline. */ + int64 timeInSamples; + /** The current play position, in seconds from the start of the timeline. */ + double timeInSeconds; + + /** For timecode, the position of the start of the timeline, in seconds from 00:00:00:00. */ + double editOriginTime; + + /** The current play position, in units of quarter-notes. */ + double ppqPosition; + + /** The position of the start of the last bar, in units of quarter-notes. + + This is the time from the start of the timeline to the start of the current + bar, in ppq units. + + Note - this value may be unavailable on some hosts, e.g. Pro-Tools. If + it's not available, the value will be 0. + */ + double ppqPositionOfLastBarStart; + + /** The video frame rate, if applicable. */ + FrameRateType frameRate; + + /** True if the transport is currently playing. */ + bool isPlaying; + + /** True if the transport is currently recording. + + (When isRecording is true, then isPlaying will also be true). + */ + bool isRecording; + + /** The current cycle start position in units of quarter-notes. + Note that not all hosts or plugin formats may provide this value. + @see isLooping + */ + double ppqLoopStart; + + /** The current cycle end position in units of quarter-notes. + Note that not all hosts or plugin formats may provide this value. + @see isLooping + */ + double ppqLoopEnd; + + /** True if the transport is currently looping. */ + bool isLooping; + + //============================================================================== + bool operator== (const CurrentPositionInfo& other) const noexcept; + bool operator!= (const CurrentPositionInfo& other) const noexcept; + + void resetToDefault(); + }; + + //============================================================================== + /** Fills-in the given structure with details about the transport's + position at the start of the current processing block. If this method returns + false then the current play head position is not available and the given + structure will be undefined. + + You can ONLY call this from your processBlock() method! Calling it at other + times will produce undefined behaviour, as the host may not have any context + in which a time would make sense, and some hosts will almost certainly have + multithreading issues if it's not called on the audio thread. + */ + virtual bool getCurrentPosition (CurrentPositionInfo& result) = 0; + + /** Returns true if this object can control the transport. */ + virtual bool canControlTransport() { return false; } + + /** Starts or stops the audio. */ + virtual void transportPlay (bool shouldStartPlaying) { ignoreUnused (shouldStartPlaying); } + + /** Starts or stops recording the audio. */ + virtual void transportRecord (bool shouldStartRecording) { ignoreUnused (shouldStartRecording); } + + /** Rewinds the audio. */ + virtual void transportRewind() {} +}; + +} // namespace juce diff --git a/JuceLibraryCode/modules/juce_audio_basics/buffers/juce_AudioChannelSet.cpp b/JuceLibraryCode/modules/juce_audio_basics/buffers/juce_AudioChannelSet.cpp index 40ba299..195d786 100644 --- a/JuceLibraryCode/modules/juce_audio_basics/buffers/juce_AudioChannelSet.cpp +++ b/JuceLibraryCode/modules/juce_audio_basics/buffers/juce_AudioChannelSet.cpp @@ -1,656 +1,656 @@ -/* - ============================================================================== - - This file is part of the JUCE library. - Copyright (c) 2017 - ROLI Ltd. - - JUCE is an open source library subject to commercial or open-source - licensing. - - The code included in this file is provided under the terms of the ISC license - http://www.isc.org/downloads/software-support-policy/isc-license. Permission - To use, copy, modify, and/or distribute this software for any purpose with or - without fee is hereby granted provided that the above copyright notice and - this permission notice appear in all copies. - - JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER - EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE - DISCLAIMED. - - ============================================================================== -*/ - -namespace juce -{ - - - -AudioChannelSet::AudioChannelSet (uint32 c) : channels (static_cast (c)) -{ -} - -AudioChannelSet::AudioChannelSet (const Array& c) -{ - for (auto channel : c) - addChannel (channel); -} - -bool AudioChannelSet::operator== (const AudioChannelSet& other) const noexcept { return channels == other.channels; } -bool AudioChannelSet::operator!= (const AudioChannelSet& other) const noexcept { return channels != other.channels; } -bool AudioChannelSet::operator< (const AudioChannelSet& other) const noexcept { return channels < other.channels; } - -String AudioChannelSet::getChannelTypeName (AudioChannelSet::ChannelType type) -{ - if (type >= discreteChannel0) - return "Discrete " + String (type - discreteChannel0 + 1); - - switch (type) - { - case left: return NEEDS_TRANS("Left"); - case right: return NEEDS_TRANS("Right"); - case centre: return NEEDS_TRANS("Centre"); - case LFE: return NEEDS_TRANS("LFE"); - case leftSurround: return NEEDS_TRANS("Left Surround"); - case rightSurround: return NEEDS_TRANS("Right Surround"); - case leftCentre: return NEEDS_TRANS("Left Centre"); - case rightCentre: return NEEDS_TRANS("Right Centre"); - case centreSurround: return NEEDS_TRANS("Centre Surround"); - case leftSurroundRear: return NEEDS_TRANS("Left Surround Rear"); - case rightSurroundRear: return NEEDS_TRANS("Right Surround Rear"); - case topMiddle: return NEEDS_TRANS("Top Middle"); - case topFrontLeft: return NEEDS_TRANS("Top Front Left"); - case topFrontCentre: return NEEDS_TRANS("Top Front Centre"); - case topFrontRight: return NEEDS_TRANS("Top Front Right"); - case topRearLeft: return NEEDS_TRANS("Top Rear Left"); - case topRearCentre: return NEEDS_TRANS("Top Rear Centre"); - case topRearRight: return NEEDS_TRANS("Top Rear Right"); - case wideLeft: return NEEDS_TRANS("Wide Left"); - case wideRight: return NEEDS_TRANS("Wide Right"); - case LFE2: return NEEDS_TRANS("LFE 2"); - case leftSurroundSide: return NEEDS_TRANS("Left Surround Side"); - case rightSurroundSide: return NEEDS_TRANS("Right Surround Side"); - case ambisonicW: return NEEDS_TRANS("Ambisonic W"); - case ambisonicX: return NEEDS_TRANS("Ambisonic X"); - case ambisonicY: return NEEDS_TRANS("Ambisonic Y"); - case ambisonicZ: return NEEDS_TRANS("Ambisonic Z"); - case topSideLeft: return NEEDS_TRANS("Top Side Left"); - case topSideRight: return NEEDS_TRANS("Top Side Right"); - case ambisonicACN4: return NEEDS_TRANS("Ambisonic 4"); - case ambisonicACN5: return NEEDS_TRANS("Ambisonic 5"); - case ambisonicACN6: return NEEDS_TRANS("Ambisonic 6"); - case ambisonicACN7: return NEEDS_TRANS("Ambisonic 7"); - case ambisonicACN8: return NEEDS_TRANS("Ambisonic 8"); - case ambisonicACN9: return NEEDS_TRANS("Ambisonic 9"); - case ambisonicACN10: return NEEDS_TRANS("Ambisonic 10"); - case ambisonicACN11: return NEEDS_TRANS("Ambisonic 11"); - case ambisonicACN12: return NEEDS_TRANS("Ambisonic 12"); - case ambisonicACN13: return NEEDS_TRANS("Ambisonic 13"); - case ambisonicACN14: return NEEDS_TRANS("Ambisonic 14"); - case ambisonicACN15: return NEEDS_TRANS("Ambisonic 15"); - case bottomFrontLeft: return NEEDS_TRANS("Bottom Front Left"); - case bottomFrontCentre: return NEEDS_TRANS("Bottom Front Centre"); - case bottomFrontRight: return NEEDS_TRANS("Bottom Front Right"); - case bottomSideLeft: return NEEDS_TRANS("Bottom Side Left"); - case bottomSideRight: return NEEDS_TRANS("Bottom Side Right"); - case bottomRearLeft: return NEEDS_TRANS("Bottom Rear Left"); - case bottomRearCentre: return NEEDS_TRANS("Bottom Rear Centre"); - case bottomRearRight: return NEEDS_TRANS("Bottom Rear Right"); - case discreteChannel0: return NEEDS_TRANS("Discrete channel"); - default: break; - } - - return "Unknown"; -} - -String AudioChannelSet::getAbbreviatedChannelTypeName (AudioChannelSet::ChannelType type) -{ - if (type >= discreteChannel0) - return String (type - discreteChannel0 + 1); - - switch (type) - { - case left: return "L"; - case right: return "R"; - case centre: return "C"; - case LFE: return "Lfe"; - case leftSurround: return "Ls"; - case rightSurround: return "Rs"; - case leftCentre: return "Lc"; - case rightCentre: return "Rc"; - case centreSurround: return "Cs"; - case leftSurroundRear: return "Lrs"; - case rightSurroundRear: return "Rrs"; - case topMiddle: return "Tm"; - case topFrontLeft: return "Tfl"; - case topFrontCentre: return "Tfc"; - case topFrontRight: return "Tfr"; - case topRearLeft: return "Trl"; - case topRearCentre: return "Trc"; - case topRearRight: return "Trr"; - case wideLeft: return "Wl"; - case wideRight: return "Wr"; - case LFE2: return "Lfe2"; - case leftSurroundSide: return "Lss"; - case rightSurroundSide: return "Rss"; - case ambisonicACN0: return "ACN0"; - case ambisonicACN1: return "ACN1"; - case ambisonicACN2: return "ACN2"; - case ambisonicACN3: return "ACN3"; - case ambisonicACN4: return "ACN4"; - case ambisonicACN5: return "ACN5"; - case ambisonicACN6: return "ACN6"; - case ambisonicACN7: return "ACN7"; - case ambisonicACN8: return "ACN8"; - case ambisonicACN9: return "ACN9"; - case ambisonicACN10: return "ACN10"; - case ambisonicACN11: return "ACN11"; - case ambisonicACN12: return "ACN12"; - case ambisonicACN13: return "ACN13"; - case ambisonicACN14: return "ACN14"; - case ambisonicACN15: return "ACN15"; - case topSideLeft: return "Tsl"; - case topSideRight: return "Tsr"; - case bottomFrontLeft: return "Bfl"; - case bottomFrontCentre: return "Bfc"; - case bottomFrontRight: return "Bfr"; - case bottomSideLeft: return "Bsl"; - case bottomSideRight: return "Bsr"; - case bottomRearLeft: return "Brl"; - case bottomRearCentre: return "Brc"; - case bottomRearRight: return "Brr"; - default: break; - } - - if (type >= ambisonicACN4 && type <= ambisonicACN35) - return "ACN" + String (type - ambisonicACN4 + 4); - - return {}; -} - -AudioChannelSet::ChannelType AudioChannelSet::getChannelTypeFromAbbreviation (const String& abbr) -{ - if (abbr.length() > 0 && (abbr[0] >= '0' && abbr[0] <= '9')) - return static_cast (static_cast (discreteChannel0) - + abbr.getIntValue() - 1); - - if (abbr == "L") return left; - if (abbr == "R") return right; - if (abbr == "C") return centre; - if (abbr == "Lfe") return LFE; - if (abbr == "Ls") return leftSurround; - if (abbr == "Rs") return rightSurround; - if (abbr == "Lc") return leftCentre; - if (abbr == "Rc") return rightCentre; - if (abbr == "Cs") return centreSurround; - if (abbr == "Lrs") return leftSurroundRear; - if (abbr == "Rrs") return rightSurroundRear; - if (abbr == "Tm") return topMiddle; - if (abbr == "Tfl") return topFrontLeft; - if (abbr == "Tfc") return topFrontCentre; - if (abbr == "Tfr") return topFrontRight; - if (abbr == "Trl") return topRearLeft; - if (abbr == "Trc") return topRearCentre; - if (abbr == "Trr") return topRearRight; - if (abbr == "Wl") return wideLeft; - if (abbr == "Wr") return wideRight; - if (abbr == "Lfe2") return LFE2; - if (abbr == "Lss") return leftSurroundSide; - if (abbr == "Rss") return rightSurroundSide; - if (abbr == "W") return ambisonicW; - if (abbr == "X") return ambisonicX; - if (abbr == "Y") return ambisonicY; - if (abbr == "Z") return ambisonicZ; - if (abbr == "ACN0") return ambisonicACN0; - if (abbr == "ACN1") return ambisonicACN1; - if (abbr == "ACN2") return ambisonicACN2; - if (abbr == "ACN3") return ambisonicACN3; - if (abbr == "ACN4") return ambisonicACN4; - if (abbr == "ACN5") return ambisonicACN5; - if (abbr == "ACN6") return ambisonicACN6; - if (abbr == "ACN7") return ambisonicACN7; - if (abbr == "ACN8") return ambisonicACN8; - if (abbr == "ACN9") return ambisonicACN9; - if (abbr == "ACN10") return ambisonicACN10; - if (abbr == "ACN11") return ambisonicACN11; - if (abbr == "ACN12") return ambisonicACN12; - if (abbr == "ACN13") return ambisonicACN13; - if (abbr == "ACN14") return ambisonicACN14; - if (abbr == "ACN15") return ambisonicACN15; - if (abbr == "Tsl") return topSideLeft; - if (abbr == "Tsr") return topSideRight; - if (abbr == "Bfl") return bottomFrontLeft; - if (abbr == "Bfc") return bottomFrontCentre; - if (abbr == "Bfr") return bottomFrontRight; - if (abbr == "Bsl") return bottomSideLeft; - if (abbr == "Bsr") return bottomSideRight; - if (abbr == "Brl") return bottomRearLeft; - if (abbr == "Brc") return bottomRearCentre; - if (abbr == "Brr") return bottomRearRight; - return unknown; -} - -String AudioChannelSet::getSpeakerArrangementAsString() const -{ - StringArray speakerTypes; - - for (auto& speaker : getChannelTypes()) - { - auto name = getAbbreviatedChannelTypeName (speaker); - - if (name.isNotEmpty()) - speakerTypes.add (name); - } - - return speakerTypes.joinIntoString (" "); -} - -AudioChannelSet AudioChannelSet::fromAbbreviatedString (const String& str) -{ - AudioChannelSet set; - - for (auto& abbr : StringArray::fromTokens (str, true)) - { - auto type = getChannelTypeFromAbbreviation (abbr); - - if (type != unknown) - set.addChannel (type); - } - - return set; -} - -String AudioChannelSet::getDescription() const -{ - if (isDiscreteLayout()) return "Discrete #" + String (size()); - if (*this == disabled()) return "Disabled"; - if (*this == mono()) return "Mono"; - if (*this == stereo()) return "Stereo"; - - if (*this == createLCR()) return "LCR"; - if (*this == createLRS()) return "LRS"; - if (*this == createLCRS()) return "LCRS"; - - if (*this == create5point0()) return "5.0 Surround"; - if (*this == create5point1()) return "5.1 Surround"; - if (*this == create6point0()) return "6.0 Surround"; - if (*this == create6point1()) return "6.1 Surround"; - if (*this == create6point0Music()) return "6.0 (Music) Surround"; - if (*this == create6point1Music()) return "6.1 (Music) Surround"; - if (*this == create7point0()) return "7.0 Surround"; - if (*this == create7point1()) return "7.1 Surround"; - if (*this == create7point0SDDS()) return "7.0 Surround SDDS"; - if (*this == create7point1SDDS()) return "7.1 Surround SDDS"; - if (*this == create7point0point2()) return "7.0.2 Surround"; - if (*this == create7point1point2()) return "7.1.2 Surround"; - - if (*this == quadraphonic()) return "Quadraphonic"; - if (*this == pentagonal()) return "Pentagonal"; - if (*this == hexagonal()) return "Hexagonal"; - if (*this == octagonal()) return "Octagonal"; - - // ambisonics - { - auto order = getAmbisonicOrder(); - - if (order >= 0) - { - String suffix; - - switch (order) - { - case 1: suffix = "st"; break; - case 2: suffix = "nd"; break; - case 3: suffix = "rd"; break; - default: suffix = "th"; break; - } - - return String (order) + suffix + " Order Ambisonics"; - } - } - - return "Unknown"; -} - -bool AudioChannelSet::isDiscreteLayout() const noexcept -{ - for (auto& speaker : getChannelTypes()) - if (speaker <= ambisonicACN35) - return false; - - return true; -} - -int AudioChannelSet::size() const noexcept -{ - return channels.countNumberOfSetBits(); -} - -AudioChannelSet::ChannelType AudioChannelSet::getTypeOfChannel (int index) const noexcept -{ - int bit = channels.findNextSetBit(0); - - for (int i = 0; i < index && bit >= 0; ++i) - bit = channels.findNextSetBit (bit + 1); - - return static_cast (bit); -} - -int AudioChannelSet::getChannelIndexForType (AudioChannelSet::ChannelType type) const noexcept -{ - int idx = 0; - - for (int bit = channels.findNextSetBit (0); bit >= 0; bit = channels.findNextSetBit (bit + 1)) - { - if (static_cast (bit) == type) - return idx; - - idx++; - } - - return -1; -} - -Array AudioChannelSet::getChannelTypes() const -{ - Array result; - - for (int bit = channels.findNextSetBit(0); bit >= 0; bit = channels.findNextSetBit (bit + 1)) - result.add (static_cast (bit)); - - return result; -} - -void AudioChannelSet::addChannel (ChannelType newChannel) -{ - const int bit = static_cast (newChannel); - jassert (bit >= 0 && bit < 1024); - channels.setBit (bit); -} - -void AudioChannelSet::removeChannel (ChannelType newChannel) -{ - const int bit = static_cast (newChannel); - jassert (bit >= 0 && bit < 1024); - channels.clearBit (bit); -} - -AudioChannelSet AudioChannelSet::disabled() { return {}; } -AudioChannelSet AudioChannelSet::mono() { return AudioChannelSet (1u << centre); } -AudioChannelSet AudioChannelSet::stereo() { return AudioChannelSet ((1u << left) | (1u << right)); } -AudioChannelSet AudioChannelSet::createLCR() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << centre)); } -AudioChannelSet AudioChannelSet::createLRS() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << surround)); } -AudioChannelSet AudioChannelSet::createLCRS() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << centre) | (1u << surround)); } -AudioChannelSet AudioChannelSet::create5point0() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << centre) | (1u << leftSurround) | (1u << rightSurround)); } -AudioChannelSet AudioChannelSet::create5point1() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << centre) | (1u << LFE) | (1u << leftSurround) | (1u << rightSurround)); } -AudioChannelSet AudioChannelSet::create6point0() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << centre) | (1u << leftSurround) | (1u << rightSurround) | (1u << centreSurround)); } -AudioChannelSet AudioChannelSet::create6point1() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << centre) | (1u << LFE) | (1u << leftSurround) | (1u << rightSurround) | (1u << centreSurround)); } -AudioChannelSet AudioChannelSet::create6point0Music() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << leftSurround) | (1u << rightSurround) | (1u << leftSurroundSide) | (1u << rightSurroundSide)); } -AudioChannelSet AudioChannelSet::create6point1Music() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << LFE) | (1u << leftSurround) | (1u << rightSurround) | (1u << leftSurroundSide) | (1u << rightSurroundSide)); } -AudioChannelSet AudioChannelSet::create7point0() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << centre) | (1u << leftSurroundSide) | (1u << rightSurroundSide) | (1u << leftSurroundRear) | (1u << rightSurroundRear)); } -AudioChannelSet AudioChannelSet::create7point0SDDS() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << centre) | (1u << leftSurround) | (1u << rightSurround) | (1u << leftCentre) | (1u << rightCentre)); } -AudioChannelSet AudioChannelSet::create7point1() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << centre) | (1u << LFE) | (1u << leftSurroundSide) | (1u << rightSurroundSide) | (1u << leftSurroundRear) | (1u << rightSurroundRear)); } -AudioChannelSet AudioChannelSet::create7point1SDDS() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << centre) | (1u << LFE) | (1u << leftSurround) | (1u << rightSurround) | (1u << leftCentre) | (1u << rightCentre)); } -AudioChannelSet AudioChannelSet::quadraphonic() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << leftSurround) | (1u << rightSurround)); } -AudioChannelSet AudioChannelSet::pentagonal() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << centre) | (1u << leftSurroundRear) | (1u << rightSurroundRear)); } -AudioChannelSet AudioChannelSet::hexagonal() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << centre) | (1u << centreSurround) | (1u << leftSurroundRear) | (1u << rightSurroundRear)); } -AudioChannelSet AudioChannelSet::octagonal() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << centre) | (1u << leftSurround) | (1u << rightSurround) | (1u << centreSurround) | (1u << wideLeft) | (1u << wideRight)); } -AudioChannelSet AudioChannelSet::create7point0point2() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << centre) | (1u << leftSurroundSide) | (1u << rightSurroundSide) | (1u << leftSurroundRear) | (1u << rightSurroundRear) | (1u << topSideLeft) | (1u << topSideRight)); } -AudioChannelSet AudioChannelSet::create7point1point2() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << centre) | (1u << LFE) | (1u << leftSurroundSide) | (1u << rightSurroundSide) | (1u << leftSurroundRear) | (1u << rightSurroundRear) | (1u << topSideLeft) | (1u << topSideRight)); } - -AudioChannelSet AudioChannelSet::ambisonic (int order) -{ - jassert (isPositiveAndBelow (order, 6)); - - if (order == 0) - return AudioChannelSet ((uint32) (1 << ambisonicACN0)); - - AudioChannelSet set ((1u << ambisonicACN0) | (1u << ambisonicACN1) | (1u << ambisonicACN2) | (1u << ambisonicACN3)); - - auto numAmbisonicChannels = (order + 1) * (order + 1); - set.channels.setRange (ambisonicACN4, numAmbisonicChannels - 4, true); - - return set; -} - -int AudioChannelSet::getAmbisonicOrder() const -{ - auto ambisonicOrder = getAmbisonicOrderForNumChannels (size()); - - if (ambisonicOrder >= 0) - return (*this == ambisonic (ambisonicOrder) ? ambisonicOrder : -1); - - return -1; -} - -AudioChannelSet AudioChannelSet::discreteChannels (int numChannels) -{ - AudioChannelSet s; - s.channels.setRange (discreteChannel0, numChannels, true); - return s; -} - -AudioChannelSet AudioChannelSet::canonicalChannelSet (int numChannels) -{ - if (numChannels == 1) return AudioChannelSet::mono(); - if (numChannels == 2) return AudioChannelSet::stereo(); - if (numChannels == 3) return AudioChannelSet::createLCR(); - if (numChannels == 4) return AudioChannelSet::quadraphonic(); - if (numChannels == 5) return AudioChannelSet::create5point0(); - if (numChannels == 6) return AudioChannelSet::create5point1(); - if (numChannels == 7) return AudioChannelSet::create7point0(); - if (numChannels == 8) return AudioChannelSet::create7point1(); - - return discreteChannels (numChannels); -} - -AudioChannelSet AudioChannelSet::namedChannelSet (int numChannels) -{ - if (numChannels == 1) return AudioChannelSet::mono(); - if (numChannels == 2) return AudioChannelSet::stereo(); - if (numChannels == 3) return AudioChannelSet::createLCR(); - if (numChannels == 4) return AudioChannelSet::quadraphonic(); - if (numChannels == 5) return AudioChannelSet::create5point0(); - if (numChannels == 6) return AudioChannelSet::create5point1(); - if (numChannels == 7) return AudioChannelSet::create7point0(); - if (numChannels == 8) return AudioChannelSet::create7point1(); - - return {}; -} - -Array AudioChannelSet::channelSetsWithNumberOfChannels (int numChannels) -{ - Array retval; - - if (numChannels != 0) - { - retval.add (AudioChannelSet::discreteChannels (numChannels)); - - if (numChannels == 1) - { - retval.add (AudioChannelSet::mono()); - } - else if (numChannels == 2) - { - retval.add (AudioChannelSet::stereo()); - } - else if (numChannels == 3) - { - retval.add (AudioChannelSet::createLCR()); - retval.add (AudioChannelSet::createLRS()); - } - else if (numChannels == 4) - { - retval.add (AudioChannelSet::quadraphonic()); - retval.add (AudioChannelSet::createLCRS()); - } - else if (numChannels == 5) - { - retval.add (AudioChannelSet::create5point0()); - retval.add (AudioChannelSet::pentagonal()); - } - else if (numChannels == 6) - { - retval.add (AudioChannelSet::create5point1()); - retval.add (AudioChannelSet::create6point0()); - retval.add (AudioChannelSet::create6point0Music()); - retval.add (AudioChannelSet::hexagonal()); - } - else if (numChannels == 7) - { - retval.add (AudioChannelSet::create7point0()); - retval.add (AudioChannelSet::create7point0SDDS()); - retval.add (AudioChannelSet::create6point1()); - retval.add (AudioChannelSet::create6point1Music()); - } - else if (numChannels == 8) - { - retval.add (AudioChannelSet::create7point1()); - retval.add (AudioChannelSet::create7point1SDDS()); - retval.add (AudioChannelSet::octagonal()); - } - - auto order = getAmbisonicOrderForNumChannels (numChannels); - if (order >= 0) - retval.add (AudioChannelSet::ambisonic (order)); - } - - return retval; -} - -AudioChannelSet JUCE_CALLTYPE AudioChannelSet::channelSetWithChannels (const Array& channelArray) -{ - AudioChannelSet set; - - for (auto ch : channelArray) - { - jassert (! set.channels[static_cast (ch)]); - - set.addChannel (ch); - } - - return set; -} - -//============================================================================== -AudioChannelSet JUCE_CALLTYPE AudioChannelSet::fromWaveChannelMask (int32 dwChannelMask) -{ - return AudioChannelSet (static_cast ((dwChannelMask & ((1 << 18) - 1)) << 1)); -} - -int32 AudioChannelSet::getWaveChannelMask() const noexcept -{ - if (channels.getHighestBit() > topRearRight) - return -1; - - return (channels.toInteger() >> 1); -} - -//============================================================================== -int JUCE_CALLTYPE AudioChannelSet::getAmbisonicOrderForNumChannels (int numChannels) -{ - auto sqrtMinusOne = std::sqrt (static_cast (numChannels)) - 1.0f; - auto ambisonicOrder = jmax (0, static_cast (std::floor (sqrtMinusOne))); - - if (ambisonicOrder > 5) - return -1; - - return (static_cast (ambisonicOrder) == sqrtMinusOne ? ambisonicOrder : -1); -} - - -//============================================================================== -//============================================================================== -#if JUCE_UNIT_TESTS - -class AudioChannelSetUnitTest : public UnitTest -{ -public: - AudioChannelSetUnitTest() - : UnitTest ("AudioChannelSetUnitTest", UnitTestCategories::audio) - {} - - void runTest() override - { - auto max = AudioChannelSet::maxChannelsOfNamedLayout; - - beginTest ("maxChannelsOfNamedLayout is non-discrete"); - expect (AudioChannelSet::channelSetsWithNumberOfChannels (max).size() >= 2); - - beginTest ("channelSetsWithNumberOfChannels returns correct speaker count"); - { - for (auto ch = 1; ch <= max; ++ch) - { - auto channelSets = AudioChannelSet::channelSetsWithNumberOfChannels (ch); - - for (auto set : channelSets) - expect (set.size() == ch); - } - } - - beginTest ("Ambisonics"); - { - uint64 mask = 0; - - mask |= (1ull << AudioChannelSet::ambisonicACN0); - checkAmbisonic (mask, 0, "0th Order Ambisonics"); - - mask |= (1ull << AudioChannelSet::ambisonicACN1) | (1ull << AudioChannelSet::ambisonicACN2) | (1ull << AudioChannelSet::ambisonicACN3); - checkAmbisonic (mask, 1, "1st Order Ambisonics"); - - mask |= (1ull << AudioChannelSet::ambisonicACN4) | (1ull << AudioChannelSet::ambisonicACN5) | (1ull << AudioChannelSet::ambisonicACN6) - | (1ull << AudioChannelSet::ambisonicACN7) | (1ull << AudioChannelSet::ambisonicACN8); - checkAmbisonic (mask, 2, "2nd Order Ambisonics"); - - mask |= (1ull << AudioChannelSet::ambisonicACN9) | (1ull << AudioChannelSet::ambisonicACN10) | (1ull << AudioChannelSet::ambisonicACN11) - | (1ull << AudioChannelSet::ambisonicACN12) | (1ull << AudioChannelSet::ambisonicACN13) | (1ull << AudioChannelSet::ambisonicACN14) - | (1ull << AudioChannelSet::ambisonicACN15); - checkAmbisonic (mask, 3, "3rd Order Ambisonics"); - - mask |= (1ull << AudioChannelSet::ambisonicACN16) | (1ull << AudioChannelSet::ambisonicACN17) | (1ull << AudioChannelSet::ambisonicACN18) - | (1ull << AudioChannelSet::ambisonicACN19) | (1ull << AudioChannelSet::ambisonicACN20) | (1ull << AudioChannelSet::ambisonicACN21) - | (1ull << AudioChannelSet::ambisonicACN22) | (1ull << AudioChannelSet::ambisonicACN23) | (1ull << AudioChannelSet::ambisonicACN24); - checkAmbisonic (mask, 4, "4th Order Ambisonics"); - - mask |= (1ull << AudioChannelSet::ambisonicACN25) | (1ull << AudioChannelSet::ambisonicACN26) | (1ull << AudioChannelSet::ambisonicACN27) - | (1ull << AudioChannelSet::ambisonicACN28) | (1ull << AudioChannelSet::ambisonicACN29) | (1ull << AudioChannelSet::ambisonicACN30) - | (1ull << AudioChannelSet::ambisonicACN31) | (1ull << AudioChannelSet::ambisonicACN32) | (1ull << AudioChannelSet::ambisonicACN33) - | (1ull << AudioChannelSet::ambisonicACN34) | (1ull << AudioChannelSet::ambisonicACN35); - checkAmbisonic (mask, 5, "5th Order Ambisonics"); - } - } - -private: - void checkAmbisonic (uint64 mask, int order, const char* layoutName) - { - auto expected = AudioChannelSet::ambisonic (order); - auto numChannels = expected.size(); - - expect (numChannels == BigInteger ((int64) mask).countNumberOfSetBits()); - expect (channelSetFromMask (mask) == expected); - - expect (order == expected.getAmbisonicOrder()); - expect (expected.getDescription() == layoutName); - - auto layouts = AudioChannelSet::channelSetsWithNumberOfChannels (numChannels); - expect (layouts.contains (expected)); - - for (auto layout : layouts) - expect (layout.getAmbisonicOrder() == (layout == expected ? order : -1)); - } - - static AudioChannelSet channelSetFromMask (uint64 mask) - { - Array channels; - for (int bit = 0; bit <= 62; ++bit) - if ((mask & (1ull << bit)) != 0) - channels.add (static_cast (bit)); - - return AudioChannelSet::channelSetWithChannels (channels); - } -}; - -static AudioChannelSetUnitTest audioChannelSetUnitTest; - -#endif - -} // namespace juce +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2017 - ROLI Ltd. + + JUCE is an open source library subject to commercial or open-source + licensing. + + The code included in this file is provided under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license. Permission + To use, copy, modify, and/or distribute this software for any purpose with or + without fee is hereby granted provided that the above copyright notice and + this permission notice appear in all copies. + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +namespace juce +{ + + + +AudioChannelSet::AudioChannelSet (uint32 c) : channels (static_cast (c)) +{ +} + +AudioChannelSet::AudioChannelSet (const Array& c) +{ + for (auto channel : c) + addChannel (channel); +} + +bool AudioChannelSet::operator== (const AudioChannelSet& other) const noexcept { return channels == other.channels; } +bool AudioChannelSet::operator!= (const AudioChannelSet& other) const noexcept { return channels != other.channels; } +bool AudioChannelSet::operator< (const AudioChannelSet& other) const noexcept { return channels < other.channels; } + +String AudioChannelSet::getChannelTypeName (AudioChannelSet::ChannelType type) +{ + if (type >= discreteChannel0) + return "Discrete " + String (type - discreteChannel0 + 1); + + switch (type) + { + case left: return NEEDS_TRANS("Left"); + case right: return NEEDS_TRANS("Right"); + case centre: return NEEDS_TRANS("Centre"); + case LFE: return NEEDS_TRANS("LFE"); + case leftSurround: return NEEDS_TRANS("Left Surround"); + case rightSurround: return NEEDS_TRANS("Right Surround"); + case leftCentre: return NEEDS_TRANS("Left Centre"); + case rightCentre: return NEEDS_TRANS("Right Centre"); + case centreSurround: return NEEDS_TRANS("Centre Surround"); + case leftSurroundRear: return NEEDS_TRANS("Left Surround Rear"); + case rightSurroundRear: return NEEDS_TRANS("Right Surround Rear"); + case topMiddle: return NEEDS_TRANS("Top Middle"); + case topFrontLeft: return NEEDS_TRANS("Top Front Left"); + case topFrontCentre: return NEEDS_TRANS("Top Front Centre"); + case topFrontRight: return NEEDS_TRANS("Top Front Right"); + case topRearLeft: return NEEDS_TRANS("Top Rear Left"); + case topRearCentre: return NEEDS_TRANS("Top Rear Centre"); + case topRearRight: return NEEDS_TRANS("Top Rear Right"); + case wideLeft: return NEEDS_TRANS("Wide Left"); + case wideRight: return NEEDS_TRANS("Wide Right"); + case LFE2: return NEEDS_TRANS("LFE 2"); + case leftSurroundSide: return NEEDS_TRANS("Left Surround Side"); + case rightSurroundSide: return NEEDS_TRANS("Right Surround Side"); + case ambisonicW: return NEEDS_TRANS("Ambisonic W"); + case ambisonicX: return NEEDS_TRANS("Ambisonic X"); + case ambisonicY: return NEEDS_TRANS("Ambisonic Y"); + case ambisonicZ: return NEEDS_TRANS("Ambisonic Z"); + case topSideLeft: return NEEDS_TRANS("Top Side Left"); + case topSideRight: return NEEDS_TRANS("Top Side Right"); + case ambisonicACN4: return NEEDS_TRANS("Ambisonic 4"); + case ambisonicACN5: return NEEDS_TRANS("Ambisonic 5"); + case ambisonicACN6: return NEEDS_TRANS("Ambisonic 6"); + case ambisonicACN7: return NEEDS_TRANS("Ambisonic 7"); + case ambisonicACN8: return NEEDS_TRANS("Ambisonic 8"); + case ambisonicACN9: return NEEDS_TRANS("Ambisonic 9"); + case ambisonicACN10: return NEEDS_TRANS("Ambisonic 10"); + case ambisonicACN11: return NEEDS_TRANS("Ambisonic 11"); + case ambisonicACN12: return NEEDS_TRANS("Ambisonic 12"); + case ambisonicACN13: return NEEDS_TRANS("Ambisonic 13"); + case ambisonicACN14: return NEEDS_TRANS("Ambisonic 14"); + case ambisonicACN15: return NEEDS_TRANS("Ambisonic 15"); + case bottomFrontLeft: return NEEDS_TRANS("Bottom Front Left"); + case bottomFrontCentre: return NEEDS_TRANS("Bottom Front Centre"); + case bottomFrontRight: return NEEDS_TRANS("Bottom Front Right"); + case bottomSideLeft: return NEEDS_TRANS("Bottom Side Left"); + case bottomSideRight: return NEEDS_TRANS("Bottom Side Right"); + case bottomRearLeft: return NEEDS_TRANS("Bottom Rear Left"); + case bottomRearCentre: return NEEDS_TRANS("Bottom Rear Centre"); + case bottomRearRight: return NEEDS_TRANS("Bottom Rear Right"); + case discreteChannel0: return NEEDS_TRANS("Discrete channel"); + default: break; + } + + return "Unknown"; +} + +String AudioChannelSet::getAbbreviatedChannelTypeName (AudioChannelSet::ChannelType type) +{ + if (type >= discreteChannel0) + return String (type - discreteChannel0 + 1); + + switch (type) + { + case left: return "L"; + case right: return "R"; + case centre: return "C"; + case LFE: return "Lfe"; + case leftSurround: return "Ls"; + case rightSurround: return "Rs"; + case leftCentre: return "Lc"; + case rightCentre: return "Rc"; + case centreSurround: return "Cs"; + case leftSurroundRear: return "Lrs"; + case rightSurroundRear: return "Rrs"; + case topMiddle: return "Tm"; + case topFrontLeft: return "Tfl"; + case topFrontCentre: return "Tfc"; + case topFrontRight: return "Tfr"; + case topRearLeft: return "Trl"; + case topRearCentre: return "Trc"; + case topRearRight: return "Trr"; + case wideLeft: return "Wl"; + case wideRight: return "Wr"; + case LFE2: return "Lfe2"; + case leftSurroundSide: return "Lss"; + case rightSurroundSide: return "Rss"; + case ambisonicACN0: return "ACN0"; + case ambisonicACN1: return "ACN1"; + case ambisonicACN2: return "ACN2"; + case ambisonicACN3: return "ACN3"; + case ambisonicACN4: return "ACN4"; + case ambisonicACN5: return "ACN5"; + case ambisonicACN6: return "ACN6"; + case ambisonicACN7: return "ACN7"; + case ambisonicACN8: return "ACN8"; + case ambisonicACN9: return "ACN9"; + case ambisonicACN10: return "ACN10"; + case ambisonicACN11: return "ACN11"; + case ambisonicACN12: return "ACN12"; + case ambisonicACN13: return "ACN13"; + case ambisonicACN14: return "ACN14"; + case ambisonicACN15: return "ACN15"; + case topSideLeft: return "Tsl"; + case topSideRight: return "Tsr"; + case bottomFrontLeft: return "Bfl"; + case bottomFrontCentre: return "Bfc"; + case bottomFrontRight: return "Bfr"; + case bottomSideLeft: return "Bsl"; + case bottomSideRight: return "Bsr"; + case bottomRearLeft: return "Brl"; + case bottomRearCentre: return "Brc"; + case bottomRearRight: return "Brr"; + default: break; + } + + if (type >= ambisonicACN4 && type <= ambisonicACN35) + return "ACN" + String (type - ambisonicACN4 + 4); + + return {}; +} + +AudioChannelSet::ChannelType AudioChannelSet::getChannelTypeFromAbbreviation (const String& abbr) +{ + if (abbr.length() > 0 && (abbr[0] >= '0' && abbr[0] <= '9')) + return static_cast (static_cast (discreteChannel0) + + abbr.getIntValue() - 1); + + if (abbr == "L") return left; + if (abbr == "R") return right; + if (abbr == "C") return centre; + if (abbr == "Lfe") return LFE; + if (abbr == "Ls") return leftSurround; + if (abbr == "Rs") return rightSurround; + if (abbr == "Lc") return leftCentre; + if (abbr == "Rc") return rightCentre; + if (abbr == "Cs") return centreSurround; + if (abbr == "Lrs") return leftSurroundRear; + if (abbr == "Rrs") return rightSurroundRear; + if (abbr == "Tm") return topMiddle; + if (abbr == "Tfl") return topFrontLeft; + if (abbr == "Tfc") return topFrontCentre; + if (abbr == "Tfr") return topFrontRight; + if (abbr == "Trl") return topRearLeft; + if (abbr == "Trc") return topRearCentre; + if (abbr == "Trr") return topRearRight; + if (abbr == "Wl") return wideLeft; + if (abbr == "Wr") return wideRight; + if (abbr == "Lfe2") return LFE2; + if (abbr == "Lss") return leftSurroundSide; + if (abbr == "Rss") return rightSurroundSide; + if (abbr == "W") return ambisonicW; + if (abbr == "X") return ambisonicX; + if (abbr == "Y") return ambisonicY; + if (abbr == "Z") return ambisonicZ; + if (abbr == "ACN0") return ambisonicACN0; + if (abbr == "ACN1") return ambisonicACN1; + if (abbr == "ACN2") return ambisonicACN2; + if (abbr == "ACN3") return ambisonicACN3; + if (abbr == "ACN4") return ambisonicACN4; + if (abbr == "ACN5") return ambisonicACN5; + if (abbr == "ACN6") return ambisonicACN6; + if (abbr == "ACN7") return ambisonicACN7; + if (abbr == "ACN8") return ambisonicACN8; + if (abbr == "ACN9") return ambisonicACN9; + if (abbr == "ACN10") return ambisonicACN10; + if (abbr == "ACN11") return ambisonicACN11; + if (abbr == "ACN12") return ambisonicACN12; + if (abbr == "ACN13") return ambisonicACN13; + if (abbr == "ACN14") return ambisonicACN14; + if (abbr == "ACN15") return ambisonicACN15; + if (abbr == "Tsl") return topSideLeft; + if (abbr == "Tsr") return topSideRight; + if (abbr == "Bfl") return bottomFrontLeft; + if (abbr == "Bfc") return bottomFrontCentre; + if (abbr == "Bfr") return bottomFrontRight; + if (abbr == "Bsl") return bottomSideLeft; + if (abbr == "Bsr") return bottomSideRight; + if (abbr == "Brl") return bottomRearLeft; + if (abbr == "Brc") return bottomRearCentre; + if (abbr == "Brr") return bottomRearRight; + return unknown; +} + +String AudioChannelSet::getSpeakerArrangementAsString() const +{ + StringArray speakerTypes; + + for (auto& speaker : getChannelTypes()) + { + auto name = getAbbreviatedChannelTypeName (speaker); + + if (name.isNotEmpty()) + speakerTypes.add (name); + } + + return speakerTypes.joinIntoString (" "); +} + +AudioChannelSet AudioChannelSet::fromAbbreviatedString (const String& str) +{ + AudioChannelSet set; + + for (auto& abbr : StringArray::fromTokens (str, true)) + { + auto type = getChannelTypeFromAbbreviation (abbr); + + if (type != unknown) + set.addChannel (type); + } + + return set; +} + +String AudioChannelSet::getDescription() const +{ + if (isDiscreteLayout()) return "Discrete #" + String (size()); + if (*this == disabled()) return "Disabled"; + if (*this == mono()) return "Mono"; + if (*this == stereo()) return "Stereo"; + + if (*this == createLCR()) return "LCR"; + if (*this == createLRS()) return "LRS"; + if (*this == createLCRS()) return "LCRS"; + + if (*this == create5point0()) return "5.0 Surround"; + if (*this == create5point1()) return "5.1 Surround"; + if (*this == create6point0()) return "6.0 Surround"; + if (*this == create6point1()) return "6.1 Surround"; + if (*this == create6point0Music()) return "6.0 (Music) Surround"; + if (*this == create6point1Music()) return "6.1 (Music) Surround"; + if (*this == create7point0()) return "7.0 Surround"; + if (*this == create7point1()) return "7.1 Surround"; + if (*this == create7point0SDDS()) return "7.0 Surround SDDS"; + if (*this == create7point1SDDS()) return "7.1 Surround SDDS"; + if (*this == create7point0point2()) return "7.0.2 Surround"; + if (*this == create7point1point2()) return "7.1.2 Surround"; + + if (*this == quadraphonic()) return "Quadraphonic"; + if (*this == pentagonal()) return "Pentagonal"; + if (*this == hexagonal()) return "Hexagonal"; + if (*this == octagonal()) return "Octagonal"; + + // ambisonics + { + auto order = getAmbisonicOrder(); + + if (order >= 0) + { + String suffix; + + switch (order) + { + case 1: suffix = "st"; break; + case 2: suffix = "nd"; break; + case 3: suffix = "rd"; break; + default: suffix = "th"; break; + } + + return String (order) + suffix + " Order Ambisonics"; + } + } + + return "Unknown"; +} + +bool AudioChannelSet::isDiscreteLayout() const noexcept +{ + for (auto& speaker : getChannelTypes()) + if (speaker <= ambisonicACN35) + return false; + + return true; +} + +int AudioChannelSet::size() const noexcept +{ + return channels.countNumberOfSetBits(); +} + +AudioChannelSet::ChannelType AudioChannelSet::getTypeOfChannel (int index) const noexcept +{ + int bit = channels.findNextSetBit(0); + + for (int i = 0; i < index && bit >= 0; ++i) + bit = channels.findNextSetBit (bit + 1); + + return static_cast (bit); +} + +int AudioChannelSet::getChannelIndexForType (AudioChannelSet::ChannelType type) const noexcept +{ + int idx = 0; + + for (int bit = channels.findNextSetBit (0); bit >= 0; bit = channels.findNextSetBit (bit + 1)) + { + if (static_cast (bit) == type) + return idx; + + idx++; + } + + return -1; +} + +Array AudioChannelSet::getChannelTypes() const +{ + Array result; + + for (int bit = channels.findNextSetBit(0); bit >= 0; bit = channels.findNextSetBit (bit + 1)) + result.add (static_cast (bit)); + + return result; +} + +void AudioChannelSet::addChannel (ChannelType newChannel) +{ + const int bit = static_cast (newChannel); + jassert (bit >= 0 && bit < 1024); + channels.setBit (bit); +} + +void AudioChannelSet::removeChannel (ChannelType newChannel) +{ + const int bit = static_cast (newChannel); + jassert (bit >= 0 && bit < 1024); + channels.clearBit (bit); +} + +AudioChannelSet AudioChannelSet::disabled() { return {}; } +AudioChannelSet AudioChannelSet::mono() { return AudioChannelSet (1u << centre); } +AudioChannelSet AudioChannelSet::stereo() { return AudioChannelSet ((1u << left) | (1u << right)); } +AudioChannelSet AudioChannelSet::createLCR() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << centre)); } +AudioChannelSet AudioChannelSet::createLRS() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << surround)); } +AudioChannelSet AudioChannelSet::createLCRS() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << centre) | (1u << surround)); } +AudioChannelSet AudioChannelSet::create5point0() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << centre) | (1u << leftSurround) | (1u << rightSurround)); } +AudioChannelSet AudioChannelSet::create5point1() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << centre) | (1u << LFE) | (1u << leftSurround) | (1u << rightSurround)); } +AudioChannelSet AudioChannelSet::create6point0() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << centre) | (1u << leftSurround) | (1u << rightSurround) | (1u << centreSurround)); } +AudioChannelSet AudioChannelSet::create6point1() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << centre) | (1u << LFE) | (1u << leftSurround) | (1u << rightSurround) | (1u << centreSurround)); } +AudioChannelSet AudioChannelSet::create6point0Music() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << leftSurround) | (1u << rightSurround) | (1u << leftSurroundSide) | (1u << rightSurroundSide)); } +AudioChannelSet AudioChannelSet::create6point1Music() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << LFE) | (1u << leftSurround) | (1u << rightSurround) | (1u << leftSurroundSide) | (1u << rightSurroundSide)); } +AudioChannelSet AudioChannelSet::create7point0() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << centre) | (1u << leftSurroundSide) | (1u << rightSurroundSide) | (1u << leftSurroundRear) | (1u << rightSurroundRear)); } +AudioChannelSet AudioChannelSet::create7point0SDDS() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << centre) | (1u << leftSurround) | (1u << rightSurround) | (1u << leftCentre) | (1u << rightCentre)); } +AudioChannelSet AudioChannelSet::create7point1() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << centre) | (1u << LFE) | (1u << leftSurroundSide) | (1u << rightSurroundSide) | (1u << leftSurroundRear) | (1u << rightSurroundRear)); } +AudioChannelSet AudioChannelSet::create7point1SDDS() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << centre) | (1u << LFE) | (1u << leftSurround) | (1u << rightSurround) | (1u << leftCentre) | (1u << rightCentre)); } +AudioChannelSet AudioChannelSet::quadraphonic() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << leftSurround) | (1u << rightSurround)); } +AudioChannelSet AudioChannelSet::pentagonal() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << centre) | (1u << leftSurroundRear) | (1u << rightSurroundRear)); } +AudioChannelSet AudioChannelSet::hexagonal() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << centre) | (1u << centreSurround) | (1u << leftSurroundRear) | (1u << rightSurroundRear)); } +AudioChannelSet AudioChannelSet::octagonal() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << centre) | (1u << leftSurround) | (1u << rightSurround) | (1u << centreSurround) | (1u << wideLeft) | (1u << wideRight)); } +AudioChannelSet AudioChannelSet::create7point0point2() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << centre) | (1u << leftSurroundSide) | (1u << rightSurroundSide) | (1u << leftSurroundRear) | (1u << rightSurroundRear) | (1u << topSideLeft) | (1u << topSideRight)); } +AudioChannelSet AudioChannelSet::create7point1point2() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << centre) | (1u << LFE) | (1u << leftSurroundSide) | (1u << rightSurroundSide) | (1u << leftSurroundRear) | (1u << rightSurroundRear) | (1u << topSideLeft) | (1u << topSideRight)); } + +AudioChannelSet AudioChannelSet::ambisonic (int order) +{ + jassert (isPositiveAndBelow (order, 6)); + + if (order == 0) + return AudioChannelSet ((uint32) (1 << ambisonicACN0)); + + AudioChannelSet set ((1u << ambisonicACN0) | (1u << ambisonicACN1) | (1u << ambisonicACN2) | (1u << ambisonicACN3)); + + auto numAmbisonicChannels = (order + 1) * (order + 1); + set.channels.setRange (ambisonicACN4, numAmbisonicChannels - 4, true); + + return set; +} + +int AudioChannelSet::getAmbisonicOrder() const +{ + auto ambisonicOrder = getAmbisonicOrderForNumChannels (size()); + + if (ambisonicOrder >= 0) + return (*this == ambisonic (ambisonicOrder) ? ambisonicOrder : -1); + + return -1; +} + +AudioChannelSet AudioChannelSet::discreteChannels (int numChannels) +{ + AudioChannelSet s; + s.channels.setRange (discreteChannel0, numChannels, true); + return s; +} + +AudioChannelSet AudioChannelSet::canonicalChannelSet (int numChannels) +{ + if (numChannels == 1) return AudioChannelSet::mono(); + if (numChannels == 2) return AudioChannelSet::stereo(); + if (numChannels == 3) return AudioChannelSet::createLCR(); + if (numChannels == 4) return AudioChannelSet::quadraphonic(); + if (numChannels == 5) return AudioChannelSet::create5point0(); + if (numChannels == 6) return AudioChannelSet::create5point1(); + if (numChannels == 7) return AudioChannelSet::create7point0(); + if (numChannels == 8) return AudioChannelSet::create7point1(); + + return discreteChannels (numChannels); +} + +AudioChannelSet AudioChannelSet::namedChannelSet (int numChannels) +{ + if (numChannels == 1) return AudioChannelSet::mono(); + if (numChannels == 2) return AudioChannelSet::stereo(); + if (numChannels == 3) return AudioChannelSet::createLCR(); + if (numChannels == 4) return AudioChannelSet::quadraphonic(); + if (numChannels == 5) return AudioChannelSet::create5point0(); + if (numChannels == 6) return AudioChannelSet::create5point1(); + if (numChannels == 7) return AudioChannelSet::create7point0(); + if (numChannels == 8) return AudioChannelSet::create7point1(); + + return {}; +} + +Array AudioChannelSet::channelSetsWithNumberOfChannels (int numChannels) +{ + Array retval; + + if (numChannels != 0) + { + retval.add (AudioChannelSet::discreteChannels (numChannels)); + + if (numChannels == 1) + { + retval.add (AudioChannelSet::mono()); + } + else if (numChannels == 2) + { + retval.add (AudioChannelSet::stereo()); + } + else if (numChannels == 3) + { + retval.add (AudioChannelSet::createLCR()); + retval.add (AudioChannelSet::createLRS()); + } + else if (numChannels == 4) + { + retval.add (AudioChannelSet::quadraphonic()); + retval.add (AudioChannelSet::createLCRS()); + } + else if (numChannels == 5) + { + retval.add (AudioChannelSet::create5point0()); + retval.add (AudioChannelSet::pentagonal()); + } + else if (numChannels == 6) + { + retval.add (AudioChannelSet::create5point1()); + retval.add (AudioChannelSet::create6point0()); + retval.add (AudioChannelSet::create6point0Music()); + retval.add (AudioChannelSet::hexagonal()); + } + else if (numChannels == 7) + { + retval.add (AudioChannelSet::create7point0()); + retval.add (AudioChannelSet::create7point0SDDS()); + retval.add (AudioChannelSet::create6point1()); + retval.add (AudioChannelSet::create6point1Music()); + } + else if (numChannels == 8) + { + retval.add (AudioChannelSet::create7point1()); + retval.add (AudioChannelSet::create7point1SDDS()); + retval.add (AudioChannelSet::octagonal()); + } + + auto order = getAmbisonicOrderForNumChannels (numChannels); + if (order >= 0) + retval.add (AudioChannelSet::ambisonic (order)); + } + + return retval; +} + +AudioChannelSet JUCE_CALLTYPE AudioChannelSet::channelSetWithChannels (const Array& channelArray) +{ + AudioChannelSet set; + + for (auto ch : channelArray) + { + jassert (! set.channels[static_cast (ch)]); + + set.addChannel (ch); + } + + return set; +} + +//============================================================================== +AudioChannelSet JUCE_CALLTYPE AudioChannelSet::fromWaveChannelMask (int32 dwChannelMask) +{ + return AudioChannelSet (static_cast ((dwChannelMask & ((1 << 18) - 1)) << 1)); +} + +int32 AudioChannelSet::getWaveChannelMask() const noexcept +{ + if (channels.getHighestBit() > topRearRight) + return -1; + + return (channels.toInteger() >> 1); +} + +//============================================================================== +int JUCE_CALLTYPE AudioChannelSet::getAmbisonicOrderForNumChannels (int numChannels) +{ + auto sqrtMinusOne = std::sqrt (static_cast (numChannels)) - 1.0f; + auto ambisonicOrder = jmax (0, static_cast (std::floor (sqrtMinusOne))); + + if (ambisonicOrder > 5) + return -1; + + return (static_cast (ambisonicOrder) == sqrtMinusOne ? ambisonicOrder : -1); +} + + +//============================================================================== +//============================================================================== +#if JUCE_UNIT_TESTS + +class AudioChannelSetUnitTest : public UnitTest +{ +public: + AudioChannelSetUnitTest() + : UnitTest ("AudioChannelSetUnitTest", UnitTestCategories::audio) + {} + + void runTest() override + { + auto max = AudioChannelSet::maxChannelsOfNamedLayout; + + beginTest ("maxChannelsOfNamedLayout is non-discrete"); + expect (AudioChannelSet::channelSetsWithNumberOfChannels (max).size() >= 2); + + beginTest ("channelSetsWithNumberOfChannels returns correct speaker count"); + { + for (auto ch = 1; ch <= max; ++ch) + { + auto channelSets = AudioChannelSet::channelSetsWithNumberOfChannels (ch); + + for (auto set : channelSets) + expect (set.size() == ch); + } + } + + beginTest ("Ambisonics"); + { + uint64 mask = 0; + + mask |= (1ull << AudioChannelSet::ambisonicACN0); + checkAmbisonic (mask, 0, "0th Order Ambisonics"); + + mask |= (1ull << AudioChannelSet::ambisonicACN1) | (1ull << AudioChannelSet::ambisonicACN2) | (1ull << AudioChannelSet::ambisonicACN3); + checkAmbisonic (mask, 1, "1st Order Ambisonics"); + + mask |= (1ull << AudioChannelSet::ambisonicACN4) | (1ull << AudioChannelSet::ambisonicACN5) | (1ull << AudioChannelSet::ambisonicACN6) + | (1ull << AudioChannelSet::ambisonicACN7) | (1ull << AudioChannelSet::ambisonicACN8); + checkAmbisonic (mask, 2, "2nd Order Ambisonics"); + + mask |= (1ull << AudioChannelSet::ambisonicACN9) | (1ull << AudioChannelSet::ambisonicACN10) | (1ull << AudioChannelSet::ambisonicACN11) + | (1ull << AudioChannelSet::ambisonicACN12) | (1ull << AudioChannelSet::ambisonicACN13) | (1ull << AudioChannelSet::ambisonicACN14) + | (1ull << AudioChannelSet::ambisonicACN15); + checkAmbisonic (mask, 3, "3rd Order Ambisonics"); + + mask |= (1ull << AudioChannelSet::ambisonicACN16) | (1ull << AudioChannelSet::ambisonicACN17) | (1ull << AudioChannelSet::ambisonicACN18) + | (1ull << AudioChannelSet::ambisonicACN19) | (1ull << AudioChannelSet::ambisonicACN20) | (1ull << AudioChannelSet::ambisonicACN21) + | (1ull << AudioChannelSet::ambisonicACN22) | (1ull << AudioChannelSet::ambisonicACN23) | (1ull << AudioChannelSet::ambisonicACN24); + checkAmbisonic (mask, 4, "4th Order Ambisonics"); + + mask |= (1ull << AudioChannelSet::ambisonicACN25) | (1ull << AudioChannelSet::ambisonicACN26) | (1ull << AudioChannelSet::ambisonicACN27) + | (1ull << AudioChannelSet::ambisonicACN28) | (1ull << AudioChannelSet::ambisonicACN29) | (1ull << AudioChannelSet::ambisonicACN30) + | (1ull << AudioChannelSet::ambisonicACN31) | (1ull << AudioChannelSet::ambisonicACN32) | (1ull << AudioChannelSet::ambisonicACN33) + | (1ull << AudioChannelSet::ambisonicACN34) | (1ull << AudioChannelSet::ambisonicACN35); + checkAmbisonic (mask, 5, "5th Order Ambisonics"); + } + } + +private: + void checkAmbisonic (uint64 mask, int order, const char* layoutName) + { + auto expected = AudioChannelSet::ambisonic (order); + auto numChannels = expected.size(); + + expect (numChannels == BigInteger ((int64) mask).countNumberOfSetBits()); + expect (channelSetFromMask (mask) == expected); + + expect (order == expected.getAmbisonicOrder()); + expect (expected.getDescription() == layoutName); + + auto layouts = AudioChannelSet::channelSetsWithNumberOfChannels (numChannels); + expect (layouts.contains (expected)); + + for (auto layout : layouts) + expect (layout.getAmbisonicOrder() == (layout == expected ? order : -1)); + } + + static AudioChannelSet channelSetFromMask (uint64 mask) + { + Array channels; + for (int bit = 0; bit <= 62; ++bit) + if ((mask & (1ull << bit)) != 0) + channels.add (static_cast (bit)); + + return AudioChannelSet::channelSetWithChannels (channels); + } +}; + +static AudioChannelSetUnitTest audioChannelSetUnitTest; + +#endif + +} // namespace juce diff --git a/JuceLibraryCode/modules/juce_audio_basics/buffers/juce_AudioChannelSet.h b/JuceLibraryCode/modules/juce_audio_basics/buffers/juce_AudioChannelSet.h index e9985a1..9d0f99e 100644 --- a/JuceLibraryCode/modules/juce_audio_basics/buffers/juce_AudioChannelSet.h +++ b/JuceLibraryCode/modules/juce_audio_basics/buffers/juce_AudioChannelSet.h @@ -1,484 +1,484 @@ -/* - ============================================================================== - - This file is part of the JUCE library. - Copyright (c) 2017 - ROLI Ltd. - - JUCE is an open source library subject to commercial or open-source - licensing. - - The code included in this file is provided under the terms of the ISC license - http://www.isc.org/downloads/software-support-policy/isc-license. Permission - To use, copy, modify, and/or distribute this software for any purpose with or - without fee is hereby granted provided that the above copyright notice and - this permission notice appear in all copies. - - JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER - EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE - DISCLAIMED. - - ============================================================================== -*/ - -namespace juce -{ - -//============================================================================== -/** - Represents a set of audio channel types. - - For example, you might have a set of left + right channels, which is a stereo - channel set. It is a collection of values from the AudioChannelSet::ChannelType - enum, where each type may only occur once within the set. - - The documentation below lists which AudioChannelSet corresponds to which native - layouts used by AAX, VST2/VST3 and CoreAudio/AU. The layout tags in CoreAudio - are particularly confusing. For example, the layout which is labeled as "7.1 SDDS" - in Logic Pro, corresponds to CoreAudio/AU's kAudioChannelLayoutTag_DTS_7_0 tag, whereas - AAX's DTS 7.1 Layout corresponds to CoreAudio/AU's - kAudioChannelLayoutTag_MPEG_7_1_A format, etc. Please do not use the CoreAudio tag - as an indication to the actual layout of the speakers. - - @see Bus - - @tags{Audio} -*/ -class JUCE_API AudioChannelSet -{ -public: - /** Creates an empty channel set. - You can call addChannel to add channels to the set. - */ - AudioChannelSet() = default; - - /** Creates a zero-channel set which can be used to indicate that a - bus is disabled. */ - static AudioChannelSet JUCE_CALLTYPE disabled(); - - //============================================================================== - /** Creates a one-channel mono set (centre). - - Is equivalent to: kMonoAAX (VST), AAX_eStemFormat_Mono (AAX), kAudioChannelLayoutTag_Mono (CoreAudio) - */ - static AudioChannelSet JUCE_CALLTYPE mono(); - - - /** Creates a set containing a stereo set (left, right). - - Is equivalent to: kStereo (VST), AAX_eStemFormat_Stereo (AAX), kAudioChannelLayoutTag_Stereo (CoreAudio) - */ - static AudioChannelSet JUCE_CALLTYPE stereo(); - - - //============================================================================== - /** Creates a set containing an LCR set (left, right, centre). - - Is equivalent to: k30Cine (VST), AAX_eStemFormat_LCR (AAX), kAudioChannelLayoutTag_MPEG_3_0_A (CoreAudio) - - This format is referred to as "LRC" in Cubase. - This format is referred to as "LCR" in Pro Tools. - */ - static AudioChannelSet JUCE_CALLTYPE createLCR(); - - - /** Creates a set containing an LRS set (left, right, surround). - - Is equivalent to: k30Music (VST), n/a (AAX), kAudioChannelLayoutTag_ITU_2_1 (CoreAudio) - - This format is referred to as "LRS" in Cubase. - */ - static AudioChannelSet JUCE_CALLTYPE createLRS(); - - - /** Creates a set containing an LCRS set (left, right, centre, surround). - - Is equivalent to: k40Cine (VST), AAX_eStemFormat_LCRS (AAX), kAudioChannelLayoutTag_MPEG_4_0_A (CoreAudio) - - This format is referred to as "LCRS (Pro Logic)" in Logic Pro. - This format is referred to as "LRCS" in Cubase. - This format is referred to as "LCRS" in Pro Tools. - */ - static AudioChannelSet JUCE_CALLTYPE createLCRS(); - - - //============================================================================== - /** Creates a set for a 5.0 surround setup (left, right, centre, leftSurround, rightSurround). - - Is equivalent to: k50 (VST), AAX_eStemFormat_5_0 (AAX), kAudioChannelLayoutTag_MPEG_5_0_A (CoreAudio) - - This format is referred to as "5.0" in Cubase. - This format is referred to as "5.0" in Pro Tools. - */ - static AudioChannelSet JUCE_CALLTYPE create5point0(); - - - /** Creates a set for a 5.1 surround setup (left, right, centre, leftSurround, rightSurround, LFE). - - Is equivalent to: k51 (VST), AAX_eStemFormat_5_1 (AAX), kAudioChannelLayoutTag_MPEG_5_1_A (CoreAudio) - - This format is referred to as "5.1 (ITU 775)" in Logic Pro. - This format is referred to as "5.1" in Cubase. - This format is referred to as "5.1" in Pro Tools. - */ - static AudioChannelSet JUCE_CALLTYPE create5point1(); - - - /** Creates a set for a 6.0 Cine surround setup (left, right, centre, leftSurround, rightSurround, centreSurround). - - Is equivalent to: k60Cine (VST), AAX_eStemFormat_6_0 (AAX), kAudioChannelLayoutTag_AudioUnit_6_0 (CoreAudio) - - Logic Pro incorrectly uses this for the surround format labeled "6.1 (ES/EX)". - This format is referred to as "6.0 Cine" in Cubase. - This format is referred to as "6.0" in Pro Tools. - */ - static AudioChannelSet JUCE_CALLTYPE create6point0(); - - - /** Creates a set for a 6.1 Cine surround setup (left, right, centre, leftSurround, rightSurround, centreSurround, LFE). - - Is equivalent to: k61Cine (VST), AAX_eStemFormat_6_1 (AAX), kAudioChannelLayoutTag_MPEG_6_1_A (CoreAudio) - - This format is referred to as "6.1" in Pro Tools. - */ - static AudioChannelSet JUCE_CALLTYPE create6point1(); - - - /** Creates a set for a 6.0 Music surround setup (left, right, leftSurround, rightSurround, leftSurroundSide, rightSurroundSide). - - Is equivalent to: k60Music (VST), n/a (AAX), kAudioChannelLayoutTag_DTS_6_0_A (CoreAudio) - - This format is referred to as "6.0 Music" in Cubase. - */ - static AudioChannelSet JUCE_CALLTYPE create6point0Music(); - - - /** Creates a set for a 6.0 Music surround setup (left, right, leftSurround, rightSurround, leftSurroundSide, rightSurroundSide, LFE). - - Is equivalent to: k61Music (VST), n/a (AAX), kAudioChannelLayoutTag_DTS_6_1_A (CoreAudio) - */ - static AudioChannelSet JUCE_CALLTYPE create6point1Music(); - - - /** Creates a set for a DTS 7.0 surround setup (left, right, centre, leftSurroundSide, rightSurroundSide, leftSurroundRear, rightSurroundRear). - - Is equivalent to: k70Music (VST), AAX_eStemFormat_7_0_DTS (AAX), kAudioChannelLayoutTag_AudioUnit_7_0 (CoreAudio) - - This format is referred to as "7.0" in Pro Tools. - */ - static AudioChannelSet JUCE_CALLTYPE create7point0(); - - - /** Creates a set for a SDDS 7.0 surround setup (left, right, centre, leftSurround, rightSurround, leftCentre, rightCentre). - - Is equivalent to: k70Cine (VST), AAX_eStemFormat_7_0_SDDS (AAX), kAudioChannelLayoutTag_AudioUnit_7_0_Front (CoreAudio) - - This format is referred to as "7.0 SDDS" in Pro Tools. - */ - static AudioChannelSet JUCE_CALLTYPE create7point0SDDS(); - - - /** Creates a set for a DTS 7.1 surround setup (left, right, centre, leftSurroundSide, rightSurroundSide, leftSurroundRear, rightSurroundRear, LFE). - - Is equivalent to: k71CineSideFill (VST), AAX_eStemFormat_7_1_DTS (AAX), kAudioChannelLayoutTag_MPEG_7_1_C/kAudioChannelLayoutTag_ITU_3_4_1 (CoreAudio) - - This format is referred to as "7.1 (3/4.1)" in Logic Pro. - This format is referred to as "7.1" in Pro Tools. - */ - static AudioChannelSet JUCE_CALLTYPE create7point1(); - - - /** Creates a set for a 7.1 surround setup (left, right, centre, leftSurround, rightSurround, leftCentre, rightCentre, LFE). - - Is equivalent to: k71Cine (VST), AAX_eStemFormat_7_1_SDDS (AAX), kAudioChannelLayoutTag_MPEG_7_1_A (CoreAudio) - - This format is referred to as "7.1 (SDDS)" in Logic Pro. - This format is referred to as "7.1 SDDS" in Pro Tools. - */ - static AudioChannelSet JUCE_CALLTYPE create7point1SDDS(); - - /** Creates a set for Dolby Atmos 7.0.2 surround setup (left, right, centre, leftSurroundSide, rightSurroundSide, leftSurroundRear, rightSurroundRear, topSideLeft, topSideRight). - - Is equivalent to: n/a (VST), AAX_eStemFormat_7_0_2 (AAX), n/a (CoreAudio) - */ - static AudioChannelSet JUCE_CALLTYPE create7point0point2(); - - /** Creates a set for Dolby Atmos 7.1.2 surround setup (left, right, centre, leftSurroundSide, rightSurroundSide, leftSurroundRear, rightSurroundRear, LFE, topSideLeft, topSideRight). - - Is equivalent to: k71_2 (VST), AAX_eStemFormat_7_1_2 (AAX), n/a (CoreAudio) - */ - static AudioChannelSet JUCE_CALLTYPE create7point1point2(); - - - //============================================================================== - /** Creates a set for quadraphonic surround setup (left, right, leftSurround, rightSurround) - - Is equivalent to: k40Music (VST), AAX_eStemFormat_Quad (AAX), kAudioChannelLayoutTag_Quadraphonic (CoreAudio) - - This format is referred to as "Quadraphonic" in Logic Pro. - This format is referred to as "Quadro" in Cubase. - This format is referred to as "Quad" in Pro Tools. - */ - static AudioChannelSet JUCE_CALLTYPE quadraphonic(); - - - /** Creates a set for pentagonal surround setup (left, right, centre, leftSurroundRear, rightSurroundRear). - - Is equivalent to: n/a (VST), n/a (AAX), kAudioChannelLayoutTag_Pentagonal (CoreAudio) - */ - static AudioChannelSet JUCE_CALLTYPE pentagonal(); - - - /** Creates a set for hexagonal surround setup (left, right, leftSurroundRear, rightSurroundRear, centre, surroundCentre). - - Is equivalent to: n/a (VST), n/a (AAX), kAudioChannelLayoutTag_Hexagonal (CoreAudio) - */ - static AudioChannelSet JUCE_CALLTYPE hexagonal(); - - - /** Creates a set for octagonal surround setup (left, right, leftSurround, rightSurround, centre, centreSurround, wideLeft, wideRight). - - Is equivalent to: n/a (VST), n/a (AAX), kAudioChannelLayoutTag_Octagonal (CoreAudio) - */ - static AudioChannelSet JUCE_CALLTYPE octagonal(); - - //============================================================================== - /** Creates a set for ACN, SN3D normalised ambisonic surround setups with a given order. - - Is equivalent to: kAmbiXXXOrderACN (VST), AAX_eStemFormat_Ambi_XXX_ACN (AAX), kAudioChannelLayoutTag_HOA_ACN_SN3D (CoreAudio) - */ - static AudioChannelSet JUCE_CALLTYPE ambisonic (int order = 1); - - /** Returns the order of the ambisonic layout represented by this AudioChannelSet. If the - AudioChannelSet is not an ambisonic layout, then this method will return -1. - */ - int getAmbisonicOrder() const; - - //============================================================================== - /** Creates a set of untyped discrete channels. */ - static AudioChannelSet JUCE_CALLTYPE discreteChannels (int numChannels); - - /** Create a canonical channel set for a given number of channels. - For example, numChannels = 1 will return mono, numChannels = 2 will return stereo, etc. */ - static AudioChannelSet JUCE_CALLTYPE canonicalChannelSet (int numChannels); - - /** Create a channel set for a given number of channels which is non-discrete. - If numChannels is larger than the number of channels of the surround format - with the maximum amount of channels (currently 7.1 Surround), then this - function returns an empty set.*/ - static AudioChannelSet JUCE_CALLTYPE namedChannelSet (int numChannels); - - /** Return an array of channel sets which have a given number of channels */ - static Array JUCE_CALLTYPE channelSetsWithNumberOfChannels (int numChannels); - - //============================================================================== - /** Represents different audio channel types. */ - enum ChannelType - { - unknown = 0, /**< Unknown channel type. */ - - //============================================================================== - left = 1, /**< L channel. */ - right = 2, /**< R channel. */ - centre = 3, /**< C channel. (Sometimes M for mono) */ - - //============================================================================== - LFE = 4, /**< LFE channel. */ - leftSurround = 5, /**< Ls channel. */ - rightSurround = 6, /**< Rs channel. */ - leftCentre = 7, /**< Lc (AAX/VST), Lc used as Lss in AU for most layouts. */ - rightCentre = 8, /**< Rc (AAX/VST), Rc used as Rss in AU for most layouts. */ - centreSurround = 9, /**< Cs/S channel. */ - surround = centreSurround, /**< Same as Centre Surround channel. */ - leftSurroundSide = 10, /**< Lss (AXX), Side Left "Sl" (VST), Left Centre "LC" (AU) channel. */ - rightSurroundSide = 11, /**< Rss (AXX), Side right "Sr" (VST), Right Centre "Rc" (AU) channel. */ - topMiddle = 12, /**< Top Middle channel. */ - topFrontLeft = 13, /**< Top Front Left channel. */ - topFrontCentre = 14, /**< Top Front Centre channel. */ - topFrontRight = 15, /**< Top Front Right channel. */ - topRearLeft = 16, /**< Top Rear Left channel. */ - topRearCentre = 17, /**< Top Rear Centre channel. */ - topRearRight = 18, /**< Top Rear Right channel. */ - LFE2 = 19, /**< Second LFE channel. */ - leftSurroundRear = 20, /**< Lsr (AAX), Lcs (VST), Rls (AU) channel. */ - rightSurroundRear = 21, /**< Rsr (AAX), Rcs (VST), Rrs (AU) channel. */ - wideLeft = 22, /**< Wide Left channel. */ - wideRight = 23, /**< Wide Right channel. */ - - //============================================================================== - // Used by Dolby Atmos 7.0.2 and 7.1.2 - topSideLeft = 28, /**< Lts (AAX), Tsl (VST) channel for Dolby Atmos. */ - topSideRight = 29, /**< Rts (AAX), Tsr (VST) channel for Dolby Atmos. */ - - //============================================================================== - // Ambisonic ACN formats - all channels are SN3D normalised - - // zero-th and first-order ambisonic ACN - ambisonicACN0 = 24, /**< Zero-th ambisonic channel number 0. */ - ambisonicACN1 = 25, /**< First-order ambisonic channel number 1. */ - ambisonicACN2 = 26, /**< First-order ambisonic channel number 2. */ - ambisonicACN3 = 27, /**< First-order ambisonic channel number 3. */ - - // second-order ambisonic - ambisonicACN4 = 30, /**< Second-order ambisonic channel number 4. */ - ambisonicACN5 = 31, /**< Second-order ambisonic channel number 5. */ - ambisonicACN6 = 32, /**< Second-order ambisonic channel number 6. */ - ambisonicACN7 = 33, /**< Second-order ambisonic channel number 7. */ - ambisonicACN8 = 34, /**< Second-order ambisonic channel number 8. */ - - // third-order ambisonic - ambisonicACN9 = 35, /**< Third-order ambisonic channel number 9. */ - ambisonicACN10 = 36, /**< Third-order ambisonic channel number 10. */ - ambisonicACN11 = 37, /**< Third-order ambisonic channel number 11. */ - ambisonicACN12 = 38, /**< Third-order ambisonic channel number 12. */ - ambisonicACN13 = 39, /**< Third-order ambisonic channel number 13. */ - ambisonicACN14 = 40, /**< Third-order ambisonic channel number 14. */ - ambisonicACN15 = 41, /**< Third-order ambisonic channel number 15. */ - - // fourth-order ambisonic - ambisonicACN16 = 42, /**< Fourth-order ambisonic channel number 16. */ - ambisonicACN17 = 43, /**< Fourth-order ambisonic channel number 17. */ - ambisonicACN18 = 44, /**< Fourth-order ambisonic channel number 18. */ - ambisonicACN19 = 45, /**< Fourth-order ambisonic channel number 19. */ - ambisonicACN20 = 46, /**< Fourth-order ambisonic channel number 20. */ - ambisonicACN21 = 47, /**< Fourth-order ambisonic channel number 21. */ - ambisonicACN22 = 48, /**< Fourth-order ambisonic channel number 22. */ - ambisonicACN23 = 49, /**< Fourth-order ambisonic channel number 23. */ - ambisonicACN24 = 50, /**< Fourth-order ambisonic channel number 24. */ - - // fifth-order ambisonic - ambisonicACN25 = 51, /**< Fifth-order ambisonic channel number 25. */ - ambisonicACN26 = 52, /**< Fifth-order ambisonic channel number 26. */ - ambisonicACN27 = 53, /**< Fifth-order ambisonic channel number 27. */ - ambisonicACN28 = 54, /**< Fifth-order ambisonic channel number 28. */ - ambisonicACN29 = 55, /**< Fifth-order ambisonic channel number 29. */ - ambisonicACN30 = 56, /**< Fifth-order ambisonic channel number 30. */ - ambisonicACN31 = 57, /**< Fifth-order ambisonic channel number 31. */ - ambisonicACN32 = 58, /**< Fifth-order ambisonic channel number 32. */ - ambisonicACN33 = 59, /**< Fifth-order ambisonic channel number 33. */ - ambisonicACN34 = 60, /**< Fifth-order ambisonic channel number 34. */ - ambisonicACN35 = 61, /**< Fifth-order ambisonic channel number 35. */ - - //============================================================================== - ambisonicW = ambisonicACN0, /**< Same as zero-th ambisonic channel number 0. */ - ambisonicX = ambisonicACN3, /**< Same as first-order ambisonic channel number 3. */ - ambisonicY = ambisonicACN1, /**< Same as first-order ambisonic channel number 1. */ - ambisonicZ = ambisonicACN2, /**< Same as first-order ambisonic channel number 2. */ - - //============================================================================== - bottomFrontLeft = 62, /**< Bottom Front Left (Bfl) */ - bottomFrontCentre = 63, /**< Bottom Front Centre (Bfc) */ - bottomFrontRight = 64, /**< Bottom Front Right (Bfr) */ - - proxymityLeft = 65, /**< Proximity Left (Pl) */ - proximityRight = 66, /**< Proximity Right (Pr) */ - - bottomSideLeft = 67, /**< Bottom Side Left (Bsl) */ - bottomSideRight = 68, /**< Bottom Side Right (Bsr) */ - bottomRearLeft = 69, /**< Bottom Rear Left (Brl) */ - bottomRearCentre = 70, /**< Bottom Rear Center (Brc) */ - bottomRearRight = 71, /**< Bottom Rear Right (Brr) */ - - //============================================================================== - discreteChannel0 = 128 /**< Non-typed individual channels are indexed upwards from this value. */ - }; - - /** Returns the name of a given channel type. For example, this method may return "Surround Left". */ - static String JUCE_CALLTYPE getChannelTypeName (ChannelType); - - /** Returns the abbreviated name of a channel type. For example, this method may return "Ls". */ - static String JUCE_CALLTYPE getAbbreviatedChannelTypeName (ChannelType); - - /** Returns the channel type from an abbreviated name. */ - static ChannelType JUCE_CALLTYPE getChannelTypeFromAbbreviation (const String& abbreviation); - - //============================================================================== - enum - { - maxChannelsOfNamedLayout = 36 - }; - - /** Adds a channel to the set. */ - void addChannel (ChannelType newChannelType); - - /** Removes a channel from the set. */ - void removeChannel (ChannelType newChannelType); - - /** Returns the number of channels in the set. */ - int size() const noexcept; - - /** Returns true if there are no channels in the set. */ - bool isDisabled() const noexcept { return size() == 0; } - - /** Returns an array of all the types in this channel set. */ - Array getChannelTypes() const; - - /** Returns the type of one of the channels in the set, by index. */ - ChannelType getTypeOfChannel (int channelIndex) const noexcept; - - /** Returns the index for a particular channel-type. - Will return -1 if the this set does not contain a channel of this type. */ - int getChannelIndexForType (ChannelType type) const noexcept; - - /** Returns a string containing a whitespace-separated list of speaker types - corresponding to each channel. For example in a 5.1 arrangement, - the string may be "L R C Lfe Ls Rs". If the speaker arrangement is unknown, - the returned string will be empty.*/ - String getSpeakerArrangementAsString() const; - - /** Returns an AudioChannelSet from a string returned by getSpeakerArrangementAsString - - @see getSpeakerArrangementAsString */ - static AudioChannelSet fromAbbreviatedString (const String& set); - - /** Returns the description of the current layout. For example, this method may return - "Quadraphonic". Note that the returned string may not be unique. */ - String getDescription() const; - - /** Returns if this is a channel layout made-up of discrete channels. */ - bool isDiscreteLayout() const noexcept; - - /** Intersect two channel layouts. */ - void intersect (const AudioChannelSet& other) { channels &= other.channels; } - - /** Creates a channel set for a list of channel types. This function will assert - if you supply a duplicate channel. - - Note that this method ignores the order in which the channels are given, i.e. - two arrays with the same elements but in a different order will still result - in the same channel set. - */ - static AudioChannelSet JUCE_CALLTYPE channelSetWithChannels (const Array&); - - //============================================================================== - // Conversion between wave and juce channel layout identifiers - - /** Create an AudioChannelSet from a WAVEFORMATEXTENSIBLE channelMask (typically used - in .wav files). */ - static AudioChannelSet JUCE_CALLTYPE fromWaveChannelMask (int32 dwChannelMask); - - /** Returns a WAVEFORMATEXTENSIBLE channelMask representation (typically used in .wav - files) of the receiver. - - Returns -1 if the receiver cannot be represented in a WAVEFORMATEXTENSIBLE channelMask - representation. - */ - int32 getWaveChannelMask() const noexcept; - - //============================================================================== - bool operator== (const AudioChannelSet&) const noexcept; - bool operator!= (const AudioChannelSet&) const noexcept; - bool operator< (const AudioChannelSet&) const noexcept; - -private: - //============================================================================== - BigInteger channels; - - //============================================================================== - explicit AudioChannelSet (uint32); - explicit AudioChannelSet (const Array&); - - //============================================================================== - static int JUCE_CALLTYPE getAmbisonicOrderForNumChannels (int); -}; - -} // namespace juce +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2017 - ROLI Ltd. + + JUCE is an open source library subject to commercial or open-source + licensing. + + The code included in this file is provided under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license. Permission + To use, copy, modify, and/or distribute this software for any purpose with or + without fee is hereby granted provided that the above copyright notice and + this permission notice appear in all copies. + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +namespace juce +{ + +//============================================================================== +/** + Represents a set of audio channel types. + + For example, you might have a set of left + right channels, which is a stereo + channel set. It is a collection of values from the AudioChannelSet::ChannelType + enum, where each type may only occur once within the set. + + The documentation below lists which AudioChannelSet corresponds to which native + layouts used by AAX, VST2/VST3 and CoreAudio/AU. The layout tags in CoreAudio + are particularly confusing. For example, the layout which is labeled as "7.1 SDDS" + in Logic Pro, corresponds to CoreAudio/AU's kAudioChannelLayoutTag_DTS_7_0 tag, whereas + AAX's DTS 7.1 Layout corresponds to CoreAudio/AU's + kAudioChannelLayoutTag_MPEG_7_1_A format, etc. Please do not use the CoreAudio tag + as an indication to the actual layout of the speakers. + + @see Bus + + @tags{Audio} +*/ +class JUCE_API AudioChannelSet +{ +public: + /** Creates an empty channel set. + You can call addChannel to add channels to the set. + */ + AudioChannelSet() = default; + + /** Creates a zero-channel set which can be used to indicate that a + bus is disabled. */ + static AudioChannelSet JUCE_CALLTYPE disabled(); + + //============================================================================== + /** Creates a one-channel mono set (centre). + + Is equivalent to: kMonoAAX (VST), AAX_eStemFormat_Mono (AAX), kAudioChannelLayoutTag_Mono (CoreAudio) + */ + static AudioChannelSet JUCE_CALLTYPE mono(); + + + /** Creates a set containing a stereo set (left, right). + + Is equivalent to: kStereo (VST), AAX_eStemFormat_Stereo (AAX), kAudioChannelLayoutTag_Stereo (CoreAudio) + */ + static AudioChannelSet JUCE_CALLTYPE stereo(); + + + //============================================================================== + /** Creates a set containing an LCR set (left, right, centre). + + Is equivalent to: k30Cine (VST), AAX_eStemFormat_LCR (AAX), kAudioChannelLayoutTag_MPEG_3_0_A (CoreAudio) + + This format is referred to as "LRC" in Cubase. + This format is referred to as "LCR" in Pro Tools. + */ + static AudioChannelSet JUCE_CALLTYPE createLCR(); + + + /** Creates a set containing an LRS set (left, right, surround). + + Is equivalent to: k30Music (VST), n/a (AAX), kAudioChannelLayoutTag_ITU_2_1 (CoreAudio) + + This format is referred to as "LRS" in Cubase. + */ + static AudioChannelSet JUCE_CALLTYPE createLRS(); + + + /** Creates a set containing an LCRS set (left, right, centre, surround). + + Is equivalent to: k40Cine (VST), AAX_eStemFormat_LCRS (AAX), kAudioChannelLayoutTag_MPEG_4_0_A (CoreAudio) + + This format is referred to as "LCRS (Pro Logic)" in Logic Pro. + This format is referred to as "LRCS" in Cubase. + This format is referred to as "LCRS" in Pro Tools. + */ + static AudioChannelSet JUCE_CALLTYPE createLCRS(); + + + //============================================================================== + /** Creates a set for a 5.0 surround setup (left, right, centre, leftSurround, rightSurround). + + Is equivalent to: k50 (VST), AAX_eStemFormat_5_0 (AAX), kAudioChannelLayoutTag_MPEG_5_0_A (CoreAudio) + + This format is referred to as "5.0" in Cubase. + This format is referred to as "5.0" in Pro Tools. + */ + static AudioChannelSet JUCE_CALLTYPE create5point0(); + + + /** Creates a set for a 5.1 surround setup (left, right, centre, leftSurround, rightSurround, LFE). + + Is equivalent to: k51 (VST), AAX_eStemFormat_5_1 (AAX), kAudioChannelLayoutTag_MPEG_5_1_A (CoreAudio) + + This format is referred to as "5.1 (ITU 775)" in Logic Pro. + This format is referred to as "5.1" in Cubase. + This format is referred to as "5.1" in Pro Tools. + */ + static AudioChannelSet JUCE_CALLTYPE create5point1(); + + + /** Creates a set for a 6.0 Cine surround setup (left, right, centre, leftSurround, rightSurround, centreSurround). + + Is equivalent to: k60Cine (VST), AAX_eStemFormat_6_0 (AAX), kAudioChannelLayoutTag_AudioUnit_6_0 (CoreAudio) + + Logic Pro incorrectly uses this for the surround format labeled "6.1 (ES/EX)". + This format is referred to as "6.0 Cine" in Cubase. + This format is referred to as "6.0" in Pro Tools. + */ + static AudioChannelSet JUCE_CALLTYPE create6point0(); + + + /** Creates a set for a 6.1 Cine surround setup (left, right, centre, leftSurround, rightSurround, centreSurround, LFE). + + Is equivalent to: k61Cine (VST), AAX_eStemFormat_6_1 (AAX), kAudioChannelLayoutTag_MPEG_6_1_A (CoreAudio) + + This format is referred to as "6.1" in Pro Tools. + */ + static AudioChannelSet JUCE_CALLTYPE create6point1(); + + + /** Creates a set for a 6.0 Music surround setup (left, right, leftSurround, rightSurround, leftSurroundSide, rightSurroundSide). + + Is equivalent to: k60Music (VST), n/a (AAX), kAudioChannelLayoutTag_DTS_6_0_A (CoreAudio) + + This format is referred to as "6.0 Music" in Cubase. + */ + static AudioChannelSet JUCE_CALLTYPE create6point0Music(); + + + /** Creates a set for a 6.0 Music surround setup (left, right, leftSurround, rightSurround, leftSurroundSide, rightSurroundSide, LFE). + + Is equivalent to: k61Music (VST), n/a (AAX), kAudioChannelLayoutTag_DTS_6_1_A (CoreAudio) + */ + static AudioChannelSet JUCE_CALLTYPE create6point1Music(); + + + /** Creates a set for a DTS 7.0 surround setup (left, right, centre, leftSurroundSide, rightSurroundSide, leftSurroundRear, rightSurroundRear). + + Is equivalent to: k70Music (VST), AAX_eStemFormat_7_0_DTS (AAX), kAudioChannelLayoutTag_AudioUnit_7_0 (CoreAudio) + + This format is referred to as "7.0" in Pro Tools. + */ + static AudioChannelSet JUCE_CALLTYPE create7point0(); + + + /** Creates a set for a SDDS 7.0 surround setup (left, right, centre, leftSurround, rightSurround, leftCentre, rightCentre). + + Is equivalent to: k70Cine (VST), AAX_eStemFormat_7_0_SDDS (AAX), kAudioChannelLayoutTag_AudioUnit_7_0_Front (CoreAudio) + + This format is referred to as "7.0 SDDS" in Pro Tools. + */ + static AudioChannelSet JUCE_CALLTYPE create7point0SDDS(); + + + /** Creates a set for a DTS 7.1 surround setup (left, right, centre, leftSurroundSide, rightSurroundSide, leftSurroundRear, rightSurroundRear, LFE). + + Is equivalent to: k71CineSideFill (VST), AAX_eStemFormat_7_1_DTS (AAX), kAudioChannelLayoutTag_MPEG_7_1_C/kAudioChannelLayoutTag_ITU_3_4_1 (CoreAudio) + + This format is referred to as "7.1 (3/4.1)" in Logic Pro. + This format is referred to as "7.1" in Pro Tools. + */ + static AudioChannelSet JUCE_CALLTYPE create7point1(); + + + /** Creates a set for a 7.1 surround setup (left, right, centre, leftSurround, rightSurround, leftCentre, rightCentre, LFE). + + Is equivalent to: k71Cine (VST), AAX_eStemFormat_7_1_SDDS (AAX), kAudioChannelLayoutTag_MPEG_7_1_A (CoreAudio) + + This format is referred to as "7.1 (SDDS)" in Logic Pro. + This format is referred to as "7.1 SDDS" in Pro Tools. + */ + static AudioChannelSet JUCE_CALLTYPE create7point1SDDS(); + + /** Creates a set for Dolby Atmos 7.0.2 surround setup (left, right, centre, leftSurroundSide, rightSurroundSide, leftSurroundRear, rightSurroundRear, topSideLeft, topSideRight). + + Is equivalent to: n/a (VST), AAX_eStemFormat_7_0_2 (AAX), n/a (CoreAudio) + */ + static AudioChannelSet JUCE_CALLTYPE create7point0point2(); + + /** Creates a set for Dolby Atmos 7.1.2 surround setup (left, right, centre, leftSurroundSide, rightSurroundSide, leftSurroundRear, rightSurroundRear, LFE, topSideLeft, topSideRight). + + Is equivalent to: k71_2 (VST), AAX_eStemFormat_7_1_2 (AAX), n/a (CoreAudio) + */ + static AudioChannelSet JUCE_CALLTYPE create7point1point2(); + + + //============================================================================== + /** Creates a set for quadraphonic surround setup (left, right, leftSurround, rightSurround) + + Is equivalent to: k40Music (VST), AAX_eStemFormat_Quad (AAX), kAudioChannelLayoutTag_Quadraphonic (CoreAudio) + + This format is referred to as "Quadraphonic" in Logic Pro. + This format is referred to as "Quadro" in Cubase. + This format is referred to as "Quad" in Pro Tools. + */ + static AudioChannelSet JUCE_CALLTYPE quadraphonic(); + + + /** Creates a set for pentagonal surround setup (left, right, centre, leftSurroundRear, rightSurroundRear). + + Is equivalent to: n/a (VST), n/a (AAX), kAudioChannelLayoutTag_Pentagonal (CoreAudio) + */ + static AudioChannelSet JUCE_CALLTYPE pentagonal(); + + + /** Creates a set for hexagonal surround setup (left, right, leftSurroundRear, rightSurroundRear, centre, surroundCentre). + + Is equivalent to: n/a (VST), n/a (AAX), kAudioChannelLayoutTag_Hexagonal (CoreAudio) + */ + static AudioChannelSet JUCE_CALLTYPE hexagonal(); + + + /** Creates a set for octagonal surround setup (left, right, leftSurround, rightSurround, centre, centreSurround, wideLeft, wideRight). + + Is equivalent to: n/a (VST), n/a (AAX), kAudioChannelLayoutTag_Octagonal (CoreAudio) + */ + static AudioChannelSet JUCE_CALLTYPE octagonal(); + + //============================================================================== + /** Creates a set for ACN, SN3D normalised ambisonic surround setups with a given order. + + Is equivalent to: kAmbiXXXOrderACN (VST), AAX_eStemFormat_Ambi_XXX_ACN (AAX), kAudioChannelLayoutTag_HOA_ACN_SN3D (CoreAudio) + */ + static AudioChannelSet JUCE_CALLTYPE ambisonic (int order = 1); + + /** Returns the order of the ambisonic layout represented by this AudioChannelSet. If the + AudioChannelSet is not an ambisonic layout, then this method will return -1. + */ + int getAmbisonicOrder() const; + + //============================================================================== + /** Creates a set of untyped discrete channels. */ + static AudioChannelSet JUCE_CALLTYPE discreteChannels (int numChannels); + + /** Create a canonical channel set for a given number of channels. + For example, numChannels = 1 will return mono, numChannels = 2 will return stereo, etc. */ + static AudioChannelSet JUCE_CALLTYPE canonicalChannelSet (int numChannels); + + /** Create a channel set for a given number of channels which is non-discrete. + If numChannels is larger than the number of channels of the surround format + with the maximum amount of channels (currently 7.1 Surround), then this + function returns an empty set.*/ + static AudioChannelSet JUCE_CALLTYPE namedChannelSet (int numChannels); + + /** Return an array of channel sets which have a given number of channels */ + static Array JUCE_CALLTYPE channelSetsWithNumberOfChannels (int numChannels); + + //============================================================================== + /** Represents different audio channel types. */ + enum ChannelType + { + unknown = 0, /**< Unknown channel type. */ + + //============================================================================== + left = 1, /**< L channel. */ + right = 2, /**< R channel. */ + centre = 3, /**< C channel. (Sometimes M for mono) */ + + //============================================================================== + LFE = 4, /**< LFE channel. */ + leftSurround = 5, /**< Ls channel. */ + rightSurround = 6, /**< Rs channel. */ + leftCentre = 7, /**< Lc (AAX/VST), Lc used as Lss in AU for most layouts. */ + rightCentre = 8, /**< Rc (AAX/VST), Rc used as Rss in AU for most layouts. */ + centreSurround = 9, /**< Cs/S channel. */ + surround = centreSurround, /**< Same as Centre Surround channel. */ + leftSurroundSide = 10, /**< Lss (AXX), Side Left "Sl" (VST), Left Centre "LC" (AU) channel. */ + rightSurroundSide = 11, /**< Rss (AXX), Side right "Sr" (VST), Right Centre "Rc" (AU) channel. */ + topMiddle = 12, /**< Top Middle channel. */ + topFrontLeft = 13, /**< Top Front Left channel. */ + topFrontCentre = 14, /**< Top Front Centre channel. */ + topFrontRight = 15, /**< Top Front Right channel. */ + topRearLeft = 16, /**< Top Rear Left channel. */ + topRearCentre = 17, /**< Top Rear Centre channel. */ + topRearRight = 18, /**< Top Rear Right channel. */ + LFE2 = 19, /**< Second LFE channel. */ + leftSurroundRear = 20, /**< Lsr (AAX), Lcs (VST), Rls (AU) channel. */ + rightSurroundRear = 21, /**< Rsr (AAX), Rcs (VST), Rrs (AU) channel. */ + wideLeft = 22, /**< Wide Left channel. */ + wideRight = 23, /**< Wide Right channel. */ + + //============================================================================== + // Used by Dolby Atmos 7.0.2 and 7.1.2 + topSideLeft = 28, /**< Lts (AAX), Tsl (VST) channel for Dolby Atmos. */ + topSideRight = 29, /**< Rts (AAX), Tsr (VST) channel for Dolby Atmos. */ + + //============================================================================== + // Ambisonic ACN formats - all channels are SN3D normalised + + // zero-th and first-order ambisonic ACN + ambisonicACN0 = 24, /**< Zero-th ambisonic channel number 0. */ + ambisonicACN1 = 25, /**< First-order ambisonic channel number 1. */ + ambisonicACN2 = 26, /**< First-order ambisonic channel number 2. */ + ambisonicACN3 = 27, /**< First-order ambisonic channel number 3. */ + + // second-order ambisonic + ambisonicACN4 = 30, /**< Second-order ambisonic channel number 4. */ + ambisonicACN5 = 31, /**< Second-order ambisonic channel number 5. */ + ambisonicACN6 = 32, /**< Second-order ambisonic channel number 6. */ + ambisonicACN7 = 33, /**< Second-order ambisonic channel number 7. */ + ambisonicACN8 = 34, /**< Second-order ambisonic channel number 8. */ + + // third-order ambisonic + ambisonicACN9 = 35, /**< Third-order ambisonic channel number 9. */ + ambisonicACN10 = 36, /**< Third-order ambisonic channel number 10. */ + ambisonicACN11 = 37, /**< Third-order ambisonic channel number 11. */ + ambisonicACN12 = 38, /**< Third-order ambisonic channel number 12. */ + ambisonicACN13 = 39, /**< Third-order ambisonic channel number 13. */ + ambisonicACN14 = 40, /**< Third-order ambisonic channel number 14. */ + ambisonicACN15 = 41, /**< Third-order ambisonic channel number 15. */ + + // fourth-order ambisonic + ambisonicACN16 = 42, /**< Fourth-order ambisonic channel number 16. */ + ambisonicACN17 = 43, /**< Fourth-order ambisonic channel number 17. */ + ambisonicACN18 = 44, /**< Fourth-order ambisonic channel number 18. */ + ambisonicACN19 = 45, /**< Fourth-order ambisonic channel number 19. */ + ambisonicACN20 = 46, /**< Fourth-order ambisonic channel number 20. */ + ambisonicACN21 = 47, /**< Fourth-order ambisonic channel number 21. */ + ambisonicACN22 = 48, /**< Fourth-order ambisonic channel number 22. */ + ambisonicACN23 = 49, /**< Fourth-order ambisonic channel number 23. */ + ambisonicACN24 = 50, /**< Fourth-order ambisonic channel number 24. */ + + // fifth-order ambisonic + ambisonicACN25 = 51, /**< Fifth-order ambisonic channel number 25. */ + ambisonicACN26 = 52, /**< Fifth-order ambisonic channel number 26. */ + ambisonicACN27 = 53, /**< Fifth-order ambisonic channel number 27. */ + ambisonicACN28 = 54, /**< Fifth-order ambisonic channel number 28. */ + ambisonicACN29 = 55, /**< Fifth-order ambisonic channel number 29. */ + ambisonicACN30 = 56, /**< Fifth-order ambisonic channel number 30. */ + ambisonicACN31 = 57, /**< Fifth-order ambisonic channel number 31. */ + ambisonicACN32 = 58, /**< Fifth-order ambisonic channel number 32. */ + ambisonicACN33 = 59, /**< Fifth-order ambisonic channel number 33. */ + ambisonicACN34 = 60, /**< Fifth-order ambisonic channel number 34. */ + ambisonicACN35 = 61, /**< Fifth-order ambisonic channel number 35. */ + + //============================================================================== + ambisonicW = ambisonicACN0, /**< Same as zero-th ambisonic channel number 0. */ + ambisonicX = ambisonicACN3, /**< Same as first-order ambisonic channel number 3. */ + ambisonicY = ambisonicACN1, /**< Same as first-order ambisonic channel number 1. */ + ambisonicZ = ambisonicACN2, /**< Same as first-order ambisonic channel number 2. */ + + //============================================================================== + bottomFrontLeft = 62, /**< Bottom Front Left (Bfl) */ + bottomFrontCentre = 63, /**< Bottom Front Centre (Bfc) */ + bottomFrontRight = 64, /**< Bottom Front Right (Bfr) */ + + proxymityLeft = 65, /**< Proximity Left (Pl) */ + proximityRight = 66, /**< Proximity Right (Pr) */ + + bottomSideLeft = 67, /**< Bottom Side Left (Bsl) */ + bottomSideRight = 68, /**< Bottom Side Right (Bsr) */ + bottomRearLeft = 69, /**< Bottom Rear Left (Brl) */ + bottomRearCentre = 70, /**< Bottom Rear Center (Brc) */ + bottomRearRight = 71, /**< Bottom Rear Right (Brr) */ + + //============================================================================== + discreteChannel0 = 128 /**< Non-typed individual channels are indexed upwards from this value. */ + }; + + /** Returns the name of a given channel type. For example, this method may return "Surround Left". */ + static String JUCE_CALLTYPE getChannelTypeName (ChannelType); + + /** Returns the abbreviated name of a channel type. For example, this method may return "Ls". */ + static String JUCE_CALLTYPE getAbbreviatedChannelTypeName (ChannelType); + + /** Returns the channel type from an abbreviated name. */ + static ChannelType JUCE_CALLTYPE getChannelTypeFromAbbreviation (const String& abbreviation); + + //============================================================================== + enum + { + maxChannelsOfNamedLayout = 36 + }; + + /** Adds a channel to the set. */ + void addChannel (ChannelType newChannelType); + + /** Removes a channel from the set. */ + void removeChannel (ChannelType newChannelType); + + /** Returns the number of channels in the set. */ + int size() const noexcept; + + /** Returns true if there are no channels in the set. */ + bool isDisabled() const noexcept { return size() == 0; } + + /** Returns an array of all the types in this channel set. */ + Array getChannelTypes() const; + + /** Returns the type of one of the channels in the set, by index. */ + ChannelType getTypeOfChannel (int channelIndex) const noexcept; + + /** Returns the index for a particular channel-type. + Will return -1 if the this set does not contain a channel of this type. */ + int getChannelIndexForType (ChannelType type) const noexcept; + + /** Returns a string containing a whitespace-separated list of speaker types + corresponding to each channel. For example in a 5.1 arrangement, + the string may be "L R C Lfe Ls Rs". If the speaker arrangement is unknown, + the returned string will be empty.*/ + String getSpeakerArrangementAsString() const; + + /** Returns an AudioChannelSet from a string returned by getSpeakerArrangementAsString + + @see getSpeakerArrangementAsString */ + static AudioChannelSet fromAbbreviatedString (const String& set); + + /** Returns the description of the current layout. For example, this method may return + "Quadraphonic". Note that the returned string may not be unique. */ + String getDescription() const; + + /** Returns if this is a channel layout made-up of discrete channels. */ + bool isDiscreteLayout() const noexcept; + + /** Intersect two channel layouts. */ + void intersect (const AudioChannelSet& other) { channels &= other.channels; } + + /** Creates a channel set for a list of channel types. This function will assert + if you supply a duplicate channel. + + Note that this method ignores the order in which the channels are given, i.e. + two arrays with the same elements but in a different order will still result + in the same channel set. + */ + static AudioChannelSet JUCE_CALLTYPE channelSetWithChannels (const Array&); + + //============================================================================== + // Conversion between wave and juce channel layout identifiers + + /** Create an AudioChannelSet from a WAVEFORMATEXTENSIBLE channelMask (typically used + in .wav files). */ + static AudioChannelSet JUCE_CALLTYPE fromWaveChannelMask (int32 dwChannelMask); + + /** Returns a WAVEFORMATEXTENSIBLE channelMask representation (typically used in .wav + files) of the receiver. + + Returns -1 if the receiver cannot be represented in a WAVEFORMATEXTENSIBLE channelMask + representation. + */ + int32 getWaveChannelMask() const noexcept; + + //============================================================================== + bool operator== (const AudioChannelSet&) const noexcept; + bool operator!= (const AudioChannelSet&) const noexcept; + bool operator< (const AudioChannelSet&) const noexcept; + +private: + //============================================================================== + BigInteger channels; + + //============================================================================== + explicit AudioChannelSet (uint32); + explicit AudioChannelSet (const Array&); + + //============================================================================== + static int JUCE_CALLTYPE getAmbisonicOrderForNumChannels (int); +}; + +} // namespace juce diff --git a/JuceLibraryCode/modules/juce_audio_basics/buffers/juce_AudioDataConverters.cpp b/JuceLibraryCode/modules/juce_audio_basics/buffers/juce_AudioDataConverters.cpp index a441a8f..0014be5 100644 --- a/JuceLibraryCode/modules/juce_audio_basics/buffers/juce_AudioDataConverters.cpp +++ b/JuceLibraryCode/modules/juce_audio_basics/buffers/juce_AudioDataConverters.cpp @@ -1,596 +1,596 @@ -/* - ============================================================================== - - This file is part of the JUCE library. - Copyright (c) 2017 - ROLI Ltd. - - JUCE is an open source library subject to commercial or open-source - licensing. - - The code included in this file is provided under the terms of the ISC license - http://www.isc.org/downloads/software-support-policy/isc-license. Permission - To use, copy, modify, and/or distribute this software for any purpose with or - without fee is hereby granted provided that the above copyright notice and - this permission notice appear in all copies. - - JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER - EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE - DISCLAIMED. - - ============================================================================== -*/ - -namespace juce -{ - -void AudioDataConverters::convertFloatToInt16LE (const float* source, void* dest, int numSamples, int destBytesPerSample) -{ - auto maxVal = (double) 0x7fff; - auto intData = static_cast (dest); - - if (dest != (void*) source || destBytesPerSample <= 4) - { - for (int i = 0; i < numSamples; ++i) - { - *reinterpret_cast (intData) = ByteOrder::swapIfBigEndian ((uint16) (short) roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i]))); - intData += destBytesPerSample; - } - } - else - { - intData += destBytesPerSample * numSamples; - - for (int i = numSamples; --i >= 0;) - { - intData -= destBytesPerSample; - *reinterpret_cast (intData) = ByteOrder::swapIfBigEndian ((uint16) (short) roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i]))); - } - } -} - -void AudioDataConverters::convertFloatToInt16BE (const float* source, void* dest, int numSamples, int destBytesPerSample) -{ - auto maxVal = (double) 0x7fff; - auto intData = static_cast (dest); - - if (dest != (void*) source || destBytesPerSample <= 4) - { - for (int i = 0; i < numSamples; ++i) - { - *reinterpret_cast (intData) = ByteOrder::swapIfLittleEndian ((uint16) (short) roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i]))); - intData += destBytesPerSample; - } - } - else - { - intData += destBytesPerSample * numSamples; - - for (int i = numSamples; --i >= 0;) - { - intData -= destBytesPerSample; - *reinterpret_cast (intData) = ByteOrder::swapIfLittleEndian ((uint16) (short) roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i]))); - } - } -} - -void AudioDataConverters::convertFloatToInt24LE (const float* source, void* dest, int numSamples, int destBytesPerSample) -{ - auto maxVal = (double) 0x7fffff; - auto intData = static_cast (dest); - - if (dest != (void*) source || destBytesPerSample <= 4) - { - for (int i = 0; i < numSamples; ++i) - { - ByteOrder::littleEndian24BitToChars (roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i])), intData); - intData += destBytesPerSample; - } - } - else - { - intData += destBytesPerSample * numSamples; - - for (int i = numSamples; --i >= 0;) - { - intData -= destBytesPerSample; - ByteOrder::littleEndian24BitToChars (roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i])), intData); - } - } -} - -void AudioDataConverters::convertFloatToInt24BE (const float* source, void* dest, int numSamples, int destBytesPerSample) -{ - auto maxVal = (double) 0x7fffff; - auto intData = static_cast (dest); - - if (dest != (void*) source || destBytesPerSample <= 4) - { - for (int i = 0; i < numSamples; ++i) - { - ByteOrder::bigEndian24BitToChars (roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i])), intData); - intData += destBytesPerSample; - } - } - else - { - intData += destBytesPerSample * numSamples; - - for (int i = numSamples; --i >= 0;) - { - intData -= destBytesPerSample; - ByteOrder::bigEndian24BitToChars (roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i])), intData); - } - } -} - -void AudioDataConverters::convertFloatToInt32LE (const float* source, void* dest, int numSamples, int destBytesPerSample) -{ - auto maxVal = (double) 0x7fffffff; - auto intData = static_cast (dest); - - if (dest != (void*) source || destBytesPerSample <= 4) - { - for (int i = 0; i < numSamples; ++i) - { - *reinterpret_cast (intData) = ByteOrder::swapIfBigEndian ((uint32) roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i]))); - intData += destBytesPerSample; - } - } - else - { - intData += destBytesPerSample * numSamples; - - for (int i = numSamples; --i >= 0;) - { - intData -= destBytesPerSample; - *reinterpret_cast (intData) = ByteOrder::swapIfBigEndian ((uint32) roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i]))); - } - } -} - -void AudioDataConverters::convertFloatToInt32BE (const float* source, void* dest, int numSamples, int destBytesPerSample) -{ - auto maxVal = (double) 0x7fffffff; - auto intData = static_cast (dest); - - if (dest != (void*) source || destBytesPerSample <= 4) - { - for (int i = 0; i < numSamples; ++i) - { - *reinterpret_cast (intData) = ByteOrder::swapIfLittleEndian ((uint32) roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i]))); - intData += destBytesPerSample; - } - } - else - { - intData += destBytesPerSample * numSamples; - - for (int i = numSamples; --i >= 0;) - { - intData -= destBytesPerSample; - *reinterpret_cast (intData) = ByteOrder::swapIfLittleEndian ((uint32) roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i]))); - } - } -} - -void AudioDataConverters::convertFloatToFloat32LE (const float* source, void* dest, int numSamples, int destBytesPerSample) -{ - jassert (dest != (void*) source || destBytesPerSample <= 4); // This op can't be performed on in-place data! - - char* d = static_cast (dest); - - for (int i = 0; i < numSamples; ++i) - { - *reinterpret_cast (d) = source[i]; - - #if JUCE_BIG_ENDIAN - *reinterpret_cast (d) = ByteOrder::swap (*reinterpret_cast (d)); - #endif - - d += destBytesPerSample; - } -} - -void AudioDataConverters::convertFloatToFloat32BE (const float* source, void* dest, int numSamples, int destBytesPerSample) -{ - jassert (dest != (void*) source || destBytesPerSample <= 4); // This op can't be performed on in-place data! - - auto d = static_cast (dest); - - for (int i = 0; i < numSamples; ++i) - { - *reinterpret_cast (d) = source[i]; - - #if JUCE_LITTLE_ENDIAN - *reinterpret_cast (d) = ByteOrder::swap (*reinterpret_cast (d)); - #endif - - d += destBytesPerSample; - } -} - -//============================================================================== -void AudioDataConverters::convertInt16LEToFloat (const void* source, float* dest, int numSamples, int srcBytesPerSample) -{ - const float scale = 1.0f / 0x7fff; - auto intData = static_cast (source); - - if (source != (void*) dest || srcBytesPerSample >= 4) - { - for (int i = 0; i < numSamples; ++i) - { - dest[i] = scale * (short) ByteOrder::swapIfBigEndian (*reinterpret_cast (intData)); - intData += srcBytesPerSample; - } - } - else - { - intData += srcBytesPerSample * numSamples; - - for (int i = numSamples; --i >= 0;) - { - intData -= srcBytesPerSample; - dest[i] = scale * (short) ByteOrder::swapIfBigEndian (*reinterpret_cast (intData)); - } - } -} - -void AudioDataConverters::convertInt16BEToFloat (const void* source, float* dest, int numSamples, int srcBytesPerSample) -{ - const float scale = 1.0f / 0x7fff; - auto intData = static_cast (source); - - if (source != (void*) dest || srcBytesPerSample >= 4) - { - for (int i = 0; i < numSamples; ++i) - { - dest[i] = scale * (short) ByteOrder::swapIfLittleEndian (*reinterpret_cast (intData)); - intData += srcBytesPerSample; - } - } - else - { - intData += srcBytesPerSample * numSamples; - - for (int i = numSamples; --i >= 0;) - { - intData -= srcBytesPerSample; - dest[i] = scale * (short) ByteOrder::swapIfLittleEndian (*reinterpret_cast (intData)); - } - } -} - -void AudioDataConverters::convertInt24LEToFloat (const void* source, float* dest, int numSamples, int srcBytesPerSample) -{ - const float scale = 1.0f / 0x7fffff; - auto intData = static_cast (source); - - if (source != (void*) dest || srcBytesPerSample >= 4) - { - for (int i = 0; i < numSamples; ++i) - { - dest[i] = scale * (short) ByteOrder::littleEndian24Bit (intData); - intData += srcBytesPerSample; - } - } - else - { - intData += srcBytesPerSample * numSamples; - - for (int i = numSamples; --i >= 0;) - { - intData -= srcBytesPerSample; - dest[i] = scale * (short) ByteOrder::littleEndian24Bit (intData); - } - } -} - -void AudioDataConverters::convertInt24BEToFloat (const void* source, float* dest, int numSamples, int srcBytesPerSample) -{ - const float scale = 1.0f / 0x7fffff; - auto intData = static_cast (source); - - if (source != (void*) dest || srcBytesPerSample >= 4) - { - for (int i = 0; i < numSamples; ++i) - { - dest[i] = scale * (short) ByteOrder::bigEndian24Bit (intData); - intData += srcBytesPerSample; - } - } - else - { - intData += srcBytesPerSample * numSamples; - - for (int i = numSamples; --i >= 0;) - { - intData -= srcBytesPerSample; - dest[i] = scale * (short) ByteOrder::bigEndian24Bit (intData); - } - } -} - -void AudioDataConverters::convertInt32LEToFloat (const void* source, float* dest, int numSamples, int srcBytesPerSample) -{ - const float scale = 1.0f / (float) 0x7fffffff; - auto intData = static_cast (source); - - if (source != (void*) dest || srcBytesPerSample >= 4) - { - for (int i = 0; i < numSamples; ++i) - { - dest[i] = scale * (int) ByteOrder::swapIfBigEndian (*reinterpret_cast (intData)); - intData += srcBytesPerSample; - } - } - else - { - intData += srcBytesPerSample * numSamples; - - for (int i = numSamples; --i >= 0;) - { - intData -= srcBytesPerSample; - dest[i] = scale * (int) ByteOrder::swapIfBigEndian (*reinterpret_cast (intData)); - } - } -} - -void AudioDataConverters::convertInt32BEToFloat (const void* source, float* dest, int numSamples, int srcBytesPerSample) -{ - const float scale = 1.0f / (float) 0x7fffffff; - auto intData = static_cast (source); - - if (source != (void*) dest || srcBytesPerSample >= 4) - { - for (int i = 0; i < numSamples; ++i) - { - dest[i] = scale * (int) ByteOrder::swapIfLittleEndian (*reinterpret_cast (intData)); - intData += srcBytesPerSample; - } - } - else - { - intData += srcBytesPerSample * numSamples; - - for (int i = numSamples; --i >= 0;) - { - intData -= srcBytesPerSample; - dest[i] = scale * (int) ByteOrder::swapIfLittleEndian (*reinterpret_cast (intData)); - } - } -} - -void AudioDataConverters::convertFloat32LEToFloat (const void* source, float* dest, int numSamples, int srcBytesPerSample) -{ - auto s = static_cast (source); - - for (int i = 0; i < numSamples; ++i) - { - dest[i] = *reinterpret_cast (s); - - #if JUCE_BIG_ENDIAN - auto d = reinterpret_cast (dest + i); - *d = ByteOrder::swap (*d); - #endif - - s += srcBytesPerSample; - } -} - -void AudioDataConverters::convertFloat32BEToFloat (const void* source, float* dest, int numSamples, int srcBytesPerSample) -{ - auto s = static_cast (source); - - for (int i = 0; i < numSamples; ++i) - { - dest[i] = *reinterpret_cast (s); - - #if JUCE_LITTLE_ENDIAN - auto d = reinterpret_cast (dest + i); - *d = ByteOrder::swap (*d); - #endif - - s += srcBytesPerSample; - } -} - - -//============================================================================== -void AudioDataConverters::convertFloatToFormat (DataFormat destFormat, const float* source, void* dest, int numSamples) -{ - switch (destFormat) - { - case int16LE: convertFloatToInt16LE (source, dest, numSamples); break; - case int16BE: convertFloatToInt16BE (source, dest, numSamples); break; - case int24LE: convertFloatToInt24LE (source, dest, numSamples); break; - case int24BE: convertFloatToInt24BE (source, dest, numSamples); break; - case int32LE: convertFloatToInt32LE (source, dest, numSamples); break; - case int32BE: convertFloatToInt32BE (source, dest, numSamples); break; - case float32LE: convertFloatToFloat32LE (source, dest, numSamples); break; - case float32BE: convertFloatToFloat32BE (source, dest, numSamples); break; - default: jassertfalse; break; - } -} - -void AudioDataConverters::convertFormatToFloat (DataFormat sourceFormat, const void* source, float* dest, int numSamples) -{ - switch (sourceFormat) - { - case int16LE: convertInt16LEToFloat (source, dest, numSamples); break; - case int16BE: convertInt16BEToFloat (source, dest, numSamples); break; - case int24LE: convertInt24LEToFloat (source, dest, numSamples); break; - case int24BE: convertInt24BEToFloat (source, dest, numSamples); break; - case int32LE: convertInt32LEToFloat (source, dest, numSamples); break; - case int32BE: convertInt32BEToFloat (source, dest, numSamples); break; - case float32LE: convertFloat32LEToFloat (source, dest, numSamples); break; - case float32BE: convertFloat32BEToFloat (source, dest, numSamples); break; - default: jassertfalse; break; - } -} - -//============================================================================== -void AudioDataConverters::interleaveSamples (const float** source, float* dest, int numSamples, int numChannels) -{ - for (int chan = 0; chan < numChannels; ++chan) - { - auto i = chan; - auto src = source [chan]; - - for (int j = 0; j < numSamples; ++j) - { - dest [i] = src [j]; - i += numChannels; - } - } -} - -void AudioDataConverters::deinterleaveSamples (const float* source, float** dest, int numSamples, int numChannels) -{ - for (int chan = 0; chan < numChannels; ++chan) - { - auto i = chan; - auto dst = dest [chan]; - - for (int j = 0; j < numSamples; ++j) - { - dst [j] = source [i]; - i += numChannels; - } - } -} - - -//============================================================================== -//============================================================================== -#if JUCE_UNIT_TESTS - -class AudioConversionTests : public UnitTest -{ -public: - AudioConversionTests() - : UnitTest ("Audio data conversion", UnitTestCategories::audio) - {} - - template - struct Test5 - { - static void test (UnitTest& unitTest, Random& r) - { - test (unitTest, false, r); - test (unitTest, true, r); - } - - static void test (UnitTest& unitTest, bool inPlace, Random& r) - { - const int numSamples = 2048; - int32 original [(size_t) numSamples], - converted[(size_t) numSamples], - reversed [(size_t) numSamples]; - - { - AudioData::Pointer d (original); - bool clippingFailed = false; - - for (int i = 0; i < numSamples / 2; ++i) - { - d.setAsFloat (r.nextFloat() * 2.2f - 1.1f); - - if (! d.isFloatingPoint()) - clippingFailed = d.getAsFloat() > 1.0f || d.getAsFloat() < -1.0f || clippingFailed; - - ++d; - d.setAsInt32 (r.nextInt()); - ++d; - } - - unitTest.expect (! clippingFailed); - } - - // convert data from the source to dest format.. - std::unique_ptr conv (new AudioData::ConverterInstance, - AudioData::Pointer>()); - conv->convertSamples (inPlace ? reversed : converted, original, numSamples); - - // ..and back again.. - conv.reset (new AudioData::ConverterInstance, - AudioData::Pointer>()); - if (! inPlace) - zeromem (reversed, sizeof (reversed)); - - conv->convertSamples (reversed, inPlace ? reversed : converted, numSamples); - - { - int biggestDiff = 0; - AudioData::Pointer d1 (original); - AudioData::Pointer d2 (reversed); - - const int errorMargin = 2 * AudioData::Pointer::get32BitResolution() - + AudioData::Pointer::get32BitResolution(); - - for (int i = 0; i < numSamples; ++i) - { - biggestDiff = jmax (biggestDiff, std::abs (d1.getAsInt32() - d2.getAsInt32())); - ++d1; - ++d2; - } - - unitTest.expect (biggestDiff <= errorMargin); - } - } - }; - - template - struct Test3 - { - static void test (UnitTest& unitTest, Random& r) - { - Test5 ::test (unitTest, r); - Test5 ::test (unitTest, r); - } - }; - - template - struct Test2 - { - static void test (UnitTest& unitTest, Random& r) - { - Test3 ::test (unitTest, r); - Test3 ::test (unitTest, r); - Test3 ::test (unitTest, r); - Test3 ::test (unitTest, r); - Test3 ::test (unitTest, r); - Test3 ::test (unitTest, r); - } - }; - - template - struct Test1 - { - static void test (UnitTest& unitTest, Random& r) - { - Test2 ::test (unitTest, r); - Test2 ::test (unitTest, r); - } - }; - - void runTest() override - { - auto r = getRandom(); - beginTest ("Round-trip conversion: Int8"); - Test1 ::test (*this, r); - beginTest ("Round-trip conversion: Int16"); - Test1 ::test (*this, r); - beginTest ("Round-trip conversion: Int24"); - Test1 ::test (*this, r); - beginTest ("Round-trip conversion: Int32"); - Test1 ::test (*this, r); - beginTest ("Round-trip conversion: Float32"); - Test1 ::test (*this, r); - } -}; - -static AudioConversionTests audioConversionUnitTests; - -#endif - -} // namespace juce +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2017 - ROLI Ltd. + + JUCE is an open source library subject to commercial or open-source + licensing. + + The code included in this file is provided under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license. Permission + To use, copy, modify, and/or distribute this software for any purpose with or + without fee is hereby granted provided that the above copyright notice and + this permission notice appear in all copies. + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +namespace juce +{ + +void AudioDataConverters::convertFloatToInt16LE (const float* source, void* dest, int numSamples, int destBytesPerSample) +{ + auto maxVal = (double) 0x7fff; + auto intData = static_cast (dest); + + if (dest != (void*) source || destBytesPerSample <= 4) + { + for (int i = 0; i < numSamples; ++i) + { + *reinterpret_cast (intData) = ByteOrder::swapIfBigEndian ((uint16) (short) roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i]))); + intData += destBytesPerSample; + } + } + else + { + intData += destBytesPerSample * numSamples; + + for (int i = numSamples; --i >= 0;) + { + intData -= destBytesPerSample; + *reinterpret_cast (intData) = ByteOrder::swapIfBigEndian ((uint16) (short) roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i]))); + } + } +} + +void AudioDataConverters::convertFloatToInt16BE (const float* source, void* dest, int numSamples, int destBytesPerSample) +{ + auto maxVal = (double) 0x7fff; + auto intData = static_cast (dest); + + if (dest != (void*) source || destBytesPerSample <= 4) + { + for (int i = 0; i < numSamples; ++i) + { + *reinterpret_cast (intData) = ByteOrder::swapIfLittleEndian ((uint16) (short) roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i]))); + intData += destBytesPerSample; + } + } + else + { + intData += destBytesPerSample * numSamples; + + for (int i = numSamples; --i >= 0;) + { + intData -= destBytesPerSample; + *reinterpret_cast (intData) = ByteOrder::swapIfLittleEndian ((uint16) (short) roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i]))); + } + } +} + +void AudioDataConverters::convertFloatToInt24LE (const float* source, void* dest, int numSamples, int destBytesPerSample) +{ + auto maxVal = (double) 0x7fffff; + auto intData = static_cast (dest); + + if (dest != (void*) source || destBytesPerSample <= 4) + { + for (int i = 0; i < numSamples; ++i) + { + ByteOrder::littleEndian24BitToChars (roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i])), intData); + intData += destBytesPerSample; + } + } + else + { + intData += destBytesPerSample * numSamples; + + for (int i = numSamples; --i >= 0;) + { + intData -= destBytesPerSample; + ByteOrder::littleEndian24BitToChars (roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i])), intData); + } + } +} + +void AudioDataConverters::convertFloatToInt24BE (const float* source, void* dest, int numSamples, int destBytesPerSample) +{ + auto maxVal = (double) 0x7fffff; + auto intData = static_cast (dest); + + if (dest != (void*) source || destBytesPerSample <= 4) + { + for (int i = 0; i < numSamples; ++i) + { + ByteOrder::bigEndian24BitToChars (roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i])), intData); + intData += destBytesPerSample; + } + } + else + { + intData += destBytesPerSample * numSamples; + + for (int i = numSamples; --i >= 0;) + { + intData -= destBytesPerSample; + ByteOrder::bigEndian24BitToChars (roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i])), intData); + } + } +} + +void AudioDataConverters::convertFloatToInt32LE (const float* source, void* dest, int numSamples, int destBytesPerSample) +{ + auto maxVal = (double) 0x7fffffff; + auto intData = static_cast (dest); + + if (dest != (void*) source || destBytesPerSample <= 4) + { + for (int i = 0; i < numSamples; ++i) + { + *reinterpret_cast (intData) = ByteOrder::swapIfBigEndian ((uint32) roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i]))); + intData += destBytesPerSample; + } + } + else + { + intData += destBytesPerSample * numSamples; + + for (int i = numSamples; --i >= 0;) + { + intData -= destBytesPerSample; + *reinterpret_cast (intData) = ByteOrder::swapIfBigEndian ((uint32) roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i]))); + } + } +} + +void AudioDataConverters::convertFloatToInt32BE (const float* source, void* dest, int numSamples, int destBytesPerSample) +{ + auto maxVal = (double) 0x7fffffff; + auto intData = static_cast (dest); + + if (dest != (void*) source || destBytesPerSample <= 4) + { + for (int i = 0; i < numSamples; ++i) + { + *reinterpret_cast (intData) = ByteOrder::swapIfLittleEndian ((uint32) roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i]))); + intData += destBytesPerSample; + } + } + else + { + intData += destBytesPerSample * numSamples; + + for (int i = numSamples; --i >= 0;) + { + intData -= destBytesPerSample; + *reinterpret_cast (intData) = ByteOrder::swapIfLittleEndian ((uint32) roundToInt (jlimit (-maxVal, maxVal, maxVal * source[i]))); + } + } +} + +void AudioDataConverters::convertFloatToFloat32LE (const float* source, void* dest, int numSamples, int destBytesPerSample) +{ + jassert (dest != (void*) source || destBytesPerSample <= 4); // This op can't be performed on in-place data! + + char* d = static_cast (dest); + + for (int i = 0; i < numSamples; ++i) + { + *reinterpret_cast (d) = source[i]; + + #if JUCE_BIG_ENDIAN + *reinterpret_cast (d) = ByteOrder::swap (*reinterpret_cast (d)); + #endif + + d += destBytesPerSample; + } +} + +void AudioDataConverters::convertFloatToFloat32BE (const float* source, void* dest, int numSamples, int destBytesPerSample) +{ + jassert (dest != (void*) source || destBytesPerSample <= 4); // This op can't be performed on in-place data! + + auto d = static_cast (dest); + + for (int i = 0; i < numSamples; ++i) + { + *reinterpret_cast (d) = source[i]; + + #if JUCE_LITTLE_ENDIAN + *reinterpret_cast (d) = ByteOrder::swap (*reinterpret_cast (d)); + #endif + + d += destBytesPerSample; + } +} + +//============================================================================== +void AudioDataConverters::convertInt16LEToFloat (const void* source, float* dest, int numSamples, int srcBytesPerSample) +{ + const float scale = 1.0f / 0x7fff; + auto intData = static_cast (source); + + if (source != (void*) dest || srcBytesPerSample >= 4) + { + for (int i = 0; i < numSamples; ++i) + { + dest[i] = scale * (short) ByteOrder::swapIfBigEndian (*reinterpret_cast (intData)); + intData += srcBytesPerSample; + } + } + else + { + intData += srcBytesPerSample * numSamples; + + for (int i = numSamples; --i >= 0;) + { + intData -= srcBytesPerSample; + dest[i] = scale * (short) ByteOrder::swapIfBigEndian (*reinterpret_cast (intData)); + } + } +} + +void AudioDataConverters::convertInt16BEToFloat (const void* source, float* dest, int numSamples, int srcBytesPerSample) +{ + const float scale = 1.0f / 0x7fff; + auto intData = static_cast (source); + + if (source != (void*) dest || srcBytesPerSample >= 4) + { + for (int i = 0; i < numSamples; ++i) + { + dest[i] = scale * (short) ByteOrder::swapIfLittleEndian (*reinterpret_cast (intData)); + intData += srcBytesPerSample; + } + } + else + { + intData += srcBytesPerSample * numSamples; + + for (int i = numSamples; --i >= 0;) + { + intData -= srcBytesPerSample; + dest[i] = scale * (short) ByteOrder::swapIfLittleEndian (*reinterpret_cast (intData)); + } + } +} + +void AudioDataConverters::convertInt24LEToFloat (const void* source, float* dest, int numSamples, int srcBytesPerSample) +{ + const float scale = 1.0f / 0x7fffff; + auto intData = static_cast (source); + + if (source != (void*) dest || srcBytesPerSample >= 4) + { + for (int i = 0; i < numSamples; ++i) + { + dest[i] = scale * (short) ByteOrder::littleEndian24Bit (intData); + intData += srcBytesPerSample; + } + } + else + { + intData += srcBytesPerSample * numSamples; + + for (int i = numSamples; --i >= 0;) + { + intData -= srcBytesPerSample; + dest[i] = scale * (short) ByteOrder::littleEndian24Bit (intData); + } + } +} + +void AudioDataConverters::convertInt24BEToFloat (const void* source, float* dest, int numSamples, int srcBytesPerSample) +{ + const float scale = 1.0f / 0x7fffff; + auto intData = static_cast (source); + + if (source != (void*) dest || srcBytesPerSample >= 4) + { + for (int i = 0; i < numSamples; ++i) + { + dest[i] = scale * (short) ByteOrder::bigEndian24Bit (intData); + intData += srcBytesPerSample; + } + } + else + { + intData += srcBytesPerSample * numSamples; + + for (int i = numSamples; --i >= 0;) + { + intData -= srcBytesPerSample; + dest[i] = scale * (short) ByteOrder::bigEndian24Bit (intData); + } + } +} + +void AudioDataConverters::convertInt32LEToFloat (const void* source, float* dest, int numSamples, int srcBytesPerSample) +{ + const float scale = 1.0f / (float) 0x7fffffff; + auto intData = static_cast (source); + + if (source != (void*) dest || srcBytesPerSample >= 4) + { + for (int i = 0; i < numSamples; ++i) + { + dest[i] = scale * (int) ByteOrder::swapIfBigEndian (*reinterpret_cast (intData)); + intData += srcBytesPerSample; + } + } + else + { + intData += srcBytesPerSample * numSamples; + + for (int i = numSamples; --i >= 0;) + { + intData -= srcBytesPerSample; + dest[i] = scale * (int) ByteOrder::swapIfBigEndian (*reinterpret_cast (intData)); + } + } +} + +void AudioDataConverters::convertInt32BEToFloat (const void* source, float* dest, int numSamples, int srcBytesPerSample) +{ + const float scale = 1.0f / (float) 0x7fffffff; + auto intData = static_cast (source); + + if (source != (void*) dest || srcBytesPerSample >= 4) + { + for (int i = 0; i < numSamples; ++i) + { + dest[i] = scale * (int) ByteOrder::swapIfLittleEndian (*reinterpret_cast (intData)); + intData += srcBytesPerSample; + } + } + else + { + intData += srcBytesPerSample * numSamples; + + for (int i = numSamples; --i >= 0;) + { + intData -= srcBytesPerSample; + dest[i] = scale * (int) ByteOrder::swapIfLittleEndian (*reinterpret_cast (intData)); + } + } +} + +void AudioDataConverters::convertFloat32LEToFloat (const void* source, float* dest, int numSamples, int srcBytesPerSample) +{ + auto s = static_cast (source); + + for (int i = 0; i < numSamples; ++i) + { + dest[i] = *reinterpret_cast (s); + + #if JUCE_BIG_ENDIAN + auto d = reinterpret_cast (dest + i); + *d = ByteOrder::swap (*d); + #endif + + s += srcBytesPerSample; + } +} + +void AudioDataConverters::convertFloat32BEToFloat (const void* source, float* dest, int numSamples, int srcBytesPerSample) +{ + auto s = static_cast (source); + + for (int i = 0; i < numSamples; ++i) + { + dest[i] = *reinterpret_cast (s); + + #if JUCE_LITTLE_ENDIAN + auto d = reinterpret_cast (dest + i); + *d = ByteOrder::swap (*d); + #endif + + s += srcBytesPerSample; + } +} + + +//============================================================================== +void AudioDataConverters::convertFloatToFormat (DataFormat destFormat, const float* source, void* dest, int numSamples) +{ + switch (destFormat) + { + case int16LE: convertFloatToInt16LE (source, dest, numSamples); break; + case int16BE: convertFloatToInt16BE (source, dest, numSamples); break; + case int24LE: convertFloatToInt24LE (source, dest, numSamples); break; + case int24BE: convertFloatToInt24BE (source, dest, numSamples); break; + case int32LE: convertFloatToInt32LE (source, dest, numSamples); break; + case int32BE: convertFloatToInt32BE (source, dest, numSamples); break; + case float32LE: convertFloatToFloat32LE (source, dest, numSamples); break; + case float32BE: convertFloatToFloat32BE (source, dest, numSamples); break; + default: jassertfalse; break; + } +} + +void AudioDataConverters::convertFormatToFloat (DataFormat sourceFormat, const void* source, float* dest, int numSamples) +{ + switch (sourceFormat) + { + case int16LE: convertInt16LEToFloat (source, dest, numSamples); break; + case int16BE: convertInt16BEToFloat (source, dest, numSamples); break; + case int24LE: convertInt24LEToFloat (source, dest, numSamples); break; + case int24BE: convertInt24BEToFloat (source, dest, numSamples); break; + case int32LE: convertInt32LEToFloat (source, dest, numSamples); break; + case int32BE: convertInt32BEToFloat (source, dest, numSamples); break; + case float32LE: convertFloat32LEToFloat (source, dest, numSamples); break; + case float32BE: convertFloat32BEToFloat (source, dest, numSamples); break; + default: jassertfalse; break; + } +} + +//============================================================================== +void AudioDataConverters::interleaveSamples (const float** source, float* dest, int numSamples, int numChannels) +{ + for (int chan = 0; chan < numChannels; ++chan) + { + auto i = chan; + auto src = source [chan]; + + for (int j = 0; j < numSamples; ++j) + { + dest [i] = src [j]; + i += numChannels; + } + } +} + +void AudioDataConverters::deinterleaveSamples (const float* source, float** dest, int numSamples, int numChannels) +{ + for (int chan = 0; chan < numChannels; ++chan) + { + auto i = chan; + auto dst = dest [chan]; + + for (int j = 0; j < numSamples; ++j) + { + dst [j] = source [i]; + i += numChannels; + } + } +} + + +//============================================================================== +//============================================================================== +#if JUCE_UNIT_TESTS + +class AudioConversionTests : public UnitTest +{ +public: + AudioConversionTests() + : UnitTest ("Audio data conversion", UnitTestCategories::audio) + {} + + template + struct Test5 + { + static void test (UnitTest& unitTest, Random& r) + { + test (unitTest, false, r); + test (unitTest, true, r); + } + + static void test (UnitTest& unitTest, bool inPlace, Random& r) + { + const int numSamples = 2048; + int32 original [(size_t) numSamples], + converted[(size_t) numSamples], + reversed [(size_t) numSamples]; + + { + AudioData::Pointer d (original); + bool clippingFailed = false; + + for (int i = 0; i < numSamples / 2; ++i) + { + d.setAsFloat (r.nextFloat() * 2.2f - 1.1f); + + if (! d.isFloatingPoint()) + clippingFailed = d.getAsFloat() > 1.0f || d.getAsFloat() < -1.0f || clippingFailed; + + ++d; + d.setAsInt32 (r.nextInt()); + ++d; + } + + unitTest.expect (! clippingFailed); + } + + // convert data from the source to dest format.. + std::unique_ptr conv (new AudioData::ConverterInstance, + AudioData::Pointer>()); + conv->convertSamples (inPlace ? reversed : converted, original, numSamples); + + // ..and back again.. + conv.reset (new AudioData::ConverterInstance, + AudioData::Pointer>()); + if (! inPlace) + zeromem (reversed, sizeof (reversed)); + + conv->convertSamples (reversed, inPlace ? reversed : converted, numSamples); + + { + int biggestDiff = 0; + AudioData::Pointer d1 (original); + AudioData::Pointer d2 (reversed); + + const int errorMargin = 2 * AudioData::Pointer::get32BitResolution() + + AudioData::Pointer::get32BitResolution(); + + for (int i = 0; i < numSamples; ++i) + { + biggestDiff = jmax (biggestDiff, std::abs (d1.getAsInt32() - d2.getAsInt32())); + ++d1; + ++d2; + } + + unitTest.expect (biggestDiff <= errorMargin); + } + } + }; + + template + struct Test3 + { + static void test (UnitTest& unitTest, Random& r) + { + Test5 ::test (unitTest, r); + Test5 ::test (unitTest, r); + } + }; + + template + struct Test2 + { + static void test (UnitTest& unitTest, Random& r) + { + Test3 ::test (unitTest, r); + Test3 ::test (unitTest, r); + Test3 ::test (unitTest, r); + Test3 ::test (unitTest, r); + Test3 ::test (unitTest, r); + Test3 ::test (unitTest, r); + } + }; + + template + struct Test1 + { + static void test (UnitTest& unitTest, Random& r) + { + Test2 ::test (unitTest, r); + Test2 ::test (unitTest, r); + } + }; + + void runTest() override + { + auto r = getRandom(); + beginTest ("Round-trip conversion: Int8"); + Test1 ::test (*this, r); + beginTest ("Round-trip conversion: Int16"); + Test1 ::test (*this, r); + beginTest ("Round-trip conversion: Int24"); + Test1 ::test (*this, r); + beginTest ("Round-trip conversion: Int32"); + Test1 ::test (*this, r); + beginTest ("Round-trip conversion: Float32"); + Test1 ::test (*this, r); + } +}; + +static AudioConversionTests audioConversionUnitTests; + +#endif + +} // namespace juce diff --git a/JuceLibraryCode/modules/juce_audio_basics/buffers/juce_AudioDataConverters.h b/JuceLibraryCode/modules/juce_audio_basics/buffers/juce_AudioDataConverters.h index e8e1fed..9880d45 100644 --- a/JuceLibraryCode/modules/juce_audio_basics/buffers/juce_AudioDataConverters.h +++ b/JuceLibraryCode/modules/juce_audio_basics/buffers/juce_AudioDataConverters.h @@ -1,716 +1,716 @@ -/* - ============================================================================== - - This file is part of the JUCE library. - Copyright (c) 2017 - ROLI Ltd. - - JUCE is an open source library subject to commercial or open-source - licensing. - - The code included in this file is provided under the terms of the ISC license - http://www.isc.org/downloads/software-support-policy/isc-license. Permission - To use, copy, modify, and/or distribute this software for any purpose with or - without fee is hereby granted provided that the above copyright notice and - this permission notice appear in all copies. - - JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER - EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE - DISCLAIMED. - - ============================================================================== -*/ - -namespace juce -{ - -//============================================================================== -/** - This class a container which holds all the classes pertaining to the AudioData::Pointer - audio sample format class. - - @see AudioData::Pointer. - - @tags{Audio} -*/ -class JUCE_API AudioData -{ -public: - //============================================================================== - // These types can be used as the SampleFormat template parameter for the AudioData::Pointer class. - - class Int8; /**< Used as a template parameter for AudioData::Pointer. Indicates an 8-bit integer packed data format. */ - class UInt8; /**< Used as a template parameter for AudioData::Pointer. Indicates an 8-bit unsigned integer packed data format. */ - class Int16; /**< Used as a template parameter for AudioData::Pointer. Indicates an 16-bit integer packed data format. */ - class Int24; /**< Used as a template parameter for AudioData::Pointer. Indicates an 24-bit integer packed data format. */ - class Int32; /**< Used as a template parameter for AudioData::Pointer. Indicates an 32-bit integer packed data format. */ - class Float32; /**< Used as a template parameter for AudioData::Pointer. Indicates an 32-bit float data format. */ - - //============================================================================== - // These types can be used as the Endianness template parameter for the AudioData::Pointer class. - - class BigEndian; /**< Used as a template parameter for AudioData::Pointer. Indicates that the samples are stored in big-endian order. */ - class LittleEndian; /**< Used as a template parameter for AudioData::Pointer. Indicates that the samples are stored in little-endian order. */ - class NativeEndian; /**< Used as a template parameter for AudioData::Pointer. Indicates that the samples are stored in the CPU's native endianness. */ - - //============================================================================== - // These types can be used as the InterleavingType template parameter for the AudioData::Pointer class. - - class NonInterleaved; /**< Used as a template parameter for AudioData::Pointer. Indicates that the samples are stored contiguously. */ - class Interleaved; /**< Used as a template parameter for AudioData::Pointer. Indicates that the samples are interleaved with a number of other channels. */ - - //============================================================================== - // These types can be used as the Constness template parameter for the AudioData::Pointer class. - - class NonConst; /**< Used as a template parameter for AudioData::Pointer. Indicates that the pointer can be used for non-const data. */ - class Const; /**< Used as a template parameter for AudioData::Pointer. Indicates that the samples can only be used for const data.. */ - - #ifndef DOXYGEN - //============================================================================== - class BigEndian - { - public: - template static inline float getAsFloat (SampleFormatType& s) noexcept { return s.getAsFloatBE(); } - template static inline void setAsFloat (SampleFormatType& s, float newValue) noexcept { s.setAsFloatBE (newValue); } - template static inline int32 getAsInt32 (SampleFormatType& s) noexcept { return s.getAsInt32BE(); } - template static inline void setAsInt32 (SampleFormatType& s, int32 newValue) noexcept { s.setAsInt32BE (newValue); } - template static inline void copyFrom (DestType& dest, SourceType& source) noexcept { dest.copyFromBE (source); } - enum { isBigEndian = 1 }; - }; - - class LittleEndian - { - public: - template static inline float getAsFloat (SampleFormatType& s) noexcept { return s.getAsFloatLE(); } - template static inline void setAsFloat (SampleFormatType& s, float newValue) noexcept { s.setAsFloatLE (newValue); } - template static inline int32 getAsInt32 (SampleFormatType& s) noexcept { return s.getAsInt32LE(); } - template static inline void setAsInt32 (SampleFormatType& s, int32 newValue) noexcept { s.setAsInt32LE (newValue); } - template static inline void copyFrom (DestType& dest, SourceType& source) noexcept { dest.copyFromLE (source); } - enum { isBigEndian = 0 }; - }; - - #if JUCE_BIG_ENDIAN - class NativeEndian : public BigEndian {}; - #else - class NativeEndian : public LittleEndian {}; - #endif - - //============================================================================== - class Int8 - { - public: - inline Int8 (void* d) noexcept : data (static_cast (d)) {} - - inline void advance() noexcept { ++data; } - inline void skip (int numSamples) noexcept { data += numSamples; } - inline float getAsFloatLE() const noexcept { return (float) (*data * (1.0 / (1.0 + (double) maxValue))); } - inline float getAsFloatBE() const noexcept { return getAsFloatLE(); } - inline void setAsFloatLE (float newValue) noexcept { *data = (int8) jlimit ((int) -maxValue, (int) maxValue, roundToInt (newValue * (1.0 + (double) maxValue))); } - inline void setAsFloatBE (float newValue) noexcept { setAsFloatLE (newValue); } - inline int32 getAsInt32LE() const noexcept { return (int) (*((uint8*) data) << 24); } - inline int32 getAsInt32BE() const noexcept { return getAsInt32LE(); } - inline void setAsInt32LE (int newValue) noexcept { *data = (int8) (newValue >> 24); } - inline void setAsInt32BE (int newValue) noexcept { setAsInt32LE (newValue); } - inline void clear() noexcept { *data = 0; } - inline void clearMultiple (int num) noexcept { zeromem (data, (size_t) (num * bytesPerSample)) ;} - template inline void copyFromLE (SourceType& source) noexcept { setAsInt32LE (source.getAsInt32()); } - template inline void copyFromBE (SourceType& source) noexcept { setAsInt32BE (source.getAsInt32()); } - inline void copyFromSameType (Int8& source) noexcept { *data = *source.data; } - - int8* data; - enum { bytesPerSample = 1, maxValue = 0x7f, resolution = (1 << 24), isFloat = 0 }; - }; - - class UInt8 - { - public: - inline UInt8 (void* d) noexcept : data (static_cast (d)) {} - - inline void advance() noexcept { ++data; } - inline void skip (int numSamples) noexcept { data += numSamples; } - inline float getAsFloatLE() const noexcept { return (float) ((*data - 128) * (1.0 / (1.0 + (double) maxValue))); } - inline float getAsFloatBE() const noexcept { return getAsFloatLE(); } - inline void setAsFloatLE (float newValue) noexcept { *data = (uint8) jlimit (0, 255, 128 + roundToInt (newValue * (1.0 + (double) maxValue))); } - inline void setAsFloatBE (float newValue) noexcept { setAsFloatLE (newValue); } - inline int32 getAsInt32LE() const noexcept { return (int) (((uint8) (*data - 128)) << 24); } - inline int32 getAsInt32BE() const noexcept { return getAsInt32LE(); } - inline void setAsInt32LE (int newValue) noexcept { *data = (uint8) (128 + (newValue >> 24)); } - inline void setAsInt32BE (int newValue) noexcept { setAsInt32LE (newValue); } - inline void clear() noexcept { *data = 128; } - inline void clearMultiple (int num) noexcept { memset (data, 128, (size_t) num) ;} - template inline void copyFromLE (SourceType& source) noexcept { setAsInt32LE (source.getAsInt32()); } - template inline void copyFromBE (SourceType& source) noexcept { setAsInt32BE (source.getAsInt32()); } - inline void copyFromSameType (UInt8& source) noexcept { *data = *source.data; } - - uint8* data; - enum { bytesPerSample = 1, maxValue = 0x7f, resolution = (1 << 24), isFloat = 0 }; - }; - - class Int16 - { - public: - inline Int16 (void* d) noexcept : data (static_cast (d)) {} - - inline void advance() noexcept { ++data; } - inline void skip (int numSamples) noexcept { data += numSamples; } - inline float getAsFloatLE() const noexcept { return (float) ((1.0 / (1.0 + (double) maxValue)) * (int16) ByteOrder::swapIfBigEndian (*data)); } - inline float getAsFloatBE() const noexcept { return (float) ((1.0 / (1.0 + (double) maxValue)) * (int16) ByteOrder::swapIfLittleEndian (*data)); } - inline void setAsFloatLE (float newValue) noexcept { *data = ByteOrder::swapIfBigEndian ((uint16) jlimit ((int) -maxValue, (int) maxValue, roundToInt (newValue * (1.0 + (double) maxValue)))); } - inline void setAsFloatBE (float newValue) noexcept { *data = ByteOrder::swapIfLittleEndian ((uint16) jlimit ((int) -maxValue, (int) maxValue, roundToInt (newValue * (1.0 + (double) maxValue)))); } - inline int32 getAsInt32LE() const noexcept { return (int32) (ByteOrder::swapIfBigEndian ((uint16) *data) << 16); } - inline int32 getAsInt32BE() const noexcept { return (int32) (ByteOrder::swapIfLittleEndian ((uint16) *data) << 16); } - inline void setAsInt32LE (int32 newValue) noexcept { *data = ByteOrder::swapIfBigEndian ((uint16) (newValue >> 16)); } - inline void setAsInt32BE (int32 newValue) noexcept { *data = ByteOrder::swapIfLittleEndian ((uint16) (newValue >> 16)); } - inline void clear() noexcept { *data = 0; } - inline void clearMultiple (int num) noexcept { zeromem (data, (size_t) (num * bytesPerSample)) ;} - template inline void copyFromLE (SourceType& source) noexcept { setAsInt32LE (source.getAsInt32()); } - template inline void copyFromBE (SourceType& source) noexcept { setAsInt32BE (source.getAsInt32()); } - inline void copyFromSameType (Int16& source) noexcept { *data = *source.data; } - - uint16* data; - enum { bytesPerSample = 2, maxValue = 0x7fff, resolution = (1 << 16), isFloat = 0 }; - }; - - class Int24 - { - public: - inline Int24 (void* d) noexcept : data (static_cast (d)) {} - - inline void advance() noexcept { data += 3; } - inline void skip (int numSamples) noexcept { data += 3 * numSamples; } - inline float getAsFloatLE() const noexcept { return (float) (ByteOrder::littleEndian24Bit (data) * (1.0 / (1.0 + (double) maxValue))); } - inline float getAsFloatBE() const noexcept { return (float) (ByteOrder::bigEndian24Bit (data) * (1.0 / (1.0 + (double) maxValue))); } - inline void setAsFloatLE (float newValue) noexcept { ByteOrder::littleEndian24BitToChars (jlimit ((int) -maxValue, (int) maxValue, roundToInt (newValue * (1.0 + (double) maxValue))), data); } - inline void setAsFloatBE (float newValue) noexcept { ByteOrder::bigEndian24BitToChars (jlimit ((int) -maxValue, (int) maxValue, roundToInt (newValue * (1.0 + (double) maxValue))), data); } - inline int32 getAsInt32LE() const noexcept { return (int32) (((unsigned int) ByteOrder::littleEndian24Bit (data)) << 8); } - inline int32 getAsInt32BE() const noexcept { return (int32) (((unsigned int) ByteOrder::bigEndian24Bit (data)) << 8); } - inline void setAsInt32LE (int32 newValue) noexcept { ByteOrder::littleEndian24BitToChars (newValue >> 8, data); } - inline void setAsInt32BE (int32 newValue) noexcept { ByteOrder::bigEndian24BitToChars (newValue >> 8, data); } - inline void clear() noexcept { data[0] = 0; data[1] = 0; data[2] = 0; } - inline void clearMultiple (int num) noexcept { zeromem (data, (size_t) (num * bytesPerSample)) ;} - template inline void copyFromLE (SourceType& source) noexcept { setAsInt32LE (source.getAsInt32()); } - template inline void copyFromBE (SourceType& source) noexcept { setAsInt32BE (source.getAsInt32()); } - inline void copyFromSameType (Int24& source) noexcept { data[0] = source.data[0]; data[1] = source.data[1]; data[2] = source.data[2]; } - - char* data; - enum { bytesPerSample = 3, maxValue = 0x7fffff, resolution = (1 << 8), isFloat = 0 }; - }; - - class Int32 - { - public: - inline Int32 (void* d) noexcept : data (static_cast (d)) {} - - inline void advance() noexcept { ++data; } - inline void skip (int numSamples) noexcept { data += numSamples; } - inline float getAsFloatLE() const noexcept { return (float) ((1.0 / (1.0 + (double) maxValue)) * (int32) ByteOrder::swapIfBigEndian (*data)); } - inline float getAsFloatBE() const noexcept { return (float) ((1.0 / (1.0 + (double) maxValue)) * (int32) ByteOrder::swapIfLittleEndian (*data)); } - inline void setAsFloatLE (float newValue) noexcept { *data = ByteOrder::swapIfBigEndian ((uint32) (int32) ((double) maxValue * jlimit (-1.0, 1.0, (double) newValue))); } - inline void setAsFloatBE (float newValue) noexcept { *data = ByteOrder::swapIfLittleEndian ((uint32) (int32) ((double) maxValue * jlimit (-1.0, 1.0, (double) newValue))); } - inline int32 getAsInt32LE() const noexcept { return (int32) ByteOrder::swapIfBigEndian (*data); } - inline int32 getAsInt32BE() const noexcept { return (int32) ByteOrder::swapIfLittleEndian (*data); } - inline void setAsInt32LE (int32 newValue) noexcept { *data = ByteOrder::swapIfBigEndian ((uint32) newValue); } - inline void setAsInt32BE (int32 newValue) noexcept { *data = ByteOrder::swapIfLittleEndian ((uint32) newValue); } - inline void clear() noexcept { *data = 0; } - inline void clearMultiple (int num) noexcept { zeromem (data, (size_t) (num * bytesPerSample)) ;} - template inline void copyFromLE (SourceType& source) noexcept { setAsInt32LE (source.getAsInt32()); } - template inline void copyFromBE (SourceType& source) noexcept { setAsInt32BE (source.getAsInt32()); } - inline void copyFromSameType (Int32& source) noexcept { *data = *source.data; } - - uint32* data; - enum { bytesPerSample = 4, maxValue = 0x7fffffff, resolution = 1, isFloat = 0 }; - }; - - /** A 32-bit integer type, of which only the bottom 24 bits are used. */ - class Int24in32 : public Int32 - { - public: - inline Int24in32 (void* d) noexcept : Int32 (d) {} - - inline float getAsFloatLE() const noexcept { return (float) ((1.0 / (1.0 + (double) maxValue)) * (int32) ByteOrder::swapIfBigEndian (*data)); } - inline float getAsFloatBE() const noexcept { return (float) ((1.0 / (1.0 + (double) maxValue)) * (int32) ByteOrder::swapIfLittleEndian (*data)); } - inline void setAsFloatLE (float newValue) noexcept { *data = ByteOrder::swapIfBigEndian ((uint32) ((double) maxValue * jlimit (-1.0, 1.0, (double) newValue))); } - inline void setAsFloatBE (float newValue) noexcept { *data = ByteOrder::swapIfLittleEndian ((uint32) ((double) maxValue * jlimit (-1.0, 1.0, (double) newValue))); } - inline int32 getAsInt32LE() const noexcept { return (int32) ByteOrder::swapIfBigEndian (*data) << 8; } - inline int32 getAsInt32BE() const noexcept { return (int32) ByteOrder::swapIfLittleEndian (*data) << 8; } - inline void setAsInt32LE (int32 newValue) noexcept { *data = ByteOrder::swapIfBigEndian ((uint32) newValue >> 8); } - inline void setAsInt32BE (int32 newValue) noexcept { *data = ByteOrder::swapIfLittleEndian ((uint32) newValue >> 8); } - template inline void copyFromLE (SourceType& source) noexcept { setAsInt32LE (source.getAsInt32()); } - template inline void copyFromBE (SourceType& source) noexcept { setAsInt32BE (source.getAsInt32()); } - inline void copyFromSameType (Int24in32& source) noexcept { *data = *source.data; } - - enum { bytesPerSample = 4, maxValue = 0x7fffff, resolution = (1 << 8), isFloat = 0 }; - }; - - class Float32 - { - public: - inline Float32 (void* d) noexcept : data (static_cast (d)) {} - - inline void advance() noexcept { ++data; } - inline void skip (int numSamples) noexcept { data += numSamples; } - #if JUCE_BIG_ENDIAN - inline float getAsFloatBE() const noexcept { return *data; } - inline void setAsFloatBE (float newValue) noexcept { *data = newValue; } - inline float getAsFloatLE() const noexcept { union { uint32 asInt; float asFloat; } n; n.asInt = ByteOrder::swap (*(uint32*) data); return n.asFloat; } - inline void setAsFloatLE (float newValue) noexcept { union { uint32 asInt; float asFloat; } n; n.asFloat = newValue; *(uint32*) data = ByteOrder::swap (n.asInt); } - #else - inline float getAsFloatLE() const noexcept { return *data; } - inline void setAsFloatLE (float newValue) noexcept { *data = newValue; } - inline float getAsFloatBE() const noexcept { union { uint32 asInt; float asFloat; } n; n.asInt = ByteOrder::swap (*(uint32*) data); return n.asFloat; } - inline void setAsFloatBE (float newValue) noexcept { union { uint32 asInt; float asFloat; } n; n.asFloat = newValue; *(uint32*) data = ByteOrder::swap (n.asInt); } - #endif - inline int32 getAsInt32LE() const noexcept { return (int32) roundToInt (jlimit (-1.0, 1.0, (double) getAsFloatLE()) * (double) maxValue); } - inline int32 getAsInt32BE() const noexcept { return (int32) roundToInt (jlimit (-1.0, 1.0, (double) getAsFloatBE()) * (double) maxValue); } - inline void setAsInt32LE (int32 newValue) noexcept { setAsFloatLE ((float) (newValue * (1.0 / (1.0 + (double) maxValue)))); } - inline void setAsInt32BE (int32 newValue) noexcept { setAsFloatBE ((float) (newValue * (1.0 / (1.0 + (double) maxValue)))); } - inline void clear() noexcept { *data = 0; } - inline void clearMultiple (int num) noexcept { zeromem (data, (size_t) (num * bytesPerSample)) ;} - template inline void copyFromLE (SourceType& source) noexcept { setAsFloatLE (source.getAsFloat()); } - template inline void copyFromBE (SourceType& source) noexcept { setAsFloatBE (source.getAsFloat()); } - inline void copyFromSameType (Float32& source) noexcept { *data = *source.data; } - - float* data; - enum { bytesPerSample = 4, maxValue = 0x7fffffff, resolution = (1 << 8), isFloat = 1 }; - }; - - //============================================================================== - class NonInterleaved - { - public: - inline NonInterleaved() = default; - inline NonInterleaved (const NonInterleaved&) = default; - inline NonInterleaved (const int) noexcept {} - inline void copyFrom (const NonInterleaved&) noexcept {} - template inline void advanceData (SampleFormatType& s) noexcept { s.advance(); } - template inline void advanceDataBy (SampleFormatType& s, int numSamples) noexcept { s.skip (numSamples); } - template inline void clear (SampleFormatType& s, int numSamples) noexcept { s.clearMultiple (numSamples); } - template inline static int getNumBytesBetweenSamples (const SampleFormatType&) noexcept { return SampleFormatType::bytesPerSample; } - - enum { isInterleavedType = 0, numInterleavedChannels = 1 }; - }; - - class Interleaved - { - public: - inline Interleaved() noexcept {} - inline Interleaved (const Interleaved& other) = default; - inline Interleaved (const int numInterleavedChans) noexcept : numInterleavedChannels (numInterleavedChans) {} - inline void copyFrom (const Interleaved& other) noexcept { numInterleavedChannels = other.numInterleavedChannels; } - template inline void advanceData (SampleFormatType& s) noexcept { s.skip (numInterleavedChannels); } - template inline void advanceDataBy (SampleFormatType& s, int numSamples) noexcept { s.skip (numInterleavedChannels * numSamples); } - template inline void clear (SampleFormatType& s, int numSamples) noexcept { while (--numSamples >= 0) { s.clear(); s.skip (numInterleavedChannels); } } - template inline int getNumBytesBetweenSamples (const SampleFormatType&) const noexcept { return numInterleavedChannels * SampleFormatType::bytesPerSample; } - int numInterleavedChannels = 1; - enum { isInterleavedType = 1 }; - }; - - //============================================================================== - class NonConst - { - public: - using VoidType = void; - static inline void* toVoidPtr (VoidType* v) noexcept { return v; } - enum { isConst = 0 }; - }; - - class Const - { - public: - using VoidType = const void; - static inline void* toVoidPtr (VoidType* v) noexcept { return const_cast (v); } - enum { isConst = 1 }; - }; - #endif - - //============================================================================== - /** - A pointer to a block of audio data with a particular encoding. - - This object can be used to read and write from blocks of encoded audio samples. To create one, you specify - the audio format as a series of template parameters, e.g. - @code - // this creates a pointer for reading from a const array of 16-bit little-endian packed samples. - AudioData::Pointer pointer (someRawAudioData); - - // These methods read the sample that is being pointed to - float firstSampleAsFloat = pointer.getAsFloat(); - int32 firstSampleAsInt = pointer.getAsInt32(); - ++pointer; // moves the pointer to the next sample. - pointer += 3; // skips the next 3 samples. - @endcode - - The convertSamples() method lets you copy a range of samples from one format to another, automatically - converting its format. - - @see AudioData::Converter - */ - template - class Pointer : private InterleavingType // (inherited for EBCO) - { - public: - //============================================================================== - /** Creates a non-interleaved pointer from some raw data in the appropriate format. - This constructor is only used if you've specified the AudioData::NonInterleaved option - - for interleaved formats, use the constructor that also takes a number of channels. - */ - Pointer (typename Constness::VoidType* sourceData) noexcept - : data (Constness::toVoidPtr (sourceData)) - { - // If you're using interleaved data, call the other constructor! If you're using non-interleaved data, - // you should pass NonInterleaved as the template parameter for the interleaving type! - static_assert (InterleavingType::isInterleavedType == 0, "Incorrect constructor for interleaved data"); - } - - /** Creates a pointer from some raw data in the appropriate format with the specified number of interleaved channels. - For non-interleaved data, use the other constructor. - */ - Pointer (typename Constness::VoidType* sourceData, int numInterleaved) noexcept - : InterleavingType (numInterleaved), data (Constness::toVoidPtr (sourceData)) - { - } - - /** Creates a copy of another pointer. */ - Pointer (const Pointer& other) noexcept - : InterleavingType (other), data (other.data) - { - } - - Pointer& operator= (const Pointer& other) noexcept - { - InterleavingType::operator= (other); - data = other.data; - return *this; - } - - //============================================================================== - /** Returns the value of the first sample as a floating point value. - The value will be in the range -1.0 to 1.0 for integer formats. For floating point - formats, the value could be outside that range, although -1 to 1 is the standard range. - */ - inline float getAsFloat() const noexcept { return Endianness::getAsFloat (data); } - - /** Sets the value of the first sample as a floating point value. - - (This method can only be used if the AudioData::NonConst option was used). - The value should be in the range -1.0 to 1.0 - for integer formats, values outside that - range will be clipped. For floating point formats, any value passed in here will be - written directly, although -1 to 1 is the standard range. - */ - inline void setAsFloat (float newValue) noexcept - { - // trying to write to a const pointer! For a writeable one, use AudioData::NonConst instead! - static_assert (Constness::isConst == 0, "Attempt to write to a const pointer"); - Endianness::setAsFloat (data, newValue); - } - - /** Returns the value of the first sample as a 32-bit integer. - The value returned will be in the range 0x80000000 to 0x7fffffff, and shorter values will be - shifted to fill this range (e.g. if you're reading from 24-bit data, the values will be shifted up - by 8 bits when returned here). If the source data is floating point, values beyond -1.0 to 1.0 will - be clipped so that -1.0 maps onto -0x7fffffff and 1.0 maps to 0x7fffffff. - */ - inline int32 getAsInt32() const noexcept { return Endianness::getAsInt32 (data); } - - /** Sets the value of the first sample as a 32-bit integer. - This will be mapped to the range of the format that is being written - see getAsInt32(). - */ - inline void setAsInt32 (int32 newValue) noexcept - { - // trying to write to a const pointer! For a writeable one, use AudioData::NonConst instead! - static_assert (Constness::isConst == 0, "Attempt to write to a const pointer"); - Endianness::setAsInt32 (data, newValue); - } - - /** Moves the pointer along to the next sample. */ - inline Pointer& operator++() noexcept { advance(); return *this; } - - /** Moves the pointer back to the previous sample. */ - inline Pointer& operator--() noexcept { this->advanceDataBy (data, -1); return *this; } - - /** Adds a number of samples to the pointer's position. */ - Pointer& operator+= (int samplesToJump) noexcept { this->advanceDataBy (data, samplesToJump); return *this; } - - /** Writes a stream of samples into this pointer from another pointer. - This will copy the specified number of samples, converting between formats appropriately. - */ - void convertSamples (Pointer source, int numSamples) const noexcept - { - // trying to write to a const pointer! For a writeable one, use AudioData::NonConst instead! - static_assert (Constness::isConst == 0, "Attempt to write to a const pointer"); - - for (Pointer dest (*this); --numSamples >= 0;) - { - dest.data.copyFromSameType (source.data); - dest.advance(); - source.advance(); - } - } - - /** Writes a stream of samples into this pointer from another pointer. - This will copy the specified number of samples, converting between formats appropriately. - */ - template - void convertSamples (OtherPointerType source, int numSamples) const noexcept - { - // trying to write to a const pointer! For a writeable one, use AudioData::NonConst instead! - static_assert (Constness::isConst == 0, "Attempt to write to a const pointer"); - - Pointer dest (*this); - - if (source.getRawData() != getRawData() || source.getNumBytesBetweenSamples() >= getNumBytesBetweenSamples()) - { - while (--numSamples >= 0) - { - Endianness::copyFrom (dest.data, source); - dest.advance(); - ++source; - } - } - else // copy backwards if we're increasing the sample width.. - { - dest += numSamples; - source += numSamples; - - while (--numSamples >= 0) - Endianness::copyFrom ((--dest).data, --source); - } - } - - /** Sets a number of samples to zero. */ - void clearSamples (int numSamples) const noexcept - { - Pointer dest (*this); - dest.clear (dest.data, numSamples); - } - - /** Scans a block of data, returning the lowest and highest levels as floats */ - Range findMinAndMax (size_t numSamples) const noexcept - { - if (numSamples == 0) - return Range(); - - Pointer dest (*this); - - if (isFloatingPoint()) - { - float mn = dest.getAsFloat(); - dest.advance(); - float mx = mn; - - while (--numSamples > 0) - { - const float v = dest.getAsFloat(); - dest.advance(); - - if (mx < v) mx = v; - if (v < mn) mn = v; - } - - return Range (mn, mx); - } - - int32 mn = dest.getAsInt32(); - dest.advance(); - int32 mx = mn; - - while (--numSamples > 0) - { - const int v = dest.getAsInt32(); - dest.advance(); - - if (mx < v) mx = v; - if (v < mn) mn = v; - } - - return Range (mn * (float) (1.0 / (1.0 + (double) Int32::maxValue)), - mx * (float) (1.0 / (1.0 + (double) Int32::maxValue))); - } - - /** Scans a block of data, returning the lowest and highest levels as floats */ - void findMinAndMax (size_t numSamples, float& minValue, float& maxValue) const noexcept - { - Range r (findMinAndMax (numSamples)); - minValue = r.getStart(); - maxValue = r.getEnd(); - } - - /** Returns true if the pointer is using a floating-point format. */ - static bool isFloatingPoint() noexcept { return (bool) SampleFormat::isFloat; } - - /** Returns true if the format is big-endian. */ - static bool isBigEndian() noexcept { return (bool) Endianness::isBigEndian; } - - /** Returns the number of bytes in each sample (ignoring the number of interleaved channels). */ - static int getBytesPerSample() noexcept { return (int) SampleFormat::bytesPerSample; } - - /** Returns the number of interleaved channels in the format. */ - int getNumInterleavedChannels() const noexcept { return (int) this->numInterleavedChannels; } - - /** Returns the number of bytes between the start address of each sample. */ - int getNumBytesBetweenSamples() const noexcept { return InterleavingType::getNumBytesBetweenSamples (data); } - - /** Returns the accuracy of this format when represented as a 32-bit integer. - This is the smallest number above 0 that can be represented in the sample format, converted to - a 32-bit range. E,g. if the format is 8-bit, its resolution is 0x01000000; if the format is 24-bit, - its resolution is 0x100. - */ - static int get32BitResolution() noexcept { return (int) SampleFormat::resolution; } - - /** Returns a pointer to the underlying data. */ - const void* getRawData() const noexcept { return data.data; } - - private: - //============================================================================== - SampleFormat data; - - inline void advance() noexcept { this->advanceData (data); } - - Pointer operator++ (int); // private to force you to use the more efficient pre-increment! - Pointer operator-- (int); - }; - - //============================================================================== - /** A base class for objects that are used to convert between two different sample formats. - - The AudioData::ConverterInstance implements this base class and can be templated, so - you can create an instance that converts between two particular formats, and then - store this in the abstract base class. - - @see AudioData::ConverterInstance - */ - class Converter - { - public: - virtual ~Converter() = default; - - /** Converts a sequence of samples from the converter's source format into the dest format. */ - virtual void convertSamples (void* destSamples, const void* sourceSamples, int numSamples) const = 0; - - /** Converts a sequence of samples from the converter's source format into the dest format. - This method takes sub-channel indexes, which can be used with interleaved formats in order to choose a - particular sub-channel of the data to be used. - */ - virtual void convertSamples (void* destSamples, int destSubChannel, - const void* sourceSamples, int sourceSubChannel, int numSamples) const = 0; - }; - - //============================================================================== - /** - A class that converts between two templated AudioData::Pointer types, and which - implements the AudioData::Converter interface. - - This can be used as a concrete instance of the AudioData::Converter abstract class. - - @see AudioData::Converter - */ - template - class ConverterInstance : public Converter - { - public: - ConverterInstance (int numSourceChannels = 1, int numDestChannels = 1) - : sourceChannels (numSourceChannels), destChannels (numDestChannels) - {} - - void convertSamples (void* dest, const void* source, int numSamples) const override - { - SourceSampleType s (source, sourceChannels); - DestSampleType d (dest, destChannels); - d.convertSamples (s, numSamples); - } - - void convertSamples (void* dest, int destSubChannel, - const void* source, int sourceSubChannel, int numSamples) const override - { - jassert (destSubChannel < destChannels && sourceSubChannel < sourceChannels); - - SourceSampleType s (addBytesToPointer (source, sourceSubChannel * SourceSampleType::getBytesPerSample()), sourceChannels); - DestSampleType d (addBytesToPointer (dest, destSubChannel * DestSampleType::getBytesPerSample()), destChannels); - d.convertSamples (s, numSamples); - } - - private: - JUCE_DECLARE_NON_COPYABLE (ConverterInstance) - - const int sourceChannels, destChannels; - }; -}; - - - -//============================================================================== -/** - A set of routines to convert buffers of 32-bit floating point data to and from - various integer formats. - - Note that these functions are deprecated - the AudioData class provides a much more - flexible set of conversion classes now. - - @tags{Audio} -*/ -class JUCE_API AudioDataConverters -{ -public: - //============================================================================== - static void convertFloatToInt16LE (const float* source, void* dest, int numSamples, int destBytesPerSample = 2); - static void convertFloatToInt16BE (const float* source, void* dest, int numSamples, int destBytesPerSample = 2); - - static void convertFloatToInt24LE (const float* source, void* dest, int numSamples, int destBytesPerSample = 3); - static void convertFloatToInt24BE (const float* source, void* dest, int numSamples, int destBytesPerSample = 3); - - static void convertFloatToInt32LE (const float* source, void* dest, int numSamples, int destBytesPerSample = 4); - static void convertFloatToInt32BE (const float* source, void* dest, int numSamples, int destBytesPerSample = 4); - - static void convertFloatToFloat32LE (const float* source, void* dest, int numSamples, int destBytesPerSample = 4); - static void convertFloatToFloat32BE (const float* source, void* dest, int numSamples, int destBytesPerSample = 4); - - //============================================================================== - static void convertInt16LEToFloat (const void* source, float* dest, int numSamples, int srcBytesPerSample = 2); - static void convertInt16BEToFloat (const void* source, float* dest, int numSamples, int srcBytesPerSample = 2); - - static void convertInt24LEToFloat (const void* source, float* dest, int numSamples, int srcBytesPerSample = 3); - static void convertInt24BEToFloat (const void* source, float* dest, int numSamples, int srcBytesPerSample = 3); - - static void convertInt32LEToFloat (const void* source, float* dest, int numSamples, int srcBytesPerSample = 4); - static void convertInt32BEToFloat (const void* source, float* dest, int numSamples, int srcBytesPerSample = 4); - - static void convertFloat32LEToFloat (const void* source, float* dest, int numSamples, int srcBytesPerSample = 4); - static void convertFloat32BEToFloat (const void* source, float* dest, int numSamples, int srcBytesPerSample = 4); - - //============================================================================== - enum DataFormat - { - int16LE, - int16BE, - int24LE, - int24BE, - int32LE, - int32BE, - float32LE, - float32BE, - }; - - static void convertFloatToFormat (DataFormat destFormat, - const float* source, void* dest, int numSamples); - - static void convertFormatToFloat (DataFormat sourceFormat, - const void* source, float* dest, int numSamples); - - //============================================================================== - static void interleaveSamples (const float** source, float* dest, - int numSamples, int numChannels); - - static void deinterleaveSamples (const float* source, float** dest, - int numSamples, int numChannels); - -private: - AudioDataConverters(); - JUCE_DECLARE_NON_COPYABLE (AudioDataConverters) -}; - -} // namespace juce +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2017 - ROLI Ltd. + + JUCE is an open source library subject to commercial or open-source + licensing. + + The code included in this file is provided under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license. Permission + To use, copy, modify, and/or distribute this software for any purpose with or + without fee is hereby granted provided that the above copyright notice and + this permission notice appear in all copies. + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +namespace juce +{ + +//============================================================================== +/** + This class a container which holds all the classes pertaining to the AudioData::Pointer + audio sample format class. + + @see AudioData::Pointer. + + @tags{Audio} +*/ +class JUCE_API AudioData +{ +public: + //============================================================================== + // These types can be used as the SampleFormat template parameter for the AudioData::Pointer class. + + class Int8; /**< Used as a template parameter for AudioData::Pointer. Indicates an 8-bit integer packed data format. */ + class UInt8; /**< Used as a template parameter for AudioData::Pointer. Indicates an 8-bit unsigned integer packed data format. */ + class Int16; /**< Used as a template parameter for AudioData::Pointer. Indicates an 16-bit integer packed data format. */ + class Int24; /**< Used as a template parameter for AudioData::Pointer. Indicates an 24-bit integer packed data format. */ + class Int32; /**< Used as a template parameter for AudioData::Pointer. Indicates an 32-bit integer packed data format. */ + class Float32; /**< Used as a template parameter for AudioData::Pointer. Indicates an 32-bit float data format. */ + + //============================================================================== + // These types can be used as the Endianness template parameter for the AudioData::Pointer class. + + class BigEndian; /**< Used as a template parameter for AudioData::Pointer. Indicates that the samples are stored in big-endian order. */ + class LittleEndian; /**< Used as a template parameter for AudioData::Pointer. Indicates that the samples are stored in little-endian order. */ + class NativeEndian; /**< Used as a template parameter for AudioData::Pointer. Indicates that the samples are stored in the CPU's native endianness. */ + + //============================================================================== + // These types can be used as the InterleavingType template parameter for the AudioData::Pointer class. + + class NonInterleaved; /**< Used as a template parameter for AudioData::Pointer. Indicates that the samples are stored contiguously. */ + class Interleaved; /**< Used as a template parameter for AudioData::Pointer. Indicates that the samples are interleaved with a number of other channels. */ + + //============================================================================== + // These types can be used as the Constness template parameter for the AudioData::Pointer class. + + class NonConst; /**< Used as a template parameter for AudioData::Pointer. Indicates that the pointer can be used for non-const data. */ + class Const; /**< Used as a template parameter for AudioData::Pointer. Indicates that the samples can only be used for const data.. */ + + #ifndef DOXYGEN + //============================================================================== + class BigEndian + { + public: + template static inline float getAsFloat (SampleFormatType& s) noexcept { return s.getAsFloatBE(); } + template static inline void setAsFloat (SampleFormatType& s, float newValue) noexcept { s.setAsFloatBE (newValue); } + template static inline int32 getAsInt32 (SampleFormatType& s) noexcept { return s.getAsInt32BE(); } + template static inline void setAsInt32 (SampleFormatType& s, int32 newValue) noexcept { s.setAsInt32BE (newValue); } + template static inline void copyFrom (DestType& dest, SourceType& source) noexcept { dest.copyFromBE (source); } + enum { isBigEndian = 1 }; + }; + + class LittleEndian + { + public: + template static inline float getAsFloat (SampleFormatType& s) noexcept { return s.getAsFloatLE(); } + template static inline void setAsFloat (SampleFormatType& s, float newValue) noexcept { s.setAsFloatLE (newValue); } + template static inline int32 getAsInt32 (SampleFormatType& s) noexcept { return s.getAsInt32LE(); } + template static inline void setAsInt32 (SampleFormatType& s, int32 newValue) noexcept { s.setAsInt32LE (newValue); } + template static inline void copyFrom (DestType& dest, SourceType& source) noexcept { dest.copyFromLE (source); } + enum { isBigEndian = 0 }; + }; + + #if JUCE_BIG_ENDIAN + class NativeEndian : public BigEndian {}; + #else + class NativeEndian : public LittleEndian {}; + #endif + + //============================================================================== + class Int8 + { + public: + inline Int8 (void* d) noexcept : data (static_cast (d)) {} + + inline void advance() noexcept { ++data; } + inline void skip (int numSamples) noexcept { data += numSamples; } + inline float getAsFloatLE() const noexcept { return (float) (*data * (1.0 / (1.0 + (double) maxValue))); } + inline float getAsFloatBE() const noexcept { return getAsFloatLE(); } + inline void setAsFloatLE (float newValue) noexcept { *data = (int8) jlimit ((int) -maxValue, (int) maxValue, roundToInt (newValue * (1.0 + (double) maxValue))); } + inline void setAsFloatBE (float newValue) noexcept { setAsFloatLE (newValue); } + inline int32 getAsInt32LE() const noexcept { return (int) (*((uint8*) data) << 24); } + inline int32 getAsInt32BE() const noexcept { return getAsInt32LE(); } + inline void setAsInt32LE (int newValue) noexcept { *data = (int8) (newValue >> 24); } + inline void setAsInt32BE (int newValue) noexcept { setAsInt32LE (newValue); } + inline void clear() noexcept { *data = 0; } + inline void clearMultiple (int num) noexcept { zeromem (data, (size_t) (num * bytesPerSample)) ;} + template inline void copyFromLE (SourceType& source) noexcept { setAsInt32LE (source.getAsInt32()); } + template inline void copyFromBE (SourceType& source) noexcept { setAsInt32BE (source.getAsInt32()); } + inline void copyFromSameType (Int8& source) noexcept { *data = *source.data; } + + int8* data; + enum { bytesPerSample = 1, maxValue = 0x7f, resolution = (1 << 24), isFloat = 0 }; + }; + + class UInt8 + { + public: + inline UInt8 (void* d) noexcept : data (static_cast (d)) {} + + inline void advance() noexcept { ++data; } + inline void skip (int numSamples) noexcept { data += numSamples; } + inline float getAsFloatLE() const noexcept { return (float) ((*data - 128) * (1.0 / (1.0 + (double) maxValue))); } + inline float getAsFloatBE() const noexcept { return getAsFloatLE(); } + inline void setAsFloatLE (float newValue) noexcept { *data = (uint8) jlimit (0, 255, 128 + roundToInt (newValue * (1.0 + (double) maxValue))); } + inline void setAsFloatBE (float newValue) noexcept { setAsFloatLE (newValue); } + inline int32 getAsInt32LE() const noexcept { return (int) (((uint8) (*data - 128)) << 24); } + inline int32 getAsInt32BE() const noexcept { return getAsInt32LE(); } + inline void setAsInt32LE (int newValue) noexcept { *data = (uint8) (128 + (newValue >> 24)); } + inline void setAsInt32BE (int newValue) noexcept { setAsInt32LE (newValue); } + inline void clear() noexcept { *data = 128; } + inline void clearMultiple (int num) noexcept { memset (data, 128, (size_t) num) ;} + template inline void copyFromLE (SourceType& source) noexcept { setAsInt32LE (source.getAsInt32()); } + template inline void copyFromBE (SourceType& source) noexcept { setAsInt32BE (source.getAsInt32()); } + inline void copyFromSameType (UInt8& source) noexcept { *data = *source.data; } + + uint8* data; + enum { bytesPerSample = 1, maxValue = 0x7f, resolution = (1 << 24), isFloat = 0 }; + }; + + class Int16 + { + public: + inline Int16 (void* d) noexcept : data (static_cast (d)) {} + + inline void advance() noexcept { ++data; } + inline void skip (int numSamples) noexcept { data += numSamples; } + inline float getAsFloatLE() const noexcept { return (float) ((1.0 / (1.0 + (double) maxValue)) * (int16) ByteOrder::swapIfBigEndian (*data)); } + inline float getAsFloatBE() const noexcept { return (float) ((1.0 / (1.0 + (double) maxValue)) * (int16) ByteOrder::swapIfLittleEndian (*data)); } + inline void setAsFloatLE (float newValue) noexcept { *data = ByteOrder::swapIfBigEndian ((uint16) jlimit ((int) -maxValue, (int) maxValue, roundToInt (newValue * (1.0 + (double) maxValue)))); } + inline void setAsFloatBE (float newValue) noexcept { *data = ByteOrder::swapIfLittleEndian ((uint16) jlimit ((int) -maxValue, (int) maxValue, roundToInt (newValue * (1.0 + (double) maxValue)))); } + inline int32 getAsInt32LE() const noexcept { return (int32) (ByteOrder::swapIfBigEndian ((uint16) *data) << 16); } + inline int32 getAsInt32BE() const noexcept { return (int32) (ByteOrder::swapIfLittleEndian ((uint16) *data) << 16); } + inline void setAsInt32LE (int32 newValue) noexcept { *data = ByteOrder::swapIfBigEndian ((uint16) (newValue >> 16)); } + inline void setAsInt32BE (int32 newValue) noexcept { *data = ByteOrder::swapIfLittleEndian ((uint16) (newValue >> 16)); } + inline void clear() noexcept { *data = 0; } + inline void clearMultiple (int num) noexcept { zeromem (data, (size_t) (num * bytesPerSample)) ;} + template inline void copyFromLE (SourceType& source) noexcept { setAsInt32LE (source.getAsInt32()); } + template inline void copyFromBE (SourceType& source) noexcept { setAsInt32BE (source.getAsInt32()); } + inline void copyFromSameType (Int16& source) noexcept { *data = *source.data; } + + uint16* data; + enum { bytesPerSample = 2, maxValue = 0x7fff, resolution = (1 << 16), isFloat = 0 }; + }; + + class Int24 + { + public: + inline Int24 (void* d) noexcept : data (static_cast (d)) {} + + inline void advance() noexcept { data += 3; } + inline void skip (int numSamples) noexcept { data += 3 * numSamples; } + inline float getAsFloatLE() const noexcept { return (float) (ByteOrder::littleEndian24Bit (data) * (1.0 / (1.0 + (double) maxValue))); } + inline float getAsFloatBE() const noexcept { return (float) (ByteOrder::bigEndian24Bit (data) * (1.0 / (1.0 + (double) maxValue))); } + inline void setAsFloatLE (float newValue) noexcept { ByteOrder::littleEndian24BitToChars (jlimit ((int) -maxValue, (int) maxValue, roundToInt (newValue * (1.0 + (double) maxValue))), data); } + inline void setAsFloatBE (float newValue) noexcept { ByteOrder::bigEndian24BitToChars (jlimit ((int) -maxValue, (int) maxValue, roundToInt (newValue * (1.0 + (double) maxValue))), data); } + inline int32 getAsInt32LE() const noexcept { return (int32) (((unsigned int) ByteOrder::littleEndian24Bit (data)) << 8); } + inline int32 getAsInt32BE() const noexcept { return (int32) (((unsigned int) ByteOrder::bigEndian24Bit (data)) << 8); } + inline void setAsInt32LE (int32 newValue) noexcept { ByteOrder::littleEndian24BitToChars (newValue >> 8, data); } + inline void setAsInt32BE (int32 newValue) noexcept { ByteOrder::bigEndian24BitToChars (newValue >> 8, data); } + inline void clear() noexcept { data[0] = 0; data[1] = 0; data[2] = 0; } + inline void clearMultiple (int num) noexcept { zeromem (data, (size_t) (num * bytesPerSample)) ;} + template inline void copyFromLE (SourceType& source) noexcept { setAsInt32LE (source.getAsInt32()); } + template inline void copyFromBE (SourceType& source) noexcept { setAsInt32BE (source.getAsInt32()); } + inline void copyFromSameType (Int24& source) noexcept { data[0] = source.data[0]; data[1] = source.data[1]; data[2] = source.data[2]; } + + char* data; + enum { bytesPerSample = 3, maxValue = 0x7fffff, resolution = (1 << 8), isFloat = 0 }; + }; + + class Int32 + { + public: + inline Int32 (void* d) noexcept : data (static_cast (d)) {} + + inline void advance() noexcept { ++data; } + inline void skip (int numSamples) noexcept { data += numSamples; } + inline float getAsFloatLE() const noexcept { return (float) ((1.0 / (1.0 + (double) maxValue)) * (int32) ByteOrder::swapIfBigEndian (*data)); } + inline float getAsFloatBE() const noexcept { return (float) ((1.0 / (1.0 + (double) maxValue)) * (int32) ByteOrder::swapIfLittleEndian (*data)); } + inline void setAsFloatLE (float newValue) noexcept { *data = ByteOrder::swapIfBigEndian ((uint32) (int32) ((double) maxValue * jlimit (-1.0, 1.0, (double) newValue))); } + inline void setAsFloatBE (float newValue) noexcept { *data = ByteOrder::swapIfLittleEndian ((uint32) (int32) ((double) maxValue * jlimit (-1.0, 1.0, (double) newValue))); } + inline int32 getAsInt32LE() const noexcept { return (int32) ByteOrder::swapIfBigEndian (*data); } + inline int32 getAsInt32BE() const noexcept { return (int32) ByteOrder::swapIfLittleEndian (*data); } + inline void setAsInt32LE (int32 newValue) noexcept { *data = ByteOrder::swapIfBigEndian ((uint32) newValue); } + inline void setAsInt32BE (int32 newValue) noexcept { *data = ByteOrder::swapIfLittleEndian ((uint32) newValue); } + inline void clear() noexcept { *data = 0; } + inline void clearMultiple (int num) noexcept { zeromem (data, (size_t) (num * bytesPerSample)) ;} + template inline void copyFromLE (SourceType& source) noexcept { setAsInt32LE (source.getAsInt32()); } + template inline void copyFromBE (SourceType& source) noexcept { setAsInt32BE (source.getAsInt32()); } + inline void copyFromSameType (Int32& source) noexcept { *data = *source.data; } + + uint32* data; + enum { bytesPerSample = 4, maxValue = 0x7fffffff, resolution = 1, isFloat = 0 }; + }; + + /** A 32-bit integer type, of which only the bottom 24 bits are used. */ + class Int24in32 : public Int32 + { + public: + inline Int24in32 (void* d) noexcept : Int32 (d) {} + + inline float getAsFloatLE() const noexcept { return (float) ((1.0 / (1.0 + (double) maxValue)) * (int32) ByteOrder::swapIfBigEndian (*data)); } + inline float getAsFloatBE() const noexcept { return (float) ((1.0 / (1.0 + (double) maxValue)) * (int32) ByteOrder::swapIfLittleEndian (*data)); } + inline void setAsFloatLE (float newValue) noexcept { *data = ByteOrder::swapIfBigEndian ((uint32) ((double) maxValue * jlimit (-1.0, 1.0, (double) newValue))); } + inline void setAsFloatBE (float newValue) noexcept { *data = ByteOrder::swapIfLittleEndian ((uint32) ((double) maxValue * jlimit (-1.0, 1.0, (double) newValue))); } + inline int32 getAsInt32LE() const noexcept { return (int32) ByteOrder::swapIfBigEndian (*data) << 8; } + inline int32 getAsInt32BE() const noexcept { return (int32) ByteOrder::swapIfLittleEndian (*data) << 8; } + inline void setAsInt32LE (int32 newValue) noexcept { *data = ByteOrder::swapIfBigEndian ((uint32) newValue >> 8); } + inline void setAsInt32BE (int32 newValue) noexcept { *data = ByteOrder::swapIfLittleEndian ((uint32) newValue >> 8); } + template inline void copyFromLE (SourceType& source) noexcept { setAsInt32LE (source.getAsInt32()); } + template inline void copyFromBE (SourceType& source) noexcept { setAsInt32BE (source.getAsInt32()); } + inline void copyFromSameType (Int24in32& source) noexcept { *data = *source.data; } + + enum { bytesPerSample = 4, maxValue = 0x7fffff, resolution = (1 << 8), isFloat = 0 }; + }; + + class Float32 + { + public: + inline Float32 (void* d) noexcept : data (static_cast (d)) {} + + inline void advance() noexcept { ++data; } + inline void skip (int numSamples) noexcept { data += numSamples; } + #if JUCE_BIG_ENDIAN + inline float getAsFloatBE() const noexcept { return *data; } + inline void setAsFloatBE (float newValue) noexcept { *data = newValue; } + inline float getAsFloatLE() const noexcept { union { uint32 asInt; float asFloat; } n; n.asInt = ByteOrder::swap (*(uint32*) data); return n.asFloat; } + inline void setAsFloatLE (float newValue) noexcept { union { uint32 asInt; float asFloat; } n; n.asFloat = newValue; *(uint32*) data = ByteOrder::swap (n.asInt); } + #else + inline float getAsFloatLE() const noexcept { return *data; } + inline void setAsFloatLE (float newValue) noexcept { *data = newValue; } + inline float getAsFloatBE() const noexcept { union { uint32 asInt; float asFloat; } n; n.asInt = ByteOrder::swap (*(uint32*) data); return n.asFloat; } + inline void setAsFloatBE (float newValue) noexcept { union { uint32 asInt; float asFloat; } n; n.asFloat = newValue; *(uint32*) data = ByteOrder::swap (n.asInt); } + #endif + inline int32 getAsInt32LE() const noexcept { return (int32) roundToInt (jlimit (-1.0, 1.0, (double) getAsFloatLE()) * (double) maxValue); } + inline int32 getAsInt32BE() const noexcept { return (int32) roundToInt (jlimit (-1.0, 1.0, (double) getAsFloatBE()) * (double) maxValue); } + inline void setAsInt32LE (int32 newValue) noexcept { setAsFloatLE ((float) (newValue * (1.0 / (1.0 + (double) maxValue)))); } + inline void setAsInt32BE (int32 newValue) noexcept { setAsFloatBE ((float) (newValue * (1.0 / (1.0 + (double) maxValue)))); } + inline void clear() noexcept { *data = 0; } + inline void clearMultiple (int num) noexcept { zeromem (data, (size_t) (num * bytesPerSample)) ;} + template inline void copyFromLE (SourceType& source) noexcept { setAsFloatLE (source.getAsFloat()); } + template inline void copyFromBE (SourceType& source) noexcept { setAsFloatBE (source.getAsFloat()); } + inline void copyFromSameType (Float32& source) noexcept { *data = *source.data; } + + float* data; + enum { bytesPerSample = 4, maxValue = 0x7fffffff, resolution = (1 << 8), isFloat = 1 }; + }; + + //============================================================================== + class NonInterleaved + { + public: + inline NonInterleaved() = default; + inline NonInterleaved (const NonInterleaved&) = default; + inline NonInterleaved (const int) noexcept {} + inline void copyFrom (const NonInterleaved&) noexcept {} + template inline void advanceData (SampleFormatType& s) noexcept { s.advance(); } + template inline void advanceDataBy (SampleFormatType& s, int numSamples) noexcept { s.skip (numSamples); } + template inline void clear (SampleFormatType& s, int numSamples) noexcept { s.clearMultiple (numSamples); } + template inline static int getNumBytesBetweenSamples (const SampleFormatType&) noexcept { return SampleFormatType::bytesPerSample; } + + enum { isInterleavedType = 0, numInterleavedChannels = 1 }; + }; + + class Interleaved + { + public: + inline Interleaved() noexcept {} + inline Interleaved (const Interleaved& other) = default; + inline Interleaved (const int numInterleavedChans) noexcept : numInterleavedChannels (numInterleavedChans) {} + inline void copyFrom (const Interleaved& other) noexcept { numInterleavedChannels = other.numInterleavedChannels; } + template inline void advanceData (SampleFormatType& s) noexcept { s.skip (numInterleavedChannels); } + template inline void advanceDataBy (SampleFormatType& s, int numSamples) noexcept { s.skip (numInterleavedChannels * numSamples); } + template inline void clear (SampleFormatType& s, int numSamples) noexcept { while (--numSamples >= 0) { s.clear(); s.skip (numInterleavedChannels); } } + template inline int getNumBytesBetweenSamples (const SampleFormatType&) const noexcept { return numInterleavedChannels * SampleFormatType::bytesPerSample; } + int numInterleavedChannels = 1; + enum { isInterleavedType = 1 }; + }; + + //============================================================================== + class NonConst + { + public: + using VoidType = void; + static inline void* toVoidPtr (VoidType* v) noexcept { return v; } + enum { isConst = 0 }; + }; + + class Const + { + public: + using VoidType = const void; + static inline void* toVoidPtr (VoidType* v) noexcept { return const_cast (v); } + enum { isConst = 1 }; + }; + #endif + + //============================================================================== + /** + A pointer to a block of audio data with a particular encoding. + + This object can be used to read and write from blocks of encoded audio samples. To create one, you specify + the audio format as a series of template parameters, e.g. + @code + // this creates a pointer for reading from a const array of 16-bit little-endian packed samples. + AudioData::Pointer pointer (someRawAudioData); + + // These methods read the sample that is being pointed to + float firstSampleAsFloat = pointer.getAsFloat(); + int32 firstSampleAsInt = pointer.getAsInt32(); + ++pointer; // moves the pointer to the next sample. + pointer += 3; // skips the next 3 samples. + @endcode + + The convertSamples() method lets you copy a range of samples from one format to another, automatically + converting its format. + + @see AudioData::Converter + */ + template + class Pointer : private InterleavingType // (inherited for EBCO) + { + public: + //============================================================================== + /** Creates a non-interleaved pointer from some raw data in the appropriate format. + This constructor is only used if you've specified the AudioData::NonInterleaved option - + for interleaved formats, use the constructor that also takes a number of channels. + */ + Pointer (typename Constness::VoidType* sourceData) noexcept + : data (Constness::toVoidPtr (sourceData)) + { + // If you're using interleaved data, call the other constructor! If you're using non-interleaved data, + // you should pass NonInterleaved as the template parameter for the interleaving type! + static_assert (InterleavingType::isInterleavedType == 0, "Incorrect constructor for interleaved data"); + } + + /** Creates a pointer from some raw data in the appropriate format with the specified number of interleaved channels. + For non-interleaved data, use the other constructor. + */ + Pointer (typename Constness::VoidType* sourceData, int numInterleaved) noexcept + : InterleavingType (numInterleaved), data (Constness::toVoidPtr (sourceData)) + { + } + + /** Creates a copy of another pointer. */ + Pointer (const Pointer& other) noexcept + : InterleavingType (other), data (other.data) + { + } + + Pointer& operator= (const Pointer& other) noexcept + { + InterleavingType::operator= (other); + data = other.data; + return *this; + } + + //============================================================================== + /** Returns the value of the first sample as a floating point value. + The value will be in the range -1.0 to 1.0 for integer formats. For floating point + formats, the value could be outside that range, although -1 to 1 is the standard range. + */ + inline float getAsFloat() const noexcept { return Endianness::getAsFloat (data); } + + /** Sets the value of the first sample as a floating point value. + + (This method can only be used if the AudioData::NonConst option was used). + The value should be in the range -1.0 to 1.0 - for integer formats, values outside that + range will be clipped. For floating point formats, any value passed in here will be + written directly, although -1 to 1 is the standard range. + */ + inline void setAsFloat (float newValue) noexcept + { + // trying to write to a const pointer! For a writeable one, use AudioData::NonConst instead! + static_assert (Constness::isConst == 0, "Attempt to write to a const pointer"); + Endianness::setAsFloat (data, newValue); + } + + /** Returns the value of the first sample as a 32-bit integer. + The value returned will be in the range 0x80000000 to 0x7fffffff, and shorter values will be + shifted to fill this range (e.g. if you're reading from 24-bit data, the values will be shifted up + by 8 bits when returned here). If the source data is floating point, values beyond -1.0 to 1.0 will + be clipped so that -1.0 maps onto -0x7fffffff and 1.0 maps to 0x7fffffff. + */ + inline int32 getAsInt32() const noexcept { return Endianness::getAsInt32 (data); } + + /** Sets the value of the first sample as a 32-bit integer. + This will be mapped to the range of the format that is being written - see getAsInt32(). + */ + inline void setAsInt32 (int32 newValue) noexcept + { + // trying to write to a const pointer! For a writeable one, use AudioData::NonConst instead! + static_assert (Constness::isConst == 0, "Attempt to write to a const pointer"); + Endianness::setAsInt32 (data, newValue); + } + + /** Moves the pointer along to the next sample. */ + inline Pointer& operator++() noexcept { advance(); return *this; } + + /** Moves the pointer back to the previous sample. */ + inline Pointer& operator--() noexcept { this->advanceDataBy (data, -1); return *this; } + + /** Adds a number of samples to the pointer's position. */ + Pointer& operator+= (int samplesToJump) noexcept { this->advanceDataBy (data, samplesToJump); return *this; } + + /** Writes a stream of samples into this pointer from another pointer. + This will copy the specified number of samples, converting between formats appropriately. + */ + void convertSamples (Pointer source, int numSamples) const noexcept + { + // trying to write to a const pointer! For a writeable one, use AudioData::NonConst instead! + static_assert (Constness::isConst == 0, "Attempt to write to a const pointer"); + + for (Pointer dest (*this); --numSamples >= 0;) + { + dest.data.copyFromSameType (source.data); + dest.advance(); + source.advance(); + } + } + + /** Writes a stream of samples into this pointer from another pointer. + This will copy the specified number of samples, converting between formats appropriately. + */ + template + void convertSamples (OtherPointerType source, int numSamples) const noexcept + { + // trying to write to a const pointer! For a writeable one, use AudioData::NonConst instead! + static_assert (Constness::isConst == 0, "Attempt to write to a const pointer"); + + Pointer dest (*this); + + if (source.getRawData() != getRawData() || source.getNumBytesBetweenSamples() >= getNumBytesBetweenSamples()) + { + while (--numSamples >= 0) + { + Endianness::copyFrom (dest.data, source); + dest.advance(); + ++source; + } + } + else // copy backwards if we're increasing the sample width.. + { + dest += numSamples; + source += numSamples; + + while (--numSamples >= 0) + Endianness::copyFrom ((--dest).data, --source); + } + } + + /** Sets a number of samples to zero. */ + void clearSamples (int numSamples) const noexcept + { + Pointer dest (*this); + dest.clear (dest.data, numSamples); + } + + /** Scans a block of data, returning the lowest and highest levels as floats */ + Range findMinAndMax (size_t numSamples) const noexcept + { + if (numSamples == 0) + return Range(); + + Pointer dest (*this); + + if (isFloatingPoint()) + { + float mn = dest.getAsFloat(); + dest.advance(); + float mx = mn; + + while (--numSamples > 0) + { + const float v = dest.getAsFloat(); + dest.advance(); + + if (mx < v) mx = v; + if (v < mn) mn = v; + } + + return Range (mn, mx); + } + + int32 mn = dest.getAsInt32(); + dest.advance(); + int32 mx = mn; + + while (--numSamples > 0) + { + const int v = dest.getAsInt32(); + dest.advance(); + + if (mx < v) mx = v; + if (v < mn) mn = v; + } + + return Range (mn * (float) (1.0 / (1.0 + (double) Int32::maxValue)), + mx * (float) (1.0 / (1.0 + (double) Int32::maxValue))); + } + + /** Scans a block of data, returning the lowest and highest levels as floats */ + void findMinAndMax (size_t numSamples, float& minValue, float& maxValue) const noexcept + { + Range r (findMinAndMax (numSamples)); + minValue = r.getStart(); + maxValue = r.getEnd(); + } + + /** Returns true if the pointer is using a floating-point format. */ + static bool isFloatingPoint() noexcept { return (bool) SampleFormat::isFloat; } + + /** Returns true if the format is big-endian. */ + static bool isBigEndian() noexcept { return (bool) Endianness::isBigEndian; } + + /** Returns the number of bytes in each sample (ignoring the number of interleaved channels). */ + static int getBytesPerSample() noexcept { return (int) SampleFormat::bytesPerSample; } + + /** Returns the number of interleaved channels in the format. */ + int getNumInterleavedChannels() const noexcept { return (int) this->numInterleavedChannels; } + + /** Returns the number of bytes between the start address of each sample. */ + int getNumBytesBetweenSamples() const noexcept { return InterleavingType::getNumBytesBetweenSamples (data); } + + /** Returns the accuracy of this format when represented as a 32-bit integer. + This is the smallest number above 0 that can be represented in the sample format, converted to + a 32-bit range. E,g. if the format is 8-bit, its resolution is 0x01000000; if the format is 24-bit, + its resolution is 0x100. + */ + static int get32BitResolution() noexcept { return (int) SampleFormat::resolution; } + + /** Returns a pointer to the underlying data. */ + const void* getRawData() const noexcept { return data.data; } + + private: + //============================================================================== + SampleFormat data; + + inline void advance() noexcept { this->advanceData (data); } + + Pointer operator++ (int); // private to force you to use the more efficient pre-increment! + Pointer operator-- (int); + }; + + //============================================================================== + /** A base class for objects that are used to convert between two different sample formats. + + The AudioData::ConverterInstance implements this base class and can be templated, so + you can create an instance that converts between two particular formats, and then + store this in the abstract base class. + + @see AudioData::ConverterInstance + */ + class Converter + { + public: + virtual ~Converter() = default; + + /** Converts a sequence of samples from the converter's source format into the dest format. */ + virtual void convertSamples (void* destSamples, const void* sourceSamples, int numSamples) const = 0; + + /** Converts a sequence of samples from the converter's source format into the dest format. + This method takes sub-channel indexes, which can be used with interleaved formats in order to choose a + particular sub-channel of the data to be used. + */ + virtual void convertSamples (void* destSamples, int destSubChannel, + const void* sourceSamples, int sourceSubChannel, int numSamples) const = 0; + }; + + //============================================================================== + /** + A class that converts between two templated AudioData::Pointer types, and which + implements the AudioData::Converter interface. + + This can be used as a concrete instance of the AudioData::Converter abstract class. + + @see AudioData::Converter + */ + template + class ConverterInstance : public Converter + { + public: + ConverterInstance (int numSourceChannels = 1, int numDestChannels = 1) + : sourceChannels (numSourceChannels), destChannels (numDestChannels) + {} + + void convertSamples (void* dest, const void* source, int numSamples) const override + { + SourceSampleType s (source, sourceChannels); + DestSampleType d (dest, destChannels); + d.convertSamples (s, numSamples); + } + + void convertSamples (void* dest, int destSubChannel, + const void* source, int sourceSubChannel, int numSamples) const override + { + jassert (destSubChannel < destChannels && sourceSubChannel < sourceChannels); + + SourceSampleType s (addBytesToPointer (source, sourceSubChannel * SourceSampleType::getBytesPerSample()), sourceChannels); + DestSampleType d (addBytesToPointer (dest, destSubChannel * DestSampleType::getBytesPerSample()), destChannels); + d.convertSamples (s, numSamples); + } + + private: + JUCE_DECLARE_NON_COPYABLE (ConverterInstance) + + const int sourceChannels, destChannels; + }; +}; + + + +//============================================================================== +/** + A set of routines to convert buffers of 32-bit floating point data to and from + various integer formats. + + Note that these functions are deprecated - the AudioData class provides a much more + flexible set of conversion classes now. + + @tags{Audio} +*/ +class JUCE_API AudioDataConverters +{ +public: + //============================================================================== + static void convertFloatToInt16LE (const float* source, void* dest, int numSamples, int destBytesPerSample = 2); + static void convertFloatToInt16BE (const float* source, void* dest, int numSamples, int destBytesPerSample = 2); + + static void convertFloatToInt24LE (const float* source, void* dest, int numSamples, int destBytesPerSample = 3); + static void convertFloatToInt24BE (const float* source, void* dest, int numSamples, int destBytesPerSample = 3); + + static void convertFloatToInt32LE (const float* source, void* dest, int numSamples, int destBytesPerSample = 4); + static void convertFloatToInt32BE (const float* source, void* dest, int numSamples, int destBytesPerSample = 4); + + static void convertFloatToFloat32LE (const float* source, void* dest, int numSamples, int destBytesPerSample = 4); + static void convertFloatToFloat32BE (const float* source, void* dest, int numSamples, int destBytesPerSample = 4); + + //============================================================================== + static void convertInt16LEToFloat (const void* source, float* dest, int numSamples, int srcBytesPerSample = 2); + static void convertInt16BEToFloat (const void* source, float* dest, int numSamples, int srcBytesPerSample = 2); + + static void convertInt24LEToFloat (const void* source, float* dest, int numSamples, int srcBytesPerSample = 3); + static void convertInt24BEToFloat (const void* source, float* dest, int numSamples, int srcBytesPerSample = 3); + + static void convertInt32LEToFloat (const void* source, float* dest, int numSamples, int srcBytesPerSample = 4); + static void convertInt32BEToFloat (const void* source, float* dest, int numSamples, int srcBytesPerSample = 4); + + static void convertFloat32LEToFloat (const void* source, float* dest, int numSamples, int srcBytesPerSample = 4); + static void convertFloat32BEToFloat (const void* source, float* dest, int numSamples, int srcBytesPerSample = 4); + + //============================================================================== + enum DataFormat + { + int16LE, + int16BE, + int24LE, + int24BE, + int32LE, + int32BE, + float32LE, + float32BE, + }; + + static void convertFloatToFormat (DataFormat destFormat, + const float* source, void* dest, int numSamples); + + static void convertFormatToFloat (DataFormat sourceFormat, + const void* source, float* dest, int numSamples); + + //============================================================================== + static void interleaveSamples (const float** source, float* dest, + int numSamples, int numChannels); + + static void deinterleaveSamples (const float* source, float** dest, + int numSamples, int numChannels); + +private: + AudioDataConverters(); + JUCE_DECLARE_NON_COPYABLE (AudioDataConverters) +}; + +} // namespace juce diff --git a/JuceLibraryCode/modules/juce_audio_basics/buffers/juce_AudioProcessLoadMeasurer.cpp b/JuceLibraryCode/modules/juce_audio_basics/buffers/juce_AudioProcessLoadMeasurer.cpp index 1795949..1e6c91f 100644 --- a/JuceLibraryCode/modules/juce_audio_basics/buffers/juce_AudioProcessLoadMeasurer.cpp +++ b/JuceLibraryCode/modules/juce_audio_basics/buffers/juce_AudioProcessLoadMeasurer.cpp @@ -1,79 +1,79 @@ -/* - ============================================================================== - - This file is part of the JUCE library. - Copyright (c) 2017 - ROLI Ltd. - - JUCE is an open source library subject to commercial or open-source - licensing. - - By using JUCE, you agree to the terms of both the JUCE 5 End-User License - Agreement and JUCE 5 Privacy Policy (both updated and effective as of the - 27th April 2017). - - End User License Agreement: www.juce.com/juce-5-licence - Privacy Policy: www.juce.com/juce-5-privacy-policy - - Or: You may also use this code under the terms of the GPL v3 (see - www.gnu.org/licenses). - - JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER - EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE - DISCLAIMED. - - ============================================================================== -*/ - -namespace juce -{ - -AudioProcessLoadMeasurer::AudioProcessLoadMeasurer() {} -AudioProcessLoadMeasurer::~AudioProcessLoadMeasurer() {} - -void AudioProcessLoadMeasurer::reset() -{ - reset (0, 0); -} - -void AudioProcessLoadMeasurer::reset (double sampleRate, int blockSize) -{ - cpuUsageMs = 0; - xruns = 0; - - if (sampleRate > 0.0 && blockSize > 0) - { - msPerBlock = 1000.0 * blockSize / sampleRate; - timeToCpuScale = (msPerBlock > 0.0) ? (1.0 / msPerBlock) : 0.0; - } - else - { - msPerBlock = 0; - timeToCpuScale = 0; - } -} - -void AudioProcessLoadMeasurer::registerBlockRenderTime (double milliseconds) -{ - const double filterAmount = 0.2; - cpuUsageMs += filterAmount * (milliseconds - cpuUsageMs); - - if (milliseconds > msPerBlock) - ++xruns; -} - -double AudioProcessLoadMeasurer::getLoadAsProportion() const { return jlimit (0.0, 1.0, timeToCpuScale * cpuUsageMs); } -double AudioProcessLoadMeasurer::getLoadAsPercentage() const { return 100.0 * getLoadAsProportion(); } - -int AudioProcessLoadMeasurer::getXRunCount() const { return xruns; } - -AudioProcessLoadMeasurer::ScopedTimer::ScopedTimer (AudioProcessLoadMeasurer& p) - : owner (p), startTime (Time::getMillisecondCounterHiRes()) -{ -} - -AudioProcessLoadMeasurer::ScopedTimer::~ScopedTimer() -{ - owner.registerBlockRenderTime (Time::getMillisecondCounterHiRes() - startTime); -} - -} // namespace juce +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2017 - ROLI Ltd. + + JUCE is an open source library subject to commercial or open-source + licensing. + + By using JUCE, you agree to the terms of both the JUCE 5 End-User License + Agreement and JUCE 5 Privacy Policy (both updated and effective as of the + 27th April 2017). + + End User License Agreement: www.juce.com/juce-5-licence + Privacy Policy: www.juce.com/juce-5-privacy-policy + + Or: You may also use this code under the terms of the GPL v3 (see + www.gnu.org/licenses). + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +namespace juce +{ + +AudioProcessLoadMeasurer::AudioProcessLoadMeasurer() {} +AudioProcessLoadMeasurer::~AudioProcessLoadMeasurer() {} + +void AudioProcessLoadMeasurer::reset() +{ + reset (0, 0); +} + +void AudioProcessLoadMeasurer::reset (double sampleRate, int blockSize) +{ + cpuUsageMs = 0; + xruns = 0; + + if (sampleRate > 0.0 && blockSize > 0) + { + msPerBlock = 1000.0 * blockSize / sampleRate; + timeToCpuScale = (msPerBlock > 0.0) ? (1.0 / msPerBlock) : 0.0; + } + else + { + msPerBlock = 0; + timeToCpuScale = 0; + } +} + +void AudioProcessLoadMeasurer::registerBlockRenderTime (double milliseconds) +{ + const double filterAmount = 0.2; + cpuUsageMs += filterAmount * (milliseconds - cpuUsageMs); + + if (milliseconds > msPerBlock) + ++xruns; +} + +double AudioProcessLoadMeasurer::getLoadAsProportion() const { return jlimit (0.0, 1.0, timeToCpuScale * cpuUsageMs); } +double AudioProcessLoadMeasurer::getLoadAsPercentage() const { return 100.0 * getLoadAsProportion(); } + +int AudioProcessLoadMeasurer::getXRunCount() const { return xruns; } + +AudioProcessLoadMeasurer::ScopedTimer::ScopedTimer (AudioProcessLoadMeasurer& p) + : owner (p), startTime (Time::getMillisecondCounterHiRes()) +{ +} + +AudioProcessLoadMeasurer::ScopedTimer::~ScopedTimer() +{ + owner.registerBlockRenderTime (Time::getMillisecondCounterHiRes() - startTime); +} + +} // namespace juce diff --git a/JuceLibraryCode/modules/juce_audio_basics/buffers/juce_AudioProcessLoadMeasurer.h b/JuceLibraryCode/modules/juce_audio_basics/buffers/juce_AudioProcessLoadMeasurer.h index 8cf5970..7c1ec17 100644 --- a/JuceLibraryCode/modules/juce_audio_basics/buffers/juce_AudioProcessLoadMeasurer.h +++ b/JuceLibraryCode/modules/juce_audio_basics/buffers/juce_AudioProcessLoadMeasurer.h @@ -1,100 +1,100 @@ -/* - ============================================================================== - - This file is part of the JUCE library. - Copyright (c) 2017 - ROLI Ltd. - - JUCE is an open source library subject to commercial or open-source - licensing. - - By using JUCE, you agree to the terms of both the JUCE 5 End-User License - Agreement and JUCE 5 Privacy Policy (both updated and effective as of the - 27th April 2017). - - End User License Agreement: www.juce.com/juce-5-licence - Privacy Policy: www.juce.com/juce-5-privacy-policy - - Or: You may also use this code under the terms of the GPL v3 (see - www.gnu.org/licenses). - - JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER - EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE - DISCLAIMED. - - ============================================================================== -*/ - -namespace juce -{ - -//============================================================================== -/** - Maintains an ongoing measurement of the proportion of time which is being - spent inside an audio callback. - - @tags{Audio} -*/ -class JUCE_API AudioProcessLoadMeasurer -{ -public: - /** */ - AudioProcessLoadMeasurer(); - - /** Destructor. */ - ~AudioProcessLoadMeasurer(); - - //============================================================================== - /** Resets the state. */ - void reset(); - - /** Resets the counter, in preparation for use with the given sample rate and block size. */ - void reset (double sampleRate, int blockSize); - - /** Returns the current load as a proportion 0 to 1.0 */ - double getLoadAsProportion() const; - - /** Returns the current load as a percentage 0 to 100.0 */ - double getLoadAsPercentage() const; - - /** Returns the number of over- (or under-) runs recorded since the state was reset. */ - int getXRunCount() const; - - //============================================================================== - /** This class measures the time between its construction and destruction and - adds it to an AudioProcessLoadMeasurer. - - e.g. - @code - { - AudioProcessLoadMeasurer::ScopedTimer timer (myProcessLoadMeasurer); - myCallback->doTheCallback(); - } - @endcode - - @tags{Audio} - */ - struct JUCE_API ScopedTimer - { - ScopedTimer (AudioProcessLoadMeasurer&); - ~ScopedTimer(); - - private: - AudioProcessLoadMeasurer& owner; - double startTime; - - JUCE_DECLARE_NON_COPYABLE (ScopedTimer) - }; - - /** Can be called manually to add the time of a callback to the stats. - Normally you probably would never call this - it's simpler and more robust to - use a ScopedTimer to measure the time using an RAII pattern. - */ - void registerBlockRenderTime (double millisecondsTaken); - -private: - double cpuUsageMs = 0, timeToCpuScale = 0, msPerBlock = 0; - int xruns = 0; -}; - - -} // namespace juce +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2017 - ROLI Ltd. + + JUCE is an open source library subject to commercial or open-source + licensing. + + By using JUCE, you agree to the terms of both the JUCE 5 End-User License + Agreement and JUCE 5 Privacy Policy (both updated and effective as of the + 27th April 2017). + + End User License Agreement: www.juce.com/juce-5-licence + Privacy Policy: www.juce.com/juce-5-privacy-policy + + Or: You may also use this code under the terms of the GPL v3 (see + www.gnu.org/licenses). + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +namespace juce +{ + +//============================================================================== +/** + Maintains an ongoing measurement of the proportion of time which is being + spent inside an audio callback. + + @tags{Audio} +*/ +class JUCE_API AudioProcessLoadMeasurer +{ +public: + /** */ + AudioProcessLoadMeasurer(); + + /** Destructor. */ + ~AudioProcessLoadMeasurer(); + + //============================================================================== + /** Resets the state. */ + void reset(); + + /** Resets the counter, in preparation for use with the given sample rate and block size. */ + void reset (double sampleRate, int blockSize); + + /** Returns the current load as a proportion 0 to 1.0 */ + double getLoadAsProportion() const; + + /** Returns the current load as a percentage 0 to 100.0 */ + double getLoadAsPercentage() const; + + /** Returns the number of over- (or under-) runs recorded since the state was reset. */ + int getXRunCount() const; + + //============================================================================== + /** This class measures the time between its construction and destruction and + adds it to an AudioProcessLoadMeasurer. + + e.g. + @code + { + AudioProcessLoadMeasurer::ScopedTimer timer (myProcessLoadMeasurer); + myCallback->doTheCallback(); + } + @endcode + + @tags{Audio} + */ + struct JUCE_API ScopedTimer + { + ScopedTimer (AudioProcessLoadMeasurer&); + ~ScopedTimer(); + + private: + AudioProcessLoadMeasurer& owner; + double startTime; + + JUCE_DECLARE_NON_COPYABLE (ScopedTimer) + }; + + /** Can be called manually to add the time of a callback to the stats. + Normally you probably would never call this - it's simpler and more robust to + use a ScopedTimer to measure the time using an RAII pattern. + */ + void registerBlockRenderTime (double millisecondsTaken); + +private: + double cpuUsageMs = 0, timeToCpuScale = 0, msPerBlock = 0; + int xruns = 0; +}; + + +} // namespace juce diff --git a/JuceLibraryCode/modules/juce_audio_basics/buffers/juce_AudioSampleBuffer.h b/JuceLibraryCode/modules/juce_audio_basics/buffers/juce_AudioSampleBuffer.h index f4ab1df..f62e90c 100644 --- a/JuceLibraryCode/modules/juce_audio_basics/buffers/juce_AudioSampleBuffer.h +++ b/JuceLibraryCode/modules/juce_audio_basics/buffers/juce_AudioSampleBuffer.h @@ -1,1145 +1,1145 @@ -/* - ============================================================================== - - This file is part of the JUCE library. - Copyright (c) 2017 - ROLI Ltd. - - JUCE is an open source library subject to commercial or open-source - licensing. - - The code included in this file is provided under the terms of the ISC license - http://www.isc.org/downloads/software-support-policy/isc-license. Permission - To use, copy, modify, and/or distribute this software for any purpose with or - without fee is hereby granted provided that the above copyright notice and - this permission notice appear in all copies. - - JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER - EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE - DISCLAIMED. - - ============================================================================== -*/ - -namespace juce -{ - -//============================================================================== -/** - A multi-channel buffer containing floating point audio samples. - - @tags{Audio} -*/ -template -class AudioBuffer -{ -public: - //============================================================================== - /** Creates an empty buffer with 0 channels and 0 length. */ - AudioBuffer() noexcept - : channels (static_cast (preallocatedChannelSpace)) - { - } - - //============================================================================== - /** Creates a buffer with a specified number of channels and samples. - - The contents of the buffer will initially be undefined, so use clear() to - set all the samples to zero. - - The buffer will allocate its memory internally, and this will be released - when the buffer is deleted. If the memory can't be allocated, this will - throw a std::bad_alloc exception. - */ - AudioBuffer (int numChannelsToAllocate, - int numSamplesToAllocate) - : numChannels (numChannelsToAllocate), - size (numSamplesToAllocate) - { - jassert (size >= 0 && numChannels >= 0); - allocateData(); - } - - /** Creates a buffer using a pre-allocated block of memory. - - Note that if the buffer is resized or its number of channels is changed, it - will re-allocate memory internally and copy the existing data to this new area, - so it will then stop directly addressing this memory. - - @param dataToReferTo a pre-allocated array containing pointers to the data - for each channel that should be used by this buffer. The - buffer will only refer to this memory, it won't try to delete - it when the buffer is deleted or resized. - @param numChannelsToUse the number of channels to use - this must correspond to the - number of elements in the array passed in - @param numSamples the number of samples to use - this must correspond to the - size of the arrays passed in - */ - AudioBuffer (Type* const* dataToReferTo, - int numChannelsToUse, - int numSamples) - : numChannels (numChannelsToUse), - size (numSamples) - { - jassert (dataToReferTo != nullptr); - jassert (numChannelsToUse >= 0 && numSamples >= 0); - allocateChannels (dataToReferTo, 0); - } - - /** Creates a buffer using a pre-allocated block of memory. - - Note that if the buffer is resized or its number of channels is changed, it - will re-allocate memory internally and copy the existing data to this new area, - so it will then stop directly addressing this memory. - - @param dataToReferTo a pre-allocated array containing pointers to the data - for each channel that should be used by this buffer. The - buffer will only refer to this memory, it won't try to delete - it when the buffer is deleted or resized. - @param numChannelsToUse the number of channels to use - this must correspond to the - number of elements in the array passed in - @param startSample the offset within the arrays at which the data begins - @param numSamples the number of samples to use - this must correspond to the - size of the arrays passed in - */ - AudioBuffer (Type* const* dataToReferTo, - int numChannelsToUse, - int startSample, - int numSamples) - : numChannels (numChannelsToUse), - size (numSamples) - { - jassert (dataToReferTo != nullptr); - jassert (numChannelsToUse >= 0 && startSample >= 0 && numSamples >= 0); - allocateChannels (dataToReferTo, startSample); - } - - /** Copies another buffer. - - This buffer will make its own copy of the other's data, unless the buffer was created - using an external data buffer, in which case both buffers will just point to the same - shared block of data. - */ - AudioBuffer (const AudioBuffer& other) - : numChannels (other.numChannels), - size (other.size), - allocatedBytes (other.allocatedBytes) - { - if (allocatedBytes == 0) - { - allocateChannels (other.channels, 0); - } - else - { - allocateData(); - - if (other.isClear) - { - clear(); - } - else - { - for (int i = 0; i < numChannels; ++i) - FloatVectorOperations::copy (channels[i], other.channels[i], size); - } - } - } - - /** Copies another buffer onto this one. - This buffer's size will be changed to that of the other buffer. - */ - AudioBuffer& operator= (const AudioBuffer& other) - { - if (this != &other) - { - setSize (other.getNumChannels(), other.getNumSamples(), false, false, false); - - if (other.isClear) - { - clear(); - } - else - { - isClear = false; - - for (int i = 0; i < numChannels; ++i) - FloatVectorOperations::copy (channels[i], other.channels[i], size); - } - } - - return *this; - } - - /** Destructor. - This will free any memory allocated by the buffer. - */ - ~AudioBuffer() = default; - - /** Move constructor */ - AudioBuffer (AudioBuffer&& other) noexcept - : numChannels (other.numChannels), - size (other.size), - allocatedBytes (other.allocatedBytes), - allocatedData (std::move (other.allocatedData)), - isClear (other.isClear.load()) - { - if (numChannels < (int) numElementsInArray (preallocatedChannelSpace)) - { - channels = preallocatedChannelSpace; - - for (int i = 0; i < numChannels; ++i) - preallocatedChannelSpace[i] = other.channels[i]; - } - else - { - channels = other.channels; - } - - other.numChannels = 0; - other.size = 0; - other.allocatedBytes = 0; - } - - /** Move assignment */ - AudioBuffer& operator= (AudioBuffer&& other) noexcept - { - numChannels = other.numChannels; - size = other.size; - allocatedBytes = other.allocatedBytes; - allocatedData = std::move (other.allocatedData); - isClear = other.isClear.load(); - - if (numChannels < (int) numElementsInArray (preallocatedChannelSpace)) - { - channels = preallocatedChannelSpace; - - for (int i = 0; i < numChannels; ++i) - preallocatedChannelSpace[i] = other.channels[i]; - } - else - { - channels = other.channels; - } - - other.numChannels = 0; - other.size = 0; - other.allocatedBytes = 0; - return *this; - } - - //============================================================================== - /** Returns the number of channels of audio data that this buffer contains. - @see getNumSamples, getReadPointer, getWritePointer - */ - int getNumChannels() const noexcept { return numChannels; } - - /** Returns the number of samples allocated in each of the buffer's channels. - @see getNumChannels, getReadPointer, getWritePointer - */ - int getNumSamples() const noexcept { return size; } - - /** Returns a pointer to an array of read-only samples in one of the buffer's channels. - For speed, this doesn't check whether the channel number is out of range, - so be careful when using it! - If you need to write to the data, do NOT call this method and const_cast the - result! Instead, you must call getWritePointer so that the buffer knows you're - planning on modifying the data. - */ - const Type* getReadPointer (int channelNumber) const noexcept - { - jassert (isPositiveAndBelow (channelNumber, numChannels)); - return channels[channelNumber]; - } - - /** Returns a pointer to an array of read-only samples in one of the buffer's channels. - For speed, this doesn't check whether the channel number or index are out of range, - so be careful when using it! - If you need to write to the data, do NOT call this method and const_cast the - result! Instead, you must call getWritePointer so that the buffer knows you're - planning on modifying the data. - */ - const Type* getReadPointer (int channelNumber, int sampleIndex) const noexcept - { - jassert (isPositiveAndBelow (channelNumber, numChannels)); - jassert (isPositiveAndBelow (sampleIndex, size)); - return channels[channelNumber] + sampleIndex; - } - - /** Returns a writeable pointer to one of the buffer's channels. - For speed, this doesn't check whether the channel number is out of range, - so be careful when using it! - Note that if you're not planning on writing to the data, you should always - use getReadPointer instead. - */ - Type* getWritePointer (int channelNumber) noexcept - { - jassert (isPositiveAndBelow (channelNumber, numChannels)); - isClear = false; - return channels[channelNumber]; - } - - /** Returns a writeable pointer to one of the buffer's channels. - For speed, this doesn't check whether the channel number or index are out of range, - so be careful when using it! - Note that if you're not planning on writing to the data, you should - use getReadPointer instead. - */ - Type* getWritePointer (int channelNumber, int sampleIndex) noexcept - { - jassert (isPositiveAndBelow (channelNumber, numChannels)); - jassert (isPositiveAndBelow (sampleIndex, size)); - isClear = false; - return channels[channelNumber] + sampleIndex; - } - - /** Returns an array of pointers to the channels in the buffer. - - Don't modify any of the pointers that are returned, and bear in mind that - these will become invalid if the buffer is resized. - */ - const Type** getArrayOfReadPointers() const noexcept { return const_cast (channels); } - - /** Returns an array of pointers to the channels in the buffer. - - Don't modify any of the pointers that are returned, and bear in mind that - these will become invalid if the buffer is resized. - */ - Type** getArrayOfWritePointers() noexcept { isClear = false; return channels; } - - //============================================================================== - /** Changes the buffer's size or number of channels. - - This can expand or contract the buffer's length, and add or remove channels. - - If keepExistingContent is true, it will try to preserve as much of the - old data as it can in the new buffer. - - If clearExtraSpace is true, then any extra channels or space that is - allocated will be also be cleared. If false, then this space is left - uninitialised. - - If avoidReallocating is true, then changing the buffer's size won't reduce the - amount of memory that is currently allocated (but it will still increase it if - the new size is bigger than the amount it currently has). If this is false, then - a new allocation will be done so that the buffer uses takes up the minimum amount - of memory that it needs. - - Note that if keepExistingContent and avoidReallocating are both true, then it will - only avoid reallocating if neither the channel count or length in samples increase. - - If the required memory can't be allocated, this will throw a std::bad_alloc exception. - */ - void setSize (int newNumChannels, - int newNumSamples, - bool keepExistingContent = false, - bool clearExtraSpace = false, - bool avoidReallocating = false) - { - jassert (newNumChannels >= 0); - jassert (newNumSamples >= 0); - - if (newNumSamples != size || newNumChannels != numChannels) - { - auto allocatedSamplesPerChannel = ((size_t) newNumSamples + 3) & ~3u; - auto channelListSize = ((static_cast (1 + newNumChannels) * sizeof (Type*)) + 15) & ~15u; - auto newTotalBytes = ((size_t) newNumChannels * (size_t) allocatedSamplesPerChannel * sizeof (Type)) - + channelListSize + 32; - - if (keepExistingContent) - { - if (avoidReallocating && newNumChannels <= numChannels && newNumSamples <= size) - { - // no need to do any remapping in this case, as the channel pointers will remain correct! - } - else - { - HeapBlock newData; - newData.allocate (newTotalBytes, clearExtraSpace || isClear); - - auto numSamplesToCopy = (size_t) jmin (newNumSamples, size); - - auto newChannels = reinterpret_cast (newData.get()); - auto newChan = reinterpret_cast (newData + channelListSize); - - for (int j = 0; j < newNumChannels; ++j) - { - newChannels[j] = newChan; - newChan += allocatedSamplesPerChannel; - } - - if (! isClear) - { - auto numChansToCopy = jmin (numChannels, newNumChannels); - - for (int i = 0; i < numChansToCopy; ++i) - FloatVectorOperations::copy (newChannels[i], channels[i], (int) numSamplesToCopy); - } - - allocatedData.swapWith (newData); - allocatedBytes = newTotalBytes; - channels = newChannels; - } - } - else - { - if (avoidReallocating && allocatedBytes >= newTotalBytes) - { - if (clearExtraSpace || isClear) - allocatedData.clear (newTotalBytes); - } - else - { - allocatedBytes = newTotalBytes; - allocatedData.allocate (newTotalBytes, clearExtraSpace || isClear); - channels = reinterpret_cast (allocatedData.get()); - } - - auto* chan = reinterpret_cast (allocatedData + channelListSize); - - for (int i = 0; i < newNumChannels; ++i) - { - channels[i] = chan; - chan += allocatedSamplesPerChannel; - } - } - - channels[newNumChannels] = nullptr; - size = newNumSamples; - numChannels = newNumChannels; - } - } - - /** Makes this buffer point to a pre-allocated set of channel data arrays. - - There's also a constructor that lets you specify arrays like this, but this - lets you change the channels dynamically. - - Note that if the buffer is resized or its number of channels is changed, it - will re-allocate memory internally and copy the existing data to this new area, - so it will then stop directly addressing this memory. - - @param dataToReferTo a pre-allocated array containing pointers to the data - for each channel that should be used by this buffer. The - buffer will only refer to this memory, it won't try to delete - it when the buffer is deleted or resized. - @param newNumChannels the number of channels to use - this must correspond to the - number of elements in the array passed in - @param newStartSample the offset within the arrays at which the data begins - @param newNumSamples the number of samples to use - this must correspond to the - size of the arrays passed in - */ - void setDataToReferTo (Type** dataToReferTo, - int newNumChannels, - int newStartSample, - int newNumSamples) - { - jassert (dataToReferTo != nullptr); - jassert (newNumChannels >= 0 && newNumSamples >= 0); - - if (allocatedBytes != 0) - { - allocatedBytes = 0; - allocatedData.free(); - } - - numChannels = newNumChannels; - size = newNumSamples; - - allocateChannels (dataToReferTo, newStartSample); - jassert (! isClear); - } - - /** Makes this buffer point to a pre-allocated set of channel data arrays. - - There's also a constructor that lets you specify arrays like this, but this - lets you change the channels dynamically. - - Note that if the buffer is resized or its number of channels is changed, it - will re-allocate memory internally and copy the existing data to this new area, - so it will then stop directly addressing this memory. - - @param dataToReferTo a pre-allocated array containing pointers to the data - for each channel that should be used by this buffer. The - buffer will only refer to this memory, it won't try to delete - it when the buffer is deleted or resized. - @param newNumChannels the number of channels to use - this must correspond to the - number of elements in the array passed in - @param newNumSamples the number of samples to use - this must correspond to the - size of the arrays passed in - */ - void setDataToReferTo (Type** dataToReferTo, - int newNumChannels, - int newNumSamples) - { - setDataToReferTo (dataToReferTo, newNumChannels, 0, newNumSamples); - } - - /** Resizes this buffer to match the given one, and copies all of its content across. - The source buffer can contain a different floating point type, so this can be used to - convert between 32 and 64 bit float buffer types. - */ - template - void makeCopyOf (const AudioBuffer& other, bool avoidReallocating = false) - { - setSize (other.getNumChannels(), other.getNumSamples(), false, false, avoidReallocating); - - if (other.hasBeenCleared()) - { - clear(); - } - else - { - isClear = false; - - for (int chan = 0; chan < numChannels; ++chan) - { - auto* dest = channels[chan]; - auto* src = other.getReadPointer (chan); - - for (int i = 0; i < size; ++i) - dest[i] = static_cast (src[i]); - } - } - } - - //============================================================================== - /** Clears all the samples in all channels. */ - void clear() noexcept - { - if (! isClear) - { - for (int i = 0; i < numChannels; ++i) - FloatVectorOperations::clear (channels[i], size); - - isClear = true; - } - } - - /** Clears a specified region of all the channels. - - For speed, this doesn't check whether the channel and sample number - are in-range, so be careful! - */ - void clear (int startSample, int numSamples) noexcept - { - jassert (startSample >= 0 && numSamples >= 0 && startSample + numSamples <= size); - - if (! isClear) - { - if (startSample == 0 && numSamples == size) - isClear = true; - - for (int i = 0; i < numChannels; ++i) - FloatVectorOperations::clear (channels[i] + startSample, numSamples); - } - } - - /** Clears a specified region of just one channel. - - For speed, this doesn't check whether the channel and sample number - are in-range, so be careful! - */ - void clear (int channel, int startSample, int numSamples) noexcept - { - jassert (isPositiveAndBelow (channel, numChannels)); - jassert (startSample >= 0 && numSamples >= 0 && startSample + numSamples <= size); - - if (! isClear) - FloatVectorOperations::clear (channels[channel] + startSample, numSamples); - } - - /** Returns true if the buffer has been entirely cleared. - Note that this does not actually measure the contents of the buffer - it simply - returns a flag that is set when the buffer is cleared, and which is reset whenever - functions like getWritePointer() are invoked. That means the method does not take - any time, but it may return false negatives when in fact the buffer is still empty. - */ - bool hasBeenCleared() const noexcept { return isClear; } - - //============================================================================== - /** Returns a sample from the buffer. - The channel and index are not checked - they are expected to be in-range. If not, - an assertion will be thrown, but in a release build, you're into 'undefined behaviour' - territory. - */ - Type getSample (int channel, int sampleIndex) const noexcept - { - jassert (isPositiveAndBelow (channel, numChannels)); - jassert (isPositiveAndBelow (sampleIndex, size)); - return *(channels[channel] + sampleIndex); - } - - /** Sets a sample in the buffer. - The channel and index are not checked - they are expected to be in-range. If not, - an assertion will be thrown, but in a release build, you're into 'undefined behaviour' - territory. - */ - void setSample (int destChannel, int destSample, Type newValue) noexcept - { - jassert (isPositiveAndBelow (destChannel, numChannels)); - jassert (isPositiveAndBelow (destSample, size)); - *(channels[destChannel] + destSample) = newValue; - isClear = false; - } - - /** Adds a value to a sample in the buffer. - The channel and index are not checked - they are expected to be in-range. If not, - an assertion will be thrown, but in a release build, you're into 'undefined behaviour' - territory. - */ - void addSample (int destChannel, int destSample, Type valueToAdd) noexcept - { - jassert (isPositiveAndBelow (destChannel, numChannels)); - jassert (isPositiveAndBelow (destSample, size)); - *(channels[destChannel] + destSample) += valueToAdd; - isClear = false; - } - - /** Applies a gain multiple to a region of one channel. - - For speed, this doesn't check whether the channel and sample number - are in-range, so be careful! - */ - void applyGain (int channel, int startSample, int numSamples, Type gain) noexcept - { - jassert (isPositiveAndBelow (channel, numChannels)); - jassert (startSample >= 0 && numSamples >= 0 && startSample + numSamples <= size); - - if (gain != Type (1) && ! isClear) - { - auto* d = channels[channel] + startSample; - - if (gain == Type()) - FloatVectorOperations::clear (d, numSamples); - else - FloatVectorOperations::multiply (d, gain, numSamples); - } - } - - /** Applies a gain multiple to a region of all the channels. - - For speed, this doesn't check whether the sample numbers - are in-range, so be careful! - */ - void applyGain (int startSample, int numSamples, Type gain) noexcept - { - for (int i = 0; i < numChannels; ++i) - applyGain (i, startSample, numSamples, gain); - } - - /** Applies a gain multiple to all the audio data. */ - void applyGain (Type gain) noexcept - { - applyGain (0, size, gain); - } - - /** Applies a range of gains to a region of a channel. - - The gain that is applied to each sample will vary from - startGain on the first sample to endGain on the last Sample, - so it can be used to do basic fades. - - For speed, this doesn't check whether the sample numbers - are in-range, so be careful! - */ - void applyGainRamp (int channel, int startSample, int numSamples, - Type startGain, Type endGain) noexcept - { - if (! isClear) - { - if (startGain == endGain) - { - applyGain (channel, startSample, numSamples, startGain); - } - else - { - jassert (isPositiveAndBelow (channel, numChannels)); - jassert (startSample >= 0 && numSamples >= 0 && startSample + numSamples <= size); - - const auto increment = (endGain - startGain) / (float) numSamples; - auto* d = channels[channel] + startSample; - - while (--numSamples >= 0) - { - *d++ *= startGain; - startGain += increment; - } - } - } - } - - /** Applies a range of gains to a region of all channels. - - The gain that is applied to each sample will vary from - startGain on the first sample to endGain on the last Sample, - so it can be used to do basic fades. - - For speed, this doesn't check whether the sample numbers - are in-range, so be careful! - */ - void applyGainRamp (int startSample, int numSamples, - Type startGain, Type endGain) noexcept - { - for (int i = 0; i < numChannels; ++i) - applyGainRamp (i, startSample, numSamples, startGain, endGain); - } - - /** Adds samples from another buffer to this one. - - @param destChannel the channel within this buffer to add the samples to - @param destStartSample the start sample within this buffer's channel - @param source the source buffer to add from - @param sourceChannel the channel within the source buffer to read from - @param sourceStartSample the offset within the source buffer's channel to start reading samples from - @param numSamples the number of samples to process - @param gainToApplyToSource an optional gain to apply to the source samples before they are - added to this buffer's samples - - @see copyFrom - */ - void addFrom (int destChannel, - int destStartSample, - const AudioBuffer& source, - int sourceChannel, - int sourceStartSample, - int numSamples, - Type gainToApplyToSource = Type (1)) noexcept - { - jassert (&source != this || sourceChannel != destChannel); - jassert (isPositiveAndBelow (destChannel, numChannels)); - jassert (destStartSample >= 0 && numSamples >= 0 && destStartSample + numSamples <= size); - jassert (isPositiveAndBelow (sourceChannel, source.numChannels)); - jassert (sourceStartSample >= 0 && sourceStartSample + numSamples <= source.size); - - if (gainToApplyToSource != 0 && numSamples > 0 && ! source.isClear) - { - auto* d = channels[destChannel] + destStartSample; - auto* s = source.channels[sourceChannel] + sourceStartSample; - - if (isClear) - { - isClear = false; - - if (gainToApplyToSource != Type (1)) - FloatVectorOperations::copyWithMultiply (d, s, gainToApplyToSource, numSamples); - else - FloatVectorOperations::copy (d, s, numSamples); - } - else - { - if (gainToApplyToSource != Type (1)) - FloatVectorOperations::addWithMultiply (d, s, gainToApplyToSource, numSamples); - else - FloatVectorOperations::add (d, s, numSamples); - } - } - } - - /** Adds samples from an array of floats to one of the channels. - - @param destChannel the channel within this buffer to add the samples to - @param destStartSample the start sample within this buffer's channel - @param source the source data to use - @param numSamples the number of samples to process - @param gainToApplyToSource an optional gain to apply to the source samples before they are - added to this buffer's samples - - @see copyFrom - */ - void addFrom (int destChannel, - int destStartSample, - const Type* source, - int numSamples, - Type gainToApplyToSource = Type (1)) noexcept - { - jassert (isPositiveAndBelow (destChannel, numChannels)); - jassert (destStartSample >= 0 && numSamples >= 0 && destStartSample + numSamples <= size); - jassert (source != nullptr); - - if (gainToApplyToSource != 0 && numSamples > 0) - { - auto* d = channels[destChannel] + destStartSample; - - if (isClear) - { - isClear = false; - - if (gainToApplyToSource != Type (1)) - FloatVectorOperations::copyWithMultiply (d, source, gainToApplyToSource, numSamples); - else - FloatVectorOperations::copy (d, source, numSamples); - } - else - { - if (gainToApplyToSource != Type (1)) - FloatVectorOperations::addWithMultiply (d, source, gainToApplyToSource, numSamples); - else - FloatVectorOperations::add (d, source, numSamples); - } - } - } - - - /** Adds samples from an array of floats, applying a gain ramp to them. - - @param destChannel the channel within this buffer to add the samples to - @param destStartSample the start sample within this buffer's channel - @param source the source data to use - @param numSamples the number of samples to process - @param startGain the gain to apply to the first sample (this is multiplied with - the source samples before they are added to this buffer) - @param endGain the gain to apply to the final sample. The gain is linearly - interpolated between the first and last samples. - */ - void addFromWithRamp (int destChannel, - int destStartSample, - const Type* source, - int numSamples, - Type startGain, - Type endGain) noexcept - { - if (startGain == endGain) - { - addFrom (destChannel, destStartSample, source, numSamples, startGain); - } - else - { - jassert (isPositiveAndBelow (destChannel, numChannels)); - jassert (destStartSample >= 0 && numSamples >= 0 && destStartSample + numSamples <= size); - jassert (source != nullptr); - - if (numSamples > 0) - { - isClear = false; - const auto increment = (endGain - startGain) / numSamples; - auto* d = channels[destChannel] + destStartSample; - - while (--numSamples >= 0) - { - *d++ += startGain * *source++; - startGain += increment; - } - } - } - } - - /** Copies samples from another buffer to this one. - - @param destChannel the channel within this buffer to copy the samples to - @param destStartSample the start sample within this buffer's channel - @param source the source buffer to read from - @param sourceChannel the channel within the source buffer to read from - @param sourceStartSample the offset within the source buffer's channel to start reading samples from - @param numSamples the number of samples to process - - @see addFrom - */ - void copyFrom (int destChannel, - int destStartSample, - const AudioBuffer& source, - int sourceChannel, - int sourceStartSample, - int numSamples) noexcept - { - jassert (&source != this || sourceChannel != destChannel); - jassert (isPositiveAndBelow (destChannel, numChannels)); - jassert (destStartSample >= 0 && destStartSample + numSamples <= size); - jassert (isPositiveAndBelow (sourceChannel, source.numChannels)); - jassert (sourceStartSample >= 0 && numSamples >= 0 && sourceStartSample + numSamples <= source.size); - - if (numSamples > 0) - { - if (source.isClear) - { - if (! isClear) - FloatVectorOperations::clear (channels[destChannel] + destStartSample, numSamples); - } - else - { - isClear = false; - FloatVectorOperations::copy (channels[destChannel] + destStartSample, - source.channels[sourceChannel] + sourceStartSample, - numSamples); - } - } - } - - /** Copies samples from an array of floats into one of the channels. - - @param destChannel the channel within this buffer to copy the samples to - @param destStartSample the start sample within this buffer's channel - @param source the source buffer to read from - @param numSamples the number of samples to process - - @see addFrom - */ - void copyFrom (int destChannel, - int destStartSample, - const Type* source, - int numSamples) noexcept - { - jassert (isPositiveAndBelow (destChannel, numChannels)); - jassert (destStartSample >= 0 && numSamples >= 0 && destStartSample + numSamples <= size); - jassert (source != nullptr); - - if (numSamples > 0) - { - isClear = false; - FloatVectorOperations::copy (channels[destChannel] + destStartSample, source, numSamples); - } - } - - /** Copies samples from an array of floats into one of the channels, applying a gain to it. - - @param destChannel the channel within this buffer to copy the samples to - @param destStartSample the start sample within this buffer's channel - @param source the source buffer to read from - @param numSamples the number of samples to process - @param gain the gain to apply - - @see addFrom - */ - void copyFrom (int destChannel, - int destStartSample, - const Type* source, - int numSamples, - Type gain) noexcept - { - jassert (isPositiveAndBelow (destChannel, numChannels)); - jassert (destStartSample >= 0 && numSamples >= 0 && destStartSample + numSamples <= size); - jassert (source != nullptr); - - if (numSamples > 0) - { - auto* d = channels[destChannel] + destStartSample; - - if (gain != Type (1)) - { - if (gain == Type()) - { - if (! isClear) - FloatVectorOperations::clear (d, numSamples); - } - else - { - isClear = false; - FloatVectorOperations::copyWithMultiply (d, source, gain, numSamples); - } - } - else - { - isClear = false; - FloatVectorOperations::copy (d, source, numSamples); - } - } - } - - /** Copies samples from an array of floats into one of the channels, applying a gain ramp. - - @param destChannel the channel within this buffer to copy the samples to - @param destStartSample the start sample within this buffer's channel - @param source the source buffer to read from - @param numSamples the number of samples to process - @param startGain the gain to apply to the first sample (this is multiplied with - the source samples before they are copied to this buffer) - @param endGain the gain to apply to the final sample. The gain is linearly - interpolated between the first and last samples. - - @see addFrom - */ - void copyFromWithRamp (int destChannel, - int destStartSample, - const Type* source, - int numSamples, - Type startGain, - Type endGain) noexcept - { - if (startGain == endGain) - { - copyFrom (destChannel, destStartSample, source, numSamples, startGain); - } - else - { - jassert (isPositiveAndBelow (destChannel, numChannels)); - jassert (destStartSample >= 0 && numSamples >= 0 && destStartSample + numSamples <= size); - jassert (source != nullptr); - - if (numSamples > 0) - { - isClear = false; - const auto increment = (endGain - startGain) / numSamples; - auto* d = channels[destChannel] + destStartSample; - - while (--numSamples >= 0) - { - *d++ = startGain * *source++; - startGain += increment; - } - } - } - } - - /** Returns a Range indicating the lowest and highest sample values in a given section. - - @param channel the channel to read from - @param startSample the start sample within the channel - @param numSamples the number of samples to check - */ - Range findMinMax (int channel, int startSample, int numSamples) const noexcept - { - jassert (isPositiveAndBelow (channel, numChannels)); - jassert (startSample >= 0 && numSamples >= 0 && startSample + numSamples <= size); - - if (isClear) - return { Type (0), Type (0) }; - - return FloatVectorOperations::findMinAndMax (channels[channel] + startSample, numSamples); - } - - /** Finds the highest absolute sample value within a region of a channel. */ - Type getMagnitude (int channel, int startSample, int numSamples) const noexcept - { - jassert (isPositiveAndBelow (channel, numChannels)); - jassert (startSample >= 0 && numSamples >= 0 && startSample + numSamples <= size); - - if (isClear) - return Type (0); - - auto r = findMinMax (channel, startSample, numSamples); - - return jmax (r.getStart(), -r.getStart(), r.getEnd(), -r.getEnd()); - } - - /** Finds the highest absolute sample value within a region on all channels. */ - Type getMagnitude (int startSample, int numSamples) const noexcept - { - Type mag (0); - - if (! isClear) - for (int i = 0; i < numChannels; ++i) - mag = jmax (mag, getMagnitude (i, startSample, numSamples)); - - return mag; - } - - /** Returns the root mean squared level for a region of a channel. */ - Type getRMSLevel (int channel, int startSample, int numSamples) const noexcept - { - jassert (isPositiveAndBelow (channel, numChannels)); - jassert (startSample >= 0 && numSamples >= 0 && startSample + numSamples <= size); - - if (numSamples <= 0 || channel < 0 || channel >= numChannels || isClear) - return Type (0); - - auto* data = channels[channel] + startSample; - double sum = 0.0; - - for (int i = 0; i < numSamples; ++i) - { - auto sample = data[i]; - sum += sample * sample; - } - - return static_cast (std::sqrt (sum / numSamples)); - } - - /** Reverses a part of a channel. */ - void reverse (int channel, int startSample, int numSamples) const noexcept - { - jassert (isPositiveAndBelow (channel, numChannels)); - jassert (startSample >= 0 && numSamples >= 0 && startSample + numSamples <= size); - - if (! isClear) - std::reverse (channels[channel] + startSample, - channels[channel] + startSample + numSamples); - } - - /** Reverses a part of the buffer. */ - void reverse (int startSample, int numSamples) const noexcept - { - for (int i = 0; i < numChannels; ++i) - reverse (i, startSample, numSamples); - } - - //============================================================================== - /** This allows templated code that takes an AudioBuffer to access its sample type. */ - using SampleType = Type; - -private: - //============================================================================== - int numChannels = 0, size = 0; - size_t allocatedBytes = 0; - Type** channels; - HeapBlock allocatedData; - Type* preallocatedChannelSpace[32]; - std::atomic isClear { false }; - - void allocateData() - { - static_assert (std::alignment_of::value <= std::alignment_of::value, - "AudioBuffer cannot hold types with alignment requirements larger than that guaranteed by malloc"); - jassert (size >= 0); - - auto channelListSize = (size_t) (numChannels + 1) * sizeof (Type*); - auto requiredSampleAlignment = std::alignment_of::value; - size_t alignmentOverflow = channelListSize % requiredSampleAlignment; - - if (alignmentOverflow != 0) - channelListSize += requiredSampleAlignment - alignmentOverflow; - - allocatedBytes = (size_t) numChannels * (size_t) size * sizeof (Type) + channelListSize + 32; - allocatedData.malloc (allocatedBytes); - channels = reinterpret_cast (allocatedData.get()); - auto chan = reinterpret_cast (allocatedData + channelListSize); - - for (int i = 0; i < numChannels; ++i) - { - channels[i] = chan; - chan += size; - } - - channels[numChannels] = nullptr; - isClear = false; - } - - void allocateChannels (Type* const* dataToReferTo, int offset) - { - jassert (offset >= 0); - - // (try to avoid doing a malloc here, as that'll blow up things like Pro-Tools) - if (numChannels < (int) numElementsInArray (preallocatedChannelSpace)) - { - channels = static_cast (preallocatedChannelSpace); - } - else - { - allocatedData.malloc (numChannels + 1, sizeof (Type*)); - channels = reinterpret_cast (allocatedData.get()); - } - - for (int i = 0; i < numChannels; ++i) - { - // you have to pass in the same number of valid pointers as numChannels - jassert (dataToReferTo[i] != nullptr); - channels[i] = dataToReferTo[i] + offset; - } - - channels[numChannels] = nullptr; - isClear = false; - } - - JUCE_LEAK_DETECTOR (AudioBuffer) -}; - -//============================================================================== -/** - A multi-channel buffer of 32-bit floating point audio samples. - - This type is here for backwards compatibility with the older AudioSampleBuffer - class, which was fixed for 32-bit data, but is otherwise the same as the new - templated AudioBuffer class. - - @see AudioBuffer -*/ -using AudioSampleBuffer = AudioBuffer; - -} // namespace juce +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2017 - ROLI Ltd. + + JUCE is an open source library subject to commercial or open-source + licensing. + + The code included in this file is provided under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license. Permission + To use, copy, modify, and/or distribute this software for any purpose with or + without fee is hereby granted provided that the above copyright notice and + this permission notice appear in all copies. + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +namespace juce +{ + +//============================================================================== +/** + A multi-channel buffer containing floating point audio samples. + + @tags{Audio} +*/ +template +class AudioBuffer +{ +public: + //============================================================================== + /** Creates an empty buffer with 0 channels and 0 length. */ + AudioBuffer() noexcept + : channels (static_cast (preallocatedChannelSpace)) + { + } + + //============================================================================== + /** Creates a buffer with a specified number of channels and samples. + + The contents of the buffer will initially be undefined, so use clear() to + set all the samples to zero. + + The buffer will allocate its memory internally, and this will be released + when the buffer is deleted. If the memory can't be allocated, this will + throw a std::bad_alloc exception. + */ + AudioBuffer (int numChannelsToAllocate, + int numSamplesToAllocate) + : numChannels (numChannelsToAllocate), + size (numSamplesToAllocate) + { + jassert (size >= 0 && numChannels >= 0); + allocateData(); + } + + /** Creates a buffer using a pre-allocated block of memory. + + Note that if the buffer is resized or its number of channels is changed, it + will re-allocate memory internally and copy the existing data to this new area, + so it will then stop directly addressing this memory. + + @param dataToReferTo a pre-allocated array containing pointers to the data + for each channel that should be used by this buffer. The + buffer will only refer to this memory, it won't try to delete + it when the buffer is deleted or resized. + @param numChannelsToUse the number of channels to use - this must correspond to the + number of elements in the array passed in + @param numSamples the number of samples to use - this must correspond to the + size of the arrays passed in + */ + AudioBuffer (Type* const* dataToReferTo, + int numChannelsToUse, + int numSamples) + : numChannels (numChannelsToUse), + size (numSamples) + { + jassert (dataToReferTo != nullptr); + jassert (numChannelsToUse >= 0 && numSamples >= 0); + allocateChannels (dataToReferTo, 0); + } + + /** Creates a buffer using a pre-allocated block of memory. + + Note that if the buffer is resized or its number of channels is changed, it + will re-allocate memory internally and copy the existing data to this new area, + so it will then stop directly addressing this memory. + + @param dataToReferTo a pre-allocated array containing pointers to the data + for each channel that should be used by this buffer. The + buffer will only refer to this memory, it won't try to delete + it when the buffer is deleted or resized. + @param numChannelsToUse the number of channels to use - this must correspond to the + number of elements in the array passed in + @param startSample the offset within the arrays at which the data begins + @param numSamples the number of samples to use - this must correspond to the + size of the arrays passed in + */ + AudioBuffer (Type* const* dataToReferTo, + int numChannelsToUse, + int startSample, + int numSamples) + : numChannels (numChannelsToUse), + size (numSamples) + { + jassert (dataToReferTo != nullptr); + jassert (numChannelsToUse >= 0 && startSample >= 0 && numSamples >= 0); + allocateChannels (dataToReferTo, startSample); + } + + /** Copies another buffer. + + This buffer will make its own copy of the other's data, unless the buffer was created + using an external data buffer, in which case both buffers will just point to the same + shared block of data. + */ + AudioBuffer (const AudioBuffer& other) + : numChannels (other.numChannels), + size (other.size), + allocatedBytes (other.allocatedBytes) + { + if (allocatedBytes == 0) + { + allocateChannels (other.channels, 0); + } + else + { + allocateData(); + + if (other.isClear) + { + clear(); + } + else + { + for (int i = 0; i < numChannels; ++i) + FloatVectorOperations::copy (channels[i], other.channels[i], size); + } + } + } + + /** Copies another buffer onto this one. + This buffer's size will be changed to that of the other buffer. + */ + AudioBuffer& operator= (const AudioBuffer& other) + { + if (this != &other) + { + setSize (other.getNumChannels(), other.getNumSamples(), false, false, false); + + if (other.isClear) + { + clear(); + } + else + { + isClear = false; + + for (int i = 0; i < numChannels; ++i) + FloatVectorOperations::copy (channels[i], other.channels[i], size); + } + } + + return *this; + } + + /** Destructor. + This will free any memory allocated by the buffer. + */ + ~AudioBuffer() = default; + + /** Move constructor */ + AudioBuffer (AudioBuffer&& other) noexcept + : numChannels (other.numChannels), + size (other.size), + allocatedBytes (other.allocatedBytes), + allocatedData (std::move (other.allocatedData)), + isClear (other.isClear.load()) + { + if (numChannels < (int) numElementsInArray (preallocatedChannelSpace)) + { + channels = preallocatedChannelSpace; + + for (int i = 0; i < numChannels; ++i) + preallocatedChannelSpace[i] = other.channels[i]; + } + else + { + channels = other.channels; + } + + other.numChannels = 0; + other.size = 0; + other.allocatedBytes = 0; + } + + /** Move assignment */ + AudioBuffer& operator= (AudioBuffer&& other) noexcept + { + numChannels = other.numChannels; + size = other.size; + allocatedBytes = other.allocatedBytes; + allocatedData = std::move (other.allocatedData); + isClear = other.isClear.load(); + + if (numChannels < (int) numElementsInArray (preallocatedChannelSpace)) + { + channels = preallocatedChannelSpace; + + for (int i = 0; i < numChannels; ++i) + preallocatedChannelSpace[i] = other.channels[i]; + } + else + { + channels = other.channels; + } + + other.numChannels = 0; + other.size = 0; + other.allocatedBytes = 0; + return *this; + } + + //============================================================================== + /** Returns the number of channels of audio data that this buffer contains. + @see getNumSamples, getReadPointer, getWritePointer + */ + int getNumChannels() const noexcept { return numChannels; } + + /** Returns the number of samples allocated in each of the buffer's channels. + @see getNumChannels, getReadPointer, getWritePointer + */ + int getNumSamples() const noexcept { return size; } + + /** Returns a pointer to an array of read-only samples in one of the buffer's channels. + For speed, this doesn't check whether the channel number is out of range, + so be careful when using it! + If you need to write to the data, do NOT call this method and const_cast the + result! Instead, you must call getWritePointer so that the buffer knows you're + planning on modifying the data. + */ + const Type* getReadPointer (int channelNumber) const noexcept + { + jassert (isPositiveAndBelow (channelNumber, numChannels)); + return channels[channelNumber]; + } + + /** Returns a pointer to an array of read-only samples in one of the buffer's channels. + For speed, this doesn't check whether the channel number or index are out of range, + so be careful when using it! + If you need to write to the data, do NOT call this method and const_cast the + result! Instead, you must call getWritePointer so that the buffer knows you're + planning on modifying the data. + */ + const Type* getReadPointer (int channelNumber, int sampleIndex) const noexcept + { + jassert (isPositiveAndBelow (channelNumber, numChannels)); + jassert (isPositiveAndBelow (sampleIndex, size)); + return channels[channelNumber] + sampleIndex; + } + + /** Returns a writeable pointer to one of the buffer's channels. + For speed, this doesn't check whether the channel number is out of range, + so be careful when using it! + Note that if you're not planning on writing to the data, you should always + use getReadPointer instead. + */ + Type* getWritePointer (int channelNumber) noexcept + { + jassert (isPositiveAndBelow (channelNumber, numChannels)); + isClear = false; + return channels[channelNumber]; + } + + /** Returns a writeable pointer to one of the buffer's channels. + For speed, this doesn't check whether the channel number or index are out of range, + so be careful when using it! + Note that if you're not planning on writing to the data, you should + use getReadPointer instead. + */ + Type* getWritePointer (int channelNumber, int sampleIndex) noexcept + { + jassert (isPositiveAndBelow (channelNumber, numChannels)); + jassert (isPositiveAndBelow (sampleIndex, size)); + isClear = false; + return channels[channelNumber] + sampleIndex; + } + + /** Returns an array of pointers to the channels in the buffer. + + Don't modify any of the pointers that are returned, and bear in mind that + these will become invalid if the buffer is resized. + */ + const Type** getArrayOfReadPointers() const noexcept { return const_cast (channels); } + + /** Returns an array of pointers to the channels in the buffer. + + Don't modify any of the pointers that are returned, and bear in mind that + these will become invalid if the buffer is resized. + */ + Type** getArrayOfWritePointers() noexcept { isClear = false; return channels; } + + //============================================================================== + /** Changes the buffer's size or number of channels. + + This can expand or contract the buffer's length, and add or remove channels. + + If keepExistingContent is true, it will try to preserve as much of the + old data as it can in the new buffer. + + If clearExtraSpace is true, then any extra channels or space that is + allocated will be also be cleared. If false, then this space is left + uninitialised. + + If avoidReallocating is true, then changing the buffer's size won't reduce the + amount of memory that is currently allocated (but it will still increase it if + the new size is bigger than the amount it currently has). If this is false, then + a new allocation will be done so that the buffer uses takes up the minimum amount + of memory that it needs. + + Note that if keepExistingContent and avoidReallocating are both true, then it will + only avoid reallocating if neither the channel count or length in samples increase. + + If the required memory can't be allocated, this will throw a std::bad_alloc exception. + */ + void setSize (int newNumChannels, + int newNumSamples, + bool keepExistingContent = false, + bool clearExtraSpace = false, + bool avoidReallocating = false) + { + jassert (newNumChannels >= 0); + jassert (newNumSamples >= 0); + + if (newNumSamples != size || newNumChannels != numChannels) + { + auto allocatedSamplesPerChannel = ((size_t) newNumSamples + 3) & ~3u; + auto channelListSize = ((static_cast (1 + newNumChannels) * sizeof (Type*)) + 15) & ~15u; + auto newTotalBytes = ((size_t) newNumChannels * (size_t) allocatedSamplesPerChannel * sizeof (Type)) + + channelListSize + 32; + + if (keepExistingContent) + { + if (avoidReallocating && newNumChannels <= numChannels && newNumSamples <= size) + { + // no need to do any remapping in this case, as the channel pointers will remain correct! + } + else + { + HeapBlock newData; + newData.allocate (newTotalBytes, clearExtraSpace || isClear); + + auto numSamplesToCopy = (size_t) jmin (newNumSamples, size); + + auto newChannels = reinterpret_cast (newData.get()); + auto newChan = reinterpret_cast (newData + channelListSize); + + for (int j = 0; j < newNumChannels; ++j) + { + newChannels[j] = newChan; + newChan += allocatedSamplesPerChannel; + } + + if (! isClear) + { + auto numChansToCopy = jmin (numChannels, newNumChannels); + + for (int i = 0; i < numChansToCopy; ++i) + FloatVectorOperations::copy (newChannels[i], channels[i], (int) numSamplesToCopy); + } + + allocatedData.swapWith (newData); + allocatedBytes = newTotalBytes; + channels = newChannels; + } + } + else + { + if (avoidReallocating && allocatedBytes >= newTotalBytes) + { + if (clearExtraSpace || isClear) + allocatedData.clear (newTotalBytes); + } + else + { + allocatedBytes = newTotalBytes; + allocatedData.allocate (newTotalBytes, clearExtraSpace || isClear); + channels = reinterpret_cast (allocatedData.get()); + } + + auto* chan = reinterpret_cast (allocatedData + channelListSize); + + for (int i = 0; i < newNumChannels; ++i) + { + channels[i] = chan; + chan += allocatedSamplesPerChannel; + } + } + + channels[newNumChannels] = nullptr; + size = newNumSamples; + numChannels = newNumChannels; + } + } + + /** Makes this buffer point to a pre-allocated set of channel data arrays. + + There's also a constructor that lets you specify arrays like this, but this + lets you change the channels dynamically. + + Note that if the buffer is resized or its number of channels is changed, it + will re-allocate memory internally and copy the existing data to this new area, + so it will then stop directly addressing this memory. + + @param dataToReferTo a pre-allocated array containing pointers to the data + for each channel that should be used by this buffer. The + buffer will only refer to this memory, it won't try to delete + it when the buffer is deleted or resized. + @param newNumChannels the number of channels to use - this must correspond to the + number of elements in the array passed in + @param newStartSample the offset within the arrays at which the data begins + @param newNumSamples the number of samples to use - this must correspond to the + size of the arrays passed in + */ + void setDataToReferTo (Type** dataToReferTo, + int newNumChannels, + int newStartSample, + int newNumSamples) + { + jassert (dataToReferTo != nullptr); + jassert (newNumChannels >= 0 && newNumSamples >= 0); + + if (allocatedBytes != 0) + { + allocatedBytes = 0; + allocatedData.free(); + } + + numChannels = newNumChannels; + size = newNumSamples; + + allocateChannels (dataToReferTo, newStartSample); + jassert (! isClear); + } + + /** Makes this buffer point to a pre-allocated set of channel data arrays. + + There's also a constructor that lets you specify arrays like this, but this + lets you change the channels dynamically. + + Note that if the buffer is resized or its number of channels is changed, it + will re-allocate memory internally and copy the existing data to this new area, + so it will then stop directly addressing this memory. + + @param dataToReferTo a pre-allocated array containing pointers to the data + for each channel that should be used by this buffer. The + buffer will only refer to this memory, it won't try to delete + it when the buffer is deleted or resized. + @param newNumChannels the number of channels to use - this must correspond to the + number of elements in the array passed in + @param newNumSamples the number of samples to use - this must correspond to the + size of the arrays passed in + */ + void setDataToReferTo (Type** dataToReferTo, + int newNumChannels, + int newNumSamples) + { + setDataToReferTo (dataToReferTo, newNumChannels, 0, newNumSamples); + } + + /** Resizes this buffer to match the given one, and copies all of its content across. + The source buffer can contain a different floating point type, so this can be used to + convert between 32 and 64 bit float buffer types. + */ + template + void makeCopyOf (const AudioBuffer& other, bool avoidReallocating = false) + { + setSize (other.getNumChannels(), other.getNumSamples(), false, false, avoidReallocating); + + if (other.hasBeenCleared()) + { + clear(); + } + else + { + isClear = false; + + for (int chan = 0; chan < numChannels; ++chan) + { + auto* dest = channels[chan]; + auto* src = other.getReadPointer (chan); + + for (int i = 0; i < size; ++i) + dest[i] = static_cast (src[i]); + } + } + } + + //============================================================================== + /** Clears all the samples in all channels. */ + void clear() noexcept + { + if (! isClear) + { + for (int i = 0; i < numChannels; ++i) + FloatVectorOperations::clear (channels[i], size); + + isClear = true; + } + } + + /** Clears a specified region of all the channels. + + For speed, this doesn't check whether the channel and sample number + are in-range, so be careful! + */ + void clear (int startSample, int numSamples) noexcept + { + jassert (startSample >= 0 && numSamples >= 0 && startSample + numSamples <= size); + + if (! isClear) + { + if (startSample == 0 && numSamples == size) + isClear = true; + + for (int i = 0; i < numChannels; ++i) + FloatVectorOperations::clear (channels[i] + startSample, numSamples); + } + } + + /** Clears a specified region of just one channel. + + For speed, this doesn't check whether the channel and sample number + are in-range, so be careful! + */ + void clear (int channel, int startSample, int numSamples) noexcept + { + jassert (isPositiveAndBelow (channel, numChannels)); + jassert (startSample >= 0 && numSamples >= 0 && startSample + numSamples <= size); + + if (! isClear) + FloatVectorOperations::clear (channels[channel] + startSample, numSamples); + } + + /** Returns true if the buffer has been entirely cleared. + Note that this does not actually measure the contents of the buffer - it simply + returns a flag that is set when the buffer is cleared, and which is reset whenever + functions like getWritePointer() are invoked. That means the method does not take + any time, but it may return false negatives when in fact the buffer is still empty. + */ + bool hasBeenCleared() const noexcept { return isClear; } + + //============================================================================== + /** Returns a sample from the buffer. + The channel and index are not checked - they are expected to be in-range. If not, + an assertion will be thrown, but in a release build, you're into 'undefined behaviour' + territory. + */ + Type getSample (int channel, int sampleIndex) const noexcept + { + jassert (isPositiveAndBelow (channel, numChannels)); + jassert (isPositiveAndBelow (sampleIndex, size)); + return *(channels[channel] + sampleIndex); + } + + /** Sets a sample in the buffer. + The channel and index are not checked - they are expected to be in-range. If not, + an assertion will be thrown, but in a release build, you're into 'undefined behaviour' + territory. + */ + void setSample (int destChannel, int destSample, Type newValue) noexcept + { + jassert (isPositiveAndBelow (destChannel, numChannels)); + jassert (isPositiveAndBelow (destSample, size)); + *(channels[destChannel] + destSample) = newValue; + isClear = false; + } + + /** Adds a value to a sample in the buffer. + The channel and index are not checked - they are expected to be in-range. If not, + an assertion will be thrown, but in a release build, you're into 'undefined behaviour' + territory. + */ + void addSample (int destChannel, int destSample, Type valueToAdd) noexcept + { + jassert (isPositiveAndBelow (destChannel, numChannels)); + jassert (isPositiveAndBelow (destSample, size)); + *(channels[destChannel] + destSample) += valueToAdd; + isClear = false; + } + + /** Applies a gain multiple to a region of one channel. + + For speed, this doesn't check whether the channel and sample number + are in-range, so be careful! + */ + void applyGain (int channel, int startSample, int numSamples, Type gain) noexcept + { + jassert (isPositiveAndBelow (channel, numChannels)); + jassert (startSample >= 0 && numSamples >= 0 && startSample + numSamples <= size); + + if (gain != Type (1) && ! isClear) + { + auto* d = channels[channel] + startSample; + + if (gain == Type()) + FloatVectorOperations::clear (d, numSamples); + else + FloatVectorOperations::multiply (d, gain, numSamples); + } + } + + /** Applies a gain multiple to a region of all the channels. + + For speed, this doesn't check whether the sample numbers + are in-range, so be careful! + */ + void applyGain (int startSample, int numSamples, Type gain) noexcept + { + for (int i = 0; i < numChannels; ++i) + applyGain (i, startSample, numSamples, gain); + } + + /** Applies a gain multiple to all the audio data. */ + void applyGain (Type gain) noexcept + { + applyGain (0, size, gain); + } + + /** Applies a range of gains to a region of a channel. + + The gain that is applied to each sample will vary from + startGain on the first sample to endGain on the last Sample, + so it can be used to do basic fades. + + For speed, this doesn't check whether the sample numbers + are in-range, so be careful! + */ + void applyGainRamp (int channel, int startSample, int numSamples, + Type startGain, Type endGain) noexcept + { + if (! isClear) + { + if (startGain == endGain) + { + applyGain (channel, startSample, numSamples, startGain); + } + else + { + jassert (isPositiveAndBelow (channel, numChannels)); + jassert (startSample >= 0 && numSamples >= 0 && startSample + numSamples <= size); + + const auto increment = (endGain - startGain) / (float) numSamples; + auto* d = channels[channel] + startSample; + + while (--numSamples >= 0) + { + *d++ *= startGain; + startGain += increment; + } + } + } + } + + /** Applies a range of gains to a region of all channels. + + The gain that is applied to each sample will vary from + startGain on the first sample to endGain on the last Sample, + so it can be used to do basic fades. + + For speed, this doesn't check whether the sample numbers + are in-range, so be careful! + */ + void applyGainRamp (int startSample, int numSamples, + Type startGain, Type endGain) noexcept + { + for (int i = 0; i < numChannels; ++i) + applyGainRamp (i, startSample, numSamples, startGain, endGain); + } + + /** Adds samples from another buffer to this one. + + @param destChannel the channel within this buffer to add the samples to + @param destStartSample the start sample within this buffer's channel + @param source the source buffer to add from + @param sourceChannel the channel within the source buffer to read from + @param sourceStartSample the offset within the source buffer's channel to start reading samples from + @param numSamples the number of samples to process + @param gainToApplyToSource an optional gain to apply to the source samples before they are + added to this buffer's samples + + @see copyFrom + */ + void addFrom (int destChannel, + int destStartSample, + const AudioBuffer& source, + int sourceChannel, + int sourceStartSample, + int numSamples, + Type gainToApplyToSource = Type (1)) noexcept + { + jassert (&source != this || sourceChannel != destChannel); + jassert (isPositiveAndBelow (destChannel, numChannels)); + jassert (destStartSample >= 0 && numSamples >= 0 && destStartSample + numSamples <= size); + jassert (isPositiveAndBelow (sourceChannel, source.numChannels)); + jassert (sourceStartSample >= 0 && sourceStartSample + numSamples <= source.size); + + if (gainToApplyToSource != 0 && numSamples > 0 && ! source.isClear) + { + auto* d = channels[destChannel] + destStartSample; + auto* s = source.channels[sourceChannel] + sourceStartSample; + + if (isClear) + { + isClear = false; + + if (gainToApplyToSource != Type (1)) + FloatVectorOperations::copyWithMultiply (d, s, gainToApplyToSource, numSamples); + else + FloatVectorOperations::copy (d, s, numSamples); + } + else + { + if (gainToApplyToSource != Type (1)) + FloatVectorOperations::addWithMultiply (d, s, gainToApplyToSource, numSamples); + else + FloatVectorOperations::add (d, s, numSamples); + } + } + } + + /** Adds samples from an array of floats to one of the channels. + + @param destChannel the channel within this buffer to add the samples to + @param destStartSample the start sample within this buffer's channel + @param source the source data to use + @param numSamples the number of samples to process + @param gainToApplyToSource an optional gain to apply to the source samples before they are + added to this buffer's samples + + @see copyFrom + */ + void addFrom (int destChannel, + int destStartSample, + const Type* source, + int numSamples, + Type gainToApplyToSource = Type (1)) noexcept + { + jassert (isPositiveAndBelow (destChannel, numChannels)); + jassert (destStartSample >= 0 && numSamples >= 0 && destStartSample + numSamples <= size); + jassert (source != nullptr); + + if (gainToApplyToSource != 0 && numSamples > 0) + { + auto* d = channels[destChannel] + destStartSample; + + if (isClear) + { + isClear = false; + + if (gainToApplyToSource != Type (1)) + FloatVectorOperations::copyWithMultiply (d, source, gainToApplyToSource, numSamples); + else + FloatVectorOperations::copy (d, source, numSamples); + } + else + { + if (gainToApplyToSource != Type (1)) + FloatVectorOperations::addWithMultiply (d, source, gainToApplyToSource, numSamples); + else + FloatVectorOperations::add (d, source, numSamples); + } + } + } + + + /** Adds samples from an array of floats, applying a gain ramp to them. + + @param destChannel the channel within this buffer to add the samples to + @param destStartSample the start sample within this buffer's channel + @param source the source data to use + @param numSamples the number of samples to process + @param startGain the gain to apply to the first sample (this is multiplied with + the source samples before they are added to this buffer) + @param endGain the gain to apply to the final sample. The gain is linearly + interpolated between the first and last samples. + */ + void addFromWithRamp (int destChannel, + int destStartSample, + const Type* source, + int numSamples, + Type startGain, + Type endGain) noexcept + { + if (startGain == endGain) + { + addFrom (destChannel, destStartSample, source, numSamples, startGain); + } + else + { + jassert (isPositiveAndBelow (destChannel, numChannels)); + jassert (destStartSample >= 0 && numSamples >= 0 && destStartSample + numSamples <= size); + jassert (source != nullptr); + + if (numSamples > 0) + { + isClear = false; + const auto increment = (endGain - startGain) / numSamples; + auto* d = channels[destChannel] + destStartSample; + + while (--numSamples >= 0) + { + *d++ += startGain * *source++; + startGain += increment; + } + } + } + } + + /** Copies samples from another buffer to this one. + + @param destChannel the channel within this buffer to copy the samples to + @param destStartSample the start sample within this buffer's channel + @param source the source buffer to read from + @param sourceChannel the channel within the source buffer to read from + @param sourceStartSample the offset within the source buffer's channel to start reading samples from + @param numSamples the number of samples to process + + @see addFrom + */ + void copyFrom (int destChannel, + int destStartSample, + const AudioBuffer& source, + int sourceChannel, + int sourceStartSample, + int numSamples) noexcept + { + jassert (&source != this || sourceChannel != destChannel); + jassert (isPositiveAndBelow (destChannel, numChannels)); + jassert (destStartSample >= 0 && destStartSample + numSamples <= size); + jassert (isPositiveAndBelow (sourceChannel, source.numChannels)); + jassert (sourceStartSample >= 0 && numSamples >= 0 && sourceStartSample + numSamples <= source.size); + + if (numSamples > 0) + { + if (source.isClear) + { + if (! isClear) + FloatVectorOperations::clear (channels[destChannel] + destStartSample, numSamples); + } + else + { + isClear = false; + FloatVectorOperations::copy (channels[destChannel] + destStartSample, + source.channels[sourceChannel] + sourceStartSample, + numSamples); + } + } + } + + /** Copies samples from an array of floats into one of the channels. + + @param destChannel the channel within this buffer to copy the samples to + @param destStartSample the start sample within this buffer's channel + @param source the source buffer to read from + @param numSamples the number of samples to process + + @see addFrom + */ + void copyFrom (int destChannel, + int destStartSample, + const Type* source, + int numSamples) noexcept + { + jassert (isPositiveAndBelow (destChannel, numChannels)); + jassert (destStartSample >= 0 && numSamples >= 0 && destStartSample + numSamples <= size); + jassert (source != nullptr); + + if (numSamples > 0) + { + isClear = false; + FloatVectorOperations::copy (channels[destChannel] + destStartSample, source, numSamples); + } + } + + /** Copies samples from an array of floats into one of the channels, applying a gain to it. + + @param destChannel the channel within this buffer to copy the samples to + @param destStartSample the start sample within this buffer's channel + @param source the source buffer to read from + @param numSamples the number of samples to process + @param gain the gain to apply + + @see addFrom + */ + void copyFrom (int destChannel, + int destStartSample, + const Type* source, + int numSamples, + Type gain) noexcept + { + jassert (isPositiveAndBelow (destChannel, numChannels)); + jassert (destStartSample >= 0 && numSamples >= 0 && destStartSample + numSamples <= size); + jassert (source != nullptr); + + if (numSamples > 0) + { + auto* d = channels[destChannel] + destStartSample; + + if (gain != Type (1)) + { + if (gain == Type()) + { + if (! isClear) + FloatVectorOperations::clear (d, numSamples); + } + else + { + isClear = false; + FloatVectorOperations::copyWithMultiply (d, source, gain, numSamples); + } + } + else + { + isClear = false; + FloatVectorOperations::copy (d, source, numSamples); + } + } + } + + /** Copies samples from an array of floats into one of the channels, applying a gain ramp. + + @param destChannel the channel within this buffer to copy the samples to + @param destStartSample the start sample within this buffer's channel + @param source the source buffer to read from + @param numSamples the number of samples to process + @param startGain the gain to apply to the first sample (this is multiplied with + the source samples before they are copied to this buffer) + @param endGain the gain to apply to the final sample. The gain is linearly + interpolated between the first and last samples. + + @see addFrom + */ + void copyFromWithRamp (int destChannel, + int destStartSample, + const Type* source, + int numSamples, + Type startGain, + Type endGain) noexcept + { + if (startGain == endGain) + { + copyFrom (destChannel, destStartSample, source, numSamples, startGain); + } + else + { + jassert (isPositiveAndBelow (destChannel, numChannels)); + jassert (destStartSample >= 0 && numSamples >= 0 && destStartSample + numSamples <= size); + jassert (source != nullptr); + + if (numSamples > 0) + { + isClear = false; + const auto increment = (endGain - startGain) / numSamples; + auto* d = channels[destChannel] + destStartSample; + + while (--numSamples >= 0) + { + *d++ = startGain * *source++; + startGain += increment; + } + } + } + } + + /** Returns a Range indicating the lowest and highest sample values in a given section. + + @param channel the channel to read from + @param startSample the start sample within the channel + @param numSamples the number of samples to check + */ + Range findMinMax (int channel, int startSample, int numSamples) const noexcept + { + jassert (isPositiveAndBelow (channel, numChannels)); + jassert (startSample >= 0 && numSamples >= 0 && startSample + numSamples <= size); + + if (isClear) + return { Type (0), Type (0) }; + + return FloatVectorOperations::findMinAndMax (channels[channel] + startSample, numSamples); + } + + /** Finds the highest absolute sample value within a region of a channel. */ + Type getMagnitude (int channel, int startSample, int numSamples) const noexcept + { + jassert (isPositiveAndBelow (channel, numChannels)); + jassert (startSample >= 0 && numSamples >= 0 && startSample + numSamples <= size); + + if (isClear) + return Type (0); + + auto r = findMinMax (channel, startSample, numSamples); + + return jmax (r.getStart(), -r.getStart(), r.getEnd(), -r.getEnd()); + } + + /** Finds the highest absolute sample value within a region on all channels. */ + Type getMagnitude (int startSample, int numSamples) const noexcept + { + Type mag (0); + + if (! isClear) + for (int i = 0; i < numChannels; ++i) + mag = jmax (mag, getMagnitude (i, startSample, numSamples)); + + return mag; + } + + /** Returns the root mean squared level for a region of a channel. */ + Type getRMSLevel (int channel, int startSample, int numSamples) const noexcept + { + jassert (isPositiveAndBelow (channel, numChannels)); + jassert (startSample >= 0 && numSamples >= 0 && startSample + numSamples <= size); + + if (numSamples <= 0 || channel < 0 || channel >= numChannels || isClear) + return Type (0); + + auto* data = channels[channel] + startSample; + double sum = 0.0; + + for (int i = 0; i < numSamples; ++i) + { + auto sample = data[i]; + sum += sample * sample; + } + + return static_cast (std::sqrt (sum / numSamples)); + } + + /** Reverses a part of a channel. */ + void reverse (int channel, int startSample, int numSamples) const noexcept + { + jassert (isPositiveAndBelow (channel, numChannels)); + jassert (startSample >= 0 && numSamples >= 0 && startSample + numSamples <= size); + + if (! isClear) + std::reverse (channels[channel] + startSample, + channels[channel] + startSample + numSamples); + } + + /** Reverses a part of the buffer. */ + void reverse (int startSample, int numSamples) const noexcept + { + for (int i = 0; i < numChannels; ++i) + reverse (i, startSample, numSamples); + } + + //============================================================================== + /** This allows templated code that takes an AudioBuffer to access its sample type. */ + using SampleType = Type; + +private: + //============================================================================== + int numChannels = 0, size = 0; + size_t allocatedBytes = 0; + Type** channels; + HeapBlock allocatedData; + Type* preallocatedChannelSpace[32]; + std::atomic isClear { false }; + + void allocateData() + { + static_assert (std::alignment_of::value <= std::alignment_of::value, + "AudioBuffer cannot hold types with alignment requirements larger than that guaranteed by malloc"); + jassert (size >= 0); + + auto channelListSize = (size_t) (numChannels + 1) * sizeof (Type*); + auto requiredSampleAlignment = std::alignment_of::value; + size_t alignmentOverflow = channelListSize % requiredSampleAlignment; + + if (alignmentOverflow != 0) + channelListSize += requiredSampleAlignment - alignmentOverflow; + + allocatedBytes = (size_t) numChannels * (size_t) size * sizeof (Type) + channelListSize + 32; + allocatedData.malloc (allocatedBytes); + channels = reinterpret_cast (allocatedData.get()); + auto chan = reinterpret_cast (allocatedData + channelListSize); + + for (int i = 0; i < numChannels; ++i) + { + channels[i] = chan; + chan += size; + } + + channels[numChannels] = nullptr; + isClear = false; + } + + void allocateChannels (Type* const* dataToReferTo, int offset) + { + jassert (offset >= 0); + + // (try to avoid doing a malloc here, as that'll blow up things like Pro-Tools) + if (numChannels < (int) numElementsInArray (preallocatedChannelSpace)) + { + channels = static_cast (preallocatedChannelSpace); + } + else + { + allocatedData.malloc (numChannels + 1, sizeof (Type*)); + channels = reinterpret_cast (allocatedData.get()); + } + + for (int i = 0; i < numChannels; ++i) + { + // you have to pass in the same number of valid pointers as numChannels + jassert (dataToReferTo[i] != nullptr); + channels[i] = dataToReferTo[i] + offset; + } + + channels[numChannels] = nullptr; + isClear = false; + } + + JUCE_LEAK_DETECTOR (AudioBuffer) +}; + +//============================================================================== +/** + A multi-channel buffer of 32-bit floating point audio samples. + + This type is here for backwards compatibility with the older AudioSampleBuffer + class, which was fixed for 32-bit data, but is otherwise the same as the new + templated AudioBuffer class. + + @see AudioBuffer +*/ +using AudioSampleBuffer = AudioBuffer; + +} // namespace juce diff --git a/JuceLibraryCode/modules/juce_audio_basics/buffers/juce_FloatVectorOperations.cpp b/JuceLibraryCode/modules/juce_audio_basics/buffers/juce_FloatVectorOperations.cpp index a01ad84..26b33f2 100644 --- a/JuceLibraryCode/modules/juce_audio_basics/buffers/juce_FloatVectorOperations.cpp +++ b/JuceLibraryCode/modules/juce_audio_basics/buffers/juce_FloatVectorOperations.cpp @@ -1,1305 +1,1305 @@ -/* - ============================================================================== - - This file is part of the JUCE library. - Copyright (c) 2017 - ROLI Ltd. - - JUCE is an open source library subject to commercial or open-source - licensing. - - The code included in this file is provided under the terms of the ISC license - http://www.isc.org/downloads/software-support-policy/isc-license. Permission - To use, copy, modify, and/or distribute this software for any purpose with or - without fee is hereby granted provided that the above copyright notice and - this permission notice appear in all copies. - - JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER - EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE - DISCLAIMED. - - ============================================================================== -*/ - -namespace juce -{ - -namespace FloatVectorHelpers -{ - #define JUCE_INCREMENT_SRC_DEST dest += (16 / sizeof (*dest)); src += (16 / sizeof (*dest)); - #define JUCE_INCREMENT_SRC1_SRC2_DEST dest += (16 / sizeof (*dest)); src1 += (16 / sizeof (*dest)); src2 += (16 / sizeof (*dest)); - #define JUCE_INCREMENT_DEST dest += (16 / sizeof (*dest)); - - #if JUCE_USE_SSE_INTRINSICS - inline static bool isAligned (const void* p) noexcept - { - return (((pointer_sized_int) p) & 15) == 0; - } - - struct BasicOps32 - { - using Type = float; - using ParallelType = __m128; - using IntegerType = __m128; - enum { numParallel = 4 }; - - // Integer and parallel types are the same for SSE. On neon they have different types - static forcedinline IntegerType toint (ParallelType v) noexcept { return v; } - static forcedinline ParallelType toflt (IntegerType v) noexcept { return v; } - - static forcedinline ParallelType load1 (Type v) noexcept { return _mm_load1_ps (&v); } - static forcedinline ParallelType loadA (const Type* v) noexcept { return _mm_load_ps (v); } - static forcedinline ParallelType loadU (const Type* v) noexcept { return _mm_loadu_ps (v); } - static forcedinline void storeA (Type* dest, ParallelType a) noexcept { _mm_store_ps (dest, a); } - static forcedinline void storeU (Type* dest, ParallelType a) noexcept { _mm_storeu_ps (dest, a); } - - static forcedinline ParallelType add (ParallelType a, ParallelType b) noexcept { return _mm_add_ps (a, b); } - static forcedinline ParallelType sub (ParallelType a, ParallelType b) noexcept { return _mm_sub_ps (a, b); } - static forcedinline ParallelType mul (ParallelType a, ParallelType b) noexcept { return _mm_mul_ps (a, b); } - static forcedinline ParallelType max (ParallelType a, ParallelType b) noexcept { return _mm_max_ps (a, b); } - static forcedinline ParallelType min (ParallelType a, ParallelType b) noexcept { return _mm_min_ps (a, b); } - - static forcedinline ParallelType bit_and (ParallelType a, ParallelType b) noexcept { return _mm_and_ps (a, b); } - static forcedinline ParallelType bit_not (ParallelType a, ParallelType b) noexcept { return _mm_andnot_ps (a, b); } - static forcedinline ParallelType bit_or (ParallelType a, ParallelType b) noexcept { return _mm_or_ps (a, b); } - static forcedinline ParallelType bit_xor (ParallelType a, ParallelType b) noexcept { return _mm_xor_ps (a, b); } - - static forcedinline Type max (ParallelType a) noexcept { Type v[numParallel]; storeU (v, a); return jmax (v[0], v[1], v[2], v[3]); } - static forcedinline Type min (ParallelType a) noexcept { Type v[numParallel]; storeU (v, a); return jmin (v[0], v[1], v[2], v[3]); } - }; - - struct BasicOps64 - { - using Type = double; - using ParallelType = __m128d; - using IntegerType = __m128d; - enum { numParallel = 2 }; - - // Integer and parallel types are the same for SSE. On neon they have different types - static forcedinline IntegerType toint (ParallelType v) noexcept { return v; } - static forcedinline ParallelType toflt (IntegerType v) noexcept { return v; } - - static forcedinline ParallelType load1 (Type v) noexcept { return _mm_load1_pd (&v); } - static forcedinline ParallelType loadA (const Type* v) noexcept { return _mm_load_pd (v); } - static forcedinline ParallelType loadU (const Type* v) noexcept { return _mm_loadu_pd (v); } - static forcedinline void storeA (Type* dest, ParallelType a) noexcept { _mm_store_pd (dest, a); } - static forcedinline void storeU (Type* dest, ParallelType a) noexcept { _mm_storeu_pd (dest, a); } - - static forcedinline ParallelType add (ParallelType a, ParallelType b) noexcept { return _mm_add_pd (a, b); } - static forcedinline ParallelType sub (ParallelType a, ParallelType b) noexcept { return _mm_sub_pd (a, b); } - static forcedinline ParallelType mul (ParallelType a, ParallelType b) noexcept { return _mm_mul_pd (a, b); } - static forcedinline ParallelType max (ParallelType a, ParallelType b) noexcept { return _mm_max_pd (a, b); } - static forcedinline ParallelType min (ParallelType a, ParallelType b) noexcept { return _mm_min_pd (a, b); } - - static forcedinline ParallelType bit_and (ParallelType a, ParallelType b) noexcept { return _mm_and_pd (a, b); } - static forcedinline ParallelType bit_not (ParallelType a, ParallelType b) noexcept { return _mm_andnot_pd (a, b); } - static forcedinline ParallelType bit_or (ParallelType a, ParallelType b) noexcept { return _mm_or_pd (a, b); } - static forcedinline ParallelType bit_xor (ParallelType a, ParallelType b) noexcept { return _mm_xor_pd (a, b); } - - static forcedinline Type max (ParallelType a) noexcept { Type v[numParallel]; storeU (v, a); return jmax (v[0], v[1]); } - static forcedinline Type min (ParallelType a) noexcept { Type v[numParallel]; storeU (v, a); return jmin (v[0], v[1]); } - }; - - - - #define JUCE_BEGIN_VEC_OP \ - using Mode = FloatVectorHelpers::ModeType::Mode; \ - { \ - const int numLongOps = num / Mode::numParallel; - - #define JUCE_FINISH_VEC_OP(normalOp) \ - num &= (Mode::numParallel - 1); \ - if (num == 0) return; \ - } \ - for (int i = 0; i < num; ++i) normalOp; - - #define JUCE_PERFORM_VEC_OP_DEST(normalOp, vecOp, locals, setupOp) \ - JUCE_BEGIN_VEC_OP \ - setupOp \ - if (FloatVectorHelpers::isAligned (dest)) JUCE_VEC_LOOP (vecOp, dummy, Mode::loadA, Mode::storeA, locals, JUCE_INCREMENT_DEST) \ - else JUCE_VEC_LOOP (vecOp, dummy, Mode::loadU, Mode::storeU, locals, JUCE_INCREMENT_DEST) \ - JUCE_FINISH_VEC_OP (normalOp) - - #define JUCE_PERFORM_VEC_OP_SRC_DEST(normalOp, vecOp, locals, increment, setupOp) \ - JUCE_BEGIN_VEC_OP \ - setupOp \ - if (FloatVectorHelpers::isAligned (dest)) \ - { \ - if (FloatVectorHelpers::isAligned (src)) JUCE_VEC_LOOP (vecOp, Mode::loadA, Mode::loadA, Mode::storeA, locals, increment) \ - else JUCE_VEC_LOOP (vecOp, Mode::loadU, Mode::loadA, Mode::storeA, locals, increment) \ - }\ - else \ - { \ - if (FloatVectorHelpers::isAligned (src)) JUCE_VEC_LOOP (vecOp, Mode::loadA, Mode::loadU, Mode::storeU, locals, increment) \ - else JUCE_VEC_LOOP (vecOp, Mode::loadU, Mode::loadU, Mode::storeU, locals, increment) \ - } \ - JUCE_FINISH_VEC_OP (normalOp) - - #define JUCE_PERFORM_VEC_OP_SRC1_SRC2_DEST(normalOp, vecOp, locals, increment, setupOp) \ - JUCE_BEGIN_VEC_OP \ - setupOp \ - if (FloatVectorHelpers::isAligned (dest)) \ - { \ - if (FloatVectorHelpers::isAligned (src1)) \ - { \ - if (FloatVectorHelpers::isAligned (src2)) JUCE_VEC_LOOP_TWO_SOURCES (vecOp, Mode::loadA, Mode::loadA, Mode::storeA, locals, increment) \ - else JUCE_VEC_LOOP_TWO_SOURCES (vecOp, Mode::loadA, Mode::loadU, Mode::storeA, locals, increment) \ - } \ - else \ - { \ - if (FloatVectorHelpers::isAligned (src2)) JUCE_VEC_LOOP_TWO_SOURCES (vecOp, Mode::loadU, Mode::loadA, Mode::storeA, locals, increment) \ - else JUCE_VEC_LOOP_TWO_SOURCES (vecOp, Mode::loadU, Mode::loadU, Mode::storeA, locals, increment) \ - } \ - } \ - else \ - { \ - if (FloatVectorHelpers::isAligned (src1)) \ - { \ - if (FloatVectorHelpers::isAligned (src2)) JUCE_VEC_LOOP_TWO_SOURCES (vecOp, Mode::loadA, Mode::loadA, Mode::storeU, locals, increment) \ - else JUCE_VEC_LOOP_TWO_SOURCES (vecOp, Mode::loadA, Mode::loadU, Mode::storeU, locals, increment) \ - } \ - else \ - { \ - if (FloatVectorHelpers::isAligned (src2)) JUCE_VEC_LOOP_TWO_SOURCES (vecOp, Mode::loadU, Mode::loadA, Mode::storeU, locals, increment) \ - else JUCE_VEC_LOOP_TWO_SOURCES (vecOp, Mode::loadU, Mode::loadU, Mode::storeU, locals, increment) \ - } \ - } \ - JUCE_FINISH_VEC_OP (normalOp) - - #define JUCE_PERFORM_VEC_OP_SRC1_SRC2_DEST_DEST(normalOp, vecOp, locals, increment, setupOp) \ - JUCE_BEGIN_VEC_OP \ - setupOp \ - if (FloatVectorHelpers::isAligned (dest)) \ - { \ - if (FloatVectorHelpers::isAligned (src1)) \ - { \ - if (FloatVectorHelpers::isAligned (src2)) JUCE_VEC_LOOP_TWO_SOURCES_WITH_DEST_LOAD (vecOp, Mode::loadA, Mode::loadA, Mode::loadA, Mode::storeA, locals, increment) \ - else JUCE_VEC_LOOP_TWO_SOURCES_WITH_DEST_LOAD (vecOp, Mode::loadA, Mode::loadU, Mode::loadA, Mode::storeA, locals, increment) \ - } \ - else \ - { \ - if (FloatVectorHelpers::isAligned (src2)) JUCE_VEC_LOOP_TWO_SOURCES_WITH_DEST_LOAD (vecOp, Mode::loadU, Mode::loadA, Mode::loadA, Mode::storeA, locals, increment) \ - else JUCE_VEC_LOOP_TWO_SOURCES_WITH_DEST_LOAD (vecOp, Mode::loadU, Mode::loadU, Mode::loadA, Mode::storeA, locals, increment) \ - } \ - } \ - else \ - { \ - if (FloatVectorHelpers::isAligned (src1)) \ - { \ - if (FloatVectorHelpers::isAligned (src2)) JUCE_VEC_LOOP_TWO_SOURCES_WITH_DEST_LOAD (vecOp, Mode::loadA, Mode::loadA, Mode::loadU, Mode::storeU, locals, increment) \ - else JUCE_VEC_LOOP_TWO_SOURCES_WITH_DEST_LOAD (vecOp, Mode::loadA, Mode::loadU, Mode::loadU, Mode::storeU, locals, increment) \ - } \ - else \ - { \ - if (FloatVectorHelpers::isAligned (src2)) JUCE_VEC_LOOP_TWO_SOURCES_WITH_DEST_LOAD (vecOp, Mode::loadU, Mode::loadA, Mode::loadU, Mode::storeU, locals, increment) \ - else JUCE_VEC_LOOP_TWO_SOURCES_WITH_DEST_LOAD (vecOp, Mode::loadU, Mode::loadU, Mode::loadU, Mode::storeU, locals, increment) \ - } \ - } \ - JUCE_FINISH_VEC_OP (normalOp) - - - //============================================================================== - #elif JUCE_USE_ARM_NEON - - struct BasicOps32 - { - using Type = float; - using ParallelType = float32x4_t; - using IntegerType = uint32x4_t; - union signMaskUnion { ParallelType f; IntegerType i; }; - enum { numParallel = 4 }; - - static forcedinline IntegerType toint (ParallelType v) noexcept { signMaskUnion u; u.f = v; return u.i; } - static forcedinline ParallelType toflt (IntegerType v) noexcept { signMaskUnion u; u.i = v; return u.f; } - - static forcedinline ParallelType load1 (Type v) noexcept { return vld1q_dup_f32 (&v); } - static forcedinline ParallelType loadA (const Type* v) noexcept { return vld1q_f32 (v); } - static forcedinline ParallelType loadU (const Type* v) noexcept { return vld1q_f32 (v); } - static forcedinline void storeA (Type* dest, ParallelType a) noexcept { vst1q_f32 (dest, a); } - static forcedinline void storeU (Type* dest, ParallelType a) noexcept { vst1q_f32 (dest, a); } - - static forcedinline ParallelType add (ParallelType a, ParallelType b) noexcept { return vaddq_f32 (a, b); } - static forcedinline ParallelType sub (ParallelType a, ParallelType b) noexcept { return vsubq_f32 (a, b); } - static forcedinline ParallelType mul (ParallelType a, ParallelType b) noexcept { return vmulq_f32 (a, b); } - static forcedinline ParallelType max (ParallelType a, ParallelType b) noexcept { return vmaxq_f32 (a, b); } - static forcedinline ParallelType min (ParallelType a, ParallelType b) noexcept { return vminq_f32 (a, b); } - - static forcedinline ParallelType bit_and (ParallelType a, ParallelType b) noexcept { return toflt (vandq_u32 (toint (a), toint (b))); } - static forcedinline ParallelType bit_not (ParallelType a, ParallelType b) noexcept { return toflt (vbicq_u32 (toint (a), toint (b))); } - static forcedinline ParallelType bit_or (ParallelType a, ParallelType b) noexcept { return toflt (vorrq_u32 (toint (a), toint (b))); } - static forcedinline ParallelType bit_xor (ParallelType a, ParallelType b) noexcept { return toflt (veorq_u32 (toint (a), toint (b))); } - - static forcedinline Type max (ParallelType a) noexcept { Type v[numParallel]; storeU (v, a); return jmax (v[0], v[1], v[2], v[3]); } - static forcedinline Type min (ParallelType a) noexcept { Type v[numParallel]; storeU (v, a); return jmin (v[0], v[1], v[2], v[3]); } - }; - - struct BasicOps64 - { - using Type = double; - using ParallelType = double; - using IntegerType = uint64; - union signMaskUnion { ParallelType f; IntegerType i; }; - enum { numParallel = 1 }; - - static forcedinline IntegerType toint (ParallelType v) noexcept { signMaskUnion u; u.f = v; return u.i; } - static forcedinline ParallelType toflt (IntegerType v) noexcept { signMaskUnion u; u.i = v; return u.f; } - - static forcedinline ParallelType load1 (Type v) noexcept { return v; } - static forcedinline ParallelType loadA (const Type* v) noexcept { return *v; } - static forcedinline ParallelType loadU (const Type* v) noexcept { return *v; } - static forcedinline void storeA (Type* dest, ParallelType a) noexcept { *dest = a; } - static forcedinline void storeU (Type* dest, ParallelType a) noexcept { *dest = a; } - - static forcedinline ParallelType add (ParallelType a, ParallelType b) noexcept { return a + b; } - static forcedinline ParallelType sub (ParallelType a, ParallelType b) noexcept { return a - b; } - static forcedinline ParallelType mul (ParallelType a, ParallelType b) noexcept { return a * b; } - static forcedinline ParallelType max (ParallelType a, ParallelType b) noexcept { return jmax (a, b); } - static forcedinline ParallelType min (ParallelType a, ParallelType b) noexcept { return jmin (a, b); } - - static forcedinline ParallelType bit_and (ParallelType a, ParallelType b) noexcept { return toflt (toint (a) & toint (b)); } - static forcedinline ParallelType bit_not (ParallelType a, ParallelType b) noexcept { return toflt ((~toint (a)) & toint (b)); } - static forcedinline ParallelType bit_or (ParallelType a, ParallelType b) noexcept { return toflt (toint (a) | toint (b)); } - static forcedinline ParallelType bit_xor (ParallelType a, ParallelType b) noexcept { return toflt (toint (a) ^ toint (b)); } - - static forcedinline Type max (ParallelType a) noexcept { return a; } - static forcedinline Type min (ParallelType a) noexcept { return a; } - }; - - #define JUCE_BEGIN_VEC_OP \ - using Mode = FloatVectorHelpers::ModeType::Mode; \ - if (Mode::numParallel > 1) \ - { \ - const int numLongOps = num / Mode::numParallel; - - #define JUCE_FINISH_VEC_OP(normalOp) \ - num &= (Mode::numParallel - 1); \ - if (num == 0) return; \ - } \ - for (int i = 0; i < num; ++i) normalOp; - - #define JUCE_PERFORM_VEC_OP_DEST(normalOp, vecOp, locals, setupOp) \ - JUCE_BEGIN_VEC_OP \ - setupOp \ - JUCE_VEC_LOOP (vecOp, dummy, Mode::loadU, Mode::storeU, locals, JUCE_INCREMENT_DEST) \ - JUCE_FINISH_VEC_OP (normalOp) - - #define JUCE_PERFORM_VEC_OP_SRC_DEST(normalOp, vecOp, locals, increment, setupOp) \ - JUCE_BEGIN_VEC_OP \ - setupOp \ - JUCE_VEC_LOOP (vecOp, Mode::loadU, Mode::loadU, Mode::storeU, locals, increment) \ - JUCE_FINISH_VEC_OP (normalOp) - - #define JUCE_PERFORM_VEC_OP_SRC1_SRC2_DEST(normalOp, vecOp, locals, increment, setupOp) \ - JUCE_BEGIN_VEC_OP \ - setupOp \ - JUCE_VEC_LOOP_TWO_SOURCES (vecOp, Mode::loadU, Mode::loadU, Mode::storeU, locals, increment) \ - JUCE_FINISH_VEC_OP (normalOp) - - #define JUCE_PERFORM_VEC_OP_SRC1_SRC2_DEST_DEST(normalOp, vecOp, locals, increment, setupOp) \ - JUCE_BEGIN_VEC_OP \ - setupOp \ - JUCE_VEC_LOOP_TWO_SOURCES_WITH_DEST_LOAD (vecOp, Mode::loadU, Mode::loadU, Mode::loadU, Mode::storeU, locals, increment) \ - JUCE_FINISH_VEC_OP (normalOp) - - - //============================================================================== - #else - #define JUCE_PERFORM_VEC_OP_DEST(normalOp, vecOp, locals, setupOp) \ - for (int i = 0; i < num; ++i) normalOp; - - #define JUCE_PERFORM_VEC_OP_SRC_DEST(normalOp, vecOp, locals, increment, setupOp) \ - for (int i = 0; i < num; ++i) normalOp; - - #define JUCE_PERFORM_VEC_OP_SRC1_SRC2_DEST(normalOp, vecOp, locals, increment, setupOp) \ - for (int i = 0; i < num; ++i) normalOp; - - #define JUCE_PERFORM_VEC_OP_SRC1_SRC2_DEST_DEST(normalOp, vecOp, locals, increment, setupOp) \ - for (int i = 0; i < num; ++i) normalOp; - - #endif - - //============================================================================== - #define JUCE_VEC_LOOP(vecOp, srcLoad, dstLoad, dstStore, locals, increment) \ - for (int i = 0; i < numLongOps; ++i) \ - { \ - locals (srcLoad, dstLoad); \ - dstStore (dest, vecOp); \ - increment; \ - } - - #define JUCE_VEC_LOOP_TWO_SOURCES(vecOp, src1Load, src2Load, dstStore, locals, increment) \ - for (int i = 0; i < numLongOps; ++i) \ - { \ - locals (src1Load, src2Load); \ - dstStore (dest, vecOp); \ - increment; \ - } - - #define JUCE_VEC_LOOP_TWO_SOURCES_WITH_DEST_LOAD(vecOp, src1Load, src2Load, dstLoad, dstStore, locals, increment) \ - for (int i = 0; i < numLongOps; ++i) \ - { \ - locals (src1Load, src2Load, dstLoad); \ - dstStore (dest, vecOp); \ - increment; \ - } - - #define JUCE_LOAD_NONE(srcLoad, dstLoad) - #define JUCE_LOAD_DEST(srcLoad, dstLoad) const Mode::ParallelType d = dstLoad (dest); - #define JUCE_LOAD_SRC(srcLoad, dstLoad) const Mode::ParallelType s = srcLoad (src); - #define JUCE_LOAD_SRC1_SRC2(src1Load, src2Load) const Mode::ParallelType s1 = src1Load (src1), s2 = src2Load (src2); - #define JUCE_LOAD_SRC1_SRC2_DEST(src1Load, src2Load, dstLoad) const Mode::ParallelType d = dstLoad (dest), s1 = src1Load (src1), s2 = src2Load (src2); - #define JUCE_LOAD_SRC_DEST(srcLoad, dstLoad) const Mode::ParallelType d = dstLoad (dest), s = srcLoad (src); - - union signMask32 { float f; uint32 i; }; - union signMask64 { double d; uint64 i; }; - - #if JUCE_USE_SSE_INTRINSICS || JUCE_USE_ARM_NEON - template struct ModeType { using Mode = BasicOps32; }; - template<> struct ModeType<8> { using Mode = BasicOps64; }; - - template - struct MinMax - { - using Type = typename Mode::Type; - using ParallelType = typename Mode::ParallelType; - - static Type findMinOrMax (const Type* src, int num, const bool isMinimum) noexcept - { - int numLongOps = num / Mode::numParallel; - - if (numLongOps > 1) - { - ParallelType val; - - #if ! JUCE_USE_ARM_NEON - if (isAligned (src)) - { - val = Mode::loadA (src); - - if (isMinimum) - { - while (--numLongOps > 0) - { - src += Mode::numParallel; - val = Mode::min (val, Mode::loadA (src)); - } - } - else - { - while (--numLongOps > 0) - { - src += Mode::numParallel; - val = Mode::max (val, Mode::loadA (src)); - } - } - } - else - #endif - { - val = Mode::loadU (src); - - if (isMinimum) - { - while (--numLongOps > 0) - { - src += Mode::numParallel; - val = Mode::min (val, Mode::loadU (src)); - } - } - else - { - while (--numLongOps > 0) - { - src += Mode::numParallel; - val = Mode::max (val, Mode::loadU (src)); - } - } - } - - Type result = isMinimum ? Mode::min (val) - : Mode::max (val); - - num &= (Mode::numParallel - 1); - src += Mode::numParallel; - - for (int i = 0; i < num; ++i) - result = isMinimum ? jmin (result, src[i]) - : jmax (result, src[i]); - - return result; - } - - return isMinimum ? juce::findMinimum (src, num) - : juce::findMaximum (src, num); - } - - static Range findMinAndMax (const Type* src, int num) noexcept - { - int numLongOps = num / Mode::numParallel; - - if (numLongOps > 1) - { - ParallelType mn, mx; - - #if ! JUCE_USE_ARM_NEON - if (isAligned (src)) - { - mn = Mode::loadA (src); - mx = mn; - - while (--numLongOps > 0) - { - src += Mode::numParallel; - const ParallelType v = Mode::loadA (src); - mn = Mode::min (mn, v); - mx = Mode::max (mx, v); - } - } - else - #endif - { - mn = Mode::loadU (src); - mx = mn; - - while (--numLongOps > 0) - { - src += Mode::numParallel; - const ParallelType v = Mode::loadU (src); - mn = Mode::min (mn, v); - mx = Mode::max (mx, v); - } - } - - Range result (Mode::min (mn), - Mode::max (mx)); - - num &= (Mode::numParallel - 1); - src += Mode::numParallel; - - for (int i = 0; i < num; ++i) - result = result.getUnionWith (src[i]); - - return result; - } - - return Range::findMinAndMax (src, num); - } - }; - #endif -} - -//============================================================================== -namespace -{ - #if JUCE_USE_VDSP_FRAMEWORK - // This casts away constness to account for slightly different vDSP function signatures - // in OSX 10.8 SDK and below. Can be safely removed once those SDKs are obsolete. - template - ValueType* osx108sdkCompatibilityCast (const ValueType* arg) noexcept { return const_cast (arg); } - #endif -} - -//============================================================================== -void JUCE_CALLTYPE FloatVectorOperations::clear (float* dest, int num) noexcept -{ - #if JUCE_USE_VDSP_FRAMEWORK - vDSP_vclr (dest, 1, (size_t) num); - #else - zeromem (dest, (size_t) num * sizeof (float)); - #endif -} - -void JUCE_CALLTYPE FloatVectorOperations::clear (double* dest, int num) noexcept -{ - #if JUCE_USE_VDSP_FRAMEWORK - vDSP_vclrD (dest, 1, (size_t) num); - #else - zeromem (dest, (size_t) num * sizeof (double)); - #endif -} - -void JUCE_CALLTYPE FloatVectorOperations::fill (float* dest, float valueToFill, int num) noexcept -{ - #if JUCE_USE_VDSP_FRAMEWORK - vDSP_vfill (&valueToFill, dest, 1, (size_t) num); - #else - JUCE_PERFORM_VEC_OP_DEST (dest[i] = valueToFill, val, JUCE_LOAD_NONE, - const Mode::ParallelType val = Mode::load1 (valueToFill);) - #endif -} - -void JUCE_CALLTYPE FloatVectorOperations::fill (double* dest, double valueToFill, int num) noexcept -{ - #if JUCE_USE_VDSP_FRAMEWORK - vDSP_vfillD (&valueToFill, dest, 1, (size_t) num); - #else - JUCE_PERFORM_VEC_OP_DEST (dest[i] = valueToFill, val, JUCE_LOAD_NONE, - const Mode::ParallelType val = Mode::load1 (valueToFill);) - #endif -} - -void JUCE_CALLTYPE FloatVectorOperations::copy (float* dest, const float* src, int num) noexcept -{ - memcpy (dest, src, (size_t) num * sizeof (float)); -} - -void JUCE_CALLTYPE FloatVectorOperations::copy (double* dest, const double* src, int num) noexcept -{ - memcpy (dest, src, (size_t) num * sizeof (double)); -} - -void JUCE_CALLTYPE FloatVectorOperations::copyWithMultiply (float* dest, const float* src, float multiplier, int num) noexcept -{ - #if JUCE_USE_VDSP_FRAMEWORK - vDSP_vsmul (src, 1, &multiplier, dest, 1, (vDSP_Length) num); - #else - JUCE_PERFORM_VEC_OP_SRC_DEST (dest[i] = src[i] * multiplier, Mode::mul (mult, s), - JUCE_LOAD_SRC, JUCE_INCREMENT_SRC_DEST, - const Mode::ParallelType mult = Mode::load1 (multiplier);) - #endif -} - -void JUCE_CALLTYPE FloatVectorOperations::copyWithMultiply (double* dest, const double* src, double multiplier, int num) noexcept -{ - #if JUCE_USE_VDSP_FRAMEWORK - vDSP_vsmulD (src, 1, &multiplier, dest, 1, (vDSP_Length) num); - #else - JUCE_PERFORM_VEC_OP_SRC_DEST (dest[i] = src[i] * multiplier, Mode::mul (mult, s), - JUCE_LOAD_SRC, JUCE_INCREMENT_SRC_DEST, - const Mode::ParallelType mult = Mode::load1 (multiplier);) - #endif -} - -void JUCE_CALLTYPE FloatVectorOperations::add (float* dest, float amount, int num) noexcept -{ - #if JUCE_USE_VDSP_FRAMEWORK - vDSP_vsadd (dest, 1, &amount, dest, 1, (vDSP_Length) num); - #else - JUCE_PERFORM_VEC_OP_DEST (dest[i] += amount, Mode::add (d, amountToAdd), JUCE_LOAD_DEST, - const Mode::ParallelType amountToAdd = Mode::load1 (amount);) - #endif -} - -void JUCE_CALLTYPE FloatVectorOperations::add (double* dest, double amount, int num) noexcept -{ - JUCE_PERFORM_VEC_OP_DEST (dest[i] += amount, Mode::add (d, amountToAdd), JUCE_LOAD_DEST, - const Mode::ParallelType amountToAdd = Mode::load1 (amount);) -} - -void JUCE_CALLTYPE FloatVectorOperations::add (float* dest, const float* src, float amount, int num) noexcept -{ - #if JUCE_USE_VDSP_FRAMEWORK - vDSP_vsadd (osx108sdkCompatibilityCast (src), 1, &amount, dest, 1, (vDSP_Length) num); - #else - JUCE_PERFORM_VEC_OP_SRC_DEST (dest[i] = src[i] + amount, Mode::add (am, s), - JUCE_LOAD_SRC, JUCE_INCREMENT_SRC_DEST, - const Mode::ParallelType am = Mode::load1 (amount);) - #endif -} - -void JUCE_CALLTYPE FloatVectorOperations::add (double* dest, const double* src, double amount, int num) noexcept -{ - #if JUCE_USE_VDSP_FRAMEWORK - vDSP_vsaddD (osx108sdkCompatibilityCast (src), 1, &amount, dest, 1, (vDSP_Length) num); - #else - JUCE_PERFORM_VEC_OP_SRC_DEST (dest[i] = src[i] + amount, Mode::add (am, s), - JUCE_LOAD_SRC, JUCE_INCREMENT_SRC_DEST, - const Mode::ParallelType am = Mode::load1 (amount);) - #endif -} - -void JUCE_CALLTYPE FloatVectorOperations::add (float* dest, const float* src, int num) noexcept -{ - #if JUCE_USE_VDSP_FRAMEWORK - vDSP_vadd (src, 1, dest, 1, dest, 1, (vDSP_Length) num); - #else - JUCE_PERFORM_VEC_OP_SRC_DEST (dest[i] += src[i], Mode::add (d, s), JUCE_LOAD_SRC_DEST, JUCE_INCREMENT_SRC_DEST, ) - #endif -} - -void JUCE_CALLTYPE FloatVectorOperations::add (double* dest, const double* src, int num) noexcept -{ - #if JUCE_USE_VDSP_FRAMEWORK - vDSP_vaddD (src, 1, dest, 1, dest, 1, (vDSP_Length) num); - #else - JUCE_PERFORM_VEC_OP_SRC_DEST (dest[i] += src[i], Mode::add (d, s), JUCE_LOAD_SRC_DEST, JUCE_INCREMENT_SRC_DEST, ) - #endif -} - -void JUCE_CALLTYPE FloatVectorOperations::add (float* dest, const float* src1, const float* src2, int num) noexcept -{ - #if JUCE_USE_VDSP_FRAMEWORK - vDSP_vadd (src1, 1, src2, 1, dest, 1, (vDSP_Length) num); - #else - JUCE_PERFORM_VEC_OP_SRC1_SRC2_DEST (dest[i] = src1[i] + src2[i], Mode::add (s1, s2), JUCE_LOAD_SRC1_SRC2, JUCE_INCREMENT_SRC1_SRC2_DEST, ) - #endif -} - -void JUCE_CALLTYPE FloatVectorOperations::add (double* dest, const double* src1, const double* src2, int num) noexcept -{ - #if JUCE_USE_VDSP_FRAMEWORK - vDSP_vaddD (src1, 1, src2, 1, dest, 1, (vDSP_Length) num); - #else - JUCE_PERFORM_VEC_OP_SRC1_SRC2_DEST (dest[i] = src1[i] + src2[i], Mode::add (s1, s2), JUCE_LOAD_SRC1_SRC2, JUCE_INCREMENT_SRC1_SRC2_DEST, ) - #endif -} - -void JUCE_CALLTYPE FloatVectorOperations::subtract (float* dest, const float* src, int num) noexcept -{ - #if JUCE_USE_VDSP_FRAMEWORK - vDSP_vsub (src, 1, dest, 1, dest, 1, (vDSP_Length) num); - #else - JUCE_PERFORM_VEC_OP_SRC_DEST (dest[i] -= src[i], Mode::sub (d, s), JUCE_LOAD_SRC_DEST, JUCE_INCREMENT_SRC_DEST, ) - #endif -} - -void JUCE_CALLTYPE FloatVectorOperations::subtract (double* dest, const double* src, int num) noexcept -{ - #if JUCE_USE_VDSP_FRAMEWORK - vDSP_vsubD (src, 1, dest, 1, dest, 1, (vDSP_Length) num); - #else - JUCE_PERFORM_VEC_OP_SRC_DEST (dest[i] -= src[i], Mode::sub (d, s), JUCE_LOAD_SRC_DEST, JUCE_INCREMENT_SRC_DEST, ) - #endif -} - -void JUCE_CALLTYPE FloatVectorOperations::subtract (float* dest, const float* src1, const float* src2, int num) noexcept -{ - #if JUCE_USE_VDSP_FRAMEWORK - vDSP_vsub (src2, 1, src1, 1, dest, 1, (vDSP_Length) num); - #else - JUCE_PERFORM_VEC_OP_SRC1_SRC2_DEST (dest[i] = src1[i] - src2[i], Mode::sub (s1, s2), JUCE_LOAD_SRC1_SRC2, JUCE_INCREMENT_SRC1_SRC2_DEST, ) - #endif -} - -void JUCE_CALLTYPE FloatVectorOperations::subtract (double* dest, const double* src1, const double* src2, int num) noexcept -{ - #if JUCE_USE_VDSP_FRAMEWORK - vDSP_vsubD (src2, 1, src1, 1, dest, 1, (vDSP_Length) num); - #else - JUCE_PERFORM_VEC_OP_SRC1_SRC2_DEST (dest[i] = src1[i] - src2[i], Mode::sub (s1, s2), JUCE_LOAD_SRC1_SRC2, JUCE_INCREMENT_SRC1_SRC2_DEST, ) - #endif -} - -void JUCE_CALLTYPE FloatVectorOperations::addWithMultiply (float* dest, const float* src, float multiplier, int num) noexcept -{ - #if JUCE_USE_VDSP_FRAMEWORK - vDSP_vsma (src, 1, &multiplier, dest, 1, dest, 1, (vDSP_Length) num); - #else - JUCE_PERFORM_VEC_OP_SRC_DEST (dest[i] += src[i] * multiplier, Mode::add (d, Mode::mul (mult, s)), - JUCE_LOAD_SRC_DEST, JUCE_INCREMENT_SRC_DEST, - const Mode::ParallelType mult = Mode::load1 (multiplier);) - #endif -} - -void JUCE_CALLTYPE FloatVectorOperations::addWithMultiply (double* dest, const double* src, double multiplier, int num) noexcept -{ - #if JUCE_USE_VDSP_FRAMEWORK - vDSP_vsmaD (src, 1, &multiplier, dest, 1, dest, 1, (vDSP_Length) num); - #else - JUCE_PERFORM_VEC_OP_SRC_DEST (dest[i] += src[i] * multiplier, Mode::add (d, Mode::mul (mult, s)), - JUCE_LOAD_SRC_DEST, JUCE_INCREMENT_SRC_DEST, - const Mode::ParallelType mult = Mode::load1 (multiplier);) - #endif -} - -void JUCE_CALLTYPE FloatVectorOperations::addWithMultiply (float* dest, const float* src1, const float* src2, int num) noexcept -{ - #if JUCE_USE_VDSP_FRAMEWORK - vDSP_vma ((float*) src1, 1, (float*) src2, 1, dest, 1, dest, 1, (vDSP_Length) num); - #else - JUCE_PERFORM_VEC_OP_SRC1_SRC2_DEST_DEST (dest[i] += src1[i] * src2[i], Mode::add (d, Mode::mul (s1, s2)), - JUCE_LOAD_SRC1_SRC2_DEST, - JUCE_INCREMENT_SRC1_SRC2_DEST, ) - #endif -} - -void JUCE_CALLTYPE FloatVectorOperations::addWithMultiply (double* dest, const double* src1, const double* src2, int num) noexcept -{ - #if JUCE_USE_VDSP_FRAMEWORK - vDSP_vmaD ((double*) src1, 1, (double*) src2, 1, dest, 1, dest, 1, (vDSP_Length) num); - #else - JUCE_PERFORM_VEC_OP_SRC1_SRC2_DEST_DEST (dest[i] += src1[i] * src2[i], Mode::add (d, Mode::mul (s1, s2)), - JUCE_LOAD_SRC1_SRC2_DEST, - JUCE_INCREMENT_SRC1_SRC2_DEST, ) - #endif -} - -void JUCE_CALLTYPE FloatVectorOperations::subtractWithMultiply (float* dest, const float* src, float multiplier, int num) noexcept -{ - JUCE_PERFORM_VEC_OP_SRC_DEST (dest[i] -= src[i] * multiplier, Mode::sub (d, Mode::mul (mult, s)), - JUCE_LOAD_SRC_DEST, JUCE_INCREMENT_SRC_DEST, - const Mode::ParallelType mult = Mode::load1 (multiplier);) -} - -void JUCE_CALLTYPE FloatVectorOperations::subtractWithMultiply (double* dest, const double* src, double multiplier, int num) noexcept -{ - JUCE_PERFORM_VEC_OP_SRC_DEST (dest[i] -= src[i] * multiplier, Mode::sub (d, Mode::mul (mult, s)), - JUCE_LOAD_SRC_DEST, JUCE_INCREMENT_SRC_DEST, - const Mode::ParallelType mult = Mode::load1 (multiplier);) -} - -void JUCE_CALLTYPE FloatVectorOperations::subtractWithMultiply (float* dest, const float* src1, const float* src2, int num) noexcept -{ - JUCE_PERFORM_VEC_OP_SRC1_SRC2_DEST_DEST (dest[i] -= src1[i] * src2[i], Mode::sub (d, Mode::mul (s1, s2)), - JUCE_LOAD_SRC1_SRC2_DEST, - JUCE_INCREMENT_SRC1_SRC2_DEST, ) -} - -void JUCE_CALLTYPE FloatVectorOperations::subtractWithMultiply (double* dest, const double* src1, const double* src2, int num) noexcept -{ - JUCE_PERFORM_VEC_OP_SRC1_SRC2_DEST_DEST (dest[i] -= src1[i] * src2[i], Mode::sub (d, Mode::mul (s1, s2)), - JUCE_LOAD_SRC1_SRC2_DEST, - JUCE_INCREMENT_SRC1_SRC2_DEST, ) -} - -void JUCE_CALLTYPE FloatVectorOperations::multiply (float* dest, const float* src, int num) noexcept -{ - #if JUCE_USE_VDSP_FRAMEWORK - vDSP_vmul (src, 1, dest, 1, dest, 1, (vDSP_Length) num); - #else - JUCE_PERFORM_VEC_OP_SRC_DEST (dest[i] *= src[i], Mode::mul (d, s), JUCE_LOAD_SRC_DEST, JUCE_INCREMENT_SRC_DEST, ) - #endif -} - -void JUCE_CALLTYPE FloatVectorOperations::multiply (double* dest, const double* src, int num) noexcept -{ - #if JUCE_USE_VDSP_FRAMEWORK - vDSP_vmulD (src, 1, dest, 1, dest, 1, (vDSP_Length) num); - #else - JUCE_PERFORM_VEC_OP_SRC_DEST (dest[i] *= src[i], Mode::mul (d, s), JUCE_LOAD_SRC_DEST, JUCE_INCREMENT_SRC_DEST, ) - #endif -} - -void JUCE_CALLTYPE FloatVectorOperations::multiply (float* dest, const float* src1, const float* src2, int num) noexcept -{ - #if JUCE_USE_VDSP_FRAMEWORK - vDSP_vmul (src1, 1, src2, 1, dest, 1, (vDSP_Length) num); - #else - JUCE_PERFORM_VEC_OP_SRC1_SRC2_DEST (dest[i] = src1[i] * src2[i], Mode::mul (s1, s2), JUCE_LOAD_SRC1_SRC2, JUCE_INCREMENT_SRC1_SRC2_DEST, ) - #endif -} - -void JUCE_CALLTYPE FloatVectorOperations::multiply (double* dest, const double* src1, const double* src2, int num) noexcept -{ - #if JUCE_USE_VDSP_FRAMEWORK - vDSP_vmulD (src1, 1, src2, 1, dest, 1, (vDSP_Length) num); - #else - JUCE_PERFORM_VEC_OP_SRC1_SRC2_DEST (dest[i] = src1[i] * src2[i], Mode::mul (s1, s2), JUCE_LOAD_SRC1_SRC2, JUCE_INCREMENT_SRC1_SRC2_DEST, ) - #endif -} - -void JUCE_CALLTYPE FloatVectorOperations::multiply (float* dest, float multiplier, int num) noexcept -{ - #if JUCE_USE_VDSP_FRAMEWORK - vDSP_vsmul (dest, 1, &multiplier, dest, 1, (vDSP_Length) num); - #else - JUCE_PERFORM_VEC_OP_DEST (dest[i] *= multiplier, Mode::mul (d, mult), JUCE_LOAD_DEST, - const Mode::ParallelType mult = Mode::load1 (multiplier);) - #endif -} - -void JUCE_CALLTYPE FloatVectorOperations::multiply (double* dest, double multiplier, int num) noexcept -{ - #if JUCE_USE_VDSP_FRAMEWORK - vDSP_vsmulD (dest, 1, &multiplier, dest, 1, (vDSP_Length) num); - #else - JUCE_PERFORM_VEC_OP_DEST (dest[i] *= multiplier, Mode::mul (d, mult), JUCE_LOAD_DEST, - const Mode::ParallelType mult = Mode::load1 (multiplier);) - #endif -} - -void JUCE_CALLTYPE FloatVectorOperations::multiply (float* dest, const float* src, float multiplier, int num) noexcept -{ - JUCE_PERFORM_VEC_OP_SRC_DEST (dest[i] = src[i] * multiplier, Mode::mul (mult, s), - JUCE_LOAD_SRC, JUCE_INCREMENT_SRC_DEST, - const Mode::ParallelType mult = Mode::load1 (multiplier);) -} - -void JUCE_CALLTYPE FloatVectorOperations::multiply (double* dest, const double* src, double multiplier, int num) noexcept -{ - JUCE_PERFORM_VEC_OP_SRC_DEST (dest[i] = src[i] * multiplier, Mode::mul (mult, s), - JUCE_LOAD_SRC, JUCE_INCREMENT_SRC_DEST, - const Mode::ParallelType mult = Mode::load1 (multiplier);) -} - -void FloatVectorOperations::negate (float* dest, const float* src, int num) noexcept -{ - #if JUCE_USE_VDSP_FRAMEWORK - vDSP_vneg ((float*) src, 1, dest, 1, (vDSP_Length) num); - #else - copyWithMultiply (dest, src, -1.0f, num); - #endif -} - -void FloatVectorOperations::negate (double* dest, const double* src, int num) noexcept -{ - #if JUCE_USE_VDSP_FRAMEWORK - vDSP_vnegD ((double*) src, 1, dest, 1, (vDSP_Length) num); - #else - copyWithMultiply (dest, src, -1.0f, num); - #endif -} - -void FloatVectorOperations::abs (float* dest, const float* src, int num) noexcept -{ - #if JUCE_USE_VDSP_FRAMEWORK - vDSP_vabs ((float*) src, 1, dest, 1, (vDSP_Length) num); - #else - FloatVectorHelpers::signMask32 signMask; - signMask.i = 0x7fffffffUL; - JUCE_PERFORM_VEC_OP_SRC_DEST (dest[i] = std::abs (src[i]), Mode::bit_and (s, mask), - JUCE_LOAD_SRC, JUCE_INCREMENT_SRC_DEST, - const Mode::ParallelType mask = Mode::load1 (signMask.f);) - - ignoreUnused (signMask); - #endif -} - -void FloatVectorOperations::abs (double* dest, const double* src, int num) noexcept -{ - #if JUCE_USE_VDSP_FRAMEWORK - vDSP_vabsD ((double*) src, 1, dest, 1, (vDSP_Length) num); - #else - FloatVectorHelpers::signMask64 signMask; - signMask.i = 0x7fffffffffffffffULL; - - JUCE_PERFORM_VEC_OP_SRC_DEST (dest[i] = std::abs (src[i]), Mode::bit_and (s, mask), - JUCE_LOAD_SRC, JUCE_INCREMENT_SRC_DEST, - const Mode::ParallelType mask = Mode::load1 (signMask.d);) - - ignoreUnused (signMask); - #endif -} - -void JUCE_CALLTYPE FloatVectorOperations::convertFixedToFloat (float* dest, const int* src, float multiplier, int num) noexcept -{ - #if JUCE_USE_ARM_NEON - JUCE_PERFORM_VEC_OP_SRC_DEST (dest[i] = src[i] * multiplier, - vmulq_n_f32 (vcvtq_f32_s32 (vld1q_s32 (src)), multiplier), - JUCE_LOAD_NONE, JUCE_INCREMENT_SRC_DEST, ) - #else - JUCE_PERFORM_VEC_OP_SRC_DEST (dest[i] = (float) src[i] * multiplier, - Mode::mul (mult, _mm_cvtepi32_ps (_mm_loadu_si128 (reinterpret_cast (src)))), - JUCE_LOAD_NONE, JUCE_INCREMENT_SRC_DEST, - const Mode::ParallelType mult = Mode::load1 (multiplier);) - #endif -} - -void JUCE_CALLTYPE FloatVectorOperations::min (float* dest, const float* src, float comp, int num) noexcept -{ - JUCE_PERFORM_VEC_OP_SRC_DEST (dest[i] = jmin (src[i], comp), Mode::min (s, cmp), - JUCE_LOAD_SRC, JUCE_INCREMENT_SRC_DEST, - const Mode::ParallelType cmp = Mode::load1 (comp);) -} - -void JUCE_CALLTYPE FloatVectorOperations::min (double* dest, const double* src, double comp, int num) noexcept -{ - JUCE_PERFORM_VEC_OP_SRC_DEST (dest[i] = jmin (src[i], comp), Mode::min (s, cmp), - JUCE_LOAD_SRC, JUCE_INCREMENT_SRC_DEST, - const Mode::ParallelType cmp = Mode::load1 (comp);) -} - -void JUCE_CALLTYPE FloatVectorOperations::min (float* dest, const float* src1, const float* src2, int num) noexcept -{ - #if JUCE_USE_VDSP_FRAMEWORK - vDSP_vmin ((float*) src1, 1, (float*) src2, 1, dest, 1, (vDSP_Length) num); - #else - JUCE_PERFORM_VEC_OP_SRC1_SRC2_DEST (dest[i] = jmin (src1[i], src2[i]), Mode::min (s1, s2), JUCE_LOAD_SRC1_SRC2, JUCE_INCREMENT_SRC1_SRC2_DEST, ) - #endif -} - -void JUCE_CALLTYPE FloatVectorOperations::min (double* dest, const double* src1, const double* src2, int num) noexcept -{ - #if JUCE_USE_VDSP_FRAMEWORK - vDSP_vminD ((double*) src1, 1, (double*) src2, 1, dest, 1, (vDSP_Length) num); - #else - JUCE_PERFORM_VEC_OP_SRC1_SRC2_DEST (dest[i] = jmin (src1[i], src2[i]), Mode::min (s1, s2), JUCE_LOAD_SRC1_SRC2, JUCE_INCREMENT_SRC1_SRC2_DEST, ) - #endif -} - -void JUCE_CALLTYPE FloatVectorOperations::max (float* dest, const float* src, float comp, int num) noexcept -{ - JUCE_PERFORM_VEC_OP_SRC_DEST (dest[i] = jmax (src[i], comp), Mode::max (s, cmp), - JUCE_LOAD_SRC, JUCE_INCREMENT_SRC_DEST, - const Mode::ParallelType cmp = Mode::load1 (comp);) -} - -void JUCE_CALLTYPE FloatVectorOperations::max (double* dest, const double* src, double comp, int num) noexcept -{ - JUCE_PERFORM_VEC_OP_SRC_DEST (dest[i] = jmax (src[i], comp), Mode::max (s, cmp), - JUCE_LOAD_SRC, JUCE_INCREMENT_SRC_DEST, - const Mode::ParallelType cmp = Mode::load1 (comp);) -} - -void JUCE_CALLTYPE FloatVectorOperations::max (float* dest, const float* src1, const float* src2, int num) noexcept -{ - #if JUCE_USE_VDSP_FRAMEWORK - vDSP_vmax ((float*) src1, 1, (float*) src2, 1, dest, 1, (vDSP_Length) num); - #else - JUCE_PERFORM_VEC_OP_SRC1_SRC2_DEST (dest[i] = jmax (src1[i], src2[i]), Mode::max (s1, s2), JUCE_LOAD_SRC1_SRC2, JUCE_INCREMENT_SRC1_SRC2_DEST, ) - #endif -} - -void JUCE_CALLTYPE FloatVectorOperations::max (double* dest, const double* src1, const double* src2, int num) noexcept -{ - #if JUCE_USE_VDSP_FRAMEWORK - vDSP_vmaxD ((double*) src1, 1, (double*) src2, 1, dest, 1, (vDSP_Length) num); - #else - JUCE_PERFORM_VEC_OP_SRC1_SRC2_DEST (dest[i] = jmax (src1[i], src2[i]), Mode::max (s1, s2), JUCE_LOAD_SRC1_SRC2, JUCE_INCREMENT_SRC1_SRC2_DEST, ) - #endif -} - -void JUCE_CALLTYPE FloatVectorOperations::clip (float* dest, const float* src, float low, float high, int num) noexcept -{ - jassert(high >= low); - - #if JUCE_USE_VDSP_FRAMEWORK - vDSP_vclip ((float*) src, 1, &low, &high, dest, 1, (vDSP_Length) num); - #else - JUCE_PERFORM_VEC_OP_SRC_DEST (dest[i] = jmax (jmin (src[i], high), low), Mode::max (Mode::min (s, hi), lo), - JUCE_LOAD_SRC, JUCE_INCREMENT_SRC_DEST, - const Mode::ParallelType lo = Mode::load1 (low); const Mode::ParallelType hi = Mode::load1 (high);) - #endif -} - -void JUCE_CALLTYPE FloatVectorOperations::clip (double* dest, const double* src, double low, double high, int num) noexcept -{ - jassert(high >= low); - - #if JUCE_USE_VDSP_FRAMEWORK - vDSP_vclipD ((double*) src, 1, &low, &high, dest, 1, (vDSP_Length) num); - #else - JUCE_PERFORM_VEC_OP_SRC_DEST (dest[i] = jmax (jmin (src[i], high), low), Mode::max (Mode::min (s, hi), lo), - JUCE_LOAD_SRC, JUCE_INCREMENT_SRC_DEST, - const Mode::ParallelType lo = Mode::load1 (low); const Mode::ParallelType hi = Mode::load1 (high);) - #endif -} - -Range JUCE_CALLTYPE FloatVectorOperations::findMinAndMax (const float* src, int num) noexcept -{ - #if JUCE_USE_SSE_INTRINSICS || JUCE_USE_ARM_NEON - return FloatVectorHelpers::MinMax::findMinAndMax (src, num); - #else - return Range::findMinAndMax (src, num); - #endif -} - -Range JUCE_CALLTYPE FloatVectorOperations::findMinAndMax (const double* src, int num) noexcept -{ - #if JUCE_USE_SSE_INTRINSICS || JUCE_USE_ARM_NEON - return FloatVectorHelpers::MinMax::findMinAndMax (src, num); - #else - return Range::findMinAndMax (src, num); - #endif -} - -float JUCE_CALLTYPE FloatVectorOperations::findMinimum (const float* src, int num) noexcept -{ - #if JUCE_USE_SSE_INTRINSICS || JUCE_USE_ARM_NEON - return FloatVectorHelpers::MinMax::findMinOrMax (src, num, true); - #else - return juce::findMinimum (src, num); - #endif -} - -double JUCE_CALLTYPE FloatVectorOperations::findMinimum (const double* src, int num) noexcept -{ - #if JUCE_USE_SSE_INTRINSICS || JUCE_USE_ARM_NEON - return FloatVectorHelpers::MinMax::findMinOrMax (src, num, true); - #else - return juce::findMinimum (src, num); - #endif -} - -float JUCE_CALLTYPE FloatVectorOperations::findMaximum (const float* src, int num) noexcept -{ - #if JUCE_USE_SSE_INTRINSICS || JUCE_USE_ARM_NEON - return FloatVectorHelpers::MinMax::findMinOrMax (src, num, false); - #else - return juce::findMaximum (src, num); - #endif -} - -double JUCE_CALLTYPE FloatVectorOperations::findMaximum (const double* src, int num) noexcept -{ - #if JUCE_USE_SSE_INTRINSICS || JUCE_USE_ARM_NEON - return FloatVectorHelpers::MinMax::findMinOrMax (src, num, false); - #else - return juce::findMaximum (src, num); - #endif -} - -intptr_t JUCE_CALLTYPE FloatVectorOperations::getFpStatusRegister() noexcept -{ - intptr_t fpsr = 0; - #if JUCE_INTEL && JUCE_USE_SSE_INTRINSICS - fpsr = static_cast (_mm_getcsr()); - #elif defined (__arm64__) || defined (__aarch64__) || JUCE_USE_ARM_NEON - #if defined (__arm64__) || defined (__aarch64__) - asm volatile("mrs %0, fpcr" : "=r" (fpsr)); - #elif JUCE_USE_ARM_NEON - asm volatile("vmrs %0, fpscr" : "=r" (fpsr)); - #endif - #else - #if ! (defined (JUCE_INTEL) || defined (JUCE_ARM)) - jassertfalse; // No support for getting the floating point status register for your platform - #endif - #endif - - return fpsr; -} - -void JUCE_CALLTYPE FloatVectorOperations::setFpStatusRegister (intptr_t fpsr) noexcept -{ - #if JUCE_INTEL && JUCE_USE_SSE_INTRINSICS - auto fpsr_w = static_cast (fpsr); - _mm_setcsr (fpsr_w); - #elif defined (__arm64__) || defined (__aarch64__) || JUCE_USE_ARM_NEON - #if defined (__arm64__) || defined (__aarch64__) - asm volatile("msr fpcr, %0" : : "ri" (fpsr)); - #elif JUCE_USE_ARM_NEON - asm volatile("vmsr fpscr, %0" : : "ri" (fpsr)); - #endif - #else - #if ! (defined (JUCE_INTEL) || defined (JUCE_ARM)) - jassertfalse; // No support for getting the floating point status register for your platform - #endif - ignoreUnused (fpsr); - #endif -} - -void JUCE_CALLTYPE FloatVectorOperations::enableFlushToZeroMode (bool shouldEnable) noexcept -{ - #if JUCE_USE_SSE_INTRINSICS || (JUCE_USE_ARM_NEON || defined (__arm64__) || defined (__aarch64__)) - #if JUCE_USE_SSE_INTRINSICS - intptr_t mask = _MM_FLUSH_ZERO_MASK; - #else /*JUCE_USE_ARM_NEON*/ - intptr_t mask = (1 << 24 /* FZ */); - #endif - setFpStatusRegister ((getFpStatusRegister() & (~mask)) | (shouldEnable ? mask : 0)); - #else - #if ! (defined (JUCE_INTEL) || defined (JUCE_ARM)) - jassertfalse; // No support for flush to zero mode on your platform - #endif - ignoreUnused (shouldEnable); - #endif -} - -void JUCE_CALLTYPE FloatVectorOperations::disableDenormalisedNumberSupport (bool shouldDisable) noexcept -{ - #if JUCE_USE_SSE_INTRINSICS || (JUCE_USE_ARM_NEON || defined (__arm64__) || defined (__aarch64__)) - #if JUCE_USE_SSE_INTRINSICS - intptr_t mask = 0x8040; - #else /*JUCE_USE_ARM_NEON*/ - intptr_t mask = (1 << 24 /* FZ */); - #endif - - setFpStatusRegister ((getFpStatusRegister() & (~mask)) | (shouldDisable ? mask : 0)); - #else - ignoreUnused (shouldDisable); - - #if ! (defined (JUCE_INTEL) || defined (JUCE_ARM)) - jassertfalse; // No support for disable denormals mode on your platform - #endif - #endif -} - -bool JUCE_CALLTYPE FloatVectorOperations::areDenormalsDisabled() noexcept -{ - #if JUCE_USE_SSE_INTRINSICS || (JUCE_USE_ARM_NEON || defined (__arm64__) || defined (__aarch64__)) - #if JUCE_USE_SSE_INTRINSICS - intptr_t mask = 0x8040; - #else /*JUCE_USE_ARM_NEON*/ - intptr_t mask = (1 << 24 /* FZ */); - #endif - - return ((getFpStatusRegister() & mask) == mask); - #else - return false; - #endif -} - -ScopedNoDenormals::ScopedNoDenormals() noexcept -{ - #if JUCE_USE_SSE_INTRINSICS || (JUCE_USE_ARM_NEON || defined (__arm64__) || defined (__aarch64__)) - #if JUCE_USE_SSE_INTRINSICS - intptr_t mask = 0x8040; - #else /*JUCE_USE_ARM_NEON*/ - intptr_t mask = (1 << 24 /* FZ */); - #endif - - fpsr = FloatVectorOperations::getFpStatusRegister(); - FloatVectorOperations::setFpStatusRegister (fpsr | mask); - #endif -} - -ScopedNoDenormals::~ScopedNoDenormals() noexcept -{ - #if JUCE_USE_SSE_INTRINSICS || (JUCE_USE_ARM_NEON || defined (__arm64__) || defined (__aarch64__)) - FloatVectorOperations::setFpStatusRegister (fpsr); - #endif -} - - -//============================================================================== -//============================================================================== -#if JUCE_UNIT_TESTS - -class FloatVectorOperationsTests : public UnitTest -{ -public: - FloatVectorOperationsTests() - : UnitTest ("FloatVectorOperations", UnitTestCategories::audio) - {} - - template - struct TestRunner - { - static void runTest (UnitTest& u, Random random) - { - const int range = random.nextBool() ? 500 : 10; - const int num = random.nextInt (range) + 1; - - HeapBlock buffer1 (num + 16), buffer2 (num + 16); - HeapBlock buffer3 (num + 16); - - #if JUCE_ARM - ValueType* const data1 = buffer1; - ValueType* const data2 = buffer2; - int* const int1 = buffer3; - #else - // These tests deliberately operate on misaligned memory and will be flagged up by - // checks for undefined behavior! - ValueType* const data1 = addBytesToPointer (buffer1.get(), random.nextInt (16)); - ValueType* const data2 = addBytesToPointer (buffer2.get(), random.nextInt (16)); - int* const int1 = addBytesToPointer (buffer3.get(), random.nextInt (16)); - #endif - - fillRandomly (random, data1, num); - fillRandomly (random, data2, num); - - Range minMax1 (FloatVectorOperations::findMinAndMax (data1, num)); - Range minMax2 (Range::findMinAndMax (data1, num)); - u.expect (minMax1 == minMax2); - - u.expect (valuesMatch (FloatVectorOperations::findMinimum (data1, num), juce::findMinimum (data1, num))); - u.expect (valuesMatch (FloatVectorOperations::findMaximum (data1, num), juce::findMaximum (data1, num))); - - u.expect (valuesMatch (FloatVectorOperations::findMinimum (data2, num), juce::findMinimum (data2, num))); - u.expect (valuesMatch (FloatVectorOperations::findMaximum (data2, num), juce::findMaximum (data2, num))); - - FloatVectorOperations::clear (data1, num); - u.expect (areAllValuesEqual (data1, num, 0)); - - FloatVectorOperations::fill (data1, (ValueType) 2, num); - u.expect (areAllValuesEqual (data1, num, (ValueType) 2)); - - FloatVectorOperations::add (data1, (ValueType) 2, num); - u.expect (areAllValuesEqual (data1, num, (ValueType) 4)); - - FloatVectorOperations::copy (data2, data1, num); - u.expect (areAllValuesEqual (data2, num, (ValueType) 4)); - - FloatVectorOperations::add (data2, data1, num); - u.expect (areAllValuesEqual (data2, num, (ValueType) 8)); - - FloatVectorOperations::copyWithMultiply (data2, data1, (ValueType) 4, num); - u.expect (areAllValuesEqual (data2, num, (ValueType) 16)); - - FloatVectorOperations::addWithMultiply (data2, data1, (ValueType) 4, num); - u.expect (areAllValuesEqual (data2, num, (ValueType) 32)); - - FloatVectorOperations::multiply (data1, (ValueType) 2, num); - u.expect (areAllValuesEqual (data1, num, (ValueType) 8)); - - FloatVectorOperations::multiply (data1, data2, num); - u.expect (areAllValuesEqual (data1, num, (ValueType) 256)); - - FloatVectorOperations::negate (data2, data1, num); - u.expect (areAllValuesEqual (data2, num, (ValueType) -256)); - - FloatVectorOperations::subtract (data1, data2, num); - u.expect (areAllValuesEqual (data1, num, (ValueType) 512)); - - FloatVectorOperations::abs (data1, data2, num); - u.expect (areAllValuesEqual (data1, num, (ValueType) 256)); - - FloatVectorOperations::abs (data2, data1, num); - u.expect (areAllValuesEqual (data2, num, (ValueType) 256)); - - fillRandomly (random, int1, num); - doConversionTest (u, data1, data2, int1, num); - - FloatVectorOperations::fill (data1, (ValueType) 2, num); - FloatVectorOperations::fill (data2, (ValueType) 3, num); - FloatVectorOperations::addWithMultiply (data1, data1, data2, num); - u.expect (areAllValuesEqual (data1, num, (ValueType) 8)); - } - - static void doConversionTest (UnitTest& u, float* data1, float* data2, int* const int1, int num) - { - FloatVectorOperations::convertFixedToFloat (data1, int1, 2.0f, num); - convertFixed (data2, int1, 2.0f, num); - u.expect (buffersMatch (data1, data2, num)); - } - - static void doConversionTest (UnitTest&, double*, double*, int*, int) {} - - static void fillRandomly (Random& random, ValueType* d, int num) - { - while (--num >= 0) - *d++ = (ValueType) (random.nextDouble() * 1000.0); - } - - static void fillRandomly (Random& random, int* d, int num) - { - while (--num >= 0) - *d++ = random.nextInt(); - } - - static void convertFixed (float* d, const int* s, ValueType multiplier, int num) - { - while (--num >= 0) - *d++ = (float) *s++ * multiplier; - } - - static bool areAllValuesEqual (const ValueType* d, int num, ValueType target) - { - while (--num >= 0) - if (*d++ != target) - return false; - - return true; - } - - static bool buffersMatch (const ValueType* d1, const ValueType* d2, int num) - { - while (--num >= 0) - if (! valuesMatch (*d1++, *d2++)) - return false; - - return true; - } - - static bool valuesMatch (ValueType v1, ValueType v2) - { - return std::abs (v1 - v2) < std::numeric_limits::epsilon(); - } - }; - - void runTest() override - { - beginTest ("FloatVectorOperations"); - - for (int i = 1000; --i >= 0;) - { - TestRunner::runTest (*this, getRandom()); - TestRunner::runTest (*this, getRandom()); - } - } -}; - -static FloatVectorOperationsTests vectorOpTests; - -#endif - -} // namespace juce +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2017 - ROLI Ltd. + + JUCE is an open source library subject to commercial or open-source + licensing. + + The code included in this file is provided under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license. Permission + To use, copy, modify, and/or distribute this software for any purpose with or + without fee is hereby granted provided that the above copyright notice and + this permission notice appear in all copies. + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +namespace juce +{ + +namespace FloatVectorHelpers +{ + #define JUCE_INCREMENT_SRC_DEST dest += (16 / sizeof (*dest)); src += (16 / sizeof (*dest)); + #define JUCE_INCREMENT_SRC1_SRC2_DEST dest += (16 / sizeof (*dest)); src1 += (16 / sizeof (*dest)); src2 += (16 / sizeof (*dest)); + #define JUCE_INCREMENT_DEST dest += (16 / sizeof (*dest)); + + #if JUCE_USE_SSE_INTRINSICS + inline static bool isAligned (const void* p) noexcept + { + return (((pointer_sized_int) p) & 15) == 0; + } + + struct BasicOps32 + { + using Type = float; + using ParallelType = __m128; + using IntegerType = __m128; + enum { numParallel = 4 }; + + // Integer and parallel types are the same for SSE. On neon they have different types + static forcedinline IntegerType toint (ParallelType v) noexcept { return v; } + static forcedinline ParallelType toflt (IntegerType v) noexcept { return v; } + + static forcedinline ParallelType load1 (Type v) noexcept { return _mm_load1_ps (&v); } + static forcedinline ParallelType loadA (const Type* v) noexcept { return _mm_load_ps (v); } + static forcedinline ParallelType loadU (const Type* v) noexcept { return _mm_loadu_ps (v); } + static forcedinline void storeA (Type* dest, ParallelType a) noexcept { _mm_store_ps (dest, a); } + static forcedinline void storeU (Type* dest, ParallelType a) noexcept { _mm_storeu_ps (dest, a); } + + static forcedinline ParallelType add (ParallelType a, ParallelType b) noexcept { return _mm_add_ps (a, b); } + static forcedinline ParallelType sub (ParallelType a, ParallelType b) noexcept { return _mm_sub_ps (a, b); } + static forcedinline ParallelType mul (ParallelType a, ParallelType b) noexcept { return _mm_mul_ps (a, b); } + static forcedinline ParallelType max (ParallelType a, ParallelType b) noexcept { return _mm_max_ps (a, b); } + static forcedinline ParallelType min (ParallelType a, ParallelType b) noexcept { return _mm_min_ps (a, b); } + + static forcedinline ParallelType bit_and (ParallelType a, ParallelType b) noexcept { return _mm_and_ps (a, b); } + static forcedinline ParallelType bit_not (ParallelType a, ParallelType b) noexcept { return _mm_andnot_ps (a, b); } + static forcedinline ParallelType bit_or (ParallelType a, ParallelType b) noexcept { return _mm_or_ps (a, b); } + static forcedinline ParallelType bit_xor (ParallelType a, ParallelType b) noexcept { return _mm_xor_ps (a, b); } + + static forcedinline Type max (ParallelType a) noexcept { Type v[numParallel]; storeU (v, a); return jmax (v[0], v[1], v[2], v[3]); } + static forcedinline Type min (ParallelType a) noexcept { Type v[numParallel]; storeU (v, a); return jmin (v[0], v[1], v[2], v[3]); } + }; + + struct BasicOps64 + { + using Type = double; + using ParallelType = __m128d; + using IntegerType = __m128d; + enum { numParallel = 2 }; + + // Integer and parallel types are the same for SSE. On neon they have different types + static forcedinline IntegerType toint (ParallelType v) noexcept { return v; } + static forcedinline ParallelType toflt (IntegerType v) noexcept { return v; } + + static forcedinline ParallelType load1 (Type v) noexcept { return _mm_load1_pd (&v); } + static forcedinline ParallelType loadA (const Type* v) noexcept { return _mm_load_pd (v); } + static forcedinline ParallelType loadU (const Type* v) noexcept { return _mm_loadu_pd (v); } + static forcedinline void storeA (Type* dest, ParallelType a) noexcept { _mm_store_pd (dest, a); } + static forcedinline void storeU (Type* dest, ParallelType a) noexcept { _mm_storeu_pd (dest, a); } + + static forcedinline ParallelType add (ParallelType a, ParallelType b) noexcept { return _mm_add_pd (a, b); } + static forcedinline ParallelType sub (ParallelType a, ParallelType b) noexcept { return _mm_sub_pd (a, b); } + static forcedinline ParallelType mul (ParallelType a, ParallelType b) noexcept { return _mm_mul_pd (a, b); } + static forcedinline ParallelType max (ParallelType a, ParallelType b) noexcept { return _mm_max_pd (a, b); } + static forcedinline ParallelType min (ParallelType a, ParallelType b) noexcept { return _mm_min_pd (a, b); } + + static forcedinline ParallelType bit_and (ParallelType a, ParallelType b) noexcept { return _mm_and_pd (a, b); } + static forcedinline ParallelType bit_not (ParallelType a, ParallelType b) noexcept { return _mm_andnot_pd (a, b); } + static forcedinline ParallelType bit_or (ParallelType a, ParallelType b) noexcept { return _mm_or_pd (a, b); } + static forcedinline ParallelType bit_xor (ParallelType a, ParallelType b) noexcept { return _mm_xor_pd (a, b); } + + static forcedinline Type max (ParallelType a) noexcept { Type v[numParallel]; storeU (v, a); return jmax (v[0], v[1]); } + static forcedinline Type min (ParallelType a) noexcept { Type v[numParallel]; storeU (v, a); return jmin (v[0], v[1]); } + }; + + + + #define JUCE_BEGIN_VEC_OP \ + using Mode = FloatVectorHelpers::ModeType::Mode; \ + { \ + const int numLongOps = num / Mode::numParallel; + + #define JUCE_FINISH_VEC_OP(normalOp) \ + num &= (Mode::numParallel - 1); \ + if (num == 0) return; \ + } \ + for (int i = 0; i < num; ++i) normalOp; + + #define JUCE_PERFORM_VEC_OP_DEST(normalOp, vecOp, locals, setupOp) \ + JUCE_BEGIN_VEC_OP \ + setupOp \ + if (FloatVectorHelpers::isAligned (dest)) JUCE_VEC_LOOP (vecOp, dummy, Mode::loadA, Mode::storeA, locals, JUCE_INCREMENT_DEST) \ + else JUCE_VEC_LOOP (vecOp, dummy, Mode::loadU, Mode::storeU, locals, JUCE_INCREMENT_DEST) \ + JUCE_FINISH_VEC_OP (normalOp) + + #define JUCE_PERFORM_VEC_OP_SRC_DEST(normalOp, vecOp, locals, increment, setupOp) \ + JUCE_BEGIN_VEC_OP \ + setupOp \ + if (FloatVectorHelpers::isAligned (dest)) \ + { \ + if (FloatVectorHelpers::isAligned (src)) JUCE_VEC_LOOP (vecOp, Mode::loadA, Mode::loadA, Mode::storeA, locals, increment) \ + else JUCE_VEC_LOOP (vecOp, Mode::loadU, Mode::loadA, Mode::storeA, locals, increment) \ + }\ + else \ + { \ + if (FloatVectorHelpers::isAligned (src)) JUCE_VEC_LOOP (vecOp, Mode::loadA, Mode::loadU, Mode::storeU, locals, increment) \ + else JUCE_VEC_LOOP (vecOp, Mode::loadU, Mode::loadU, Mode::storeU, locals, increment) \ + } \ + JUCE_FINISH_VEC_OP (normalOp) + + #define JUCE_PERFORM_VEC_OP_SRC1_SRC2_DEST(normalOp, vecOp, locals, increment, setupOp) \ + JUCE_BEGIN_VEC_OP \ + setupOp \ + if (FloatVectorHelpers::isAligned (dest)) \ + { \ + if (FloatVectorHelpers::isAligned (src1)) \ + { \ + if (FloatVectorHelpers::isAligned (src2)) JUCE_VEC_LOOP_TWO_SOURCES (vecOp, Mode::loadA, Mode::loadA, Mode::storeA, locals, increment) \ + else JUCE_VEC_LOOP_TWO_SOURCES (vecOp, Mode::loadA, Mode::loadU, Mode::storeA, locals, increment) \ + } \ + else \ + { \ + if (FloatVectorHelpers::isAligned (src2)) JUCE_VEC_LOOP_TWO_SOURCES (vecOp, Mode::loadU, Mode::loadA, Mode::storeA, locals, increment) \ + else JUCE_VEC_LOOP_TWO_SOURCES (vecOp, Mode::loadU, Mode::loadU, Mode::storeA, locals, increment) \ + } \ + } \ + else \ + { \ + if (FloatVectorHelpers::isAligned (src1)) \ + { \ + if (FloatVectorHelpers::isAligned (src2)) JUCE_VEC_LOOP_TWO_SOURCES (vecOp, Mode::loadA, Mode::loadA, Mode::storeU, locals, increment) \ + else JUCE_VEC_LOOP_TWO_SOURCES (vecOp, Mode::loadA, Mode::loadU, Mode::storeU, locals, increment) \ + } \ + else \ + { \ + if (FloatVectorHelpers::isAligned (src2)) JUCE_VEC_LOOP_TWO_SOURCES (vecOp, Mode::loadU, Mode::loadA, Mode::storeU, locals, increment) \ + else JUCE_VEC_LOOP_TWO_SOURCES (vecOp, Mode::loadU, Mode::loadU, Mode::storeU, locals, increment) \ + } \ + } \ + JUCE_FINISH_VEC_OP (normalOp) + + #define JUCE_PERFORM_VEC_OP_SRC1_SRC2_DEST_DEST(normalOp, vecOp, locals, increment, setupOp) \ + JUCE_BEGIN_VEC_OP \ + setupOp \ + if (FloatVectorHelpers::isAligned (dest)) \ + { \ + if (FloatVectorHelpers::isAligned (src1)) \ + { \ + if (FloatVectorHelpers::isAligned (src2)) JUCE_VEC_LOOP_TWO_SOURCES_WITH_DEST_LOAD (vecOp, Mode::loadA, Mode::loadA, Mode::loadA, Mode::storeA, locals, increment) \ + else JUCE_VEC_LOOP_TWO_SOURCES_WITH_DEST_LOAD (vecOp, Mode::loadA, Mode::loadU, Mode::loadA, Mode::storeA, locals, increment) \ + } \ + else \ + { \ + if (FloatVectorHelpers::isAligned (src2)) JUCE_VEC_LOOP_TWO_SOURCES_WITH_DEST_LOAD (vecOp, Mode::loadU, Mode::loadA, Mode::loadA, Mode::storeA, locals, increment) \ + else JUCE_VEC_LOOP_TWO_SOURCES_WITH_DEST_LOAD (vecOp, Mode::loadU, Mode::loadU, Mode::loadA, Mode::storeA, locals, increment) \ + } \ + } \ + else \ + { \ + if (FloatVectorHelpers::isAligned (src1)) \ + { \ + if (FloatVectorHelpers::isAligned (src2)) JUCE_VEC_LOOP_TWO_SOURCES_WITH_DEST_LOAD (vecOp, Mode::loadA, Mode::loadA, Mode::loadU, Mode::storeU, locals, increment) \ + else JUCE_VEC_LOOP_TWO_SOURCES_WITH_DEST_LOAD (vecOp, Mode::loadA, Mode::loadU, Mode::loadU, Mode::storeU, locals, increment) \ + } \ + else \ + { \ + if (FloatVectorHelpers::isAligned (src2)) JUCE_VEC_LOOP_TWO_SOURCES_WITH_DEST_LOAD (vecOp, Mode::loadU, Mode::loadA, Mode::loadU, Mode::storeU, locals, increment) \ + else JUCE_VEC_LOOP_TWO_SOURCES_WITH_DEST_LOAD (vecOp, Mode::loadU, Mode::loadU, Mode::loadU, Mode::storeU, locals, increment) \ + } \ + } \ + JUCE_FINISH_VEC_OP (normalOp) + + + //============================================================================== + #elif JUCE_USE_ARM_NEON + + struct BasicOps32 + { + using Type = float; + using ParallelType = float32x4_t; + using IntegerType = uint32x4_t; + union signMaskUnion { ParallelType f; IntegerType i; }; + enum { numParallel = 4 }; + + static forcedinline IntegerType toint (ParallelType v) noexcept { signMaskUnion u; u.f = v; return u.i; } + static forcedinline ParallelType toflt (IntegerType v) noexcept { signMaskUnion u; u.i = v; return u.f; } + + static forcedinline ParallelType load1 (Type v) noexcept { return vld1q_dup_f32 (&v); } + static forcedinline ParallelType loadA (const Type* v) noexcept { return vld1q_f32 (v); } + static forcedinline ParallelType loadU (const Type* v) noexcept { return vld1q_f32 (v); } + static forcedinline void storeA (Type* dest, ParallelType a) noexcept { vst1q_f32 (dest, a); } + static forcedinline void storeU (Type* dest, ParallelType a) noexcept { vst1q_f32 (dest, a); } + + static forcedinline ParallelType add (ParallelType a, ParallelType b) noexcept { return vaddq_f32 (a, b); } + static forcedinline ParallelType sub (ParallelType a, ParallelType b) noexcept { return vsubq_f32 (a, b); } + static forcedinline ParallelType mul (ParallelType a, ParallelType b) noexcept { return vmulq_f32 (a, b); } + static forcedinline ParallelType max (ParallelType a, ParallelType b) noexcept { return vmaxq_f32 (a, b); } + static forcedinline ParallelType min (ParallelType a, ParallelType b) noexcept { return vminq_f32 (a, b); } + + static forcedinline ParallelType bit_and (ParallelType a, ParallelType b) noexcept { return toflt (vandq_u32 (toint (a), toint (b))); } + static forcedinline ParallelType bit_not (ParallelType a, ParallelType b) noexcept { return toflt (vbicq_u32 (toint (a), toint (b))); } + static forcedinline ParallelType bit_or (ParallelType a, ParallelType b) noexcept { return toflt (vorrq_u32 (toint (a), toint (b))); } + static forcedinline ParallelType bit_xor (ParallelType a, ParallelType b) noexcept { return toflt (veorq_u32 (toint (a), toint (b))); } + + static forcedinline Type max (ParallelType a) noexcept { Type v[numParallel]; storeU (v, a); return jmax (v[0], v[1], v[2], v[3]); } + static forcedinline Type min (ParallelType a) noexcept { Type v[numParallel]; storeU (v, a); return jmin (v[0], v[1], v[2], v[3]); } + }; + + struct BasicOps64 + { + using Type = double; + using ParallelType = double; + using IntegerType = uint64; + union signMaskUnion { ParallelType f; IntegerType i; }; + enum { numParallel = 1 }; + + static forcedinline IntegerType toint (ParallelType v) noexcept { signMaskUnion u; u.f = v; return u.i; } + static forcedinline ParallelType toflt (IntegerType v) noexcept { signMaskUnion u; u.i = v; return u.f; } + + static forcedinline ParallelType load1 (Type v) noexcept { return v; } + static forcedinline ParallelType loadA (const Type* v) noexcept { return *v; } + static forcedinline ParallelType loadU (const Type* v) noexcept { return *v; } + static forcedinline void storeA (Type* dest, ParallelType a) noexcept { *dest = a; } + static forcedinline void storeU (Type* dest, ParallelType a) noexcept { *dest = a; } + + static forcedinline ParallelType add (ParallelType a, ParallelType b) noexcept { return a + b; } + static forcedinline ParallelType sub (ParallelType a, ParallelType b) noexcept { return a - b; } + static forcedinline ParallelType mul (ParallelType a, ParallelType b) noexcept { return a * b; } + static forcedinline ParallelType max (ParallelType a, ParallelType b) noexcept { return jmax (a, b); } + static forcedinline ParallelType min (ParallelType a, ParallelType b) noexcept { return jmin (a, b); } + + static forcedinline ParallelType bit_and (ParallelType a, ParallelType b) noexcept { return toflt (toint (a) & toint (b)); } + static forcedinline ParallelType bit_not (ParallelType a, ParallelType b) noexcept { return toflt ((~toint (a)) & toint (b)); } + static forcedinline ParallelType bit_or (ParallelType a, ParallelType b) noexcept { return toflt (toint (a) | toint (b)); } + static forcedinline ParallelType bit_xor (ParallelType a, ParallelType b) noexcept { return toflt (toint (a) ^ toint (b)); } + + static forcedinline Type max (ParallelType a) noexcept { return a; } + static forcedinline Type min (ParallelType a) noexcept { return a; } + }; + + #define JUCE_BEGIN_VEC_OP \ + using Mode = FloatVectorHelpers::ModeType::Mode; \ + if (Mode::numParallel > 1) \ + { \ + const int numLongOps = num / Mode::numParallel; + + #define JUCE_FINISH_VEC_OP(normalOp) \ + num &= (Mode::numParallel - 1); \ + if (num == 0) return; \ + } \ + for (int i = 0; i < num; ++i) normalOp; + + #define JUCE_PERFORM_VEC_OP_DEST(normalOp, vecOp, locals, setupOp) \ + JUCE_BEGIN_VEC_OP \ + setupOp \ + JUCE_VEC_LOOP (vecOp, dummy, Mode::loadU, Mode::storeU, locals, JUCE_INCREMENT_DEST) \ + JUCE_FINISH_VEC_OP (normalOp) + + #define JUCE_PERFORM_VEC_OP_SRC_DEST(normalOp, vecOp, locals, increment, setupOp) \ + JUCE_BEGIN_VEC_OP \ + setupOp \ + JUCE_VEC_LOOP (vecOp, Mode::loadU, Mode::loadU, Mode::storeU, locals, increment) \ + JUCE_FINISH_VEC_OP (normalOp) + + #define JUCE_PERFORM_VEC_OP_SRC1_SRC2_DEST(normalOp, vecOp, locals, increment, setupOp) \ + JUCE_BEGIN_VEC_OP \ + setupOp \ + JUCE_VEC_LOOP_TWO_SOURCES (vecOp, Mode::loadU, Mode::loadU, Mode::storeU, locals, increment) \ + JUCE_FINISH_VEC_OP (normalOp) + + #define JUCE_PERFORM_VEC_OP_SRC1_SRC2_DEST_DEST(normalOp, vecOp, locals, increment, setupOp) \ + JUCE_BEGIN_VEC_OP \ + setupOp \ + JUCE_VEC_LOOP_TWO_SOURCES_WITH_DEST_LOAD (vecOp, Mode::loadU, Mode::loadU, Mode::loadU, Mode::storeU, locals, increment) \ + JUCE_FINISH_VEC_OP (normalOp) + + + //============================================================================== + #else + #define JUCE_PERFORM_VEC_OP_DEST(normalOp, vecOp, locals, setupOp) \ + for (int i = 0; i < num; ++i) normalOp; + + #define JUCE_PERFORM_VEC_OP_SRC_DEST(normalOp, vecOp, locals, increment, setupOp) \ + for (int i = 0; i < num; ++i) normalOp; + + #define JUCE_PERFORM_VEC_OP_SRC1_SRC2_DEST(normalOp, vecOp, locals, increment, setupOp) \ + for (int i = 0; i < num; ++i) normalOp; + + #define JUCE_PERFORM_VEC_OP_SRC1_SRC2_DEST_DEST(normalOp, vecOp, locals, increment, setupOp) \ + for (int i = 0; i < num; ++i) normalOp; + + #endif + + //============================================================================== + #define JUCE_VEC_LOOP(vecOp, srcLoad, dstLoad, dstStore, locals, increment) \ + for (int i = 0; i < numLongOps; ++i) \ + { \ + locals (srcLoad, dstLoad); \ + dstStore (dest, vecOp); \ + increment; \ + } + + #define JUCE_VEC_LOOP_TWO_SOURCES(vecOp, src1Load, src2Load, dstStore, locals, increment) \ + for (int i = 0; i < numLongOps; ++i) \ + { \ + locals (src1Load, src2Load); \ + dstStore (dest, vecOp); \ + increment; \ + } + + #define JUCE_VEC_LOOP_TWO_SOURCES_WITH_DEST_LOAD(vecOp, src1Load, src2Load, dstLoad, dstStore, locals, increment) \ + for (int i = 0; i < numLongOps; ++i) \ + { \ + locals (src1Load, src2Load, dstLoad); \ + dstStore (dest, vecOp); \ + increment; \ + } + + #define JUCE_LOAD_NONE(srcLoad, dstLoad) + #define JUCE_LOAD_DEST(srcLoad, dstLoad) const Mode::ParallelType d = dstLoad (dest); + #define JUCE_LOAD_SRC(srcLoad, dstLoad) const Mode::ParallelType s = srcLoad (src); + #define JUCE_LOAD_SRC1_SRC2(src1Load, src2Load) const Mode::ParallelType s1 = src1Load (src1), s2 = src2Load (src2); + #define JUCE_LOAD_SRC1_SRC2_DEST(src1Load, src2Load, dstLoad) const Mode::ParallelType d = dstLoad (dest), s1 = src1Load (src1), s2 = src2Load (src2); + #define JUCE_LOAD_SRC_DEST(srcLoad, dstLoad) const Mode::ParallelType d = dstLoad (dest), s = srcLoad (src); + + union signMask32 { float f; uint32 i; }; + union signMask64 { double d; uint64 i; }; + + #if JUCE_USE_SSE_INTRINSICS || JUCE_USE_ARM_NEON + template struct ModeType { using Mode = BasicOps32; }; + template<> struct ModeType<8> { using Mode = BasicOps64; }; + + template + struct MinMax + { + using Type = typename Mode::Type; + using ParallelType = typename Mode::ParallelType; + + static Type findMinOrMax (const Type* src, int num, const bool isMinimum) noexcept + { + int numLongOps = num / Mode::numParallel; + + if (numLongOps > 1) + { + ParallelType val; + + #if ! JUCE_USE_ARM_NEON + if (isAligned (src)) + { + val = Mode::loadA (src); + + if (isMinimum) + { + while (--numLongOps > 0) + { + src += Mode::numParallel; + val = Mode::min (val, Mode::loadA (src)); + } + } + else + { + while (--numLongOps > 0) + { + src += Mode::numParallel; + val = Mode::max (val, Mode::loadA (src)); + } + } + } + else + #endif + { + val = Mode::loadU (src); + + if (isMinimum) + { + while (--numLongOps > 0) + { + src += Mode::numParallel; + val = Mode::min (val, Mode::loadU (src)); + } + } + else + { + while (--numLongOps > 0) + { + src += Mode::numParallel; + val = Mode::max (val, Mode::loadU (src)); + } + } + } + + Type result = isMinimum ? Mode::min (val) + : Mode::max (val); + + num &= (Mode::numParallel - 1); + src += Mode::numParallel; + + for (int i = 0; i < num; ++i) + result = isMinimum ? jmin (result, src[i]) + : jmax (result, src[i]); + + return result; + } + + return isMinimum ? juce::findMinimum (src, num) + : juce::findMaximum (src, num); + } + + static Range findMinAndMax (const Type* src, int num) noexcept + { + int numLongOps = num / Mode::numParallel; + + if (numLongOps > 1) + { + ParallelType mn, mx; + + #if ! JUCE_USE_ARM_NEON + if (isAligned (src)) + { + mn = Mode::loadA (src); + mx = mn; + + while (--numLongOps > 0) + { + src += Mode::numParallel; + const ParallelType v = Mode::loadA (src); + mn = Mode::min (mn, v); + mx = Mode::max (mx, v); + } + } + else + #endif + { + mn = Mode::loadU (src); + mx = mn; + + while (--numLongOps > 0) + { + src += Mode::numParallel; + const ParallelType v = Mode::loadU (src); + mn = Mode::min (mn, v); + mx = Mode::max (mx, v); + } + } + + Range result (Mode::min (mn), + Mode::max (mx)); + + num &= (Mode::numParallel - 1); + src += Mode::numParallel; + + for (int i = 0; i < num; ++i) + result = result.getUnionWith (src[i]); + + return result; + } + + return Range::findMinAndMax (src, num); + } + }; + #endif +} + +//============================================================================== +namespace +{ + #if JUCE_USE_VDSP_FRAMEWORK + // This casts away constness to account for slightly different vDSP function signatures + // in OSX 10.8 SDK and below. Can be safely removed once those SDKs are obsolete. + template + ValueType* osx108sdkCompatibilityCast (const ValueType* arg) noexcept { return const_cast (arg); } + #endif +} + +//============================================================================== +void JUCE_CALLTYPE FloatVectorOperations::clear (float* dest, int num) noexcept +{ + #if JUCE_USE_VDSP_FRAMEWORK + vDSP_vclr (dest, 1, (size_t) num); + #else + zeromem (dest, (size_t) num * sizeof (float)); + #endif +} + +void JUCE_CALLTYPE FloatVectorOperations::clear (double* dest, int num) noexcept +{ + #if JUCE_USE_VDSP_FRAMEWORK + vDSP_vclrD (dest, 1, (size_t) num); + #else + zeromem (dest, (size_t) num * sizeof (double)); + #endif +} + +void JUCE_CALLTYPE FloatVectorOperations::fill (float* dest, float valueToFill, int num) noexcept +{ + #if JUCE_USE_VDSP_FRAMEWORK + vDSP_vfill (&valueToFill, dest, 1, (size_t) num); + #else + JUCE_PERFORM_VEC_OP_DEST (dest[i] = valueToFill, val, JUCE_LOAD_NONE, + const Mode::ParallelType val = Mode::load1 (valueToFill);) + #endif +} + +void JUCE_CALLTYPE FloatVectorOperations::fill (double* dest, double valueToFill, int num) noexcept +{ + #if JUCE_USE_VDSP_FRAMEWORK + vDSP_vfillD (&valueToFill, dest, 1, (size_t) num); + #else + JUCE_PERFORM_VEC_OP_DEST (dest[i] = valueToFill, val, JUCE_LOAD_NONE, + const Mode::ParallelType val = Mode::load1 (valueToFill);) + #endif +} + +void JUCE_CALLTYPE FloatVectorOperations::copy (float* dest, const float* src, int num) noexcept +{ + memcpy (dest, src, (size_t) num * sizeof (float)); +} + +void JUCE_CALLTYPE FloatVectorOperations::copy (double* dest, const double* src, int num) noexcept +{ + memcpy (dest, src, (size_t) num * sizeof (double)); +} + +void JUCE_CALLTYPE FloatVectorOperations::copyWithMultiply (float* dest, const float* src, float multiplier, int num) noexcept +{ + #if JUCE_USE_VDSP_FRAMEWORK + vDSP_vsmul (src, 1, &multiplier, dest, 1, (vDSP_Length) num); + #else + JUCE_PERFORM_VEC_OP_SRC_DEST (dest[i] = src[i] * multiplier, Mode::mul (mult, s), + JUCE_LOAD_SRC, JUCE_INCREMENT_SRC_DEST, + const Mode::ParallelType mult = Mode::load1 (multiplier);) + #endif +} + +void JUCE_CALLTYPE FloatVectorOperations::copyWithMultiply (double* dest, const double* src, double multiplier, int num) noexcept +{ + #if JUCE_USE_VDSP_FRAMEWORK + vDSP_vsmulD (src, 1, &multiplier, dest, 1, (vDSP_Length) num); + #else + JUCE_PERFORM_VEC_OP_SRC_DEST (dest[i] = src[i] * multiplier, Mode::mul (mult, s), + JUCE_LOAD_SRC, JUCE_INCREMENT_SRC_DEST, + const Mode::ParallelType mult = Mode::load1 (multiplier);) + #endif +} + +void JUCE_CALLTYPE FloatVectorOperations::add (float* dest, float amount, int num) noexcept +{ + #if JUCE_USE_VDSP_FRAMEWORK + vDSP_vsadd (dest, 1, &amount, dest, 1, (vDSP_Length) num); + #else + JUCE_PERFORM_VEC_OP_DEST (dest[i] += amount, Mode::add (d, amountToAdd), JUCE_LOAD_DEST, + const Mode::ParallelType amountToAdd = Mode::load1 (amount);) + #endif +} + +void JUCE_CALLTYPE FloatVectorOperations::add (double* dest, double amount, int num) noexcept +{ + JUCE_PERFORM_VEC_OP_DEST (dest[i] += amount, Mode::add (d, amountToAdd), JUCE_LOAD_DEST, + const Mode::ParallelType amountToAdd = Mode::load1 (amount);) +} + +void JUCE_CALLTYPE FloatVectorOperations::add (float* dest, const float* src, float amount, int num) noexcept +{ + #if JUCE_USE_VDSP_FRAMEWORK + vDSP_vsadd (osx108sdkCompatibilityCast (src), 1, &amount, dest, 1, (vDSP_Length) num); + #else + JUCE_PERFORM_VEC_OP_SRC_DEST (dest[i] = src[i] + amount, Mode::add (am, s), + JUCE_LOAD_SRC, JUCE_INCREMENT_SRC_DEST, + const Mode::ParallelType am = Mode::load1 (amount);) + #endif +} + +void JUCE_CALLTYPE FloatVectorOperations::add (double* dest, const double* src, double amount, int num) noexcept +{ + #if JUCE_USE_VDSP_FRAMEWORK + vDSP_vsaddD (osx108sdkCompatibilityCast (src), 1, &amount, dest, 1, (vDSP_Length) num); + #else + JUCE_PERFORM_VEC_OP_SRC_DEST (dest[i] = src[i] + amount, Mode::add (am, s), + JUCE_LOAD_SRC, JUCE_INCREMENT_SRC_DEST, + const Mode::ParallelType am = Mode::load1 (amount);) + #endif +} + +void JUCE_CALLTYPE FloatVectorOperations::add (float* dest, const float* src, int num) noexcept +{ + #if JUCE_USE_VDSP_FRAMEWORK + vDSP_vadd (src, 1, dest, 1, dest, 1, (vDSP_Length) num); + #else + JUCE_PERFORM_VEC_OP_SRC_DEST (dest[i] += src[i], Mode::add (d, s), JUCE_LOAD_SRC_DEST, JUCE_INCREMENT_SRC_DEST, ) + #endif +} + +void JUCE_CALLTYPE FloatVectorOperations::add (double* dest, const double* src, int num) noexcept +{ + #if JUCE_USE_VDSP_FRAMEWORK + vDSP_vaddD (src, 1, dest, 1, dest, 1, (vDSP_Length) num); + #else + JUCE_PERFORM_VEC_OP_SRC_DEST (dest[i] += src[i], Mode::add (d, s), JUCE_LOAD_SRC_DEST, JUCE_INCREMENT_SRC_DEST, ) + #endif +} + +void JUCE_CALLTYPE FloatVectorOperations::add (float* dest, const float* src1, const float* src2, int num) noexcept +{ + #if JUCE_USE_VDSP_FRAMEWORK + vDSP_vadd (src1, 1, src2, 1, dest, 1, (vDSP_Length) num); + #else + JUCE_PERFORM_VEC_OP_SRC1_SRC2_DEST (dest[i] = src1[i] + src2[i], Mode::add (s1, s2), JUCE_LOAD_SRC1_SRC2, JUCE_INCREMENT_SRC1_SRC2_DEST, ) + #endif +} + +void JUCE_CALLTYPE FloatVectorOperations::add (double* dest, const double* src1, const double* src2, int num) noexcept +{ + #if JUCE_USE_VDSP_FRAMEWORK + vDSP_vaddD (src1, 1, src2, 1, dest, 1, (vDSP_Length) num); + #else + JUCE_PERFORM_VEC_OP_SRC1_SRC2_DEST (dest[i] = src1[i] + src2[i], Mode::add (s1, s2), JUCE_LOAD_SRC1_SRC2, JUCE_INCREMENT_SRC1_SRC2_DEST, ) + #endif +} + +void JUCE_CALLTYPE FloatVectorOperations::subtract (float* dest, const float* src, int num) noexcept +{ + #if JUCE_USE_VDSP_FRAMEWORK + vDSP_vsub (src, 1, dest, 1, dest, 1, (vDSP_Length) num); + #else + JUCE_PERFORM_VEC_OP_SRC_DEST (dest[i] -= src[i], Mode::sub (d, s), JUCE_LOAD_SRC_DEST, JUCE_INCREMENT_SRC_DEST, ) + #endif +} + +void JUCE_CALLTYPE FloatVectorOperations::subtract (double* dest, const double* src, int num) noexcept +{ + #if JUCE_USE_VDSP_FRAMEWORK + vDSP_vsubD (src, 1, dest, 1, dest, 1, (vDSP_Length) num); + #else + JUCE_PERFORM_VEC_OP_SRC_DEST (dest[i] -= src[i], Mode::sub (d, s), JUCE_LOAD_SRC_DEST, JUCE_INCREMENT_SRC_DEST, ) + #endif +} + +void JUCE_CALLTYPE FloatVectorOperations::subtract (float* dest, const float* src1, const float* src2, int num) noexcept +{ + #if JUCE_USE_VDSP_FRAMEWORK + vDSP_vsub (src2, 1, src1, 1, dest, 1, (vDSP_Length) num); + #else + JUCE_PERFORM_VEC_OP_SRC1_SRC2_DEST (dest[i] = src1[i] - src2[i], Mode::sub (s1, s2), JUCE_LOAD_SRC1_SRC2, JUCE_INCREMENT_SRC1_SRC2_DEST, ) + #endif +} + +void JUCE_CALLTYPE FloatVectorOperations::subtract (double* dest, const double* src1, const double* src2, int num) noexcept +{ + #if JUCE_USE_VDSP_FRAMEWORK + vDSP_vsubD (src2, 1, src1, 1, dest, 1, (vDSP_Length) num); + #else + JUCE_PERFORM_VEC_OP_SRC1_SRC2_DEST (dest[i] = src1[i] - src2[i], Mode::sub (s1, s2), JUCE_LOAD_SRC1_SRC2, JUCE_INCREMENT_SRC1_SRC2_DEST, ) + #endif +} + +void JUCE_CALLTYPE FloatVectorOperations::addWithMultiply (float* dest, const float* src, float multiplier, int num) noexcept +{ + #if JUCE_USE_VDSP_FRAMEWORK + vDSP_vsma (src, 1, &multiplier, dest, 1, dest, 1, (vDSP_Length) num); + #else + JUCE_PERFORM_VEC_OP_SRC_DEST (dest[i] += src[i] * multiplier, Mode::add (d, Mode::mul (mult, s)), + JUCE_LOAD_SRC_DEST, JUCE_INCREMENT_SRC_DEST, + const Mode::ParallelType mult = Mode::load1 (multiplier);) + #endif +} + +void JUCE_CALLTYPE FloatVectorOperations::addWithMultiply (double* dest, const double* src, double multiplier, int num) noexcept +{ + #if JUCE_USE_VDSP_FRAMEWORK + vDSP_vsmaD (src, 1, &multiplier, dest, 1, dest, 1, (vDSP_Length) num); + #else + JUCE_PERFORM_VEC_OP_SRC_DEST (dest[i] += src[i] * multiplier, Mode::add (d, Mode::mul (mult, s)), + JUCE_LOAD_SRC_DEST, JUCE_INCREMENT_SRC_DEST, + const Mode::ParallelType mult = Mode::load1 (multiplier);) + #endif +} + +void JUCE_CALLTYPE FloatVectorOperations::addWithMultiply (float* dest, const float* src1, const float* src2, int num) noexcept +{ + #if JUCE_USE_VDSP_FRAMEWORK + vDSP_vma ((float*) src1, 1, (float*) src2, 1, dest, 1, dest, 1, (vDSP_Length) num); + #else + JUCE_PERFORM_VEC_OP_SRC1_SRC2_DEST_DEST (dest[i] += src1[i] * src2[i], Mode::add (d, Mode::mul (s1, s2)), + JUCE_LOAD_SRC1_SRC2_DEST, + JUCE_INCREMENT_SRC1_SRC2_DEST, ) + #endif +} + +void JUCE_CALLTYPE FloatVectorOperations::addWithMultiply (double* dest, const double* src1, const double* src2, int num) noexcept +{ + #if JUCE_USE_VDSP_FRAMEWORK + vDSP_vmaD ((double*) src1, 1, (double*) src2, 1, dest, 1, dest, 1, (vDSP_Length) num); + #else + JUCE_PERFORM_VEC_OP_SRC1_SRC2_DEST_DEST (dest[i] += src1[i] * src2[i], Mode::add (d, Mode::mul (s1, s2)), + JUCE_LOAD_SRC1_SRC2_DEST, + JUCE_INCREMENT_SRC1_SRC2_DEST, ) + #endif +} + +void JUCE_CALLTYPE FloatVectorOperations::subtractWithMultiply (float* dest, const float* src, float multiplier, int num) noexcept +{ + JUCE_PERFORM_VEC_OP_SRC_DEST (dest[i] -= src[i] * multiplier, Mode::sub (d, Mode::mul (mult, s)), + JUCE_LOAD_SRC_DEST, JUCE_INCREMENT_SRC_DEST, + const Mode::ParallelType mult = Mode::load1 (multiplier);) +} + +void JUCE_CALLTYPE FloatVectorOperations::subtractWithMultiply (double* dest, const double* src, double multiplier, int num) noexcept +{ + JUCE_PERFORM_VEC_OP_SRC_DEST (dest[i] -= src[i] * multiplier, Mode::sub (d, Mode::mul (mult, s)), + JUCE_LOAD_SRC_DEST, JUCE_INCREMENT_SRC_DEST, + const Mode::ParallelType mult = Mode::load1 (multiplier);) +} + +void JUCE_CALLTYPE FloatVectorOperations::subtractWithMultiply (float* dest, const float* src1, const float* src2, int num) noexcept +{ + JUCE_PERFORM_VEC_OP_SRC1_SRC2_DEST_DEST (dest[i] -= src1[i] * src2[i], Mode::sub (d, Mode::mul (s1, s2)), + JUCE_LOAD_SRC1_SRC2_DEST, + JUCE_INCREMENT_SRC1_SRC2_DEST, ) +} + +void JUCE_CALLTYPE FloatVectorOperations::subtractWithMultiply (double* dest, const double* src1, const double* src2, int num) noexcept +{ + JUCE_PERFORM_VEC_OP_SRC1_SRC2_DEST_DEST (dest[i] -= src1[i] * src2[i], Mode::sub (d, Mode::mul (s1, s2)), + JUCE_LOAD_SRC1_SRC2_DEST, + JUCE_INCREMENT_SRC1_SRC2_DEST, ) +} + +void JUCE_CALLTYPE FloatVectorOperations::multiply (float* dest, const float* src, int num) noexcept +{ + #if JUCE_USE_VDSP_FRAMEWORK + vDSP_vmul (src, 1, dest, 1, dest, 1, (vDSP_Length) num); + #else + JUCE_PERFORM_VEC_OP_SRC_DEST (dest[i] *= src[i], Mode::mul (d, s), JUCE_LOAD_SRC_DEST, JUCE_INCREMENT_SRC_DEST, ) + #endif +} + +void JUCE_CALLTYPE FloatVectorOperations::multiply (double* dest, const double* src, int num) noexcept +{ + #if JUCE_USE_VDSP_FRAMEWORK + vDSP_vmulD (src, 1, dest, 1, dest, 1, (vDSP_Length) num); + #else + JUCE_PERFORM_VEC_OP_SRC_DEST (dest[i] *= src[i], Mode::mul (d, s), JUCE_LOAD_SRC_DEST, JUCE_INCREMENT_SRC_DEST, ) + #endif +} + +void JUCE_CALLTYPE FloatVectorOperations::multiply (float* dest, const float* src1, const float* src2, int num) noexcept +{ + #if JUCE_USE_VDSP_FRAMEWORK + vDSP_vmul (src1, 1, src2, 1, dest, 1, (vDSP_Length) num); + #else + JUCE_PERFORM_VEC_OP_SRC1_SRC2_DEST (dest[i] = src1[i] * src2[i], Mode::mul (s1, s2), JUCE_LOAD_SRC1_SRC2, JUCE_INCREMENT_SRC1_SRC2_DEST, ) + #endif +} + +void JUCE_CALLTYPE FloatVectorOperations::multiply (double* dest, const double* src1, const double* src2, int num) noexcept +{ + #if JUCE_USE_VDSP_FRAMEWORK + vDSP_vmulD (src1, 1, src2, 1, dest, 1, (vDSP_Length) num); + #else + JUCE_PERFORM_VEC_OP_SRC1_SRC2_DEST (dest[i] = src1[i] * src2[i], Mode::mul (s1, s2), JUCE_LOAD_SRC1_SRC2, JUCE_INCREMENT_SRC1_SRC2_DEST, ) + #endif +} + +void JUCE_CALLTYPE FloatVectorOperations::multiply (float* dest, float multiplier, int num) noexcept +{ + #if JUCE_USE_VDSP_FRAMEWORK + vDSP_vsmul (dest, 1, &multiplier, dest, 1, (vDSP_Length) num); + #else + JUCE_PERFORM_VEC_OP_DEST (dest[i] *= multiplier, Mode::mul (d, mult), JUCE_LOAD_DEST, + const Mode::ParallelType mult = Mode::load1 (multiplier);) + #endif +} + +void JUCE_CALLTYPE FloatVectorOperations::multiply (double* dest, double multiplier, int num) noexcept +{ + #if JUCE_USE_VDSP_FRAMEWORK + vDSP_vsmulD (dest, 1, &multiplier, dest, 1, (vDSP_Length) num); + #else + JUCE_PERFORM_VEC_OP_DEST (dest[i] *= multiplier, Mode::mul (d, mult), JUCE_LOAD_DEST, + const Mode::ParallelType mult = Mode::load1 (multiplier);) + #endif +} + +void JUCE_CALLTYPE FloatVectorOperations::multiply (float* dest, const float* src, float multiplier, int num) noexcept +{ + JUCE_PERFORM_VEC_OP_SRC_DEST (dest[i] = src[i] * multiplier, Mode::mul (mult, s), + JUCE_LOAD_SRC, JUCE_INCREMENT_SRC_DEST, + const Mode::ParallelType mult = Mode::load1 (multiplier);) +} + +void JUCE_CALLTYPE FloatVectorOperations::multiply (double* dest, const double* src, double multiplier, int num) noexcept +{ + JUCE_PERFORM_VEC_OP_SRC_DEST (dest[i] = src[i] * multiplier, Mode::mul (mult, s), + JUCE_LOAD_SRC, JUCE_INCREMENT_SRC_DEST, + const Mode::ParallelType mult = Mode::load1 (multiplier);) +} + +void FloatVectorOperations::negate (float* dest, const float* src, int num) noexcept +{ + #if JUCE_USE_VDSP_FRAMEWORK + vDSP_vneg ((float*) src, 1, dest, 1, (vDSP_Length) num); + #else + copyWithMultiply (dest, src, -1.0f, num); + #endif +} + +void FloatVectorOperations::negate (double* dest, const double* src, int num) noexcept +{ + #if JUCE_USE_VDSP_FRAMEWORK + vDSP_vnegD ((double*) src, 1, dest, 1, (vDSP_Length) num); + #else + copyWithMultiply (dest, src, -1.0f, num); + #endif +} + +void FloatVectorOperations::abs (float* dest, const float* src, int num) noexcept +{ + #if JUCE_USE_VDSP_FRAMEWORK + vDSP_vabs ((float*) src, 1, dest, 1, (vDSP_Length) num); + #else + FloatVectorHelpers::signMask32 signMask; + signMask.i = 0x7fffffffUL; + JUCE_PERFORM_VEC_OP_SRC_DEST (dest[i] = std::abs (src[i]), Mode::bit_and (s, mask), + JUCE_LOAD_SRC, JUCE_INCREMENT_SRC_DEST, + const Mode::ParallelType mask = Mode::load1 (signMask.f);) + + ignoreUnused (signMask); + #endif +} + +void FloatVectorOperations::abs (double* dest, const double* src, int num) noexcept +{ + #if JUCE_USE_VDSP_FRAMEWORK + vDSP_vabsD ((double*) src, 1, dest, 1, (vDSP_Length) num); + #else + FloatVectorHelpers::signMask64 signMask; + signMask.i = 0x7fffffffffffffffULL; + + JUCE_PERFORM_VEC_OP_SRC_DEST (dest[i] = std::abs (src[i]), Mode::bit_and (s, mask), + JUCE_LOAD_SRC, JUCE_INCREMENT_SRC_DEST, + const Mode::ParallelType mask = Mode::load1 (signMask.d);) + + ignoreUnused (signMask); + #endif +} + +void JUCE_CALLTYPE FloatVectorOperations::convertFixedToFloat (float* dest, const int* src, float multiplier, int num) noexcept +{ + #if JUCE_USE_ARM_NEON + JUCE_PERFORM_VEC_OP_SRC_DEST (dest[i] = src[i] * multiplier, + vmulq_n_f32 (vcvtq_f32_s32 (vld1q_s32 (src)), multiplier), + JUCE_LOAD_NONE, JUCE_INCREMENT_SRC_DEST, ) + #else + JUCE_PERFORM_VEC_OP_SRC_DEST (dest[i] = (float) src[i] * multiplier, + Mode::mul (mult, _mm_cvtepi32_ps (_mm_loadu_si128 (reinterpret_cast (src)))), + JUCE_LOAD_NONE, JUCE_INCREMENT_SRC_DEST, + const Mode::ParallelType mult = Mode::load1 (multiplier);) + #endif +} + +void JUCE_CALLTYPE FloatVectorOperations::min (float* dest, const float* src, float comp, int num) noexcept +{ + JUCE_PERFORM_VEC_OP_SRC_DEST (dest[i] = jmin (src[i], comp), Mode::min (s, cmp), + JUCE_LOAD_SRC, JUCE_INCREMENT_SRC_DEST, + const Mode::ParallelType cmp = Mode::load1 (comp);) +} + +void JUCE_CALLTYPE FloatVectorOperations::min (double* dest, const double* src, double comp, int num) noexcept +{ + JUCE_PERFORM_VEC_OP_SRC_DEST (dest[i] = jmin (src[i], comp), Mode::min (s, cmp), + JUCE_LOAD_SRC, JUCE_INCREMENT_SRC_DEST, + const Mode::ParallelType cmp = Mode::load1 (comp);) +} + +void JUCE_CALLTYPE FloatVectorOperations::min (float* dest, const float* src1, const float* src2, int num) noexcept +{ + #if JUCE_USE_VDSP_FRAMEWORK + vDSP_vmin ((float*) src1, 1, (float*) src2, 1, dest, 1, (vDSP_Length) num); + #else + JUCE_PERFORM_VEC_OP_SRC1_SRC2_DEST (dest[i] = jmin (src1[i], src2[i]), Mode::min (s1, s2), JUCE_LOAD_SRC1_SRC2, JUCE_INCREMENT_SRC1_SRC2_DEST, ) + #endif +} + +void JUCE_CALLTYPE FloatVectorOperations::min (double* dest, const double* src1, const double* src2, int num) noexcept +{ + #if JUCE_USE_VDSP_FRAMEWORK + vDSP_vminD ((double*) src1, 1, (double*) src2, 1, dest, 1, (vDSP_Length) num); + #else + JUCE_PERFORM_VEC_OP_SRC1_SRC2_DEST (dest[i] = jmin (src1[i], src2[i]), Mode::min (s1, s2), JUCE_LOAD_SRC1_SRC2, JUCE_INCREMENT_SRC1_SRC2_DEST, ) + #endif +} + +void JUCE_CALLTYPE FloatVectorOperations::max (float* dest, const float* src, float comp, int num) noexcept +{ + JUCE_PERFORM_VEC_OP_SRC_DEST (dest[i] = jmax (src[i], comp), Mode::max (s, cmp), + JUCE_LOAD_SRC, JUCE_INCREMENT_SRC_DEST, + const Mode::ParallelType cmp = Mode::load1 (comp);) +} + +void JUCE_CALLTYPE FloatVectorOperations::max (double* dest, const double* src, double comp, int num) noexcept +{ + JUCE_PERFORM_VEC_OP_SRC_DEST (dest[i] = jmax (src[i], comp), Mode::max (s, cmp), + JUCE_LOAD_SRC, JUCE_INCREMENT_SRC_DEST, + const Mode::ParallelType cmp = Mode::load1 (comp);) +} + +void JUCE_CALLTYPE FloatVectorOperations::max (float* dest, const float* src1, const float* src2, int num) noexcept +{ + #if JUCE_USE_VDSP_FRAMEWORK + vDSP_vmax ((float*) src1, 1, (float*) src2, 1, dest, 1, (vDSP_Length) num); + #else + JUCE_PERFORM_VEC_OP_SRC1_SRC2_DEST (dest[i] = jmax (src1[i], src2[i]), Mode::max (s1, s2), JUCE_LOAD_SRC1_SRC2, JUCE_INCREMENT_SRC1_SRC2_DEST, ) + #endif +} + +void JUCE_CALLTYPE FloatVectorOperations::max (double* dest, const double* src1, const double* src2, int num) noexcept +{ + #if JUCE_USE_VDSP_FRAMEWORK + vDSP_vmaxD ((double*) src1, 1, (double*) src2, 1, dest, 1, (vDSP_Length) num); + #else + JUCE_PERFORM_VEC_OP_SRC1_SRC2_DEST (dest[i] = jmax (src1[i], src2[i]), Mode::max (s1, s2), JUCE_LOAD_SRC1_SRC2, JUCE_INCREMENT_SRC1_SRC2_DEST, ) + #endif +} + +void JUCE_CALLTYPE FloatVectorOperations::clip (float* dest, const float* src, float low, float high, int num) noexcept +{ + jassert(high >= low); + + #if JUCE_USE_VDSP_FRAMEWORK + vDSP_vclip ((float*) src, 1, &low, &high, dest, 1, (vDSP_Length) num); + #else + JUCE_PERFORM_VEC_OP_SRC_DEST (dest[i] = jmax (jmin (src[i], high), low), Mode::max (Mode::min (s, hi), lo), + JUCE_LOAD_SRC, JUCE_INCREMENT_SRC_DEST, + const Mode::ParallelType lo = Mode::load1 (low); const Mode::ParallelType hi = Mode::load1 (high);) + #endif +} + +void JUCE_CALLTYPE FloatVectorOperations::clip (double* dest, const double* src, double low, double high, int num) noexcept +{ + jassert(high >= low); + + #if JUCE_USE_VDSP_FRAMEWORK + vDSP_vclipD ((double*) src, 1, &low, &high, dest, 1, (vDSP_Length) num); + #else + JUCE_PERFORM_VEC_OP_SRC_DEST (dest[i] = jmax (jmin (src[i], high), low), Mode::max (Mode::min (s, hi), lo), + JUCE_LOAD_SRC, JUCE_INCREMENT_SRC_DEST, + const Mode::ParallelType lo = Mode::load1 (low); const Mode::ParallelType hi = Mode::load1 (high);) + #endif +} + +Range JUCE_CALLTYPE FloatVectorOperations::findMinAndMax (const float* src, int num) noexcept +{ + #if JUCE_USE_SSE_INTRINSICS || JUCE_USE_ARM_NEON + return FloatVectorHelpers::MinMax::findMinAndMax (src, num); + #else + return Range::findMinAndMax (src, num); + #endif +} + +Range JUCE_CALLTYPE FloatVectorOperations::findMinAndMax (const double* src, int num) noexcept +{ + #if JUCE_USE_SSE_INTRINSICS || JUCE_USE_ARM_NEON + return FloatVectorHelpers::MinMax::findMinAndMax (src, num); + #else + return Range::findMinAndMax (src, num); + #endif +} + +float JUCE_CALLTYPE FloatVectorOperations::findMinimum (const float* src, int num) noexcept +{ + #if JUCE_USE_SSE_INTRINSICS || JUCE_USE_ARM_NEON + return FloatVectorHelpers::MinMax::findMinOrMax (src, num, true); + #else + return juce::findMinimum (src, num); + #endif +} + +double JUCE_CALLTYPE FloatVectorOperations::findMinimum (const double* src, int num) noexcept +{ + #if JUCE_USE_SSE_INTRINSICS || JUCE_USE_ARM_NEON + return FloatVectorHelpers::MinMax::findMinOrMax (src, num, true); + #else + return juce::findMinimum (src, num); + #endif +} + +float JUCE_CALLTYPE FloatVectorOperations::findMaximum (const float* src, int num) noexcept +{ + #if JUCE_USE_SSE_INTRINSICS || JUCE_USE_ARM_NEON + return FloatVectorHelpers::MinMax::findMinOrMax (src, num, false); + #else + return juce::findMaximum (src, num); + #endif +} + +double JUCE_CALLTYPE FloatVectorOperations::findMaximum (const double* src, int num) noexcept +{ + #if JUCE_USE_SSE_INTRINSICS || JUCE_USE_ARM_NEON + return FloatVectorHelpers::MinMax::findMinOrMax (src, num, false); + #else + return juce::findMaximum (src, num); + #endif +} + +intptr_t JUCE_CALLTYPE FloatVectorOperations::getFpStatusRegister() noexcept +{ + intptr_t fpsr = 0; + #if JUCE_INTEL && JUCE_USE_SSE_INTRINSICS + fpsr = static_cast (_mm_getcsr()); + #elif defined (__arm64__) || defined (__aarch64__) || JUCE_USE_ARM_NEON + #if defined (__arm64__) || defined (__aarch64__) + asm volatile("mrs %0, fpcr" : "=r" (fpsr)); + #elif JUCE_USE_ARM_NEON + asm volatile("vmrs %0, fpscr" : "=r" (fpsr)); + #endif + #else + #if ! (defined (JUCE_INTEL) || defined (JUCE_ARM)) + jassertfalse; // No support for getting the floating point status register for your platform + #endif + #endif + + return fpsr; +} + +void JUCE_CALLTYPE FloatVectorOperations::setFpStatusRegister (intptr_t fpsr) noexcept +{ + #if JUCE_INTEL && JUCE_USE_SSE_INTRINSICS + auto fpsr_w = static_cast (fpsr); + _mm_setcsr (fpsr_w); + #elif defined (__arm64__) || defined (__aarch64__) || JUCE_USE_ARM_NEON + #if defined (__arm64__) || defined (__aarch64__) + asm volatile("msr fpcr, %0" : : "ri" (fpsr)); + #elif JUCE_USE_ARM_NEON + asm volatile("vmsr fpscr, %0" : : "ri" (fpsr)); + #endif + #else + #if ! (defined (JUCE_INTEL) || defined (JUCE_ARM)) + jassertfalse; // No support for getting the floating point status register for your platform + #endif + ignoreUnused (fpsr); + #endif +} + +void JUCE_CALLTYPE FloatVectorOperations::enableFlushToZeroMode (bool shouldEnable) noexcept +{ + #if JUCE_USE_SSE_INTRINSICS || (JUCE_USE_ARM_NEON || defined (__arm64__) || defined (__aarch64__)) + #if JUCE_USE_SSE_INTRINSICS + intptr_t mask = _MM_FLUSH_ZERO_MASK; + #else /*JUCE_USE_ARM_NEON*/ + intptr_t mask = (1 << 24 /* FZ */); + #endif + setFpStatusRegister ((getFpStatusRegister() & (~mask)) | (shouldEnable ? mask : 0)); + #else + #if ! (defined (JUCE_INTEL) || defined (JUCE_ARM)) + jassertfalse; // No support for flush to zero mode on your platform + #endif + ignoreUnused (shouldEnable); + #endif +} + +void JUCE_CALLTYPE FloatVectorOperations::disableDenormalisedNumberSupport (bool shouldDisable) noexcept +{ + #if JUCE_USE_SSE_INTRINSICS || (JUCE_USE_ARM_NEON || defined (__arm64__) || defined (__aarch64__)) + #if JUCE_USE_SSE_INTRINSICS + intptr_t mask = 0x8040; + #else /*JUCE_USE_ARM_NEON*/ + intptr_t mask = (1 << 24 /* FZ */); + #endif + + setFpStatusRegister ((getFpStatusRegister() & (~mask)) | (shouldDisable ? mask : 0)); + #else + ignoreUnused (shouldDisable); + + #if ! (defined (JUCE_INTEL) || defined (JUCE_ARM)) + jassertfalse; // No support for disable denormals mode on your platform + #endif + #endif +} + +bool JUCE_CALLTYPE FloatVectorOperations::areDenormalsDisabled() noexcept +{ + #if JUCE_USE_SSE_INTRINSICS || (JUCE_USE_ARM_NEON || defined (__arm64__) || defined (__aarch64__)) + #if JUCE_USE_SSE_INTRINSICS + intptr_t mask = 0x8040; + #else /*JUCE_USE_ARM_NEON*/ + intptr_t mask = (1 << 24 /* FZ */); + #endif + + return ((getFpStatusRegister() & mask) == mask); + #else + return false; + #endif +} + +ScopedNoDenormals::ScopedNoDenormals() noexcept +{ + #if JUCE_USE_SSE_INTRINSICS || (JUCE_USE_ARM_NEON || defined (__arm64__) || defined (__aarch64__)) + #if JUCE_USE_SSE_INTRINSICS + intptr_t mask = 0x8040; + #else /*JUCE_USE_ARM_NEON*/ + intptr_t mask = (1 << 24 /* FZ */); + #endif + + fpsr = FloatVectorOperations::getFpStatusRegister(); + FloatVectorOperations::setFpStatusRegister (fpsr | mask); + #endif +} + +ScopedNoDenormals::~ScopedNoDenormals() noexcept +{ + #if JUCE_USE_SSE_INTRINSICS || (JUCE_USE_ARM_NEON || defined (__arm64__) || defined (__aarch64__)) + FloatVectorOperations::setFpStatusRegister (fpsr); + #endif +} + + +//============================================================================== +//============================================================================== +#if JUCE_UNIT_TESTS + +class FloatVectorOperationsTests : public UnitTest +{ +public: + FloatVectorOperationsTests() + : UnitTest ("FloatVectorOperations", UnitTestCategories::audio) + {} + + template + struct TestRunner + { + static void runTest (UnitTest& u, Random random) + { + const int range = random.nextBool() ? 500 : 10; + const int num = random.nextInt (range) + 1; + + HeapBlock buffer1 (num + 16), buffer2 (num + 16); + HeapBlock buffer3 (num + 16); + + #if JUCE_ARM + ValueType* const data1 = buffer1; + ValueType* const data2 = buffer2; + int* const int1 = buffer3; + #else + // These tests deliberately operate on misaligned memory and will be flagged up by + // checks for undefined behavior! + ValueType* const data1 = addBytesToPointer (buffer1.get(), random.nextInt (16)); + ValueType* const data2 = addBytesToPointer (buffer2.get(), random.nextInt (16)); + int* const int1 = addBytesToPointer (buffer3.get(), random.nextInt (16)); + #endif + + fillRandomly (random, data1, num); + fillRandomly (random, data2, num); + + Range minMax1 (FloatVectorOperations::findMinAndMax (data1, num)); + Range minMax2 (Range::findMinAndMax (data1, num)); + u.expect (minMax1 == minMax2); + + u.expect (valuesMatch (FloatVectorOperations::findMinimum (data1, num), juce::findMinimum (data1, num))); + u.expect (valuesMatch (FloatVectorOperations::findMaximum (data1, num), juce::findMaximum (data1, num))); + + u.expect (valuesMatch (FloatVectorOperations::findMinimum (data2, num), juce::findMinimum (data2, num))); + u.expect (valuesMatch (FloatVectorOperations::findMaximum (data2, num), juce::findMaximum (data2, num))); + + FloatVectorOperations::clear (data1, num); + u.expect (areAllValuesEqual (data1, num, 0)); + + FloatVectorOperations::fill (data1, (ValueType) 2, num); + u.expect (areAllValuesEqual (data1, num, (ValueType) 2)); + + FloatVectorOperations::add (data1, (ValueType) 2, num); + u.expect (areAllValuesEqual (data1, num, (ValueType) 4)); + + FloatVectorOperations::copy (data2, data1, num); + u.expect (areAllValuesEqual (data2, num, (ValueType) 4)); + + FloatVectorOperations::add (data2, data1, num); + u.expect (areAllValuesEqual (data2, num, (ValueType) 8)); + + FloatVectorOperations::copyWithMultiply (data2, data1, (ValueType) 4, num); + u.expect (areAllValuesEqual (data2, num, (ValueType) 16)); + + FloatVectorOperations::addWithMultiply (data2, data1, (ValueType) 4, num); + u.expect (areAllValuesEqual (data2, num, (ValueType) 32)); + + FloatVectorOperations::multiply (data1, (ValueType) 2, num); + u.expect (areAllValuesEqual (data1, num, (ValueType) 8)); + + FloatVectorOperations::multiply (data1, data2, num); + u.expect (areAllValuesEqual (data1, num, (ValueType) 256)); + + FloatVectorOperations::negate (data2, data1, num); + u.expect (areAllValuesEqual (data2, num, (ValueType) -256)); + + FloatVectorOperations::subtract (data1, data2, num); + u.expect (areAllValuesEqual (data1, num, (ValueType) 512)); + + FloatVectorOperations::abs (data1, data2, num); + u.expect (areAllValuesEqual (data1, num, (ValueType) 256)); + + FloatVectorOperations::abs (data2, data1, num); + u.expect (areAllValuesEqual (data2, num, (ValueType) 256)); + + fillRandomly (random, int1, num); + doConversionTest (u, data1, data2, int1, num); + + FloatVectorOperations::fill (data1, (ValueType) 2, num); + FloatVectorOperations::fill (data2, (ValueType) 3, num); + FloatVectorOperations::addWithMultiply (data1, data1, data2, num); + u.expect (areAllValuesEqual (data1, num, (ValueType) 8)); + } + + static void doConversionTest (UnitTest& u, float* data1, float* data2, int* const int1, int num) + { + FloatVectorOperations::convertFixedToFloat (data1, int1, 2.0f, num); + convertFixed (data2, int1, 2.0f, num); + u.expect (buffersMatch (data1, data2, num)); + } + + static void doConversionTest (UnitTest&, double*, double*, int*, int) {} + + static void fillRandomly (Random& random, ValueType* d, int num) + { + while (--num >= 0) + *d++ = (ValueType) (random.nextDouble() * 1000.0); + } + + static void fillRandomly (Random& random, int* d, int num) + { + while (--num >= 0) + *d++ = random.nextInt(); + } + + static void convertFixed (float* d, const int* s, ValueType multiplier, int num) + { + while (--num >= 0) + *d++ = (float) *s++ * multiplier; + } + + static bool areAllValuesEqual (const ValueType* d, int num, ValueType target) + { + while (--num >= 0) + if (*d++ != target) + return false; + + return true; + } + + static bool buffersMatch (const ValueType* d1, const ValueType* d2, int num) + { + while (--num >= 0) + if (! valuesMatch (*d1++, *d2++)) + return false; + + return true; + } + + static bool valuesMatch (ValueType v1, ValueType v2) + { + return std::abs (v1 - v2) < std::numeric_limits::epsilon(); + } + }; + + void runTest() override + { + beginTest ("FloatVectorOperations"); + + for (int i = 1000; --i >= 0;) + { + TestRunner::runTest (*this, getRandom()); + TestRunner::runTest (*this, getRandom()); + } + } +}; + +static FloatVectorOperationsTests vectorOpTests; + +#endif + +} // namespace juce diff --git a/JuceLibraryCode/modules/juce_audio_basics/buffers/juce_FloatVectorOperations.h b/JuceLibraryCode/modules/juce_audio_basics/buffers/juce_FloatVectorOperations.h index 4ad122b..b43c820 100644 --- a/JuceLibraryCode/modules/juce_audio_basics/buffers/juce_FloatVectorOperations.h +++ b/JuceLibraryCode/modules/juce_audio_basics/buffers/juce_FloatVectorOperations.h @@ -1,257 +1,257 @@ -/* - ============================================================================== - - This file is part of the JUCE library. - Copyright (c) 2017 - ROLI Ltd. - - JUCE is an open source library subject to commercial or open-source - licensing. - - The code included in this file is provided under the terms of the ISC license - http://www.isc.org/downloads/software-support-policy/isc-license. Permission - To use, copy, modify, and/or distribute this software for any purpose with or - without fee is hereby granted provided that the above copyright notice and - this permission notice appear in all copies. - - JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER - EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE - DISCLAIMED. - - ============================================================================== -*/ - -namespace juce -{ - -#ifndef JUCE_SNAP_TO_ZERO - #if JUCE_INTEL - #define JUCE_SNAP_TO_ZERO(n) if (! (n < -1.0e-8f || n > 1.0e-8f)) n = 0; - #else - #define JUCE_SNAP_TO_ZERO(n) ignoreUnused (n) - #endif -#endif -class ScopedNoDenormals; - -//============================================================================== -/** - A collection of simple vector operations on arrays of floats, accelerated with - SIMD instructions where possible. - - @tags{Audio} -*/ -class JUCE_API FloatVectorOperations -{ -public: - //============================================================================== - /** Clears a vector of floats. */ - static void JUCE_CALLTYPE clear (float* dest, int numValues) noexcept; - - /** Clears a vector of doubles. */ - static void JUCE_CALLTYPE clear (double* dest, int numValues) noexcept; - - /** Copies a repeated value into a vector of floats. */ - static void JUCE_CALLTYPE fill (float* dest, float valueToFill, int numValues) noexcept; - - /** Copies a repeated value into a vector of doubles. */ - static void JUCE_CALLTYPE fill (double* dest, double valueToFill, int numValues) noexcept; - - /** Copies a vector of floats. */ - static void JUCE_CALLTYPE copy (float* dest, const float* src, int numValues) noexcept; - - /** Copies a vector of doubles. */ - static void JUCE_CALLTYPE copy (double* dest, const double* src, int numValues) noexcept; - - /** Copies a vector of floats, multiplying each value by a given multiplier */ - static void JUCE_CALLTYPE copyWithMultiply (float* dest, const float* src, float multiplier, int numValues) noexcept; - - /** Copies a vector of doubles, multiplying each value by a given multiplier */ - static void JUCE_CALLTYPE copyWithMultiply (double* dest, const double* src, double multiplier, int numValues) noexcept; - - /** Adds a fixed value to the destination values. */ - static void JUCE_CALLTYPE add (float* dest, float amountToAdd, int numValues) noexcept; - - /** Adds a fixed value to the destination values. */ - static void JUCE_CALLTYPE add (double* dest, double amountToAdd, int numValues) noexcept; - - /** Adds a fixed value to each source value and stores it in the destination array. */ - static void JUCE_CALLTYPE add (float* dest, const float* src, float amount, int numValues) noexcept; - - /** Adds a fixed value to each source value and stores it in the destination array. */ - static void JUCE_CALLTYPE add (double* dest, const double* src, double amount, int numValues) noexcept; - - /** Adds the source values to the destination values. */ - static void JUCE_CALLTYPE add (float* dest, const float* src, int numValues) noexcept; - - /** Adds the source values to the destination values. */ - static void JUCE_CALLTYPE add (double* dest, const double* src, int numValues) noexcept; - - /** Adds each source1 value to the corresponding source2 value and stores the result in the destination array. */ - static void JUCE_CALLTYPE add (float* dest, const float* src1, const float* src2, int num) noexcept; - - /** Adds each source1 value to the corresponding source2 value and stores the result in the destination array. */ - static void JUCE_CALLTYPE add (double* dest, const double* src1, const double* src2, int num) noexcept; - - /** Subtracts the source values from the destination values. */ - static void JUCE_CALLTYPE subtract (float* dest, const float* src, int numValues) noexcept; - - /** Subtracts the source values from the destination values. */ - static void JUCE_CALLTYPE subtract (double* dest, const double* src, int numValues) noexcept; - - /** Subtracts each source2 value from the corresponding source1 value and stores the result in the destination array. */ - static void JUCE_CALLTYPE subtract (float* dest, const float* src1, const float* src2, int num) noexcept; - - /** Subtracts each source2 value from the corresponding source1 value and stores the result in the destination array. */ - static void JUCE_CALLTYPE subtract (double* dest, const double* src1, const double* src2, int num) noexcept; - - /** Multiplies each source value by the given multiplier, then adds it to the destination value. */ - static void JUCE_CALLTYPE addWithMultiply (float* dest, const float* src, float multiplier, int numValues) noexcept; - - /** Multiplies each source value by the given multiplier, then adds it to the destination value. */ - static void JUCE_CALLTYPE addWithMultiply (double* dest, const double* src, double multiplier, int numValues) noexcept; - - /** Multiplies each source1 value by the corresponding source2 value, then adds it to the destination value. */ - static void JUCE_CALLTYPE addWithMultiply (float* dest, const float* src1, const float* src2, int num) noexcept; - - /** Multiplies each source1 value by the corresponding source2 value, then adds it to the destination value. */ - static void JUCE_CALLTYPE addWithMultiply (double* dest, const double* src1, const double* src2, int num) noexcept; - - /** Multiplies each source value by the given multiplier, then subtracts it to the destination value. */ - static void JUCE_CALLTYPE subtractWithMultiply (float* dest, const float* src, float multiplier, int numValues) noexcept; - - /** Multiplies each source value by the given multiplier, then subtracts it to the destination value. */ - static void JUCE_CALLTYPE subtractWithMultiply (double* dest, const double* src, double multiplier, int numValues) noexcept; - - /** Multiplies each source1 value by the corresponding source2 value, then subtracts it to the destination value. */ - static void JUCE_CALLTYPE subtractWithMultiply (float* dest, const float* src1, const float* src2, int num) noexcept; - - /** Multiplies each source1 value by the corresponding source2 value, then subtracts it to the destination value. */ - static void JUCE_CALLTYPE subtractWithMultiply (double* dest, const double* src1, const double* src2, int num) noexcept; - - /** Multiplies the destination values by the source values. */ - static void JUCE_CALLTYPE multiply (float* dest, const float* src, int numValues) noexcept; - - /** Multiplies the destination values by the source values. */ - static void JUCE_CALLTYPE multiply (double* dest, const double* src, int numValues) noexcept; - - /** Multiplies each source1 value by the correspinding source2 value, then stores it in the destination array. */ - static void JUCE_CALLTYPE multiply (float* dest, const float* src1, const float* src2, int numValues) noexcept; - - /** Multiplies each source1 value by the correspinding source2 value, then stores it in the destination array. */ - static void JUCE_CALLTYPE multiply (double* dest, const double* src1, const double* src2, int numValues) noexcept; - - /** Multiplies each of the destination values by a fixed multiplier. */ - static void JUCE_CALLTYPE multiply (float* dest, float multiplier, int numValues) noexcept; - - /** Multiplies each of the destination values by a fixed multiplier. */ - static void JUCE_CALLTYPE multiply (double* dest, double multiplier, int numValues) noexcept; - - /** Multiplies each of the source values by a fixed multiplier and stores the result in the destination array. */ - static void JUCE_CALLTYPE multiply (float* dest, const float* src, float multiplier, int num) noexcept; - - /** Multiplies each of the source values by a fixed multiplier and stores the result in the destination array. */ - static void JUCE_CALLTYPE multiply (double* dest, const double* src, double multiplier, int num) noexcept; - - /** Copies a source vector to a destination, negating each value. */ - static void JUCE_CALLTYPE negate (float* dest, const float* src, int numValues) noexcept; - - /** Copies a source vector to a destination, negating each value. */ - static void JUCE_CALLTYPE negate (double* dest, const double* src, int numValues) noexcept; - - /** Copies a source vector to a destination, taking the absolute of each value. */ - static void JUCE_CALLTYPE abs (float* dest, const float* src, int numValues) noexcept; - - /** Copies a source vector to a destination, taking the absolute of each value. */ - static void JUCE_CALLTYPE abs (double* dest, const double* src, int numValues) noexcept; - - /** Converts a stream of integers to floats, multiplying each one by the given multiplier. */ - static void JUCE_CALLTYPE convertFixedToFloat (float* dest, const int* src, float multiplier, int numValues) noexcept; - - /** Each element of dest will be the minimum of the corresponding element of the source array and the given comp value. */ - static void JUCE_CALLTYPE min (float* dest, const float* src, float comp, int num) noexcept; - - /** Each element of dest will be the minimum of the corresponding element of the source array and the given comp value. */ - static void JUCE_CALLTYPE min (double* dest, const double* src, double comp, int num) noexcept; - - /** Each element of dest will be the minimum of the corresponding source1 and source2 values. */ - static void JUCE_CALLTYPE min (float* dest, const float* src1, const float* src2, int num) noexcept; - - /** Each element of dest will be the minimum of the corresponding source1 and source2 values. */ - static void JUCE_CALLTYPE min (double* dest, const double* src1, const double* src2, int num) noexcept; - - /** Each element of dest will be the maximum of the corresponding element of the source array and the given comp value. */ - static void JUCE_CALLTYPE max (float* dest, const float* src, float comp, int num) noexcept; - - /** Each element of dest will be the maximum of the corresponding element of the source array and the given comp value. */ - static void JUCE_CALLTYPE max (double* dest, const double* src, double comp, int num) noexcept; - - /** Each element of dest will be the maximum of the corresponding source1 and source2 values. */ - static void JUCE_CALLTYPE max (float* dest, const float* src1, const float* src2, int num) noexcept; - - /** Each element of dest will be the maximum of the corresponding source1 and source2 values. */ - static void JUCE_CALLTYPE max (double* dest, const double* src1, const double* src2, int num) noexcept; - - /** Each element of dest is calculated by hard clipping the corresponding src element so that it is in the range specified by the arguments low and high. */ - static void JUCE_CALLTYPE clip (float* dest, const float* src, float low, float high, int num) noexcept; - - /** Each element of dest is calculated by hard clipping the corresponding src element so that it is in the range specified by the arguments low and high. */ - static void JUCE_CALLTYPE clip (double* dest, const double* src, double low, double high, int num) noexcept; - - /** Finds the minimum and maximum values in the given array. */ - static Range JUCE_CALLTYPE findMinAndMax (const float* src, int numValues) noexcept; - - /** Finds the minimum and maximum values in the given array. */ - static Range JUCE_CALLTYPE findMinAndMax (const double* src, int numValues) noexcept; - - /** Finds the minimum value in the given array. */ - static float JUCE_CALLTYPE findMinimum (const float* src, int numValues) noexcept; - - /** Finds the minimum value in the given array. */ - static double JUCE_CALLTYPE findMinimum (const double* src, int numValues) noexcept; - - /** Finds the maximum value in the given array. */ - static float JUCE_CALLTYPE findMaximum (const float* src, int numValues) noexcept; - - /** Finds the maximum value in the given array. */ - static double JUCE_CALLTYPE findMaximum (const double* src, int numValues) noexcept; - - /** This method enables or disables the SSE/NEON flush-to-zero mode. */ - static void JUCE_CALLTYPE enableFlushToZeroMode (bool shouldEnable) noexcept; - - /** On Intel CPUs, this method enables the SSE flush-to-zero and denormalised-are-zero modes. - This effectively sets the DAZ and FZ bits of the MXCSR register. On arm CPUs this will - enable flush to zero mode. - It's a convenient thing to call before audio processing code where you really want to - avoid denormalisation performance hits. - */ - static void JUCE_CALLTYPE disableDenormalisedNumberSupport (bool shouldDisable = true) noexcept; - - /** This method returns true if denormals are currently disabled. */ - static bool JUCE_CALLTYPE areDenormalsDisabled() noexcept; - -private: - friend ScopedNoDenormals; - - static intptr_t JUCE_CALLTYPE getFpStatusRegister() noexcept; - static void JUCE_CALLTYPE setFpStatusRegister (intptr_t) noexcept; -}; - -//============================================================================== -/** - Helper class providing an RAII-based mechanism for temporarily disabling - denormals on your CPU. - - @tags{Audio} -*/ -class ScopedNoDenormals -{ -public: - ScopedNoDenormals() noexcept; - ~ScopedNoDenormals() noexcept; - -private: - #if JUCE_USE_SSE_INTRINSICS || (JUCE_USE_ARM_NEON || defined (__arm64__) || defined (__aarch64__)) - intptr_t fpsr; - #endif -}; - -} // namespace juce +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2017 - ROLI Ltd. + + JUCE is an open source library subject to commercial or open-source + licensing. + + The code included in this file is provided under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license. Permission + To use, copy, modify, and/or distribute this software for any purpose with or + without fee is hereby granted provided that the above copyright notice and + this permission notice appear in all copies. + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +namespace juce +{ + +#ifndef JUCE_SNAP_TO_ZERO + #if JUCE_INTEL + #define JUCE_SNAP_TO_ZERO(n) if (! (n < -1.0e-8f || n > 1.0e-8f)) n = 0; + #else + #define JUCE_SNAP_TO_ZERO(n) ignoreUnused (n) + #endif +#endif +class ScopedNoDenormals; + +//============================================================================== +/** + A collection of simple vector operations on arrays of floats, accelerated with + SIMD instructions where possible. + + @tags{Audio} +*/ +class JUCE_API FloatVectorOperations +{ +public: + //============================================================================== + /** Clears a vector of floats. */ + static void JUCE_CALLTYPE clear (float* dest, int numValues) noexcept; + + /** Clears a vector of doubles. */ + static void JUCE_CALLTYPE clear (double* dest, int numValues) noexcept; + + /** Copies a repeated value into a vector of floats. */ + static void JUCE_CALLTYPE fill (float* dest, float valueToFill, int numValues) noexcept; + + /** Copies a repeated value into a vector of doubles. */ + static void JUCE_CALLTYPE fill (double* dest, double valueToFill, int numValues) noexcept; + + /** Copies a vector of floats. */ + static void JUCE_CALLTYPE copy (float* dest, const float* src, int numValues) noexcept; + + /** Copies a vector of doubles. */ + static void JUCE_CALLTYPE copy (double* dest, const double* src, int numValues) noexcept; + + /** Copies a vector of floats, multiplying each value by a given multiplier */ + static void JUCE_CALLTYPE copyWithMultiply (float* dest, const float* src, float multiplier, int numValues) noexcept; + + /** Copies a vector of doubles, multiplying each value by a given multiplier */ + static void JUCE_CALLTYPE copyWithMultiply (double* dest, const double* src, double multiplier, int numValues) noexcept; + + /** Adds a fixed value to the destination values. */ + static void JUCE_CALLTYPE add (float* dest, float amountToAdd, int numValues) noexcept; + + /** Adds a fixed value to the destination values. */ + static void JUCE_CALLTYPE add (double* dest, double amountToAdd, int numValues) noexcept; + + /** Adds a fixed value to each source value and stores it in the destination array. */ + static void JUCE_CALLTYPE add (float* dest, const float* src, float amount, int numValues) noexcept; + + /** Adds a fixed value to each source value and stores it in the destination array. */ + static void JUCE_CALLTYPE add (double* dest, const double* src, double amount, int numValues) noexcept; + + /** Adds the source values to the destination values. */ + static void JUCE_CALLTYPE add (float* dest, const float* src, int numValues) noexcept; + + /** Adds the source values to the destination values. */ + static void JUCE_CALLTYPE add (double* dest, const double* src, int numValues) noexcept; + + /** Adds each source1 value to the corresponding source2 value and stores the result in the destination array. */ + static void JUCE_CALLTYPE add (float* dest, const float* src1, const float* src2, int num) noexcept; + + /** Adds each source1 value to the corresponding source2 value and stores the result in the destination array. */ + static void JUCE_CALLTYPE add (double* dest, const double* src1, const double* src2, int num) noexcept; + + /** Subtracts the source values from the destination values. */ + static void JUCE_CALLTYPE subtract (float* dest, const float* src, int numValues) noexcept; + + /** Subtracts the source values from the destination values. */ + static void JUCE_CALLTYPE subtract (double* dest, const double* src, int numValues) noexcept; + + /** Subtracts each source2 value from the corresponding source1 value and stores the result in the destination array. */ + static void JUCE_CALLTYPE subtract (float* dest, const float* src1, const float* src2, int num) noexcept; + + /** Subtracts each source2 value from the corresponding source1 value and stores the result in the destination array. */ + static void JUCE_CALLTYPE subtract (double* dest, const double* src1, const double* src2, int num) noexcept; + + /** Multiplies each source value by the given multiplier, then adds it to the destination value. */ + static void JUCE_CALLTYPE addWithMultiply (float* dest, const float* src, float multiplier, int numValues) noexcept; + + /** Multiplies each source value by the given multiplier, then adds it to the destination value. */ + static void JUCE_CALLTYPE addWithMultiply (double* dest, const double* src, double multiplier, int numValues) noexcept; + + /** Multiplies each source1 value by the corresponding source2 value, then adds it to the destination value. */ + static void JUCE_CALLTYPE addWithMultiply (float* dest, const float* src1, const float* src2, int num) noexcept; + + /** Multiplies each source1 value by the corresponding source2 value, then adds it to the destination value. */ + static void JUCE_CALLTYPE addWithMultiply (double* dest, const double* src1, const double* src2, int num) noexcept; + + /** Multiplies each source value by the given multiplier, then subtracts it to the destination value. */ + static void JUCE_CALLTYPE subtractWithMultiply (float* dest, const float* src, float multiplier, int numValues) noexcept; + + /** Multiplies each source value by the given multiplier, then subtracts it to the destination value. */ + static void JUCE_CALLTYPE subtractWithMultiply (double* dest, const double* src, double multiplier, int numValues) noexcept; + + /** Multiplies each source1 value by the corresponding source2 value, then subtracts it to the destination value. */ + static void JUCE_CALLTYPE subtractWithMultiply (float* dest, const float* src1, const float* src2, int num) noexcept; + + /** Multiplies each source1 value by the corresponding source2 value, then subtracts it to the destination value. */ + static void JUCE_CALLTYPE subtractWithMultiply (double* dest, const double* src1, const double* src2, int num) noexcept; + + /** Multiplies the destination values by the source values. */ + static void JUCE_CALLTYPE multiply (float* dest, const float* src, int numValues) noexcept; + + /** Multiplies the destination values by the source values. */ + static void JUCE_CALLTYPE multiply (double* dest, const double* src, int numValues) noexcept; + + /** Multiplies each source1 value by the correspinding source2 value, then stores it in the destination array. */ + static void JUCE_CALLTYPE multiply (float* dest, const float* src1, const float* src2, int numValues) noexcept; + + /** Multiplies each source1 value by the correspinding source2 value, then stores it in the destination array. */ + static void JUCE_CALLTYPE multiply (double* dest, const double* src1, const double* src2, int numValues) noexcept; + + /** Multiplies each of the destination values by a fixed multiplier. */ + static void JUCE_CALLTYPE multiply (float* dest, float multiplier, int numValues) noexcept; + + /** Multiplies each of the destination values by a fixed multiplier. */ + static void JUCE_CALLTYPE multiply (double* dest, double multiplier, int numValues) noexcept; + + /** Multiplies each of the source values by a fixed multiplier and stores the result in the destination array. */ + static void JUCE_CALLTYPE multiply (float* dest, const float* src, float multiplier, int num) noexcept; + + /** Multiplies each of the source values by a fixed multiplier and stores the result in the destination array. */ + static void JUCE_CALLTYPE multiply (double* dest, const double* src, double multiplier, int num) noexcept; + + /** Copies a source vector to a destination, negating each value. */ + static void JUCE_CALLTYPE negate (float* dest, const float* src, int numValues) noexcept; + + /** Copies a source vector to a destination, negating each value. */ + static void JUCE_CALLTYPE negate (double* dest, const double* src, int numValues) noexcept; + + /** Copies a source vector to a destination, taking the absolute of each value. */ + static void JUCE_CALLTYPE abs (float* dest, const float* src, int numValues) noexcept; + + /** Copies a source vector to a destination, taking the absolute of each value. */ + static void JUCE_CALLTYPE abs (double* dest, const double* src, int numValues) noexcept; + + /** Converts a stream of integers to floats, multiplying each one by the given multiplier. */ + static void JUCE_CALLTYPE convertFixedToFloat (float* dest, const int* src, float multiplier, int numValues) noexcept; + + /** Each element of dest will be the minimum of the corresponding element of the source array and the given comp value. */ + static void JUCE_CALLTYPE min (float* dest, const float* src, float comp, int num) noexcept; + + /** Each element of dest will be the minimum of the corresponding element of the source array and the given comp value. */ + static void JUCE_CALLTYPE min (double* dest, const double* src, double comp, int num) noexcept; + + /** Each element of dest will be the minimum of the corresponding source1 and source2 values. */ + static void JUCE_CALLTYPE min (float* dest, const float* src1, const float* src2, int num) noexcept; + + /** Each element of dest will be the minimum of the corresponding source1 and source2 values. */ + static void JUCE_CALLTYPE min (double* dest, const double* src1, const double* src2, int num) noexcept; + + /** Each element of dest will be the maximum of the corresponding element of the source array and the given comp value. */ + static void JUCE_CALLTYPE max (float* dest, const float* src, float comp, int num) noexcept; + + /** Each element of dest will be the maximum of the corresponding element of the source array and the given comp value. */ + static void JUCE_CALLTYPE max (double* dest, const double* src, double comp, int num) noexcept; + + /** Each element of dest will be the maximum of the corresponding source1 and source2 values. */ + static void JUCE_CALLTYPE max (float* dest, const float* src1, const float* src2, int num) noexcept; + + /** Each element of dest will be the maximum of the corresponding source1 and source2 values. */ + static void JUCE_CALLTYPE max (double* dest, const double* src1, const double* src2, int num) noexcept; + + /** Each element of dest is calculated by hard clipping the corresponding src element so that it is in the range specified by the arguments low and high. */ + static void JUCE_CALLTYPE clip (float* dest, const float* src, float low, float high, int num) noexcept; + + /** Each element of dest is calculated by hard clipping the corresponding src element so that it is in the range specified by the arguments low and high. */ + static void JUCE_CALLTYPE clip (double* dest, const double* src, double low, double high, int num) noexcept; + + /** Finds the minimum and maximum values in the given array. */ + static Range JUCE_CALLTYPE findMinAndMax (const float* src, int numValues) noexcept; + + /** Finds the minimum and maximum values in the given array. */ + static Range JUCE_CALLTYPE findMinAndMax (const double* src, int numValues) noexcept; + + /** Finds the minimum value in the given array. */ + static float JUCE_CALLTYPE findMinimum (const float* src, int numValues) noexcept; + + /** Finds the minimum value in the given array. */ + static double JUCE_CALLTYPE findMinimum (const double* src, int numValues) noexcept; + + /** Finds the maximum value in the given array. */ + static float JUCE_CALLTYPE findMaximum (const float* src, int numValues) noexcept; + + /** Finds the maximum value in the given array. */ + static double JUCE_CALLTYPE findMaximum (const double* src, int numValues) noexcept; + + /** This method enables or disables the SSE/NEON flush-to-zero mode. */ + static void JUCE_CALLTYPE enableFlushToZeroMode (bool shouldEnable) noexcept; + + /** On Intel CPUs, this method enables the SSE flush-to-zero and denormalised-are-zero modes. + This effectively sets the DAZ and FZ bits of the MXCSR register. On arm CPUs this will + enable flush to zero mode. + It's a convenient thing to call before audio processing code where you really want to + avoid denormalisation performance hits. + */ + static void JUCE_CALLTYPE disableDenormalisedNumberSupport (bool shouldDisable = true) noexcept; + + /** This method returns true if denormals are currently disabled. */ + static bool JUCE_CALLTYPE areDenormalsDisabled() noexcept; + +private: + friend ScopedNoDenormals; + + static intptr_t JUCE_CALLTYPE getFpStatusRegister() noexcept; + static void JUCE_CALLTYPE setFpStatusRegister (intptr_t) noexcept; +}; + +//============================================================================== +/** + Helper class providing an RAII-based mechanism for temporarily disabling + denormals on your CPU. + + @tags{Audio} +*/ +class ScopedNoDenormals +{ +public: + ScopedNoDenormals() noexcept; + ~ScopedNoDenormals() noexcept; + +private: + #if JUCE_USE_SSE_INTRINSICS || (JUCE_USE_ARM_NEON || defined (__arm64__) || defined (__aarch64__)) + intptr_t fpsr; + #endif +}; + +} // namespace juce diff --git a/JuceLibraryCode/modules/juce_audio_basics/juce_audio_basics.cpp b/JuceLibraryCode/modules/juce_audio_basics/juce_audio_basics.cpp index c142c6d..74cfe45 100644 --- a/JuceLibraryCode/modules/juce_audio_basics/juce_audio_basics.cpp +++ b/JuceLibraryCode/modules/juce_audio_basics/juce_audio_basics.cpp @@ -1,87 +1,87 @@ -/* - ============================================================================== - - This file is part of the JUCE library. - Copyright (c) 2017 - ROLI Ltd. - - JUCE is an open source library subject to commercial or open-source - licensing. - - The code included in this file is provided under the terms of the ISC license - http://www.isc.org/downloads/software-support-policy/isc-license. Permission - To use, copy, modify, and/or distribute this software for any purpose with or - without fee is hereby granted provided that the above copyright notice and - this permission notice appear in all copies. - - JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER - EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE - DISCLAIMED. - - ============================================================================== -*/ - -#ifdef JUCE_AUDIO_BASICS_H_INCLUDED - /* When you add this cpp file to your project, you mustn't include it in a file where you've - already included any other headers - just put it inside a file on its own, possibly with your config - flags preceding it, but don't include anything else. That also includes avoiding any automatic prefix - header files that the compiler may be using. - */ - #error "Incorrect use of JUCE cpp file" -#endif - -#include "juce_audio_basics.h" - -#if JUCE_MINGW && ! defined (alloca) - #define alloca __builtin_alloca -#endif - -#if JUCE_USE_SSE_INTRINSICS - #include -#endif - -#ifndef JUCE_USE_VDSP_FRAMEWORK - #define JUCE_USE_VDSP_FRAMEWORK 1 -#endif - -#if (JUCE_MAC || JUCE_IOS) && JUCE_USE_VDSP_FRAMEWORK - #include -#else - #undef JUCE_USE_VDSP_FRAMEWORK -#endif - -#if JUCE_USE_ARM_NEON - #include -#endif - -#include "buffers/juce_AudioDataConverters.cpp" -#include "buffers/juce_FloatVectorOperations.cpp" -#include "buffers/juce_AudioChannelSet.cpp" -#include "buffers/juce_AudioProcessLoadMeasurer.cpp" -#include "utilities/juce_IIRFilter.cpp" -#include "utilities/juce_LagrangeInterpolator.cpp" -#include "utilities/juce_CatmullRomInterpolator.cpp" -#include "utilities/juce_SmoothedValue.cpp" -#include "midi/juce_MidiBuffer.cpp" -#include "midi/juce_MidiFile.cpp" -#include "midi/juce_MidiKeyboardState.cpp" -#include "midi/juce_MidiMessage.cpp" -#include "midi/juce_MidiMessageSequence.cpp" -#include "midi/juce_MidiRPN.cpp" -#include "mpe/juce_MPEValue.cpp" -#include "mpe/juce_MPENote.cpp" -#include "mpe/juce_MPEZoneLayout.cpp" -#include "mpe/juce_MPEInstrument.cpp" -#include "mpe/juce_MPEMessages.cpp" -#include "mpe/juce_MPESynthesiserBase.cpp" -#include "mpe/juce_MPESynthesiserVoice.cpp" -#include "mpe/juce_MPESynthesiser.cpp" -#include "mpe/juce_MPEUtils.cpp" -#include "sources/juce_BufferingAudioSource.cpp" -#include "sources/juce_ChannelRemappingAudioSource.cpp" -#include "sources/juce_IIRFilterAudioSource.cpp" -#include "sources/juce_MemoryAudioSource.cpp" -#include "sources/juce_MixerAudioSource.cpp" -#include "sources/juce_ResamplingAudioSource.cpp" -#include "sources/juce_ReverbAudioSource.cpp" -#include "sources/juce_ToneGeneratorAudioSource.cpp" -#include "synthesisers/juce_Synthesiser.cpp" +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2017 - ROLI Ltd. + + JUCE is an open source library subject to commercial or open-source + licensing. + + The code included in this file is provided under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license. Permission + To use, copy, modify, and/or distribute this software for any purpose with or + without fee is hereby granted provided that the above copyright notice and + this permission notice appear in all copies. + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +#ifdef JUCE_AUDIO_BASICS_H_INCLUDED + /* When you add this cpp file to your project, you mustn't include it in a file where you've + already included any other headers - just put it inside a file on its own, possibly with your config + flags preceding it, but don't include anything else. That also includes avoiding any automatic prefix + header files that the compiler may be using. + */ + #error "Incorrect use of JUCE cpp file" +#endif + +#include "juce_audio_basics.h" + +#if JUCE_MINGW && ! defined (alloca) + #define alloca __builtin_alloca +#endif + +#if JUCE_USE_SSE_INTRINSICS + #include +#endif + +#ifndef JUCE_USE_VDSP_FRAMEWORK + #define JUCE_USE_VDSP_FRAMEWORK 1 +#endif + +#if (JUCE_MAC || JUCE_IOS) && JUCE_USE_VDSP_FRAMEWORK + #include +#else + #undef JUCE_USE_VDSP_FRAMEWORK +#endif + +#if JUCE_USE_ARM_NEON + #include +#endif + +#include "buffers/juce_AudioDataConverters.cpp" +#include "buffers/juce_FloatVectorOperations.cpp" +#include "buffers/juce_AudioChannelSet.cpp" +#include "buffers/juce_AudioProcessLoadMeasurer.cpp" +#include "utilities/juce_IIRFilter.cpp" +#include "utilities/juce_LagrangeInterpolator.cpp" +#include "utilities/juce_CatmullRomInterpolator.cpp" +#include "utilities/juce_SmoothedValue.cpp" +#include "midi/juce_MidiBuffer.cpp" +#include "midi/juce_MidiFile.cpp" +#include "midi/juce_MidiKeyboardState.cpp" +#include "midi/juce_MidiMessage.cpp" +#include "midi/juce_MidiMessageSequence.cpp" +#include "midi/juce_MidiRPN.cpp" +#include "mpe/juce_MPEValue.cpp" +#include "mpe/juce_MPENote.cpp" +#include "mpe/juce_MPEZoneLayout.cpp" +#include "mpe/juce_MPEInstrument.cpp" +#include "mpe/juce_MPEMessages.cpp" +#include "mpe/juce_MPESynthesiserBase.cpp" +#include "mpe/juce_MPESynthesiserVoice.cpp" +#include "mpe/juce_MPESynthesiser.cpp" +#include "mpe/juce_MPEUtils.cpp" +#include "sources/juce_BufferingAudioSource.cpp" +#include "sources/juce_ChannelRemappingAudioSource.cpp" +#include "sources/juce_IIRFilterAudioSource.cpp" +#include "sources/juce_MemoryAudioSource.cpp" +#include "sources/juce_MixerAudioSource.cpp" +#include "sources/juce_ResamplingAudioSource.cpp" +#include "sources/juce_ReverbAudioSource.cpp" +#include "sources/juce_ToneGeneratorAudioSource.cpp" +#include "synthesisers/juce_Synthesiser.cpp" diff --git a/JuceLibraryCode/modules/juce_audio_basics/juce_audio_basics.h b/JuceLibraryCode/modules/juce_audio_basics/juce_audio_basics.h index 93fd4c6..60f11a7 100644 --- a/JuceLibraryCode/modules/juce_audio_basics/juce_audio_basics.h +++ b/JuceLibraryCode/modules/juce_audio_basics/juce_audio_basics.h @@ -1,122 +1,122 @@ -/* - ============================================================================== - - This file is part of the JUCE library. - Copyright (c) 2017 - ROLI Ltd. - - JUCE is an open source library subject to commercial or open-source - licensing. - - The code included in this file is provided under the terms of the ISC license - http://www.isc.org/downloads/software-support-policy/isc-license. Permission - To use, copy, modify, and/or distribute this software for any purpose with or - without fee is hereby granted provided that the above copyright notice and - this permission notice appear in all copies. - - JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER - EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE - DISCLAIMED. - - ============================================================================== -*/ - - -/******************************************************************************* - The block below describes the properties of this module, and is read by - the Projucer to automatically generate project code that uses it. - For details about the syntax and how to create or use a module, see the - JUCE Module Format.txt file. - - - BEGIN_JUCE_MODULE_DECLARATION - - ID: juce_audio_basics - vendor: juce - version: 5.4.7 - name: JUCE audio and MIDI data classes - description: Classes for audio buffer manipulation, midi message handling, synthesis, etc. - website: http://www.juce.com/juce - license: ISC - - dependencies: juce_core - OSXFrameworks: Accelerate - iOSFrameworks: Accelerate - - END_JUCE_MODULE_DECLARATION - -*******************************************************************************/ - - -#pragma once -#define JUCE_AUDIO_BASICS_H_INCLUDED - -#include - -//============================================================================== -#undef Complex // apparently some C libraries actually define these symbols (!) -#undef Factor - -//============================================================================== -#if JUCE_MINGW && ! defined (__SSE2__) - #define JUCE_USE_SSE_INTRINSICS 0 -#endif - -#ifndef JUCE_USE_SSE_INTRINSICS - #define JUCE_USE_SSE_INTRINSICS 1 -#endif - -#if ! JUCE_INTEL - #undef JUCE_USE_SSE_INTRINSICS -#endif - -#if __ARM_NEON__ && ! (JUCE_USE_VDSP_FRAMEWORK || defined (JUCE_USE_ARM_NEON)) - #define JUCE_USE_ARM_NEON 1 -#endif - -#if TARGET_IPHONE_SIMULATOR - #ifdef JUCE_USE_ARM_NEON - #undef JUCE_USE_ARM_NEON - #endif - #define JUCE_USE_ARM_NEON 0 -#endif - -//============================================================================== -#include "buffers/juce_AudioDataConverters.h" -#include "buffers/juce_FloatVectorOperations.h" -#include "buffers/juce_AudioSampleBuffer.h" -#include "buffers/juce_AudioChannelSet.h" -#include "buffers/juce_AudioProcessLoadMeasurer.h" -#include "utilities/juce_Decibels.h" -#include "utilities/juce_IIRFilter.h" -#include "utilities/juce_LagrangeInterpolator.h" -#include "utilities/juce_CatmullRomInterpolator.h" -#include "utilities/juce_SmoothedValue.h" -#include "utilities/juce_Reverb.h" -#include "utilities/juce_ADSR.h" -#include "midi/juce_MidiMessage.h" -#include "midi/juce_MidiBuffer.h" -#include "midi/juce_MidiMessageSequence.h" -#include "midi/juce_MidiFile.h" -#include "midi/juce_MidiKeyboardState.h" -#include "midi/juce_MidiRPN.h" -#include "mpe/juce_MPEValue.h" -#include "mpe/juce_MPENote.h" -#include "mpe/juce_MPEZoneLayout.h" -#include "mpe/juce_MPEInstrument.h" -#include "mpe/juce_MPEMessages.h" -#include "mpe/juce_MPESynthesiserBase.h" -#include "mpe/juce_MPESynthesiserVoice.h" -#include "mpe/juce_MPESynthesiser.h" -#include "mpe/juce_MPEUtils.h" -#include "sources/juce_AudioSource.h" -#include "sources/juce_PositionableAudioSource.h" -#include "sources/juce_BufferingAudioSource.h" -#include "sources/juce_ChannelRemappingAudioSource.h" -#include "sources/juce_IIRFilterAudioSource.h" -#include "sources/juce_MemoryAudioSource.h" -#include "sources/juce_MixerAudioSource.h" -#include "sources/juce_ResamplingAudioSource.h" -#include "sources/juce_ReverbAudioSource.h" -#include "sources/juce_ToneGeneratorAudioSource.h" -#include "synthesisers/juce_Synthesiser.h" -#include "audio_play_head/juce_AudioPlayHead.h" +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2017 - ROLI Ltd. + + JUCE is an open source library subject to commercial or open-source + licensing. + + The code included in this file is provided under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license. Permission + To use, copy, modify, and/or distribute this software for any purpose with or + without fee is hereby granted provided that the above copyright notice and + this permission notice appear in all copies. + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + + +/******************************************************************************* + The block below describes the properties of this module, and is read by + the Projucer to automatically generate project code that uses it. + For details about the syntax and how to create or use a module, see the + JUCE Module Format.txt file. + + + BEGIN_JUCE_MODULE_DECLARATION + + ID: juce_audio_basics + vendor: juce + version: 5.4.7 + name: JUCE audio and MIDI data classes + description: Classes for audio buffer manipulation, midi message handling, synthesis, etc. + website: http://www.juce.com/juce + license: ISC + + dependencies: juce_core + OSXFrameworks: Accelerate + iOSFrameworks: Accelerate + + END_JUCE_MODULE_DECLARATION + +*******************************************************************************/ + + +#pragma once +#define JUCE_AUDIO_BASICS_H_INCLUDED + +#include + +//============================================================================== +#undef Complex // apparently some C libraries actually define these symbols (!) +#undef Factor + +//============================================================================== +#if JUCE_MINGW && ! defined (__SSE2__) + #define JUCE_USE_SSE_INTRINSICS 0 +#endif + +#ifndef JUCE_USE_SSE_INTRINSICS + #define JUCE_USE_SSE_INTRINSICS 1 +#endif + +#if ! JUCE_INTEL + #undef JUCE_USE_SSE_INTRINSICS +#endif + +#if __ARM_NEON__ && ! (JUCE_USE_VDSP_FRAMEWORK || defined (JUCE_USE_ARM_NEON)) + #define JUCE_USE_ARM_NEON 1 +#endif + +#if TARGET_IPHONE_SIMULATOR + #ifdef JUCE_USE_ARM_NEON + #undef JUCE_USE_ARM_NEON + #endif + #define JUCE_USE_ARM_NEON 0 +#endif + +//============================================================================== +#include "buffers/juce_AudioDataConverters.h" +#include "buffers/juce_FloatVectorOperations.h" +#include "buffers/juce_AudioSampleBuffer.h" +#include "buffers/juce_AudioChannelSet.h" +#include "buffers/juce_AudioProcessLoadMeasurer.h" +#include "utilities/juce_Decibels.h" +#include "utilities/juce_IIRFilter.h" +#include "utilities/juce_LagrangeInterpolator.h" +#include "utilities/juce_CatmullRomInterpolator.h" +#include "utilities/juce_SmoothedValue.h" +#include "utilities/juce_Reverb.h" +#include "utilities/juce_ADSR.h" +#include "midi/juce_MidiMessage.h" +#include "midi/juce_MidiBuffer.h" +#include "midi/juce_MidiMessageSequence.h" +#include "midi/juce_MidiFile.h" +#include "midi/juce_MidiKeyboardState.h" +#include "midi/juce_MidiRPN.h" +#include "mpe/juce_MPEValue.h" +#include "mpe/juce_MPENote.h" +#include "mpe/juce_MPEZoneLayout.h" +#include "mpe/juce_MPEInstrument.h" +#include "mpe/juce_MPEMessages.h" +#include "mpe/juce_MPESynthesiserBase.h" +#include "mpe/juce_MPESynthesiserVoice.h" +#include "mpe/juce_MPESynthesiser.h" +#include "mpe/juce_MPEUtils.h" +#include "sources/juce_AudioSource.h" +#include "sources/juce_PositionableAudioSource.h" +#include "sources/juce_BufferingAudioSource.h" +#include "sources/juce_ChannelRemappingAudioSource.h" +#include "sources/juce_IIRFilterAudioSource.h" +#include "sources/juce_MemoryAudioSource.h" +#include "sources/juce_MixerAudioSource.h" +#include "sources/juce_ResamplingAudioSource.h" +#include "sources/juce_ReverbAudioSource.h" +#include "sources/juce_ToneGeneratorAudioSource.h" +#include "synthesisers/juce_Synthesiser.h" +#include "audio_play_head/juce_AudioPlayHead.h" diff --git a/JuceLibraryCode/modules/juce_audio_basics/juce_audio_basics.mm b/JuceLibraryCode/modules/juce_audio_basics/juce_audio_basics.mm index 588078c..c52bbb3 100644 --- a/JuceLibraryCode/modules/juce_audio_basics/juce_audio_basics.mm +++ b/JuceLibraryCode/modules/juce_audio_basics/juce_audio_basics.mm @@ -1,23 +1,23 @@ -/* - ============================================================================== - - This file is part of the JUCE library. - Copyright (c) 2017 - ROLI Ltd. - - JUCE is an open source library subject to commercial or open-source - licensing. - - The code included in this file is provided under the terms of the ISC license - http://www.isc.org/downloads/software-support-policy/isc-license. Permission - To use, copy, modify, and/or distribute this software for any purpose with or - without fee is hereby granted provided that the above copyright notice and - this permission notice appear in all copies. - - JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER - EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE - DISCLAIMED. - - ============================================================================== -*/ - -#include "juce_audio_basics.cpp" +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2017 - ROLI Ltd. + + JUCE is an open source library subject to commercial or open-source + licensing. + + The code included in this file is provided under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license. Permission + To use, copy, modify, and/or distribute this software for any purpose with or + without fee is hereby granted provided that the above copyright notice and + this permission notice appear in all copies. + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +#include "juce_audio_basics.cpp" diff --git a/JuceLibraryCode/modules/juce_audio_basics/midi/juce_MidiBuffer.cpp b/JuceLibraryCode/modules/juce_audio_basics/midi/juce_MidiBuffer.cpp index 0501698..3419690 100644 --- a/JuceLibraryCode/modules/juce_audio_basics/midi/juce_MidiBuffer.cpp +++ b/JuceLibraryCode/modules/juce_audio_basics/midi/juce_MidiBuffer.cpp @@ -1,232 +1,232 @@ -/* - ============================================================================== - - This file is part of the JUCE library. - Copyright (c) 2017 - ROLI Ltd. - - JUCE is an open source library subject to commercial or open-source - licensing. - - The code included in this file is provided under the terms of the ISC license - http://www.isc.org/downloads/software-support-policy/isc-license. Permission - To use, copy, modify, and/or distribute this software for any purpose with or - without fee is hereby granted provided that the above copyright notice and - this permission notice appear in all copies. - - JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER - EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE - DISCLAIMED. - - ============================================================================== -*/ - -namespace juce -{ - -namespace MidiBufferHelpers -{ - inline int getEventTime (const void* d) noexcept - { - return readUnaligned (d); - } - - inline uint16 getEventDataSize (const void* d) noexcept - { - return readUnaligned (static_cast (d) + sizeof (int32)); - } - - inline uint16 getEventTotalSize (const void* d) noexcept - { - return (uint16) (getEventDataSize (d) + sizeof (int32) + sizeof (uint16)); - } - - static int findActualEventLength (const uint8* data, int maxBytes) noexcept - { - auto byte = (unsigned int) *data; - - if (byte == 0xf0 || byte == 0xf7) - { - int i = 1; - - while (i < maxBytes) - if (data[i++] == 0xf7) - break; - - return i; - } - - if (byte == 0xff) - { - if (maxBytes == 1) - return 1; - - int n; - auto bytesLeft = MidiMessage::readVariableLengthVal (data + 1, n); - return jmin (maxBytes, n + 2 + bytesLeft); - } - - if (byte >= 0x80) - return jmin (maxBytes, MidiMessage::getMessageLengthFromFirstByte ((uint8) byte)); - - return 0; - } - - static uint8* findEventAfter (uint8* d, uint8* endData, int samplePosition) noexcept - { - while (d < endData && getEventTime (d) <= samplePosition) - d += getEventTotalSize (d); - - return d; - } -} - -//============================================================================== -MidiBuffer::MidiBuffer() noexcept {} -MidiBuffer::~MidiBuffer() {} - -MidiBuffer::MidiBuffer (const MidiBuffer& other) noexcept : data (other.data) {} - -MidiBuffer& MidiBuffer::operator= (const MidiBuffer& other) noexcept -{ - data = other.data; - return *this; -} - -MidiBuffer::MidiBuffer (const MidiMessage& message) noexcept -{ - addEvent (message, 0); -} - -void MidiBuffer::swapWith (MidiBuffer& other) noexcept { data.swapWith (other.data); } -void MidiBuffer::clear() noexcept { data.clearQuick(); } -void MidiBuffer::ensureSize (size_t minimumNumBytes) { data.ensureStorageAllocated ((int) minimumNumBytes); } -bool MidiBuffer::isEmpty() const noexcept { return data.size() == 0; } - -void MidiBuffer::clear (int startSample, int numSamples) -{ - auto start = MidiBufferHelpers::findEventAfter (data.begin(), data.end(), startSample - 1); - auto end = MidiBufferHelpers::findEventAfter (start, data.end(), startSample + numSamples - 1); - - data.removeRange ((int) (start - data.begin()), (int) (end - data.begin())); -} - -void MidiBuffer::addEvent (const MidiMessage& m, int sampleNumber) -{ - addEvent (m.getRawData(), m.getRawDataSize(), sampleNumber); -} - -void MidiBuffer::addEvent (const void* newData, int maxBytes, int sampleNumber) -{ - auto numBytes = MidiBufferHelpers::findActualEventLength (static_cast (newData), maxBytes); - - if (numBytes > 0) - { - auto newItemSize = (size_t) numBytes + sizeof (int32) + sizeof (uint16); - auto offset = (int) (MidiBufferHelpers::findEventAfter (data.begin(), data.end(), sampleNumber) - data.begin()); - - data.insertMultiple (offset, 0, (int) newItemSize); - - auto* d = data.begin() + offset; - writeUnaligned (d, sampleNumber); - d += sizeof (int32); - writeUnaligned (d, static_cast (numBytes)); - d += sizeof (uint16); - memcpy (d, newData, (size_t) numBytes); - } -} - -void MidiBuffer::addEvents (const MidiBuffer& otherBuffer, - int startSample, int numSamples, int sampleDeltaToAdd) -{ - Iterator i (otherBuffer); - i.setNextSamplePosition (startSample); - - const uint8* eventData; - int eventSize, position; - - while (i.getNextEvent (eventData, eventSize, position) - && (position < startSample + numSamples || numSamples < 0)) - { - addEvent (eventData, eventSize, position + sampleDeltaToAdd); - } -} - -int MidiBuffer::getNumEvents() const noexcept -{ - int n = 0; - auto end = data.end(); - - for (auto d = data.begin(); d < end; ++n) - d += MidiBufferHelpers::getEventTotalSize (d); - - return n; -} - -int MidiBuffer::getFirstEventTime() const noexcept -{ - return data.size() > 0 ? MidiBufferHelpers::getEventTime (data.begin()) : 0; -} - -int MidiBuffer::getLastEventTime() const noexcept -{ - if (data.size() == 0) - return 0; - - auto endData = data.end(); - - for (auto d = data.begin();;) - { - auto nextOne = d + MidiBufferHelpers::getEventTotalSize (d); - - if (nextOne >= endData) - return MidiBufferHelpers::getEventTime (d); - - d = nextOne; - } -} - -//============================================================================== -MidiBuffer::Iterator::Iterator (const MidiBuffer& b) noexcept - : buffer (b), data (b.data.begin()) -{ -} - -MidiBuffer::Iterator::~Iterator() noexcept {} - -void MidiBuffer::Iterator::setNextSamplePosition (int samplePosition) noexcept -{ - data = buffer.data.begin(); - auto dataEnd = buffer.data.end(); - - while (data < dataEnd && MidiBufferHelpers::getEventTime (data) < samplePosition) - data += MidiBufferHelpers::getEventTotalSize (data); -} - -bool MidiBuffer::Iterator::getNextEvent (const uint8*& midiData, int& numBytes, int& samplePosition) noexcept -{ - if (data >= buffer.data.end()) - return false; - - samplePosition = MidiBufferHelpers::getEventTime (data); - auto itemSize = MidiBufferHelpers::getEventDataSize (data); - numBytes = itemSize; - midiData = data + sizeof (int32) + sizeof (uint16); - data += sizeof (int32) + sizeof (uint16) + (size_t) itemSize; - - return true; -} - -bool MidiBuffer::Iterator::getNextEvent (MidiMessage& result, int& samplePosition) noexcept -{ - if (data >= buffer.data.end()) - return false; - - samplePosition = MidiBufferHelpers::getEventTime (data); - auto itemSize = MidiBufferHelpers::getEventDataSize (data); - result = MidiMessage (data + sizeof (int32) + sizeof (uint16), itemSize, samplePosition); - data += sizeof (int32) + sizeof (uint16) + (size_t) itemSize; - - return true; -} - -} // namespace juce +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2017 - ROLI Ltd. + + JUCE is an open source library subject to commercial or open-source + licensing. + + The code included in this file is provided under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license. Permission + To use, copy, modify, and/or distribute this software for any purpose with or + without fee is hereby granted provided that the above copyright notice and + this permission notice appear in all copies. + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +namespace juce +{ + +namespace MidiBufferHelpers +{ + inline int getEventTime (const void* d) noexcept + { + return readUnaligned (d); + } + + inline uint16 getEventDataSize (const void* d) noexcept + { + return readUnaligned (static_cast (d) + sizeof (int32)); + } + + inline uint16 getEventTotalSize (const void* d) noexcept + { + return (uint16) (getEventDataSize (d) + sizeof (int32) + sizeof (uint16)); + } + + static int findActualEventLength (const uint8* data, int maxBytes) noexcept + { + auto byte = (unsigned int) *data; + + if (byte == 0xf0 || byte == 0xf7) + { + int i = 1; + + while (i < maxBytes) + if (data[i++] == 0xf7) + break; + + return i; + } + + if (byte == 0xff) + { + if (maxBytes == 1) + return 1; + + int n; + auto bytesLeft = MidiMessage::readVariableLengthVal (data + 1, n); + return jmin (maxBytes, n + 2 + bytesLeft); + } + + if (byte >= 0x80) + return jmin (maxBytes, MidiMessage::getMessageLengthFromFirstByte ((uint8) byte)); + + return 0; + } + + static uint8* findEventAfter (uint8* d, uint8* endData, int samplePosition) noexcept + { + while (d < endData && getEventTime (d) <= samplePosition) + d += getEventTotalSize (d); + + return d; + } +} + +//============================================================================== +MidiBuffer::MidiBuffer() noexcept {} +MidiBuffer::~MidiBuffer() {} + +MidiBuffer::MidiBuffer (const MidiBuffer& other) noexcept : data (other.data) {} + +MidiBuffer& MidiBuffer::operator= (const MidiBuffer& other) noexcept +{ + data = other.data; + return *this; +} + +MidiBuffer::MidiBuffer (const MidiMessage& message) noexcept +{ + addEvent (message, 0); +} + +void MidiBuffer::swapWith (MidiBuffer& other) noexcept { data.swapWith (other.data); } +void MidiBuffer::clear() noexcept { data.clearQuick(); } +void MidiBuffer::ensureSize (size_t minimumNumBytes) { data.ensureStorageAllocated ((int) minimumNumBytes); } +bool MidiBuffer::isEmpty() const noexcept { return data.size() == 0; } + +void MidiBuffer::clear (int startSample, int numSamples) +{ + auto start = MidiBufferHelpers::findEventAfter (data.begin(), data.end(), startSample - 1); + auto end = MidiBufferHelpers::findEventAfter (start, data.end(), startSample + numSamples - 1); + + data.removeRange ((int) (start - data.begin()), (int) (end - data.begin())); +} + +void MidiBuffer::addEvent (const MidiMessage& m, int sampleNumber) +{ + addEvent (m.getRawData(), m.getRawDataSize(), sampleNumber); +} + +void MidiBuffer::addEvent (const void* newData, int maxBytes, int sampleNumber) +{ + auto numBytes = MidiBufferHelpers::findActualEventLength (static_cast (newData), maxBytes); + + if (numBytes > 0) + { + auto newItemSize = (size_t) numBytes + sizeof (int32) + sizeof (uint16); + auto offset = (int) (MidiBufferHelpers::findEventAfter (data.begin(), data.end(), sampleNumber) - data.begin()); + + data.insertMultiple (offset, 0, (int) newItemSize); + + auto* d = data.begin() + offset; + writeUnaligned (d, sampleNumber); + d += sizeof (int32); + writeUnaligned (d, static_cast (numBytes)); + d += sizeof (uint16); + memcpy (d, newData, (size_t) numBytes); + } +} + +void MidiBuffer::addEvents (const MidiBuffer& otherBuffer, + int startSample, int numSamples, int sampleDeltaToAdd) +{ + Iterator i (otherBuffer); + i.setNextSamplePosition (startSample); + + const uint8* eventData; + int eventSize, position; + + while (i.getNextEvent (eventData, eventSize, position) + && (position < startSample + numSamples || numSamples < 0)) + { + addEvent (eventData, eventSize, position + sampleDeltaToAdd); + } +} + +int MidiBuffer::getNumEvents() const noexcept +{ + int n = 0; + auto end = data.end(); + + for (auto d = data.begin(); d < end; ++n) + d += MidiBufferHelpers::getEventTotalSize (d); + + return n; +} + +int MidiBuffer::getFirstEventTime() const noexcept +{ + return data.size() > 0 ? MidiBufferHelpers::getEventTime (data.begin()) : 0; +} + +int MidiBuffer::getLastEventTime() const noexcept +{ + if (data.size() == 0) + return 0; + + auto endData = data.end(); + + for (auto d = data.begin();;) + { + auto nextOne = d + MidiBufferHelpers::getEventTotalSize (d); + + if (nextOne >= endData) + return MidiBufferHelpers::getEventTime (d); + + d = nextOne; + } +} + +//============================================================================== +MidiBuffer::Iterator::Iterator (const MidiBuffer& b) noexcept + : buffer (b), data (b.data.begin()) +{ +} + +MidiBuffer::Iterator::~Iterator() noexcept {} + +void MidiBuffer::Iterator::setNextSamplePosition (int samplePosition) noexcept +{ + data = buffer.data.begin(); + auto dataEnd = buffer.data.end(); + + while (data < dataEnd && MidiBufferHelpers::getEventTime (data) < samplePosition) + data += MidiBufferHelpers::getEventTotalSize (data); +} + +bool MidiBuffer::Iterator::getNextEvent (const uint8*& midiData, int& numBytes, int& samplePosition) noexcept +{ + if (data >= buffer.data.end()) + return false; + + samplePosition = MidiBufferHelpers::getEventTime (data); + auto itemSize = MidiBufferHelpers::getEventDataSize (data); + numBytes = itemSize; + midiData = data + sizeof (int32) + sizeof (uint16); + data += sizeof (int32) + sizeof (uint16) + (size_t) itemSize; + + return true; +} + +bool MidiBuffer::Iterator::getNextEvent (MidiMessage& result, int& samplePosition) noexcept +{ + if (data >= buffer.data.end()) + return false; + + samplePosition = MidiBufferHelpers::getEventTime (data); + auto itemSize = MidiBufferHelpers::getEventDataSize (data); + result = MidiMessage (data + sizeof (int32) + sizeof (uint16), itemSize, samplePosition); + data += sizeof (int32) + sizeof (uint16) + (size_t) itemSize; + + return true; +} + +} // namespace juce diff --git a/JuceLibraryCode/modules/juce_audio_basics/midi/juce_MidiBuffer.h b/JuceLibraryCode/modules/juce_audio_basics/midi/juce_MidiBuffer.h index 8344a5e..9675197 100644 --- a/JuceLibraryCode/modules/juce_audio_basics/midi/juce_MidiBuffer.h +++ b/JuceLibraryCode/modules/juce_audio_basics/midi/juce_MidiBuffer.h @@ -1,234 +1,234 @@ -/* - ============================================================================== - - This file is part of the JUCE library. - Copyright (c) 2017 - ROLI Ltd. - - JUCE is an open source library subject to commercial or open-source - licensing. - - The code included in this file is provided under the terms of the ISC license - http://www.isc.org/downloads/software-support-policy/isc-license. Permission - To use, copy, modify, and/or distribute this software for any purpose with or - without fee is hereby granted provided that the above copyright notice and - this permission notice appear in all copies. - - JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER - EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE - DISCLAIMED. - - ============================================================================== -*/ - -namespace juce -{ - -//============================================================================== -/** - Holds a sequence of time-stamped midi events. - - Analogous to the AudioBuffer, this holds a set of midi events with - integer time-stamps. The buffer is kept sorted in order of the time-stamps. - - If you're working with a sequence of midi events that may need to be manipulated - or read/written to a midi file, then MidiMessageSequence is probably a more - appropriate container. MidiBuffer is designed for lower-level streams of raw - midi data. - - @see MidiMessage - - @tags{Audio} -*/ -class JUCE_API MidiBuffer -{ -public: - //============================================================================== - /** Creates an empty MidiBuffer. */ - MidiBuffer() noexcept; - - /** Creates a MidiBuffer containing a single midi message. */ - explicit MidiBuffer (const MidiMessage& message) noexcept; - - /** Creates a copy of another MidiBuffer. */ - MidiBuffer (const MidiBuffer&) noexcept; - - /** Makes a copy of another MidiBuffer. */ - MidiBuffer& operator= (const MidiBuffer&) noexcept; - - /** Destructor */ - ~MidiBuffer(); - - //============================================================================== - /** Removes all events from the buffer. */ - void clear() noexcept; - - /** Removes all events between two times from the buffer. - - All events for which (start <= event position < start + numSamples) will - be removed. - */ - void clear (int start, int numSamples); - - /** Returns true if the buffer is empty. - To actually retrieve the events, use a MidiBuffer::Iterator object - */ - bool isEmpty() const noexcept; - - /** Counts the number of events in the buffer. - - This is actually quite a slow operation, as it has to iterate through all - the events, so you might prefer to call isEmpty() if that's all you need - to know. - */ - int getNumEvents() const noexcept; - - /** Adds an event to the buffer. - - The sample number will be used to determine the position of the event in - the buffer, which is always kept sorted. The MidiMessage's timestamp is - ignored. - - If an event is added whose sample position is the same as one or more events - already in the buffer, the new event will be placed after the existing ones. - - To retrieve events, use a MidiBuffer::Iterator object - */ - void addEvent (const MidiMessage& midiMessage, int sampleNumber); - - /** Adds an event to the buffer from raw midi data. - - The sample number will be used to determine the position of the event in - the buffer, which is always kept sorted. - - If an event is added whose sample position is the same as one or more events - already in the buffer, the new event will be placed after the existing ones. - - The event data will be inspected to calculate the number of bytes in length that - the midi event really takes up, so maxBytesOfMidiData may be longer than the data - that actually gets stored. E.g. if you pass in a note-on and a length of 4 bytes, - it'll actually only store 3 bytes. If the midi data is invalid, it might not - add an event at all. - - To retrieve events, use a MidiBuffer::Iterator object - */ - void addEvent (const void* rawMidiData, - int maxBytesOfMidiData, - int sampleNumber); - - /** Adds some events from another buffer to this one. - - @param otherBuffer the buffer containing the events you want to add - @param startSample the lowest sample number in the source buffer for which - events should be added. Any source events whose timestamp is - less than this will be ignored - @param numSamples the valid range of samples from the source buffer for which - events should be added - i.e. events in the source buffer whose - timestamp is greater than or equal to (startSample + numSamples) - will be ignored. If this value is less than 0, all events after - startSample will be taken. - @param sampleDeltaToAdd a value which will be added to the source timestamps of the events - that are added to this buffer - */ - void addEvents (const MidiBuffer& otherBuffer, - int startSample, - int numSamples, - int sampleDeltaToAdd); - - /** Returns the sample number of the first event in the buffer. - If the buffer's empty, this will just return 0. - */ - int getFirstEventTime() const noexcept; - - /** Returns the sample number of the last event in the buffer. - If the buffer's empty, this will just return 0. - */ - int getLastEventTime() const noexcept; - - //============================================================================== - /** Exchanges the contents of this buffer with another one. - - This is a quick operation, because no memory allocating or copying is done, it - just swaps the internal state of the two buffers. - */ - void swapWith (MidiBuffer&) noexcept; - - /** Preallocates some memory for the buffer to use. - This helps to avoid needing to reallocate space when the buffer has messages - added to it. - */ - void ensureSize (size_t minimumNumBytes); - - //============================================================================== - /** - Used to iterate through the events in a MidiBuffer. - - Note that altering the buffer while an iterator is using it will produce - undefined behaviour. - - @see MidiBuffer - */ - class JUCE_API Iterator - { - public: - //============================================================================== - /** Creates an Iterator for this MidiBuffer. */ - Iterator (const MidiBuffer&) noexcept; - - /** Creates a copy of an iterator. */ - Iterator (const Iterator&) = default; - - /** Destructor. */ - ~Iterator() noexcept; - - //============================================================================== - /** Repositions the iterator so that the next event retrieved will be the first - one whose sample position is at greater than or equal to the given position. - */ - void setNextSamplePosition (int samplePosition) noexcept; - - /** Retrieves a copy of the next event from the buffer. - - @param result on return, this will be the message. The MidiMessage's timestamp - is set to the same value as samplePosition. - @param samplePosition on return, this will be the position of the event, as a - sample index in the buffer - @returns true if an event was found, or false if the iterator has reached - the end of the buffer - */ - bool getNextEvent (MidiMessage& result, - int& samplePosition) noexcept; - - /** Retrieves the next event from the buffer. - - @param midiData on return, this pointer will be set to a block of data containing - the midi message. Note that to make it fast, this is a pointer - directly into the MidiBuffer's internal data, so is only valid - temporarily until the MidiBuffer is altered. - @param numBytesOfMidiData on return, this is the number of bytes of data used by the - midi message - @param samplePosition on return, this will be the position of the event, as a - sample index in the buffer - @returns true if an event was found, or false if the iterator has reached - the end of the buffer - */ - bool getNextEvent (const uint8* &midiData, - int& numBytesOfMidiData, - int& samplePosition) noexcept; - - private: - //============================================================================== - const MidiBuffer& buffer; - const uint8* data; - }; - - /** The raw data holding this buffer. - Obviously access to this data is provided at your own risk. Its internal format could - change in future, so don't write code that relies on it! - */ - Array data; - -private: - JUCE_LEAK_DETECTOR (MidiBuffer) -}; - -} // namespace juce +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2017 - ROLI Ltd. + + JUCE is an open source library subject to commercial or open-source + licensing. + + The code included in this file is provided under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license. Permission + To use, copy, modify, and/or distribute this software for any purpose with or + without fee is hereby granted provided that the above copyright notice and + this permission notice appear in all copies. + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +namespace juce +{ + +//============================================================================== +/** + Holds a sequence of time-stamped midi events. + + Analogous to the AudioBuffer, this holds a set of midi events with + integer time-stamps. The buffer is kept sorted in order of the time-stamps. + + If you're working with a sequence of midi events that may need to be manipulated + or read/written to a midi file, then MidiMessageSequence is probably a more + appropriate container. MidiBuffer is designed for lower-level streams of raw + midi data. + + @see MidiMessage + + @tags{Audio} +*/ +class JUCE_API MidiBuffer +{ +public: + //============================================================================== + /** Creates an empty MidiBuffer. */ + MidiBuffer() noexcept; + + /** Creates a MidiBuffer containing a single midi message. */ + explicit MidiBuffer (const MidiMessage& message) noexcept; + + /** Creates a copy of another MidiBuffer. */ + MidiBuffer (const MidiBuffer&) noexcept; + + /** Makes a copy of another MidiBuffer. */ + MidiBuffer& operator= (const MidiBuffer&) noexcept; + + /** Destructor */ + ~MidiBuffer(); + + //============================================================================== + /** Removes all events from the buffer. */ + void clear() noexcept; + + /** Removes all events between two times from the buffer. + + All events for which (start <= event position < start + numSamples) will + be removed. + */ + void clear (int start, int numSamples); + + /** Returns true if the buffer is empty. + To actually retrieve the events, use a MidiBuffer::Iterator object + */ + bool isEmpty() const noexcept; + + /** Counts the number of events in the buffer. + + This is actually quite a slow operation, as it has to iterate through all + the events, so you might prefer to call isEmpty() if that's all you need + to know. + */ + int getNumEvents() const noexcept; + + /** Adds an event to the buffer. + + The sample number will be used to determine the position of the event in + the buffer, which is always kept sorted. The MidiMessage's timestamp is + ignored. + + If an event is added whose sample position is the same as one or more events + already in the buffer, the new event will be placed after the existing ones. + + To retrieve events, use a MidiBuffer::Iterator object + */ + void addEvent (const MidiMessage& midiMessage, int sampleNumber); + + /** Adds an event to the buffer from raw midi data. + + The sample number will be used to determine the position of the event in + the buffer, which is always kept sorted. + + If an event is added whose sample position is the same as one or more events + already in the buffer, the new event will be placed after the existing ones. + + The event data will be inspected to calculate the number of bytes in length that + the midi event really takes up, so maxBytesOfMidiData may be longer than the data + that actually gets stored. E.g. if you pass in a note-on and a length of 4 bytes, + it'll actually only store 3 bytes. If the midi data is invalid, it might not + add an event at all. + + To retrieve events, use a MidiBuffer::Iterator object + */ + void addEvent (const void* rawMidiData, + int maxBytesOfMidiData, + int sampleNumber); + + /** Adds some events from another buffer to this one. + + @param otherBuffer the buffer containing the events you want to add + @param startSample the lowest sample number in the source buffer for which + events should be added. Any source events whose timestamp is + less than this will be ignored + @param numSamples the valid range of samples from the source buffer for which + events should be added - i.e. events in the source buffer whose + timestamp is greater than or equal to (startSample + numSamples) + will be ignored. If this value is less than 0, all events after + startSample will be taken. + @param sampleDeltaToAdd a value which will be added to the source timestamps of the events + that are added to this buffer + */ + void addEvents (const MidiBuffer& otherBuffer, + int startSample, + int numSamples, + int sampleDeltaToAdd); + + /** Returns the sample number of the first event in the buffer. + If the buffer's empty, this will just return 0. + */ + int getFirstEventTime() const noexcept; + + /** Returns the sample number of the last event in the buffer. + If the buffer's empty, this will just return 0. + */ + int getLastEventTime() const noexcept; + + //============================================================================== + /** Exchanges the contents of this buffer with another one. + + This is a quick operation, because no memory allocating or copying is done, it + just swaps the internal state of the two buffers. + */ + void swapWith (MidiBuffer&) noexcept; + + /** Preallocates some memory for the buffer to use. + This helps to avoid needing to reallocate space when the buffer has messages + added to it. + */ + void ensureSize (size_t minimumNumBytes); + + //============================================================================== + /** + Used to iterate through the events in a MidiBuffer. + + Note that altering the buffer while an iterator is using it will produce + undefined behaviour. + + @see MidiBuffer + */ + class JUCE_API Iterator + { + public: + //============================================================================== + /** Creates an Iterator for this MidiBuffer. */ + Iterator (const MidiBuffer&) noexcept; + + /** Creates a copy of an iterator. */ + Iterator (const Iterator&) = default; + + /** Destructor. */ + ~Iterator() noexcept; + + //============================================================================== + /** Repositions the iterator so that the next event retrieved will be the first + one whose sample position is at greater than or equal to the given position. + */ + void setNextSamplePosition (int samplePosition) noexcept; + + /** Retrieves a copy of the next event from the buffer. + + @param result on return, this will be the message. The MidiMessage's timestamp + is set to the same value as samplePosition. + @param samplePosition on return, this will be the position of the event, as a + sample index in the buffer + @returns true if an event was found, or false if the iterator has reached + the end of the buffer + */ + bool getNextEvent (MidiMessage& result, + int& samplePosition) noexcept; + + /** Retrieves the next event from the buffer. + + @param midiData on return, this pointer will be set to a block of data containing + the midi message. Note that to make it fast, this is a pointer + directly into the MidiBuffer's internal data, so is only valid + temporarily until the MidiBuffer is altered. + @param numBytesOfMidiData on return, this is the number of bytes of data used by the + midi message + @param samplePosition on return, this will be the position of the event, as a + sample index in the buffer + @returns true if an event was found, or false if the iterator has reached + the end of the buffer + */ + bool getNextEvent (const uint8* &midiData, + int& numBytesOfMidiData, + int& samplePosition) noexcept; + + private: + //============================================================================== + const MidiBuffer& buffer; + const uint8* data; + }; + + /** The raw data holding this buffer. + Obviously access to this data is provided at your own risk. Its internal format could + change in future, so don't write code that relies on it! + */ + Array data; + +private: + JUCE_LEAK_DETECTOR (MidiBuffer) +}; + +} // namespace juce diff --git a/JuceLibraryCode/modules/juce_audio_basics/midi/juce_MidiFile.cpp b/JuceLibraryCode/modules/juce_audio_basics/midi/juce_MidiFile.cpp index 58cbf7c..7850be8 100644 --- a/JuceLibraryCode/modules/juce_audio_basics/midi/juce_MidiFile.cpp +++ b/JuceLibraryCode/modules/juce_audio_basics/midi/juce_MidiFile.cpp @@ -1,446 +1,446 @@ -/* - ============================================================================== - - This file is part of the JUCE library. - Copyright (c) 2017 - ROLI Ltd. - - JUCE is an open source library subject to commercial or open-source - licensing. - - The code included in this file is provided under the terms of the ISC license - http://www.isc.org/downloads/software-support-policy/isc-license. Permission - To use, copy, modify, and/or distribute this software for any purpose with or - without fee is hereby granted provided that the above copyright notice and - this permission notice appear in all copies. - - JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER - EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE - DISCLAIMED. - - ============================================================================== -*/ - -namespace juce -{ - -namespace MidiFileHelpers -{ - static void writeVariableLengthInt (OutputStream& out, uint32 v) - { - auto buffer = v & 0x7f; - - while ((v >>= 7) != 0) - { - buffer <<= 8; - buffer |= ((v & 0x7f) | 0x80); - } - - for (;;) - { - out.writeByte ((char) buffer); - - if (buffer & 0x80) - buffer >>= 8; - else - break; - } - } - - static bool parseMidiHeader (const uint8* &data, short& timeFormat, short& fileType, short& numberOfTracks) noexcept - { - auto ch = ByteOrder::bigEndianInt (data); - data += 4; - - if (ch != ByteOrder::bigEndianInt ("MThd")) - { - bool ok = false; - - if (ch == ByteOrder::bigEndianInt ("RIFF")) - { - for (int i = 0; i < 8; ++i) - { - ch = ByteOrder::bigEndianInt (data); - data += 4; - - if (ch == ByteOrder::bigEndianInt ("MThd")) - { - ok = true; - break; - } - } - } - - if (! ok) - return false; - } - - auto bytesRemaining = ByteOrder::bigEndianInt (data); - data += 4; - fileType = (short) ByteOrder::bigEndianShort (data); - data += 2; - numberOfTracks = (short) ByteOrder::bigEndianShort (data); - data += 2; - timeFormat = (short) ByteOrder::bigEndianShort (data); - data += 2; - bytesRemaining -= 6; - data += bytesRemaining; - - return true; - } - - static double convertTicksToSeconds (double time, - const MidiMessageSequence& tempoEvents, - int timeFormat) - { - if (timeFormat < 0) - return time / (-(timeFormat >> 8) * (timeFormat & 0xff)); - - double lastTime = 0, correctedTime = 0; - auto tickLen = 1.0 / (timeFormat & 0x7fff); - auto secsPerTick = 0.5 * tickLen; - auto numEvents = tempoEvents.getNumEvents(); - - for (int i = 0; i < numEvents; ++i) - { - auto& m = tempoEvents.getEventPointer(i)->message; - auto eventTime = m.getTimeStamp(); - - if (eventTime >= time) - break; - - correctedTime += (eventTime - lastTime) * secsPerTick; - lastTime = eventTime; - - if (m.isTempoMetaEvent()) - secsPerTick = tickLen * m.getTempoSecondsPerQuarterNote(); - - while (i + 1 < numEvents) - { - auto& m2 = tempoEvents.getEventPointer(i + 1)->message; - - if (m2.getTimeStamp() != eventTime) - break; - - if (m2.isTempoMetaEvent()) - secsPerTick = tickLen * m2.getTempoSecondsPerQuarterNote(); - - ++i; - } - } - - return correctedTime + (time - lastTime) * secsPerTick; - } - - template - static void findAllMatchingEvents (const OwnedArray& tracks, - MidiMessageSequence& results, - MethodType method) - { - for (auto* track : tracks) - { - auto numEvents = track->getNumEvents(); - - for (int j = 0; j < numEvents; ++j) - { - auto& m = track->getEventPointer(j)->message; - - if ((m.*method)()) - results.addEvent (m); - } - } - } -} - -//============================================================================== -MidiFile::MidiFile() : timeFormat ((short) (unsigned short) 0xe728) {} -MidiFile::~MidiFile() {} - -MidiFile::MidiFile (const MidiFile& other) : timeFormat (other.timeFormat) -{ - tracks.addCopiesOf (other.tracks); -} - -MidiFile& MidiFile::operator= (const MidiFile& other) -{ - tracks.clear(); - tracks.addCopiesOf (other.tracks); - timeFormat = other.timeFormat; - return *this; -} - -MidiFile::MidiFile (MidiFile&& other) - : tracks (std::move (other.tracks)), - timeFormat (other.timeFormat) -{ -} - -MidiFile& MidiFile::operator= (MidiFile&& other) -{ - tracks = std::move (other.tracks); - timeFormat = other.timeFormat; - return *this; -} - -void MidiFile::clear() -{ - tracks.clear(); -} - -//============================================================================== -int MidiFile::getNumTracks() const noexcept -{ - return tracks.size(); -} - -const MidiMessageSequence* MidiFile::getTrack (int index) const noexcept -{ - return tracks[index]; -} - -void MidiFile::addTrack (const MidiMessageSequence& trackSequence) -{ - tracks.add (new MidiMessageSequence (trackSequence)); -} - -//============================================================================== -short MidiFile::getTimeFormat() const noexcept -{ - return timeFormat; -} - -void MidiFile::setTicksPerQuarterNote (int ticks) noexcept -{ - timeFormat = (short) ticks; -} - -void MidiFile::setSmpteTimeFormat (int framesPerSecond, int subframeResolution) noexcept -{ - timeFormat = (short) (((-framesPerSecond) << 8) | subframeResolution); -} - -//============================================================================== -void MidiFile::findAllTempoEvents (MidiMessageSequence& results) const -{ - MidiFileHelpers::findAllMatchingEvents (tracks, results, &MidiMessage::isTempoMetaEvent); -} - -void MidiFile::findAllTimeSigEvents (MidiMessageSequence& results) const -{ - MidiFileHelpers::findAllMatchingEvents (tracks, results, &MidiMessage::isTimeSignatureMetaEvent); -} - -void MidiFile::findAllKeySigEvents (MidiMessageSequence& results) const -{ - MidiFileHelpers::findAllMatchingEvents (tracks, results, &MidiMessage::isKeySignatureMetaEvent); -} - -double MidiFile::getLastTimestamp() const -{ - double t = 0.0; - - for (auto* ms : tracks) - t = jmax (t, ms->getEndTime()); - - return t; -} - -//============================================================================== -bool MidiFile::readFrom (InputStream& sourceStream, bool createMatchingNoteOffs) -{ - clear(); - MemoryBlock data; - - const int maxSensibleMidiFileSize = 200 * 1024 * 1024; - - // (put a sanity-check on the file size, as midi files are generally small) - if (sourceStream.readIntoMemoryBlock (data, maxSensibleMidiFileSize)) - { - auto size = data.getSize(); - auto d = static_cast (data.getData()); - short fileType, expectedTracks; - - if (size > 16 && MidiFileHelpers::parseMidiHeader (d, timeFormat, fileType, expectedTracks)) - { - size -= (size_t) (d - static_cast (data.getData())); - int track = 0; - - for (;;) - { - auto chunkType = (int) ByteOrder::bigEndianInt (d); - d += 4; - auto chunkSize = (int) ByteOrder::bigEndianInt (d); - d += 4; - - if (chunkSize <= 0 || (size_t) chunkSize > size) - break; - - if (chunkType == (int) ByteOrder::bigEndianInt ("MTrk")) - readNextTrack (d, chunkSize, createMatchingNoteOffs); - - if (++track >= expectedTracks) - break; - - size -= (size_t) chunkSize + 8; - d += chunkSize; - } - - return true; - } - } - - return false; -} - -void MidiFile::readNextTrack (const uint8* data, int size, bool createMatchingNoteOffs) -{ - double time = 0; - uint8 lastStatusByte = 0; - - MidiMessageSequence result; - - while (size > 0) - { - int bytesUsed; - auto delay = MidiMessage::readVariableLengthVal (data, bytesUsed); - data += bytesUsed; - size -= bytesUsed; - time += delay; - - int messSize = 0; - const MidiMessage mm (data, size, messSize, lastStatusByte, time); - - if (messSize <= 0) - break; - - size -= messSize; - data += messSize; - - result.addEvent (mm); - - auto firstByte = *(mm.getRawData()); - - if ((firstByte & 0xf0) != 0xf0) - lastStatusByte = firstByte; - } - - // sort so that we put all the note-offs before note-ons that have the same time - std::stable_sort (result.list.begin(), result.list.end(), - [] (const MidiMessageSequence::MidiEventHolder* a, - const MidiMessageSequence::MidiEventHolder* b) - { - auto t1 = a->message.getTimeStamp(); - auto t2 = b->message.getTimeStamp(); - - if (t1 < t2) return true; - if (t2 < t1) return false; - - return a->message.isNoteOff() && b->message.isNoteOn(); - }); - - addTrack (result); - - if (createMatchingNoteOffs) - tracks.getLast()->updateMatchedPairs(); -} - -//============================================================================== -void MidiFile::convertTimestampTicksToSeconds() -{ - MidiMessageSequence tempoEvents; - findAllTempoEvents (tempoEvents); - findAllTimeSigEvents (tempoEvents); - - if (timeFormat != 0) - { - for (auto* ms : tracks) - { - for (int j = ms->getNumEvents(); --j >= 0;) - { - auto& m = ms->getEventPointer(j)->message; - m.setTimeStamp (MidiFileHelpers::convertTicksToSeconds (m.getTimeStamp(), tempoEvents, timeFormat)); - } - } - } -} - -//============================================================================== -bool MidiFile::writeTo (OutputStream& out, int midiFileType) const -{ - jassert (midiFileType >= 0 && midiFileType <= 2); - - if (! out.writeIntBigEndian ((int) ByteOrder::bigEndianInt ("MThd"))) return false; - if (! out.writeIntBigEndian (6)) return false; - if (! out.writeShortBigEndian ((short) midiFileType)) return false; - if (! out.writeShortBigEndian ((short) tracks.size())) return false; - if (! out.writeShortBigEndian (timeFormat)) return false; - - for (auto* ms : tracks) - if (! writeTrack (out, *ms)) - return false; - - out.flush(); - return true; -} - -bool MidiFile::writeTrack (OutputStream& mainOut, const MidiMessageSequence& ms) const -{ - MemoryOutputStream out; - - int lastTick = 0; - uint8 lastStatusByte = 0; - bool endOfTrackEventWritten = false; - - for (int i = 0; i < ms.getNumEvents(); ++i) - { - auto& mm = ms.getEventPointer(i)->message; - - if (mm.isEndOfTrackMetaEvent()) - endOfTrackEventWritten = true; - - auto tick = roundToInt (mm.getTimeStamp()); - auto delta = jmax (0, tick - lastTick); - MidiFileHelpers::writeVariableLengthInt (out, (uint32) delta); - lastTick = tick; - - auto* data = mm.getRawData(); - auto dataSize = mm.getRawDataSize(); - auto statusByte = data[0]; - - if (statusByte == lastStatusByte - && (statusByte & 0xf0) != 0xf0 - && dataSize > 1 - && i > 0) - { - ++data; - --dataSize; - } - else if (statusByte == 0xf0) // Write sysex message with length bytes. - { - out.writeByte ((char) statusByte); - - ++data; - --dataSize; - - MidiFileHelpers::writeVariableLengthInt (out, (uint32) dataSize); - } - - out.write (data, (size_t) dataSize); - lastStatusByte = statusByte; - } - - if (! endOfTrackEventWritten) - { - out.writeByte (0); // (tick delta) - auto m = MidiMessage::endOfTrack(); - out.write (m.getRawData(), (size_t) m.getRawDataSize()); - } - - if (! mainOut.writeIntBigEndian ((int) ByteOrder::bigEndianInt ("MTrk"))) return false; - if (! mainOut.writeIntBigEndian ((int) out.getDataSize())) return false; - - mainOut << out; - - return true; -} - -} // namespace juce +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2017 - ROLI Ltd. + + JUCE is an open source library subject to commercial or open-source + licensing. + + The code included in this file is provided under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license. Permission + To use, copy, modify, and/or distribute this software for any purpose with or + without fee is hereby granted provided that the above copyright notice and + this permission notice appear in all copies. + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +namespace juce +{ + +namespace MidiFileHelpers +{ + static void writeVariableLengthInt (OutputStream& out, uint32 v) + { + auto buffer = v & 0x7f; + + while ((v >>= 7) != 0) + { + buffer <<= 8; + buffer |= ((v & 0x7f) | 0x80); + } + + for (;;) + { + out.writeByte ((char) buffer); + + if (buffer & 0x80) + buffer >>= 8; + else + break; + } + } + + static bool parseMidiHeader (const uint8* &data, short& timeFormat, short& fileType, short& numberOfTracks) noexcept + { + auto ch = ByteOrder::bigEndianInt (data); + data += 4; + + if (ch != ByteOrder::bigEndianInt ("MThd")) + { + bool ok = false; + + if (ch == ByteOrder::bigEndianInt ("RIFF")) + { + for (int i = 0; i < 8; ++i) + { + ch = ByteOrder::bigEndianInt (data); + data += 4; + + if (ch == ByteOrder::bigEndianInt ("MThd")) + { + ok = true; + break; + } + } + } + + if (! ok) + return false; + } + + auto bytesRemaining = ByteOrder::bigEndianInt (data); + data += 4; + fileType = (short) ByteOrder::bigEndianShort (data); + data += 2; + numberOfTracks = (short) ByteOrder::bigEndianShort (data); + data += 2; + timeFormat = (short) ByteOrder::bigEndianShort (data); + data += 2; + bytesRemaining -= 6; + data += bytesRemaining; + + return true; + } + + static double convertTicksToSeconds (double time, + const MidiMessageSequence& tempoEvents, + int timeFormat) + { + if (timeFormat < 0) + return time / (-(timeFormat >> 8) * (timeFormat & 0xff)); + + double lastTime = 0, correctedTime = 0; + auto tickLen = 1.0 / (timeFormat & 0x7fff); + auto secsPerTick = 0.5 * tickLen; + auto numEvents = tempoEvents.getNumEvents(); + + for (int i = 0; i < numEvents; ++i) + { + auto& m = tempoEvents.getEventPointer(i)->message; + auto eventTime = m.getTimeStamp(); + + if (eventTime >= time) + break; + + correctedTime += (eventTime - lastTime) * secsPerTick; + lastTime = eventTime; + + if (m.isTempoMetaEvent()) + secsPerTick = tickLen * m.getTempoSecondsPerQuarterNote(); + + while (i + 1 < numEvents) + { + auto& m2 = tempoEvents.getEventPointer(i + 1)->message; + + if (m2.getTimeStamp() != eventTime) + break; + + if (m2.isTempoMetaEvent()) + secsPerTick = tickLen * m2.getTempoSecondsPerQuarterNote(); + + ++i; + } + } + + return correctedTime + (time - lastTime) * secsPerTick; + } + + template + static void findAllMatchingEvents (const OwnedArray& tracks, + MidiMessageSequence& results, + MethodType method) + { + for (auto* track : tracks) + { + auto numEvents = track->getNumEvents(); + + for (int j = 0; j < numEvents; ++j) + { + auto& m = track->getEventPointer(j)->message; + + if ((m.*method)()) + results.addEvent (m); + } + } + } +} + +//============================================================================== +MidiFile::MidiFile() : timeFormat ((short) (unsigned short) 0xe728) {} +MidiFile::~MidiFile() {} + +MidiFile::MidiFile (const MidiFile& other) : timeFormat (other.timeFormat) +{ + tracks.addCopiesOf (other.tracks); +} + +MidiFile& MidiFile::operator= (const MidiFile& other) +{ + tracks.clear(); + tracks.addCopiesOf (other.tracks); + timeFormat = other.timeFormat; + return *this; +} + +MidiFile::MidiFile (MidiFile&& other) + : tracks (std::move (other.tracks)), + timeFormat (other.timeFormat) +{ +} + +MidiFile& MidiFile::operator= (MidiFile&& other) +{ + tracks = std::move (other.tracks); + timeFormat = other.timeFormat; + return *this; +} + +void MidiFile::clear() +{ + tracks.clear(); +} + +//============================================================================== +int MidiFile::getNumTracks() const noexcept +{ + return tracks.size(); +} + +const MidiMessageSequence* MidiFile::getTrack (int index) const noexcept +{ + return tracks[index]; +} + +void MidiFile::addTrack (const MidiMessageSequence& trackSequence) +{ + tracks.add (new MidiMessageSequence (trackSequence)); +} + +//============================================================================== +short MidiFile::getTimeFormat() const noexcept +{ + return timeFormat; +} + +void MidiFile::setTicksPerQuarterNote (int ticks) noexcept +{ + timeFormat = (short) ticks; +} + +void MidiFile::setSmpteTimeFormat (int framesPerSecond, int subframeResolution) noexcept +{ + timeFormat = (short) (((-framesPerSecond) << 8) | subframeResolution); +} + +//============================================================================== +void MidiFile::findAllTempoEvents (MidiMessageSequence& results) const +{ + MidiFileHelpers::findAllMatchingEvents (tracks, results, &MidiMessage::isTempoMetaEvent); +} + +void MidiFile::findAllTimeSigEvents (MidiMessageSequence& results) const +{ + MidiFileHelpers::findAllMatchingEvents (tracks, results, &MidiMessage::isTimeSignatureMetaEvent); +} + +void MidiFile::findAllKeySigEvents (MidiMessageSequence& results) const +{ + MidiFileHelpers::findAllMatchingEvents (tracks, results, &MidiMessage::isKeySignatureMetaEvent); +} + +double MidiFile::getLastTimestamp() const +{ + double t = 0.0; + + for (auto* ms : tracks) + t = jmax (t, ms->getEndTime()); + + return t; +} + +//============================================================================== +bool MidiFile::readFrom (InputStream& sourceStream, bool createMatchingNoteOffs) +{ + clear(); + MemoryBlock data; + + const int maxSensibleMidiFileSize = 200 * 1024 * 1024; + + // (put a sanity-check on the file size, as midi files are generally small) + if (sourceStream.readIntoMemoryBlock (data, maxSensibleMidiFileSize)) + { + auto size = data.getSize(); + auto d = static_cast (data.getData()); + short fileType, expectedTracks; + + if (size > 16 && MidiFileHelpers::parseMidiHeader (d, timeFormat, fileType, expectedTracks)) + { + size -= (size_t) (d - static_cast (data.getData())); + int track = 0; + + for (;;) + { + auto chunkType = (int) ByteOrder::bigEndianInt (d); + d += 4; + auto chunkSize = (int) ByteOrder::bigEndianInt (d); + d += 4; + + if (chunkSize <= 0 || (size_t) chunkSize > size) + break; + + if (chunkType == (int) ByteOrder::bigEndianInt ("MTrk")) + readNextTrack (d, chunkSize, createMatchingNoteOffs); + + if (++track >= expectedTracks) + break; + + size -= (size_t) chunkSize + 8; + d += chunkSize; + } + + return true; + } + } + + return false; +} + +void MidiFile::readNextTrack (const uint8* data, int size, bool createMatchingNoteOffs) +{ + double time = 0; + uint8 lastStatusByte = 0; + + MidiMessageSequence result; + + while (size > 0) + { + int bytesUsed; + auto delay = MidiMessage::readVariableLengthVal (data, bytesUsed); + data += bytesUsed; + size -= bytesUsed; + time += delay; + + int messSize = 0; + const MidiMessage mm (data, size, messSize, lastStatusByte, time); + + if (messSize <= 0) + break; + + size -= messSize; + data += messSize; + + result.addEvent (mm); + + auto firstByte = *(mm.getRawData()); + + if ((firstByte & 0xf0) != 0xf0) + lastStatusByte = firstByte; + } + + // sort so that we put all the note-offs before note-ons that have the same time + std::stable_sort (result.list.begin(), result.list.end(), + [] (const MidiMessageSequence::MidiEventHolder* a, + const MidiMessageSequence::MidiEventHolder* b) + { + auto t1 = a->message.getTimeStamp(); + auto t2 = b->message.getTimeStamp(); + + if (t1 < t2) return true; + if (t2 < t1) return false; + + return a->message.isNoteOff() && b->message.isNoteOn(); + }); + + addTrack (result); + + if (createMatchingNoteOffs) + tracks.getLast()->updateMatchedPairs(); +} + +//============================================================================== +void MidiFile::convertTimestampTicksToSeconds() +{ + MidiMessageSequence tempoEvents; + findAllTempoEvents (tempoEvents); + findAllTimeSigEvents (tempoEvents); + + if (timeFormat != 0) + { + for (auto* ms : tracks) + { + for (int j = ms->getNumEvents(); --j >= 0;) + { + auto& m = ms->getEventPointer(j)->message; + m.setTimeStamp (MidiFileHelpers::convertTicksToSeconds (m.getTimeStamp(), tempoEvents, timeFormat)); + } + } + } +} + +//============================================================================== +bool MidiFile::writeTo (OutputStream& out, int midiFileType) const +{ + jassert (midiFileType >= 0 && midiFileType <= 2); + + if (! out.writeIntBigEndian ((int) ByteOrder::bigEndianInt ("MThd"))) return false; + if (! out.writeIntBigEndian (6)) return false; + if (! out.writeShortBigEndian ((short) midiFileType)) return false; + if (! out.writeShortBigEndian ((short) tracks.size())) return false; + if (! out.writeShortBigEndian (timeFormat)) return false; + + for (auto* ms : tracks) + if (! writeTrack (out, *ms)) + return false; + + out.flush(); + return true; +} + +bool MidiFile::writeTrack (OutputStream& mainOut, const MidiMessageSequence& ms) const +{ + MemoryOutputStream out; + + int lastTick = 0; + uint8 lastStatusByte = 0; + bool endOfTrackEventWritten = false; + + for (int i = 0; i < ms.getNumEvents(); ++i) + { + auto& mm = ms.getEventPointer(i)->message; + + if (mm.isEndOfTrackMetaEvent()) + endOfTrackEventWritten = true; + + auto tick = roundToInt (mm.getTimeStamp()); + auto delta = jmax (0, tick - lastTick); + MidiFileHelpers::writeVariableLengthInt (out, (uint32) delta); + lastTick = tick; + + auto* data = mm.getRawData(); + auto dataSize = mm.getRawDataSize(); + auto statusByte = data[0]; + + if (statusByte == lastStatusByte + && (statusByte & 0xf0) != 0xf0 + && dataSize > 1 + && i > 0) + { + ++data; + --dataSize; + } + else if (statusByte == 0xf0) // Write sysex message with length bytes. + { + out.writeByte ((char) statusByte); + + ++data; + --dataSize; + + MidiFileHelpers::writeVariableLengthInt (out, (uint32) dataSize); + } + + out.write (data, (size_t) dataSize); + lastStatusByte = statusByte; + } + + if (! endOfTrackEventWritten) + { + out.writeByte (0); // (tick delta) + auto m = MidiMessage::endOfTrack(); + out.write (m.getRawData(), (size_t) m.getRawDataSize()); + } + + if (! mainOut.writeIntBigEndian ((int) ByteOrder::bigEndianInt ("MTrk"))) return false; + if (! mainOut.writeIntBigEndian ((int) out.getDataSize())) return false; + + mainOut << out; + + return true; +} + +} // namespace juce diff --git a/JuceLibraryCode/modules/juce_audio_basics/midi/juce_MidiFile.h b/JuceLibraryCode/modules/juce_audio_basics/midi/juce_MidiFile.h index 0f3f9f0..323523e 100644 --- a/JuceLibraryCode/modules/juce_audio_basics/midi/juce_MidiFile.h +++ b/JuceLibraryCode/modules/juce_audio_basics/midi/juce_MidiFile.h @@ -1,197 +1,197 @@ -/* - ============================================================================== - - This file is part of the JUCE library. - Copyright (c) 2017 - ROLI Ltd. - - JUCE is an open source library subject to commercial or open-source - licensing. - - The code included in this file is provided under the terms of the ISC license - http://www.isc.org/downloads/software-support-policy/isc-license. Permission - To use, copy, modify, and/or distribute this software for any purpose with or - without fee is hereby granted provided that the above copyright notice and - this permission notice appear in all copies. - - JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER - EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE - DISCLAIMED. - - ============================================================================== -*/ - -namespace juce -{ - -//============================================================================== -/** - Reads/writes standard midi format files. - - To read a midi file, create a MidiFile object and call its readFrom() method. You - can then get the individual midi tracks from it using the getTrack() method. - - To write a file, create a MidiFile object, add some MidiMessageSequence objects - to it using the addTrack() method, and then call its writeTo() method to stream - it out. - - @see MidiMessageSequence - - @tags{Audio} -*/ -class JUCE_API MidiFile -{ -public: - //============================================================================== - /** Creates an empty MidiFile object. */ - MidiFile(); - - /** Destructor. */ - ~MidiFile(); - - /** Creates a copy of another MidiFile. */ - MidiFile (const MidiFile&); - - /** Copies from another MidiFile object */ - MidiFile& operator= (const MidiFile&); - - /** Creates a copy of another MidiFile. */ - MidiFile (MidiFile&&); - - /** Copies from another MidiFile object */ - MidiFile& operator= (MidiFile&&); - - //============================================================================== - /** Returns the number of tracks in the file. - @see getTrack, addTrack - */ - int getNumTracks() const noexcept; - - /** Returns a pointer to one of the tracks in the file. - @returns a pointer to the track, or nullptr if the index is out-of-range - @see getNumTracks, addTrack - */ - const MidiMessageSequence* getTrack (int index) const noexcept; - - /** Adds a midi track to the file. - This will make its own internal copy of the sequence that is passed-in. - @see getNumTracks, getTrack - */ - void addTrack (const MidiMessageSequence& trackSequence); - - /** Removes all midi tracks from the file. - @see getNumTracks - */ - void clear(); - - /** Returns the raw time format code that will be written to a stream. - - After reading a midi file, this method will return the time-format that - was read from the file's header. It can be changed using the setTicksPerQuarterNote() - or setSmpteTimeFormat() methods. - - If the value returned is positive, it indicates the number of midi ticks - per quarter-note - see setTicksPerQuarterNote(). - - It it's negative, the upper byte indicates the frames-per-second (but negative), and - the lower byte is the number of ticks per frame - see setSmpteTimeFormat(). - */ - short getTimeFormat() const noexcept; - - /** Sets the time format to use when this file is written to a stream. - - If this is called, the file will be written as bars/beats using the - specified resolution, rather than SMPTE absolute times, as would be - used if setSmpteTimeFormat() had been called instead. - - @param ticksPerQuarterNote e.g. 96, 960 - @see setSmpteTimeFormat - */ - void setTicksPerQuarterNote (int ticksPerQuarterNote) noexcept; - - /** Sets the time format to use when this file is written to a stream. - - If this is called, the file will be written using absolute times, rather - than bars/beats as would be the case if setTicksPerBeat() had been called - instead. - - @param framesPerSecond must be 24, 25, 29 or 30 - @param subframeResolution the sub-second resolution, e.g. 4 (midi time code), - 8, 10, 80 (SMPTE bit resolution), or 100. For millisecond - timing, setSmpteTimeFormat (25, 40) - @see setTicksPerBeat - */ - void setSmpteTimeFormat (int framesPerSecond, - int subframeResolution) noexcept; - - //============================================================================== - /** Makes a list of all the tempo-change meta-events from all tracks in the midi file. - Useful for finding the positions of all the tempo changes in a file. - @param tempoChangeEvents a list to which all the events will be added - */ - void findAllTempoEvents (MidiMessageSequence& tempoChangeEvents) const; - - /** Makes a list of all the time-signature meta-events from all tracks in the midi file. - Useful for finding the positions of all the tempo changes in a file. - @param timeSigEvents a list to which all the events will be added - */ - void findAllTimeSigEvents (MidiMessageSequence& timeSigEvents) const; - - /** Makes a list of all the time-signature meta-events from all tracks in the midi file. - @param keySigEvents a list to which all the events will be added - */ - void findAllKeySigEvents (MidiMessageSequence& keySigEvents) const; - - /** Returns the latest timestamp in any of the tracks. - (Useful for finding the length of the file). - */ - double getLastTimestamp() const; - - //============================================================================== - /** Reads a midi file format stream. - - After calling this, you can get the tracks that were read from the file by using the - getNumTracks() and getTrack() methods. - - The timestamps of the midi events in the tracks will represent their positions in - terms of midi ticks. To convert them to seconds, use the convertTimestampTicksToSeconds() - method. - - @param sourceStream the source stream - @param createMatchingNoteOffs if true, any missing note-offs for previous note-ons will - be automatically added at the end of the file by calling - MidiMessageSequence::updateMatchedPairs on each track. - - @returns true if the stream was read successfully - */ - bool readFrom (InputStream& sourceStream, bool createMatchingNoteOffs = true); - - /** Writes the midi tracks as a standard midi file. - The midiFileType value is written as the file's format type, which can be 0, 1 - or 2 - see the midi file spec for more info about that. - - @param destStream the destination stream - @param midiFileType the type of midi file - - @returns true if the operation succeeded. - */ - bool writeTo (OutputStream& destStream, int midiFileType = 1) const; - - /** Converts the timestamp of all the midi events from midi ticks to seconds. - - This will use the midi time format and tempo/time signature info in the - tracks to convert all the timestamps to absolute values in seconds. - */ - void convertTimestampTicksToSeconds(); - -private: - //============================================================================== - OwnedArray tracks; - short timeFormat; - - void readNextTrack (const uint8*, int, bool); - bool writeTrack (OutputStream&, const MidiMessageSequence&) const; - - JUCE_LEAK_DETECTOR (MidiFile) -}; - -} // namespace juce +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2017 - ROLI Ltd. + + JUCE is an open source library subject to commercial or open-source + licensing. + + The code included in this file is provided under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license. Permission + To use, copy, modify, and/or distribute this software for any purpose with or + without fee is hereby granted provided that the above copyright notice and + this permission notice appear in all copies. + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +namespace juce +{ + +//============================================================================== +/** + Reads/writes standard midi format files. + + To read a midi file, create a MidiFile object and call its readFrom() method. You + can then get the individual midi tracks from it using the getTrack() method. + + To write a file, create a MidiFile object, add some MidiMessageSequence objects + to it using the addTrack() method, and then call its writeTo() method to stream + it out. + + @see MidiMessageSequence + + @tags{Audio} +*/ +class JUCE_API MidiFile +{ +public: + //============================================================================== + /** Creates an empty MidiFile object. */ + MidiFile(); + + /** Destructor. */ + ~MidiFile(); + + /** Creates a copy of another MidiFile. */ + MidiFile (const MidiFile&); + + /** Copies from another MidiFile object */ + MidiFile& operator= (const MidiFile&); + + /** Creates a copy of another MidiFile. */ + MidiFile (MidiFile&&); + + /** Copies from another MidiFile object */ + MidiFile& operator= (MidiFile&&); + + //============================================================================== + /** Returns the number of tracks in the file. + @see getTrack, addTrack + */ + int getNumTracks() const noexcept; + + /** Returns a pointer to one of the tracks in the file. + @returns a pointer to the track, or nullptr if the index is out-of-range + @see getNumTracks, addTrack + */ + const MidiMessageSequence* getTrack (int index) const noexcept; + + /** Adds a midi track to the file. + This will make its own internal copy of the sequence that is passed-in. + @see getNumTracks, getTrack + */ + void addTrack (const MidiMessageSequence& trackSequence); + + /** Removes all midi tracks from the file. + @see getNumTracks + */ + void clear(); + + /** Returns the raw time format code that will be written to a stream. + + After reading a midi file, this method will return the time-format that + was read from the file's header. It can be changed using the setTicksPerQuarterNote() + or setSmpteTimeFormat() methods. + + If the value returned is positive, it indicates the number of midi ticks + per quarter-note - see setTicksPerQuarterNote(). + + It it's negative, the upper byte indicates the frames-per-second (but negative), and + the lower byte is the number of ticks per frame - see setSmpteTimeFormat(). + */ + short getTimeFormat() const noexcept; + + /** Sets the time format to use when this file is written to a stream. + + If this is called, the file will be written as bars/beats using the + specified resolution, rather than SMPTE absolute times, as would be + used if setSmpteTimeFormat() had been called instead. + + @param ticksPerQuarterNote e.g. 96, 960 + @see setSmpteTimeFormat + */ + void setTicksPerQuarterNote (int ticksPerQuarterNote) noexcept; + + /** Sets the time format to use when this file is written to a stream. + + If this is called, the file will be written using absolute times, rather + than bars/beats as would be the case if setTicksPerBeat() had been called + instead. + + @param framesPerSecond must be 24, 25, 29 or 30 + @param subframeResolution the sub-second resolution, e.g. 4 (midi time code), + 8, 10, 80 (SMPTE bit resolution), or 100. For millisecond + timing, setSmpteTimeFormat (25, 40) + @see setTicksPerBeat + */ + void setSmpteTimeFormat (int framesPerSecond, + int subframeResolution) noexcept; + + //============================================================================== + /** Makes a list of all the tempo-change meta-events from all tracks in the midi file. + Useful for finding the positions of all the tempo changes in a file. + @param tempoChangeEvents a list to which all the events will be added + */ + void findAllTempoEvents (MidiMessageSequence& tempoChangeEvents) const; + + /** Makes a list of all the time-signature meta-events from all tracks in the midi file. + Useful for finding the positions of all the tempo changes in a file. + @param timeSigEvents a list to which all the events will be added + */ + void findAllTimeSigEvents (MidiMessageSequence& timeSigEvents) const; + + /** Makes a list of all the time-signature meta-events from all tracks in the midi file. + @param keySigEvents a list to which all the events will be added + */ + void findAllKeySigEvents (MidiMessageSequence& keySigEvents) const; + + /** Returns the latest timestamp in any of the tracks. + (Useful for finding the length of the file). + */ + double getLastTimestamp() const; + + //============================================================================== + /** Reads a midi file format stream. + + After calling this, you can get the tracks that were read from the file by using the + getNumTracks() and getTrack() methods. + + The timestamps of the midi events in the tracks will represent their positions in + terms of midi ticks. To convert them to seconds, use the convertTimestampTicksToSeconds() + method. + + @param sourceStream the source stream + @param createMatchingNoteOffs if true, any missing note-offs for previous note-ons will + be automatically added at the end of the file by calling + MidiMessageSequence::updateMatchedPairs on each track. + + @returns true if the stream was read successfully + */ + bool readFrom (InputStream& sourceStream, bool createMatchingNoteOffs = true); + + /** Writes the midi tracks as a standard midi file. + The midiFileType value is written as the file's format type, which can be 0, 1 + or 2 - see the midi file spec for more info about that. + + @param destStream the destination stream + @param midiFileType the type of midi file + + @returns true if the operation succeeded. + */ + bool writeTo (OutputStream& destStream, int midiFileType = 1) const; + + /** Converts the timestamp of all the midi events from midi ticks to seconds. + + This will use the midi time format and tempo/time signature info in the + tracks to convert all the timestamps to absolute values in seconds. + */ + void convertTimestampTicksToSeconds(); + +private: + //============================================================================== + OwnedArray tracks; + short timeFormat; + + void readNextTrack (const uint8*, int, bool); + bool writeTrack (OutputStream&, const MidiMessageSequence&) const; + + JUCE_LEAK_DETECTOR (MidiFile) +}; + +} // namespace juce diff --git a/JuceLibraryCode/modules/juce_audio_basics/midi/juce_MidiKeyboardState.cpp b/JuceLibraryCode/modules/juce_audio_basics/midi/juce_MidiKeyboardState.cpp index 68b8db0..6c5a3df 100644 --- a/JuceLibraryCode/modules/juce_audio_basics/midi/juce_MidiKeyboardState.cpp +++ b/JuceLibraryCode/modules/juce_audio_basics/midi/juce_MidiKeyboardState.cpp @@ -1,186 +1,186 @@ -/* - ============================================================================== - - This file is part of the JUCE library. - Copyright (c) 2017 - ROLI Ltd. - - JUCE is an open source library subject to commercial or open-source - licensing. - - The code included in this file is provided under the terms of the ISC license - http://www.isc.org/downloads/software-support-policy/isc-license. Permission - To use, copy, modify, and/or distribute this software for any purpose with or - without fee is hereby granted provided that the above copyright notice and - this permission notice appear in all copies. - - JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER - EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE - DISCLAIMED. - - ============================================================================== -*/ - -namespace juce -{ - -MidiKeyboardState::MidiKeyboardState() -{ - zerostruct (noteStates); -} - -MidiKeyboardState::~MidiKeyboardState() -{ -} - -//============================================================================== -void MidiKeyboardState::reset() -{ - const ScopedLock sl (lock); - zerostruct (noteStates); - eventsToAdd.clear(); -} - -bool MidiKeyboardState::isNoteOn (const int midiChannel, const int n) const noexcept -{ - jassert (midiChannel >= 0 && midiChannel <= 16); - - return isPositiveAndBelow (n, 128) - && (noteStates[n] & (1 << (midiChannel - 1))) != 0; -} - -bool MidiKeyboardState::isNoteOnForChannels (const int midiChannelMask, const int n) const noexcept -{ - return isPositiveAndBelow (n, 128) - && (noteStates[n] & midiChannelMask) != 0; -} - -void MidiKeyboardState::noteOn (const int midiChannel, const int midiNoteNumber, const float velocity) -{ - jassert (midiChannel >= 0 && midiChannel <= 16); - jassert (isPositiveAndBelow (midiNoteNumber, 128)); - - const ScopedLock sl (lock); - - if (isPositiveAndBelow (midiNoteNumber, 128)) - { - const int timeNow = (int) Time::getMillisecondCounter(); - eventsToAdd.addEvent (MidiMessage::noteOn (midiChannel, midiNoteNumber, velocity), timeNow); - eventsToAdd.clear (0, timeNow - 500); - - noteOnInternal (midiChannel, midiNoteNumber, velocity); - } -} - -void MidiKeyboardState::noteOnInternal (const int midiChannel, const int midiNoteNumber, const float velocity) -{ - if (isPositiveAndBelow (midiNoteNumber, 128)) - { - noteStates [midiNoteNumber] |= (1 << (midiChannel - 1)); - - for (int i = listeners.size(); --i >= 0;) - listeners.getUnchecked(i)->handleNoteOn (this, midiChannel, midiNoteNumber, velocity); - } -} - -void MidiKeyboardState::noteOff (const int midiChannel, const int midiNoteNumber, const float velocity) -{ - const ScopedLock sl (lock); - - if (isNoteOn (midiChannel, midiNoteNumber)) - { - const int timeNow = (int) Time::getMillisecondCounter(); - eventsToAdd.addEvent (MidiMessage::noteOff (midiChannel, midiNoteNumber), timeNow); - eventsToAdd.clear (0, timeNow - 500); - - noteOffInternal (midiChannel, midiNoteNumber, velocity); - } -} - -void MidiKeyboardState::noteOffInternal (const int midiChannel, const int midiNoteNumber, const float velocity) -{ - if (isNoteOn (midiChannel, midiNoteNumber)) - { - noteStates [midiNoteNumber] &= ~(1 << (midiChannel - 1)); - - for (int i = listeners.size(); --i >= 0;) - listeners.getUnchecked(i)->handleNoteOff (this, midiChannel, midiNoteNumber, velocity); - } -} - -void MidiKeyboardState::allNotesOff (const int midiChannel) -{ - const ScopedLock sl (lock); - - if (midiChannel <= 0) - { - for (int i = 1; i <= 16; ++i) - allNotesOff (i); - } - else - { - for (int i = 0; i < 128; ++i) - noteOff (midiChannel, i, 0.0f); - } -} - -void MidiKeyboardState::processNextMidiEvent (const MidiMessage& message) -{ - if (message.isNoteOn()) - { - noteOnInternal (message.getChannel(), message.getNoteNumber(), message.getFloatVelocity()); - } - else if (message.isNoteOff()) - { - noteOffInternal (message.getChannel(), message.getNoteNumber(), message.getFloatVelocity()); - } - else if (message.isAllNotesOff()) - { - for (int i = 0; i < 128; ++i) - noteOffInternal (message.getChannel(), i, 0.0f); - } -} - -void MidiKeyboardState::processNextMidiBuffer (MidiBuffer& buffer, - const int startSample, - const int numSamples, - const bool injectIndirectEvents) -{ - MidiBuffer::Iterator i (buffer); - MidiMessage message; - int time; - - const ScopedLock sl (lock); - - while (i.getNextEvent (message, time)) - processNextMidiEvent (message); - - if (injectIndirectEvents) - { - MidiBuffer::Iterator i2 (eventsToAdd); - const int firstEventToAdd = eventsToAdd.getFirstEventTime(); - const double scaleFactor = numSamples / (double) (eventsToAdd.getLastEventTime() + 1 - firstEventToAdd); - - while (i2.getNextEvent (message, time)) - { - const int pos = jlimit (0, numSamples - 1, roundToInt ((time - firstEventToAdd) * scaleFactor)); - buffer.addEvent (message, startSample + pos); - } - } - - eventsToAdd.clear(); -} - -//============================================================================== -void MidiKeyboardState::addListener (MidiKeyboardStateListener* const listener) -{ - const ScopedLock sl (lock); - listeners.addIfNotAlreadyThere (listener); -} - -void MidiKeyboardState::removeListener (MidiKeyboardStateListener* const listener) -{ - const ScopedLock sl (lock); - listeners.removeFirstMatchingValue (listener); -} - -} // namespace juce +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2017 - ROLI Ltd. + + JUCE is an open source library subject to commercial or open-source + licensing. + + The code included in this file is provided under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license. Permission + To use, copy, modify, and/or distribute this software for any purpose with or + without fee is hereby granted provided that the above copyright notice and + this permission notice appear in all copies. + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +namespace juce +{ + +MidiKeyboardState::MidiKeyboardState() +{ + zerostruct (noteStates); +} + +MidiKeyboardState::~MidiKeyboardState() +{ +} + +//============================================================================== +void MidiKeyboardState::reset() +{ + const ScopedLock sl (lock); + zerostruct (noteStates); + eventsToAdd.clear(); +} + +bool MidiKeyboardState::isNoteOn (const int midiChannel, const int n) const noexcept +{ + jassert (midiChannel >= 0 && midiChannel <= 16); + + return isPositiveAndBelow (n, 128) + && (noteStates[n] & (1 << (midiChannel - 1))) != 0; +} + +bool MidiKeyboardState::isNoteOnForChannels (const int midiChannelMask, const int n) const noexcept +{ + return isPositiveAndBelow (n, 128) + && (noteStates[n] & midiChannelMask) != 0; +} + +void MidiKeyboardState::noteOn (const int midiChannel, const int midiNoteNumber, const float velocity) +{ + jassert (midiChannel >= 0 && midiChannel <= 16); + jassert (isPositiveAndBelow (midiNoteNumber, 128)); + + const ScopedLock sl (lock); + + if (isPositiveAndBelow (midiNoteNumber, 128)) + { + const int timeNow = (int) Time::getMillisecondCounter(); + eventsToAdd.addEvent (MidiMessage::noteOn (midiChannel, midiNoteNumber, velocity), timeNow); + eventsToAdd.clear (0, timeNow - 500); + + noteOnInternal (midiChannel, midiNoteNumber, velocity); + } +} + +void MidiKeyboardState::noteOnInternal (const int midiChannel, const int midiNoteNumber, const float velocity) +{ + if (isPositiveAndBelow (midiNoteNumber, 128)) + { + noteStates [midiNoteNumber] |= (1 << (midiChannel - 1)); + + for (int i = listeners.size(); --i >= 0;) + listeners.getUnchecked(i)->handleNoteOn (this, midiChannel, midiNoteNumber, velocity); + } +} + +void MidiKeyboardState::noteOff (const int midiChannel, const int midiNoteNumber, const float velocity) +{ + const ScopedLock sl (lock); + + if (isNoteOn (midiChannel, midiNoteNumber)) + { + const int timeNow = (int) Time::getMillisecondCounter(); + eventsToAdd.addEvent (MidiMessage::noteOff (midiChannel, midiNoteNumber), timeNow); + eventsToAdd.clear (0, timeNow - 500); + + noteOffInternal (midiChannel, midiNoteNumber, velocity); + } +} + +void MidiKeyboardState::noteOffInternal (const int midiChannel, const int midiNoteNumber, const float velocity) +{ + if (isNoteOn (midiChannel, midiNoteNumber)) + { + noteStates [midiNoteNumber] &= ~(1 << (midiChannel - 1)); + + for (int i = listeners.size(); --i >= 0;) + listeners.getUnchecked(i)->handleNoteOff (this, midiChannel, midiNoteNumber, velocity); + } +} + +void MidiKeyboardState::allNotesOff (const int midiChannel) +{ + const ScopedLock sl (lock); + + if (midiChannel <= 0) + { + for (int i = 1; i <= 16; ++i) + allNotesOff (i); + } + else + { + for (int i = 0; i < 128; ++i) + noteOff (midiChannel, i, 0.0f); + } +} + +void MidiKeyboardState::processNextMidiEvent (const MidiMessage& message) +{ + if (message.isNoteOn()) + { + noteOnInternal (message.getChannel(), message.getNoteNumber(), message.getFloatVelocity()); + } + else if (message.isNoteOff()) + { + noteOffInternal (message.getChannel(), message.getNoteNumber(), message.getFloatVelocity()); + } + else if (message.isAllNotesOff()) + { + for (int i = 0; i < 128; ++i) + noteOffInternal (message.getChannel(), i, 0.0f); + } +} + +void MidiKeyboardState::processNextMidiBuffer (MidiBuffer& buffer, + const int startSample, + const int numSamples, + const bool injectIndirectEvents) +{ + MidiBuffer::Iterator i (buffer); + MidiMessage message; + int time; + + const ScopedLock sl (lock); + + while (i.getNextEvent (message, time)) + processNextMidiEvent (message); + + if (injectIndirectEvents) + { + MidiBuffer::Iterator i2 (eventsToAdd); + const int firstEventToAdd = eventsToAdd.getFirstEventTime(); + const double scaleFactor = numSamples / (double) (eventsToAdd.getLastEventTime() + 1 - firstEventToAdd); + + while (i2.getNextEvent (message, time)) + { + const int pos = jlimit (0, numSamples - 1, roundToInt ((time - firstEventToAdd) * scaleFactor)); + buffer.addEvent (message, startSample + pos); + } + } + + eventsToAdd.clear(); +} + +//============================================================================== +void MidiKeyboardState::addListener (MidiKeyboardStateListener* const listener) +{ + const ScopedLock sl (lock); + listeners.addIfNotAlreadyThere (listener); +} + +void MidiKeyboardState::removeListener (MidiKeyboardStateListener* const listener) +{ + const ScopedLock sl (lock); + listeners.removeFirstMatchingValue (listener); +} + +} // namespace juce diff --git a/JuceLibraryCode/modules/juce_audio_basics/midi/juce_MidiKeyboardState.h b/JuceLibraryCode/modules/juce_audio_basics/midi/juce_MidiKeyboardState.h index 47aaf69..cd958ba 100644 --- a/JuceLibraryCode/modules/juce_audio_basics/midi/juce_MidiKeyboardState.h +++ b/JuceLibraryCode/modules/juce_audio_basics/midi/juce_MidiKeyboardState.h @@ -1,206 +1,206 @@ -/* - ============================================================================== - - This file is part of the JUCE library. - Copyright (c) 2017 - ROLI Ltd. - - JUCE is an open source library subject to commercial or open-source - licensing. - - The code included in this file is provided under the terms of the ISC license - http://www.isc.org/downloads/software-support-policy/isc-license. Permission - To use, copy, modify, and/or distribute this software for any purpose with or - without fee is hereby granted provided that the above copyright notice and - this permission notice appear in all copies. - - JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER - EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE - DISCLAIMED. - - ============================================================================== -*/ - -namespace juce -{ - -class MidiKeyboardState; - - -//============================================================================== -/** - Receives events from a MidiKeyboardState object. - - @see MidiKeyboardState - - @tags{Audio} -*/ -class JUCE_API MidiKeyboardStateListener -{ -public: - //============================================================================== - MidiKeyboardStateListener() = default; - virtual ~MidiKeyboardStateListener() = default; - - //============================================================================== - /** Called when one of the MidiKeyboardState's keys is pressed. - - This will be called synchronously when the state is either processing a - buffer in its MidiKeyboardState::processNextMidiBuffer() method, or - when a note is being played with its MidiKeyboardState::noteOn() method. - - Note that this callback could happen from an audio callback thread, so be - careful not to block, and avoid any UI activity in the callback. - */ - virtual void handleNoteOn (MidiKeyboardState* source, - int midiChannel, int midiNoteNumber, float velocity) = 0; - - /** Called when one of the MidiKeyboardState's keys is released. - - This will be called synchronously when the state is either processing a - buffer in its MidiKeyboardState::processNextMidiBuffer() method, or - when a note is being played with its MidiKeyboardState::noteOff() method. - - Note that this callback could happen from an audio callback thread, so be - careful not to block, and avoid any UI activity in the callback. - */ - virtual void handleNoteOff (MidiKeyboardState* source, - int midiChannel, int midiNoteNumber, float velocity) = 0; -}; - - -//============================================================================== -/** - Represents a piano keyboard, keeping track of which keys are currently pressed. - - This object can parse a stream of midi events, using them to update its idea - of which keys are pressed for each individual midi channel. - - When keys go up or down, it can broadcast these events to listener objects. - - It also allows key up/down events to be triggered with its noteOn() and noteOff() - methods, and midi messages for these events will be merged into the - midi stream that gets processed by processNextMidiBuffer(). - - @tags{Audio} -*/ -class JUCE_API MidiKeyboardState -{ -public: - //============================================================================== - MidiKeyboardState(); - ~MidiKeyboardState(); - - //============================================================================== - /** Resets the state of the object. - - All internal data for all the channels is reset, but no events are sent as a - result. - - If you want to release any keys that are currently down, and to send out note-up - midi messages for this, use the allNotesOff() method instead. - */ - void reset(); - - /** Returns true if the given midi key is currently held down for the given midi channel. - - The channel number must be between 1 and 16. If you want to see if any notes are - on for a range of channels, use the isNoteOnForChannels() method. - */ - bool isNoteOn (int midiChannel, int midiNoteNumber) const noexcept; - - /** Returns true if the given midi key is currently held down on any of a set of midi channels. - - The channel mask has a bit set for each midi channel you want to test for - bit - 0 = midi channel 1, bit 1 = midi channel 2, etc. - - If a note is on for at least one of the specified channels, this returns true. - */ - bool isNoteOnForChannels (int midiChannelMask, int midiNoteNumber) const noexcept; - - /** Turns a specified note on. - - This will cause a suitable midi note-on event to be injected into the midi buffer during the - next call to processNextMidiBuffer(). - - It will also trigger a synchronous callback to the listeners to tell them that the key has - gone down. - */ - void noteOn (int midiChannel, int midiNoteNumber, float velocity); - - /** Turns a specified note off. - - This will cause a suitable midi note-off event to be injected into the midi buffer during the - next call to processNextMidiBuffer(). - - It will also trigger a synchronous callback to the listeners to tell them that the key has - gone up. - - But if the note isn't actually down for the given channel, this method will in fact do nothing. - */ - void noteOff (int midiChannel, int midiNoteNumber, float velocity); - - /** This will turn off any currently-down notes for the given midi channel. - - If you pass 0 for the midi channel, it will in fact turn off all notes on all channels. - - Calling this method will make calls to noteOff(), so can trigger synchronous callbacks - and events being added to the midi stream. - */ - void allNotesOff (int midiChannel); - - //============================================================================== - /** Looks at a key-up/down event and uses it to update the state of this object. - - To process a buffer full of midi messages, use the processNextMidiBuffer() method - instead. - */ - void processNextMidiEvent (const MidiMessage& message); - - /** Scans a midi stream for up/down events and adds its own events to it. - - This will look for any up/down events and use them to update the internal state, - synchronously making suitable callbacks to the listeners. - - If injectIndirectEvents is true, then midi events to produce the recent noteOn() - and noteOff() calls will be added into the buffer. - - Only the section of the buffer whose timestamps are between startSample and - (startSample + numSamples) will be affected, and any events added will be placed - between these times. - - If you're going to use this method, you'll need to keep calling it regularly for - it to work satisfactorily. - - To process a single midi event at a time, use the processNextMidiEvent() method - instead. - */ - void processNextMidiBuffer (MidiBuffer& buffer, - int startSample, - int numSamples, - bool injectIndirectEvents); - - //============================================================================== - /** Registers a listener for callbacks when keys go up or down. - @see removeListener - */ - void addListener (MidiKeyboardStateListener* listener); - - /** Deregisters a listener. - @see addListener - */ - void removeListener (MidiKeyboardStateListener* listener); - -private: - //============================================================================== - CriticalSection lock; - uint16 noteStates [128]; - MidiBuffer eventsToAdd; - Array listeners; - - void noteOnInternal (int midiChannel, int midiNoteNumber, float velocity); - void noteOffInternal (int midiChannel, int midiNoteNumber, float velocity); - - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MidiKeyboardState) -}; - -} // namespace juce +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2017 - ROLI Ltd. + + JUCE is an open source library subject to commercial or open-source + licensing. + + The code included in this file is provided under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license. Permission + To use, copy, modify, and/or distribute this software for any purpose with or + without fee is hereby granted provided that the above copyright notice and + this permission notice appear in all copies. + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +namespace juce +{ + +class MidiKeyboardState; + + +//============================================================================== +/** + Receives events from a MidiKeyboardState object. + + @see MidiKeyboardState + + @tags{Audio} +*/ +class JUCE_API MidiKeyboardStateListener +{ +public: + //============================================================================== + MidiKeyboardStateListener() = default; + virtual ~MidiKeyboardStateListener() = default; + + //============================================================================== + /** Called when one of the MidiKeyboardState's keys is pressed. + + This will be called synchronously when the state is either processing a + buffer in its MidiKeyboardState::processNextMidiBuffer() method, or + when a note is being played with its MidiKeyboardState::noteOn() method. + + Note that this callback could happen from an audio callback thread, so be + careful not to block, and avoid any UI activity in the callback. + */ + virtual void handleNoteOn (MidiKeyboardState* source, + int midiChannel, int midiNoteNumber, float velocity) = 0; + + /** Called when one of the MidiKeyboardState's keys is released. + + This will be called synchronously when the state is either processing a + buffer in its MidiKeyboardState::processNextMidiBuffer() method, or + when a note is being played with its MidiKeyboardState::noteOff() method. + + Note that this callback could happen from an audio callback thread, so be + careful not to block, and avoid any UI activity in the callback. + */ + virtual void handleNoteOff (MidiKeyboardState* source, + int midiChannel, int midiNoteNumber, float velocity) = 0; +}; + + +//============================================================================== +/** + Represents a piano keyboard, keeping track of which keys are currently pressed. + + This object can parse a stream of midi events, using them to update its idea + of which keys are pressed for each individual midi channel. + + When keys go up or down, it can broadcast these events to listener objects. + + It also allows key up/down events to be triggered with its noteOn() and noteOff() + methods, and midi messages for these events will be merged into the + midi stream that gets processed by processNextMidiBuffer(). + + @tags{Audio} +*/ +class JUCE_API MidiKeyboardState +{ +public: + //============================================================================== + MidiKeyboardState(); + ~MidiKeyboardState(); + + //============================================================================== + /** Resets the state of the object. + + All internal data for all the channels is reset, but no events are sent as a + result. + + If you want to release any keys that are currently down, and to send out note-up + midi messages for this, use the allNotesOff() method instead. + */ + void reset(); + + /** Returns true if the given midi key is currently held down for the given midi channel. + + The channel number must be between 1 and 16. If you want to see if any notes are + on for a range of channels, use the isNoteOnForChannels() method. + */ + bool isNoteOn (int midiChannel, int midiNoteNumber) const noexcept; + + /** Returns true if the given midi key is currently held down on any of a set of midi channels. + + The channel mask has a bit set for each midi channel you want to test for - bit + 0 = midi channel 1, bit 1 = midi channel 2, etc. + + If a note is on for at least one of the specified channels, this returns true. + */ + bool isNoteOnForChannels (int midiChannelMask, int midiNoteNumber) const noexcept; + + /** Turns a specified note on. + + This will cause a suitable midi note-on event to be injected into the midi buffer during the + next call to processNextMidiBuffer(). + + It will also trigger a synchronous callback to the listeners to tell them that the key has + gone down. + */ + void noteOn (int midiChannel, int midiNoteNumber, float velocity); + + /** Turns a specified note off. + + This will cause a suitable midi note-off event to be injected into the midi buffer during the + next call to processNextMidiBuffer(). + + It will also trigger a synchronous callback to the listeners to tell them that the key has + gone up. + + But if the note isn't actually down for the given channel, this method will in fact do nothing. + */ + void noteOff (int midiChannel, int midiNoteNumber, float velocity); + + /** This will turn off any currently-down notes for the given midi channel. + + If you pass 0 for the midi channel, it will in fact turn off all notes on all channels. + + Calling this method will make calls to noteOff(), so can trigger synchronous callbacks + and events being added to the midi stream. + */ + void allNotesOff (int midiChannel); + + //============================================================================== + /** Looks at a key-up/down event and uses it to update the state of this object. + + To process a buffer full of midi messages, use the processNextMidiBuffer() method + instead. + */ + void processNextMidiEvent (const MidiMessage& message); + + /** Scans a midi stream for up/down events and adds its own events to it. + + This will look for any up/down events and use them to update the internal state, + synchronously making suitable callbacks to the listeners. + + If injectIndirectEvents is true, then midi events to produce the recent noteOn() + and noteOff() calls will be added into the buffer. + + Only the section of the buffer whose timestamps are between startSample and + (startSample + numSamples) will be affected, and any events added will be placed + between these times. + + If you're going to use this method, you'll need to keep calling it regularly for + it to work satisfactorily. + + To process a single midi event at a time, use the processNextMidiEvent() method + instead. + */ + void processNextMidiBuffer (MidiBuffer& buffer, + int startSample, + int numSamples, + bool injectIndirectEvents); + + //============================================================================== + /** Registers a listener for callbacks when keys go up or down. + @see removeListener + */ + void addListener (MidiKeyboardStateListener* listener); + + /** Deregisters a listener. + @see addListener + */ + void removeListener (MidiKeyboardStateListener* listener); + +private: + //============================================================================== + CriticalSection lock; + uint16 noteStates [128]; + MidiBuffer eventsToAdd; + Array listeners; + + void noteOnInternal (int midiChannel, int midiNoteNumber, float velocity); + void noteOffInternal (int midiChannel, int midiNoteNumber, float velocity); + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MidiKeyboardState) +}; + +} // namespace juce diff --git a/JuceLibraryCode/modules/juce_audio_basics/midi/juce_MidiMessage.cpp b/JuceLibraryCode/modules/juce_audio_basics/midi/juce_MidiMessage.cpp index 9ccccfa..89de06a 100644 --- a/JuceLibraryCode/modules/juce_audio_basics/midi/juce_MidiMessage.cpp +++ b/JuceLibraryCode/modules/juce_audio_basics/midi/juce_MidiMessage.cpp @@ -1,1144 +1,1144 @@ -/* - ============================================================================== - - This file is part of the JUCE library. - Copyright (c) 2017 - ROLI Ltd. - - JUCE is an open source library subject to commercial or open-source - licensing. - - The code included in this file is provided under the terms of the ISC license - http://www.isc.org/downloads/software-support-policy/isc-license. Permission - To use, copy, modify, and/or distribute this software for any purpose with or - without fee is hereby granted provided that the above copyright notice and - this permission notice appear in all copies. - - JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER - EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE - DISCLAIMED. - - ============================================================================== -*/ - -namespace juce -{ - -namespace MidiHelpers -{ - inline uint8 initialByte (const int type, const int channel) noexcept - { - return (uint8) (type | jlimit (0, 15, channel - 1)); - } - - inline uint8 validVelocity (const int v) noexcept - { - return (uint8) jlimit (0, 127, v); - } -} - -//============================================================================== -uint8 MidiMessage::floatValueToMidiByte (const float v) noexcept -{ - jassert (v >= 0 && v <= 1.0f); // if your value is > 1, maybe you're passing an - // integer value to a float method by mistake? - - return MidiHelpers::validVelocity (roundToInt (v * 127.0f)); -} - -uint16 MidiMessage::pitchbendToPitchwheelPos (const float pitchbend, - const float pitchbendRange) noexcept -{ - // can't translate a pitchbend value that is outside of the given range! - jassert (std::abs (pitchbend) <= pitchbendRange); - - return static_cast (pitchbend > 0.0f - ? jmap (pitchbend, 0.0f, pitchbendRange, 8192.0f, 16383.0f) - : jmap (pitchbend, -pitchbendRange, 0.0f, 0.0f, 8192.0f)); -} - -//============================================================================== -int MidiMessage::readVariableLengthVal (const uint8* data, int& numBytesUsed) noexcept -{ - numBytesUsed = 0; - int v = 0, i; - - do - { - i = (int) *data++; - - if (++numBytesUsed > 6) - break; - - v = (v << 7) + (i & 0x7f); - - } while (i & 0x80); - - return v; -} - -int MidiMessage::getMessageLengthFromFirstByte (const uint8 firstByte) noexcept -{ - // this method only works for valid starting bytes of a short midi message - jassert (firstByte >= 0x80 && firstByte != 0xf0 && firstByte != 0xf7); - - static const char messageLengths[] = - { - 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, - 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, - 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, - 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, - 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, - 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, - 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, - 1, 2, 3, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 - }; - - return messageLengths[firstByte & 0x7f]; -} - -//============================================================================== -MidiMessage::MidiMessage() noexcept - : size (2) -{ - packedData.asBytes[0] = 0xf0; - packedData.asBytes[1] = 0xf7; -} - -MidiMessage::MidiMessage (const void* const d, const int dataSize, const double t) - : timeStamp (t), size (dataSize) -{ - jassert (dataSize > 0); - // this checks that the length matches the data.. - jassert (dataSize > 3 || *(uint8*)d >= 0xf0 || getMessageLengthFromFirstByte (*(uint8*)d) == size); - - memcpy (allocateSpace (dataSize), d, (size_t) dataSize); -} - -MidiMessage::MidiMessage (const int byte1, const double t) noexcept - : timeStamp (t), size (1) -{ - packedData.asBytes[0] = (uint8) byte1; - - // check that the length matches the data.. - jassert (byte1 >= 0xf0 || getMessageLengthFromFirstByte ((uint8) byte1) == 1); -} - -MidiMessage::MidiMessage (const int byte1, const int byte2, const double t) noexcept - : timeStamp (t), size (2) -{ - packedData.asBytes[0] = (uint8) byte1; - packedData.asBytes[1] = (uint8) byte2; - - // check that the length matches the data.. - jassert (byte1 >= 0xf0 || getMessageLengthFromFirstByte ((uint8) byte1) == 2); -} - -MidiMessage::MidiMessage (const int byte1, const int byte2, const int byte3, const double t) noexcept - : timeStamp (t), size (3) -{ - packedData.asBytes[0] = (uint8) byte1; - packedData.asBytes[1] = (uint8) byte2; - packedData.asBytes[2] = (uint8) byte3; - - // check that the length matches the data.. - jassert (byte1 >= 0xf0 || getMessageLengthFromFirstByte ((uint8) byte1) == 3); -} - -MidiMessage::MidiMessage (const MidiMessage& other) - : timeStamp (other.timeStamp), size (other.size) -{ - if (isHeapAllocated()) - memcpy (allocateSpace (size), other.getData(), (size_t) size); - else - packedData.allocatedData = other.packedData.allocatedData; -} - -MidiMessage::MidiMessage (const MidiMessage& other, const double newTimeStamp) - : timeStamp (newTimeStamp), size (other.size) -{ - if (isHeapAllocated()) - memcpy (allocateSpace (size), other.getData(), (size_t) size); - else - packedData.allocatedData = other.packedData.allocatedData; -} - -MidiMessage::MidiMessage (const void* srcData, int sz, int& numBytesUsed, const uint8 lastStatusByte, - double t, bool sysexHasEmbeddedLength) - : timeStamp (t) -{ - auto src = static_cast (srcData); - auto byte = (unsigned int) *src; - - if (byte < 0x80) - { - byte = (unsigned int) lastStatusByte; - numBytesUsed = -1; - } - else - { - numBytesUsed = 0; - --sz; - ++src; - } - - if (byte >= 0x80) - { - if (byte == 0xf0) - { - auto d = src; - bool haveReadAllLengthBytes = ! sysexHasEmbeddedLength; - int numVariableLengthSysexBytes = 0; - - while (d < src + sz) - { - if (*d >= 0x80) - { - if (*d == 0xf7) - { - ++d; // include the trailing 0xf7 when we hit it - break; - } - - if (haveReadAllLengthBytes) // if we see a 0x80 bit set after the initial data length - break; // bytes, assume it's the end of the sysex - - ++numVariableLengthSysexBytes; - } - else if (! haveReadAllLengthBytes) - { - haveReadAllLengthBytes = true; - ++numVariableLengthSysexBytes; - } - - ++d; - } - - src += numVariableLengthSysexBytes; - size = 1 + (int) (d - src); - - auto dest = allocateSpace (size); - *dest = (uint8) byte; - memcpy (dest + 1, src, (size_t) (size - 1)); - - numBytesUsed += (numVariableLengthSysexBytes + size); // (these aren't counted in the size) - } - else if (byte == 0xff) - { - if (sz == 1) - { - size = 1; - } - else - { - int n; - const int bytesLeft = readVariableLengthVal (src + 1, n); - size = jmin (sz + 1, n + 2 + bytesLeft); - } - - auto dest = allocateSpace (size); - *dest = (uint8) byte; - memcpy (dest + 1, src, (size_t) size - 1); - - numBytesUsed += size; - } - else - { - size = getMessageLengthFromFirstByte ((uint8) byte); - packedData.asBytes[0] = (uint8) byte; - - if (size > 1) - { - packedData.asBytes[1] = (sz > 0 ? src[0] : 0); - - if (size > 2) - packedData.asBytes[2] = (sz > 1 ? src[1] : 0); - } - - numBytesUsed += jmin (size, sz + 1); - } - } - else - { - packedData.allocatedData = nullptr; - size = 0; - } -} - -MidiMessage& MidiMessage::operator= (const MidiMessage& other) -{ - if (this != &other) - { - if (other.isHeapAllocated()) - { - if (isHeapAllocated()) - packedData.allocatedData = static_cast (std::realloc (packedData.allocatedData, (size_t) other.size)); - else - packedData.allocatedData = static_cast (std::malloc ((size_t) other.size)); - - memcpy (packedData.allocatedData, other.packedData.allocatedData, (size_t) other.size); - } - else - { - if (isHeapAllocated()) - std::free (packedData.allocatedData); - - packedData.allocatedData = other.packedData.allocatedData; - } - - timeStamp = other.timeStamp; - size = other.size; - } - - return *this; -} - -MidiMessage::MidiMessage (MidiMessage&& other) noexcept - : timeStamp (other.timeStamp), size (other.size) -{ - packedData.allocatedData = other.packedData.allocatedData; - other.size = 0; -} - -MidiMessage& MidiMessage::operator= (MidiMessage&& other) noexcept -{ - packedData.allocatedData = other.packedData.allocatedData; - timeStamp = other.timeStamp; - size = other.size; - other.size = 0; - return *this; -} - -MidiMessage::~MidiMessage() noexcept -{ - if (isHeapAllocated()) - std::free (packedData.allocatedData); -} - -uint8* MidiMessage::allocateSpace (int bytes) -{ - if (bytes > (int) sizeof (packedData)) - { - auto d = static_cast (std::malloc ((size_t) bytes)); - packedData.allocatedData = d; - return d; - } - - return packedData.asBytes; -} - -String MidiMessage::getDescription() const -{ - if (isNoteOn()) return "Note on " + MidiMessage::getMidiNoteName (getNoteNumber(), true, true, 3) + " Velocity " + String (getVelocity()) + " Channel " + String (getChannel()); - if (isNoteOff()) return "Note off " + MidiMessage::getMidiNoteName (getNoteNumber(), true, true, 3) + " Velocity " + String (getVelocity()) + " Channel " + String (getChannel()); - if (isProgramChange()) return "Program change " + String (getProgramChangeNumber()) + " Channel " + String (getChannel()); - if (isPitchWheel()) return "Pitch wheel " + String (getPitchWheelValue()) + " Channel " + String (getChannel()); - if (isAftertouch()) return "Aftertouch " + MidiMessage::getMidiNoteName (getNoteNumber(), true, true, 3) + ": " + String (getAfterTouchValue()) + " Channel " + String (getChannel()); - if (isChannelPressure()) return "Channel pressure " + String (getChannelPressureValue()) + " Channel " + String (getChannel()); - if (isAllNotesOff()) return "All notes off Channel " + String (getChannel()); - if (isAllSoundOff()) return "All sound off Channel " + String (getChannel()); - if (isMetaEvent()) return "Meta event"; - - if (isController()) - { - String name (MidiMessage::getControllerName (getControllerNumber())); - - if (name.isEmpty()) - name = String (getControllerNumber()); - - return "Controller " + name + ": " + String (getControllerValue()) + " Channel " + String (getChannel()); - } - - return String::toHexString (getRawData(), getRawDataSize()); -} - -MidiMessage MidiMessage::withTimeStamp (double newTimestamp) const -{ - return { *this, newTimestamp }; -} - -int MidiMessage::getChannel() const noexcept -{ - auto data = getRawData(); - - if ((data[0] & 0xf0) != 0xf0) - return (data[0] & 0xf) + 1; - - return 0; -} - -bool MidiMessage::isForChannel (const int channel) const noexcept -{ - jassert (channel > 0 && channel <= 16); // valid channels are numbered 1 to 16 - - auto data = getRawData(); - - return ((data[0] & 0xf) == channel - 1) - && ((data[0] & 0xf0) != 0xf0); -} - -void MidiMessage::setChannel (const int channel) noexcept -{ - jassert (channel > 0 && channel <= 16); // valid channels are numbered 1 to 16 - - auto data = getData(); - - if ((data[0] & 0xf0) != (uint8) 0xf0) - data[0] = (uint8) ((data[0] & (uint8) 0xf0) - | (uint8)(channel - 1)); -} - -bool MidiMessage::isNoteOn (const bool returnTrueForVelocity0) const noexcept -{ - auto data = getRawData(); - - return ((data[0] & 0xf0) == 0x90) - && (returnTrueForVelocity0 || data[2] != 0); -} - -bool MidiMessage::isNoteOff (const bool returnTrueForNoteOnVelocity0) const noexcept -{ - auto data = getRawData(); - - return ((data[0] & 0xf0) == 0x80) - || (returnTrueForNoteOnVelocity0 && (data[2] == 0) && ((data[0] & 0xf0) == 0x90)); -} - -bool MidiMessage::isNoteOnOrOff() const noexcept -{ - auto d = getRawData()[0] & 0xf0; - return (d == 0x90) || (d == 0x80); -} - -int MidiMessage::getNoteNumber() const noexcept -{ - return getRawData()[1]; -} - -void MidiMessage::setNoteNumber (const int newNoteNumber) noexcept -{ - if (isNoteOnOrOff() || isAftertouch()) - getData()[1] = (uint8) (newNoteNumber & 127); -} - -uint8 MidiMessage::getVelocity() const noexcept -{ - if (isNoteOnOrOff()) - return getRawData()[2]; - - return 0; -} - -float MidiMessage::getFloatVelocity() const noexcept -{ - return getVelocity() * (1.0f / 127.0f); -} - -void MidiMessage::setVelocity (const float newVelocity) noexcept -{ - if (isNoteOnOrOff()) - getData()[2] = floatValueToMidiByte (newVelocity); -} - -void MidiMessage::multiplyVelocity (const float scaleFactor) noexcept -{ - if (isNoteOnOrOff()) - { - auto data = getData(); - data[2] = MidiHelpers::validVelocity (roundToInt (scaleFactor * data[2])); - } -} - -bool MidiMessage::isAftertouch() const noexcept -{ - return (getRawData()[0] & 0xf0) == 0xa0; -} - -int MidiMessage::getAfterTouchValue() const noexcept -{ - jassert (isAftertouch()); - return getRawData()[2]; -} - -MidiMessage MidiMessage::aftertouchChange (const int channel, - const int noteNum, - const int aftertouchValue) noexcept -{ - jassert (channel > 0 && channel <= 16); // valid channels are numbered 1 to 16 - jassert (isPositiveAndBelow (noteNum, 128)); - jassert (isPositiveAndBelow (aftertouchValue, 128)); - - return MidiMessage (MidiHelpers::initialByte (0xa0, channel), - noteNum & 0x7f, - aftertouchValue & 0x7f); -} - -bool MidiMessage::isChannelPressure() const noexcept -{ - return (getRawData()[0] & 0xf0) == 0xd0; -} - -int MidiMessage::getChannelPressureValue() const noexcept -{ - jassert (isChannelPressure()); - return getRawData()[1]; -} - -MidiMessage MidiMessage::channelPressureChange (const int channel, const int pressure) noexcept -{ - jassert (channel > 0 && channel <= 16); // valid channels are numbered 1 to 16 - jassert (isPositiveAndBelow (pressure, 128)); - - return MidiMessage (MidiHelpers::initialByte (0xd0, channel), pressure & 0x7f); -} - -bool MidiMessage::isSustainPedalOn() const noexcept { return isControllerOfType (0x40) && getRawData()[2] >= 64; } -bool MidiMessage::isSustainPedalOff() const noexcept { return isControllerOfType (0x40) && getRawData()[2] < 64; } - -bool MidiMessage::isSostenutoPedalOn() const noexcept { return isControllerOfType (0x42) && getRawData()[2] >= 64; } -bool MidiMessage::isSostenutoPedalOff() const noexcept { return isControllerOfType (0x42) && getRawData()[2] < 64; } - -bool MidiMessage::isSoftPedalOn() const noexcept { return isControllerOfType (0x43) && getRawData()[2] >= 64; } -bool MidiMessage::isSoftPedalOff() const noexcept { return isControllerOfType (0x43) && getRawData()[2] < 64; } - - -bool MidiMessage::isProgramChange() const noexcept -{ - return (getRawData()[0] & 0xf0) == 0xc0; -} - -int MidiMessage::getProgramChangeNumber() const noexcept -{ - jassert (isProgramChange()); - return getRawData()[1]; -} - -MidiMessage MidiMessage::programChange (const int channel, const int programNumber) noexcept -{ - jassert (channel > 0 && channel <= 16); // valid channels are numbered 1 to 16 - - return MidiMessage (MidiHelpers::initialByte (0xc0, channel), programNumber & 0x7f); -} - -bool MidiMessage::isPitchWheel() const noexcept -{ - return (getRawData()[0] & 0xf0) == 0xe0; -} - -int MidiMessage::getPitchWheelValue() const noexcept -{ - jassert (isPitchWheel()); - auto data = getRawData(); - return data[1] | (data[2] << 7); -} - -MidiMessage MidiMessage::pitchWheel (const int channel, const int position) noexcept -{ - jassert (channel > 0 && channel <= 16); // valid channels are numbered 1 to 16 - jassert (isPositiveAndBelow (position, 0x4000)); - - return MidiMessage (MidiHelpers::initialByte (0xe0, channel), - position & 127, (position >> 7) & 127); -} - -bool MidiMessage::isController() const noexcept -{ - return (getRawData()[0] & 0xf0) == 0xb0; -} - -bool MidiMessage::isControllerOfType (const int controllerType) const noexcept -{ - auto data = getRawData(); - return (data[0] & 0xf0) == 0xb0 && data[1] == controllerType; -} - -int MidiMessage::getControllerNumber() const noexcept -{ - jassert (isController()); - return getRawData()[1]; -} - -int MidiMessage::getControllerValue() const noexcept -{ - jassert (isController()); - return getRawData()[2]; -} - -MidiMessage MidiMessage::controllerEvent (const int channel, const int controllerType, const int value) noexcept -{ - // the channel must be between 1 and 16 inclusive - jassert (channel > 0 && channel <= 16); - - return MidiMessage (MidiHelpers::initialByte (0xb0, channel), - controllerType & 127, value & 127); -} - -MidiMessage MidiMessage::noteOn (const int channel, const int noteNumber, const uint8 velocity) noexcept -{ - jassert (channel > 0 && channel <= 16); - jassert (isPositiveAndBelow (noteNumber, 128)); - - return MidiMessage (MidiHelpers::initialByte (0x90, channel), - noteNumber & 127, MidiHelpers::validVelocity (velocity)); -} - -MidiMessage MidiMessage::noteOn (const int channel, const int noteNumber, const float velocity) noexcept -{ - return noteOn (channel, noteNumber, floatValueToMidiByte (velocity)); -} - -MidiMessage MidiMessage::noteOff (const int channel, const int noteNumber, uint8 velocity) noexcept -{ - jassert (channel > 0 && channel <= 16); - jassert (isPositiveAndBelow (noteNumber, 128)); - - return MidiMessage (MidiHelpers::initialByte (0x80, channel), - noteNumber & 127, MidiHelpers::validVelocity (velocity)); -} - -MidiMessage MidiMessage::noteOff (const int channel, const int noteNumber, float velocity) noexcept -{ - return noteOff (channel, noteNumber, floatValueToMidiByte (velocity)); -} - -MidiMessage MidiMessage::noteOff (const int channel, const int noteNumber) noexcept -{ - jassert (channel > 0 && channel <= 16); - jassert (isPositiveAndBelow (noteNumber, 128)); - - return MidiMessage (MidiHelpers::initialByte (0x80, channel), noteNumber & 127, 0); -} - -MidiMessage MidiMessage::allNotesOff (const int channel) noexcept -{ - return controllerEvent (channel, 123, 0); -} - -bool MidiMessage::isAllNotesOff() const noexcept -{ - auto data = getRawData(); - return (data[0] & 0xf0) == 0xb0 && data[1] == 123; -} - -MidiMessage MidiMessage::allSoundOff (const int channel) noexcept -{ - return controllerEvent (channel, 120, 0); -} - -bool MidiMessage::isAllSoundOff() const noexcept -{ - auto data = getRawData(); - return data[1] == 120 && (data[0] & 0xf0) == 0xb0; -} - -bool MidiMessage::isResetAllControllers() const noexcept -{ - auto data = getRawData(); - return (data[0] & 0xf0) == 0xb0 && data[1] == 121; -} - -MidiMessage MidiMessage::allControllersOff (const int channel) noexcept -{ - return controllerEvent (channel, 121, 0); -} - -MidiMessage MidiMessage::masterVolume (const float volume) -{ - auto vol = jlimit (0, 0x3fff, roundToInt (volume * 0x4000)); - - return { 0xf0, 0x7f, 0x7f, 0x04, 0x01, vol & 0x7f, vol >> 7, 0xf7 }; -} - -//============================================================================== -bool MidiMessage::isSysEx() const noexcept -{ - return *getRawData() == 0xf0; -} - -MidiMessage MidiMessage::createSysExMessage (const void* sysexData, const int dataSize) -{ - HeapBlock m (dataSize + 2); - - m[0] = 0xf0; - memcpy (m + 1, sysexData, (size_t) dataSize); - m[dataSize + 1] = 0xf7; - - return MidiMessage (m, dataSize + 2); -} - -const uint8* MidiMessage::getSysExData() const noexcept -{ - return isSysEx() ? getRawData() + 1 : nullptr; -} - -int MidiMessage::getSysExDataSize() const noexcept -{ - return isSysEx() ? size - 2 : 0; -} - -//============================================================================== -bool MidiMessage::isMetaEvent() const noexcept { return *getRawData() == 0xff; } -bool MidiMessage::isActiveSense() const noexcept { return *getRawData() == 0xfe; } - -int MidiMessage::getMetaEventType() const noexcept -{ - auto data = getRawData(); - return *data != 0xff ? -1 : data[1]; -} - -int MidiMessage::getMetaEventLength() const noexcept -{ - auto data = getRawData(); - - if (*data == 0xff) - { - int n; - return jmin (size - 2, readVariableLengthVal (data + 2, n)); - } - - return 0; -} - -const uint8* MidiMessage::getMetaEventData() const noexcept -{ - jassert (isMetaEvent()); - - int n; - auto d = getRawData() + 2; - readVariableLengthVal (d, n); - return d + n; -} - -bool MidiMessage::isTrackMetaEvent() const noexcept { return getMetaEventType() == 0; } -bool MidiMessage::isEndOfTrackMetaEvent() const noexcept { return getMetaEventType() == 47; } - -bool MidiMessage::isTextMetaEvent() const noexcept -{ - auto t = getMetaEventType(); - return t > 0 && t < 16; -} - -String MidiMessage::getTextFromTextMetaEvent() const -{ - auto textData = reinterpret_cast (getMetaEventData()); - - return String (CharPointer_UTF8 (textData), - CharPointer_UTF8 (textData + getMetaEventLength())); -} - -MidiMessage MidiMessage::textMetaEvent (int type, StringRef text) -{ - jassert (type > 0 && type < 16); - - MidiMessage result; - - const size_t textSize = text.text.sizeInBytes() - 1; - - uint8 header[8]; - size_t n = sizeof (header); - - header[--n] = (uint8) (textSize & 0x7f); - - for (size_t i = textSize; (i >>= 7) != 0;) - header[--n] = (uint8) ((i & 0x7f) | 0x80); - - header[--n] = (uint8) type; - header[--n] = 0xff; - - const size_t headerLen = sizeof (header) - n; - const int totalSize = (int) (headerLen + textSize); - - auto dest = result.allocateSpace (totalSize); - result.size = totalSize; - - memcpy (dest, header + n, headerLen); - memcpy (dest + headerLen, text.text.getAddress(), textSize); - - return result; -} - -bool MidiMessage::isTrackNameEvent() const noexcept { auto data = getRawData(); return (data[1] == 3) && (*data == 0xff); } -bool MidiMessage::isTempoMetaEvent() const noexcept { auto data = getRawData(); return (data[1] == 81) && (*data == 0xff); } -bool MidiMessage::isMidiChannelMetaEvent() const noexcept { auto data = getRawData(); return (data[1] == 0x20) && (*data == 0xff) && (data[2] == 1); } - -int MidiMessage::getMidiChannelMetaEventChannel() const noexcept -{ - jassert (isMidiChannelMetaEvent()); - return getRawData()[3] + 1; -} - -double MidiMessage::getTempoSecondsPerQuarterNote() const noexcept -{ - if (! isTempoMetaEvent()) - return 0.0; - - auto d = getMetaEventData(); - - return (((unsigned int) d[0] << 16) - | ((unsigned int) d[1] << 8) - | d[2]) - / 1000000.0; -} - -double MidiMessage::getTempoMetaEventTickLength (const short timeFormat) const noexcept -{ - if (timeFormat > 0) - { - if (! isTempoMetaEvent()) - return 0.5 / timeFormat; - - return getTempoSecondsPerQuarterNote() / timeFormat; - } - - const int frameCode = (-timeFormat) >> 8; - double framesPerSecond; - - switch (frameCode) - { - case 24: framesPerSecond = 24.0; break; - case 25: framesPerSecond = 25.0; break; - case 29: framesPerSecond = 30.0 * 1000.0 / 1001.0; break; - case 30: framesPerSecond = 30.0; break; - default: framesPerSecond = 30.0; break; - } - - return (1.0 / framesPerSecond) / (timeFormat & 0xff); -} - -MidiMessage MidiMessage::tempoMetaEvent (int microsecondsPerQuarterNote) noexcept -{ - return { 0xff, 81, 3, - (uint8) (microsecondsPerQuarterNote >> 16), - (uint8) (microsecondsPerQuarterNote >> 8), - (uint8) microsecondsPerQuarterNote }; -} - -bool MidiMessage::isTimeSignatureMetaEvent() const noexcept -{ - auto data = getRawData(); - return (data[1] == 0x58) && (*data == (uint8) 0xff); -} - -void MidiMessage::getTimeSignatureInfo (int& numerator, int& denominator) const noexcept -{ - if (isTimeSignatureMetaEvent()) - { - auto d = getMetaEventData(); - numerator = d[0]; - denominator = 1 << d[1]; - } - else - { - numerator = 4; - denominator = 4; - } -} - -MidiMessage MidiMessage::timeSignatureMetaEvent (const int numerator, const int denominator) -{ - int n = 1; - int powerOfTwo = 0; - - while (n < denominator) - { - n <<= 1; - ++powerOfTwo; - } - - return { 0xff, 0x58, 0x04, numerator, powerOfTwo, 1, 96 }; -} - -MidiMessage MidiMessage::midiChannelMetaEvent (const int channel) noexcept -{ - return { 0xff, 0x20, 0x01, jlimit (0, 0xff, channel - 1) }; -} - -bool MidiMessage::isKeySignatureMetaEvent() const noexcept -{ - return getMetaEventType() == 0x59; -} - -int MidiMessage::getKeySignatureNumberOfSharpsOrFlats() const noexcept -{ - return (int) (int8) getMetaEventData()[0]; -} - -bool MidiMessage::isKeySignatureMajorKey() const noexcept -{ - return getMetaEventData()[1] == 0; -} - -MidiMessage MidiMessage::keySignatureMetaEvent (int numberOfSharpsOrFlats, bool isMinorKey) -{ - jassert (numberOfSharpsOrFlats >= -7 && numberOfSharpsOrFlats <= 7); - - return { 0xff, 0x59, 0x02, numberOfSharpsOrFlats, isMinorKey ? 1 : 0 }; -} - -MidiMessage MidiMessage::endOfTrack() noexcept -{ - return { 0xff, 0x2f, 0x00 }; -} - -//============================================================================== -bool MidiMessage::isSongPositionPointer() const noexcept { return *getRawData() == 0xf2; } -int MidiMessage::getSongPositionPointerMidiBeat() const noexcept { auto data = getRawData(); return data[1] | (data[2] << 7); } - -MidiMessage MidiMessage::songPositionPointer (const int positionInMidiBeats) noexcept -{ - return { 0xf2, - positionInMidiBeats & 127, - (positionInMidiBeats >> 7) & 127 }; -} - -bool MidiMessage::isMidiStart() const noexcept { return *getRawData() == 0xfa; } -MidiMessage MidiMessage::midiStart() noexcept { return MidiMessage (0xfa); } - -bool MidiMessage::isMidiContinue() const noexcept { return *getRawData() == 0xfb; } -MidiMessage MidiMessage::midiContinue() noexcept { return MidiMessage (0xfb); } - -bool MidiMessage::isMidiStop() const noexcept { return *getRawData() == 0xfc; } -MidiMessage MidiMessage::midiStop() noexcept { return MidiMessage (0xfc); } - -bool MidiMessage::isMidiClock() const noexcept { return *getRawData() == 0xf8; } -MidiMessage MidiMessage::midiClock() noexcept { return MidiMessage (0xf8); } - -bool MidiMessage::isQuarterFrame() const noexcept { return *getRawData() == 0xf1; } -int MidiMessage::getQuarterFrameSequenceNumber() const noexcept { return ((int) getRawData()[1]) >> 4; } -int MidiMessage::getQuarterFrameValue() const noexcept { return ((int) getRawData()[1]) & 0x0f; } - -MidiMessage MidiMessage::quarterFrame (const int sequenceNumber, const int value) noexcept -{ - return MidiMessage (0xf1, (sequenceNumber << 4) | value); -} - -bool MidiMessage::isFullFrame() const noexcept -{ - auto data = getRawData(); - - return data[0] == 0xf0 - && data[1] == 0x7f - && size >= 10 - && data[3] == 0x01 - && data[4] == 0x01; -} - -void MidiMessage::getFullFrameParameters (int& hours, int& minutes, int& seconds, int& frames, - MidiMessage::SmpteTimecodeType& timecodeType) const noexcept -{ - jassert (isFullFrame()); - - auto data = getRawData(); - timecodeType = (SmpteTimecodeType) (data[5] >> 5); - hours = data[5] & 0x1f; - minutes = data[6]; - seconds = data[7]; - frames = data[8]; -} - -MidiMessage MidiMessage::fullFrame (int hours, int minutes, int seconds, int frames, - MidiMessage::SmpteTimecodeType timecodeType) -{ - return { 0xf0, 0x7f, 0x7f, 0x01, 0x01, - (hours & 0x01f) | (timecodeType << 5), - minutes, seconds, frames, - 0xf7 }; -} - -bool MidiMessage::isMidiMachineControlMessage() const noexcept -{ - auto data = getRawData(); - - return data[0] == 0xf0 - && data[1] == 0x7f - && data[3] == 0x06 - && size > 5; -} - -MidiMessage::MidiMachineControlCommand MidiMessage::getMidiMachineControlCommand() const noexcept -{ - jassert (isMidiMachineControlMessage()); - - return (MidiMachineControlCommand) getRawData()[4]; -} - -MidiMessage MidiMessage::midiMachineControlCommand (MidiMessage::MidiMachineControlCommand command) -{ - return { 0xf0, 0x7f, 0, 6, command, 0xf7 }; -} - -//============================================================================== -bool MidiMessage::isMidiMachineControlGoto (int& hours, int& minutes, int& seconds, int& frames) const noexcept -{ - auto data = getRawData(); - - if (size >= 12 - && data[0] == 0xf0 - && data[1] == 0x7f - && data[3] == 0x06 - && data[4] == 0x44 - && data[5] == 0x06 - && data[6] == 0x01) - { - hours = data[7] % 24; // (that some machines send out hours > 24) - minutes = data[8]; - seconds = data[9]; - frames = data[10]; - - return true; - } - - return false; -} - -MidiMessage MidiMessage::midiMachineControlGoto (int hours, int minutes, int seconds, int frames) -{ - return { 0xf0, 0x7f, 0, 6, 0x44, 6, 1, hours, minutes, seconds, frames, 0xf7 }; -} - -//============================================================================== -String MidiMessage::getMidiNoteName (int note, bool useSharps, bool includeOctaveNumber, int octaveNumForMiddleC) -{ - static const char* const sharpNoteNames[] = { "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B" }; - static const char* const flatNoteNames[] = { "C", "Db", "D", "Eb", "E", "F", "Gb", "G", "Ab", "A", "Bb", "B" }; - - if (isPositiveAndBelow (note, 128)) - { - String s (useSharps ? sharpNoteNames[note % 12] - : flatNoteNames [note % 12]); - - if (includeOctaveNumber) - s << (note / 12 + (octaveNumForMiddleC - 5)); - - return s; - } - - return {}; -} - -double MidiMessage::getMidiNoteInHertz (const int noteNumber, const double frequencyOfA) noexcept -{ - return frequencyOfA * std::pow (2.0, (noteNumber - 69) / 12.0); -} - -bool MidiMessage::isMidiNoteBlack (int noteNumber) noexcept -{ - return ((1 << (noteNumber % 12)) & 0x054a) != 0; -} - -const char* MidiMessage::getGMInstrumentName (const int n) -{ - static const char* names[] = - { - NEEDS_TRANS("Acoustic Grand Piano"), NEEDS_TRANS("Bright Acoustic Piano"), NEEDS_TRANS("Electric Grand Piano"), NEEDS_TRANS("Honky-tonk Piano"), - NEEDS_TRANS("Electric Piano 1"), NEEDS_TRANS("Electric Piano 2"), NEEDS_TRANS("Harpsichord"), NEEDS_TRANS("Clavinet"), - NEEDS_TRANS("Celesta"), NEEDS_TRANS("Glockenspiel"), NEEDS_TRANS("Music Box"), NEEDS_TRANS("Vibraphone"), - NEEDS_TRANS("Marimba"), NEEDS_TRANS("Xylophone"), NEEDS_TRANS("Tubular Bells"), NEEDS_TRANS("Dulcimer"), - NEEDS_TRANS("Drawbar Organ"), NEEDS_TRANS("Percussive Organ"), NEEDS_TRANS("Rock Organ"), NEEDS_TRANS("Church Organ"), - NEEDS_TRANS("Reed Organ"), NEEDS_TRANS("Accordion"), NEEDS_TRANS("Harmonica"), NEEDS_TRANS("Tango Accordion"), - NEEDS_TRANS("Acoustic Guitar (nylon)"), NEEDS_TRANS("Acoustic Guitar (steel)"), NEEDS_TRANS("Electric Guitar (jazz)"), NEEDS_TRANS("Electric Guitar (clean)"), - NEEDS_TRANS("Electric Guitar (mute)"), NEEDS_TRANS("Overdriven Guitar"), NEEDS_TRANS("Distortion Guitar"), NEEDS_TRANS("Guitar Harmonics"), - NEEDS_TRANS("Acoustic Bass"), NEEDS_TRANS("Electric Bass (finger)"), NEEDS_TRANS("Electric Bass (pick)"), NEEDS_TRANS("Fretless Bass"), - NEEDS_TRANS("Slap Bass 1"), NEEDS_TRANS("Slap Bass 2"), NEEDS_TRANS("Synth Bass 1"), NEEDS_TRANS("Synth Bass 2"), - NEEDS_TRANS("Violin"), NEEDS_TRANS("Viola"), NEEDS_TRANS("Cello"), NEEDS_TRANS("Contrabass"), - NEEDS_TRANS("Tremolo Strings"), NEEDS_TRANS("Pizzicato Strings"), NEEDS_TRANS("Orchestral Harp"), NEEDS_TRANS("Timpani"), - NEEDS_TRANS("String Ensemble 1"), NEEDS_TRANS("String Ensemble 2"), NEEDS_TRANS("SynthStrings 1"), NEEDS_TRANS("SynthStrings 2"), - NEEDS_TRANS("Choir Aahs"), NEEDS_TRANS("Voice Oohs"), NEEDS_TRANS("Synth Voice"), NEEDS_TRANS("Orchestra Hit"), - NEEDS_TRANS("Trumpet"), NEEDS_TRANS("Trombone"), NEEDS_TRANS("Tuba"), NEEDS_TRANS("Muted Trumpet"), - NEEDS_TRANS("French Horn"), NEEDS_TRANS("Brass Section"), NEEDS_TRANS("SynthBrass 1"), NEEDS_TRANS("SynthBrass 2"), - NEEDS_TRANS("Soprano Sax"), NEEDS_TRANS("Alto Sax"), NEEDS_TRANS("Tenor Sax"), NEEDS_TRANS("Baritone Sax"), - NEEDS_TRANS("Oboe"), NEEDS_TRANS("English Horn"), NEEDS_TRANS("Bassoon"), NEEDS_TRANS("Clarinet"), - NEEDS_TRANS("Piccolo"), NEEDS_TRANS("Flute"), NEEDS_TRANS("Recorder"), NEEDS_TRANS("Pan Flute"), - NEEDS_TRANS("Blown Bottle"), NEEDS_TRANS("Shakuhachi"), NEEDS_TRANS("Whistle"), NEEDS_TRANS("Ocarina"), - NEEDS_TRANS("Lead 1 (square)"), NEEDS_TRANS("Lead 2 (sawtooth)"), NEEDS_TRANS("Lead 3 (calliope)"), NEEDS_TRANS("Lead 4 (chiff)"), - NEEDS_TRANS("Lead 5 (charang)"), NEEDS_TRANS("Lead 6 (voice)"), NEEDS_TRANS("Lead 7 (fifths)"), NEEDS_TRANS("Lead 8 (bass+lead)"), - NEEDS_TRANS("Pad 1 (new age)"), NEEDS_TRANS("Pad 2 (warm)"), NEEDS_TRANS("Pad 3 (polysynth)"), NEEDS_TRANS("Pad 4 (choir)"), - NEEDS_TRANS("Pad 5 (bowed)"), NEEDS_TRANS("Pad 6 (metallic)"), NEEDS_TRANS("Pad 7 (halo)"), NEEDS_TRANS("Pad 8 (sweep)"), - NEEDS_TRANS("FX 1 (rain)"), NEEDS_TRANS("FX 2 (soundtrack)"), NEEDS_TRANS("FX 3 (crystal)"), NEEDS_TRANS("FX 4 (atmosphere)"), - NEEDS_TRANS("FX 5 (brightness)"), NEEDS_TRANS("FX 6 (goblins)"), NEEDS_TRANS("FX 7 (echoes)"), NEEDS_TRANS("FX 8 (sci-fi)"), - NEEDS_TRANS("Sitar"), NEEDS_TRANS("Banjo"), NEEDS_TRANS("Shamisen"), NEEDS_TRANS("Koto"), - NEEDS_TRANS("Kalimba"), NEEDS_TRANS("Bag pipe"), NEEDS_TRANS("Fiddle"), NEEDS_TRANS("Shanai"), - NEEDS_TRANS("Tinkle Bell"), NEEDS_TRANS("Agogo"), NEEDS_TRANS("Steel Drums"), NEEDS_TRANS("Woodblock"), - NEEDS_TRANS("Taiko Drum"), NEEDS_TRANS("Melodic Tom"), NEEDS_TRANS("Synth Drum"), NEEDS_TRANS("Reverse Cymbal"), - NEEDS_TRANS("Guitar Fret Noise"), NEEDS_TRANS("Breath Noise"), NEEDS_TRANS("Seashore"), NEEDS_TRANS("Bird Tweet"), - NEEDS_TRANS("Telephone Ring"), NEEDS_TRANS("Helicopter"), NEEDS_TRANS("Applause"), NEEDS_TRANS("Gunshot") - }; - - return isPositiveAndBelow (n, numElementsInArray (names)) ? names[n] : nullptr; -} - -const char* MidiMessage::getGMInstrumentBankName (const int n) -{ - static const char* names[] = - { - NEEDS_TRANS("Piano"), NEEDS_TRANS("Chromatic Percussion"), NEEDS_TRANS("Organ"), NEEDS_TRANS("Guitar"), - NEEDS_TRANS("Bass"), NEEDS_TRANS("Strings"), NEEDS_TRANS("Ensemble"), NEEDS_TRANS("Brass"), - NEEDS_TRANS("Reed"), NEEDS_TRANS("Pipe"), NEEDS_TRANS("Synth Lead"), NEEDS_TRANS("Synth Pad"), - NEEDS_TRANS("Synth Effects"), NEEDS_TRANS("Ethnic"), NEEDS_TRANS("Percussive"), NEEDS_TRANS("Sound Effects") - }; - - return isPositiveAndBelow (n, numElementsInArray (names)) ? names[n] : nullptr; -} - -const char* MidiMessage::getRhythmInstrumentName (const int n) -{ - static const char* names[] = - { - NEEDS_TRANS("Acoustic Bass Drum"), NEEDS_TRANS("Bass Drum 1"), NEEDS_TRANS("Side Stick"), NEEDS_TRANS("Acoustic Snare"), - NEEDS_TRANS("Hand Clap"), NEEDS_TRANS("Electric Snare"), NEEDS_TRANS("Low Floor Tom"), NEEDS_TRANS("Closed Hi-Hat"), - NEEDS_TRANS("High Floor Tom"), NEEDS_TRANS("Pedal Hi-Hat"), NEEDS_TRANS("Low Tom"), NEEDS_TRANS("Open Hi-Hat"), - NEEDS_TRANS("Low-Mid Tom"), NEEDS_TRANS("Hi-Mid Tom"), NEEDS_TRANS("Crash Cymbal 1"), NEEDS_TRANS("High Tom"), - NEEDS_TRANS("Ride Cymbal 1"), NEEDS_TRANS("Chinese Cymbal"), NEEDS_TRANS("Ride Bell"), NEEDS_TRANS("Tambourine"), - NEEDS_TRANS("Splash Cymbal"), NEEDS_TRANS("Cowbell"), NEEDS_TRANS("Crash Cymbal 2"), NEEDS_TRANS("Vibraslap"), - NEEDS_TRANS("Ride Cymbal 2"), NEEDS_TRANS("Hi Bongo"), NEEDS_TRANS("Low Bongo"), NEEDS_TRANS("Mute Hi Conga"), - NEEDS_TRANS("Open Hi Conga"), NEEDS_TRANS("Low Conga"), NEEDS_TRANS("High Timbale"), NEEDS_TRANS("Low Timbale"), - NEEDS_TRANS("High Agogo"), NEEDS_TRANS("Low Agogo"), NEEDS_TRANS("Cabasa"), NEEDS_TRANS("Maracas"), - NEEDS_TRANS("Short Whistle"), NEEDS_TRANS("Long Whistle"), NEEDS_TRANS("Short Guiro"), NEEDS_TRANS("Long Guiro"), - NEEDS_TRANS("Claves"), NEEDS_TRANS("Hi Wood Block"), NEEDS_TRANS("Low Wood Block"), NEEDS_TRANS("Mute Cuica"), - NEEDS_TRANS("Open Cuica"), NEEDS_TRANS("Mute Triangle"), NEEDS_TRANS("Open Triangle") - }; - - return (n >= 35 && n <= 81) ? names[n - 35] : nullptr; -} - -const char* MidiMessage::getControllerName (const int n) -{ - static const char* names[] = - { - NEEDS_TRANS("Bank Select"), NEEDS_TRANS("Modulation Wheel (coarse)"), NEEDS_TRANS("Breath controller (coarse)"), - nullptr, - NEEDS_TRANS("Foot Pedal (coarse)"), NEEDS_TRANS("Portamento Time (coarse)"), NEEDS_TRANS("Data Entry (coarse)"), - NEEDS_TRANS("Volume (coarse)"), NEEDS_TRANS("Balance (coarse)"), - nullptr, - NEEDS_TRANS("Pan position (coarse)"), NEEDS_TRANS("Expression (coarse)"), NEEDS_TRANS("Effect Control 1 (coarse)"), - NEEDS_TRANS("Effect Control 2 (coarse)"), - nullptr, nullptr, - NEEDS_TRANS("General Purpose Slider 1"), NEEDS_TRANS("General Purpose Slider 2"), - NEEDS_TRANS("General Purpose Slider 3"), NEEDS_TRANS("General Purpose Slider 4"), - nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, - NEEDS_TRANS("Bank Select (fine)"), NEEDS_TRANS("Modulation Wheel (fine)"), NEEDS_TRANS("Breath controller (fine)"), - nullptr, - NEEDS_TRANS("Foot Pedal (fine)"), NEEDS_TRANS("Portamento Time (fine)"), NEEDS_TRANS("Data Entry (fine)"), NEEDS_TRANS("Volume (fine)"), - NEEDS_TRANS("Balance (fine)"), nullptr, NEEDS_TRANS("Pan position (fine)"), NEEDS_TRANS("Expression (fine)"), - NEEDS_TRANS("Effect Control 1 (fine)"), NEEDS_TRANS("Effect Control 2 (fine)"), - nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, - nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, - NEEDS_TRANS("Hold Pedal (on/off)"), NEEDS_TRANS("Portamento (on/off)"), NEEDS_TRANS("Sustenuto Pedal (on/off)"), NEEDS_TRANS("Soft Pedal (on/off)"), - NEEDS_TRANS("Legato Pedal (on/off)"), NEEDS_TRANS("Hold 2 Pedal (on/off)"), NEEDS_TRANS("Sound Variation"), NEEDS_TRANS("Sound Timbre"), - NEEDS_TRANS("Sound Release Time"), NEEDS_TRANS("Sound Attack Time"), NEEDS_TRANS("Sound Brightness"), NEEDS_TRANS("Sound Control 6"), - NEEDS_TRANS("Sound Control 7"), NEEDS_TRANS("Sound Control 8"), NEEDS_TRANS("Sound Control 9"), NEEDS_TRANS("Sound Control 10"), - NEEDS_TRANS("General Purpose Button 1 (on/off)"), NEEDS_TRANS("General Purpose Button 2 (on/off)"), - NEEDS_TRANS("General Purpose Button 3 (on/off)"), NEEDS_TRANS("General Purpose Button 4 (on/off)"), - nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, - NEEDS_TRANS("Reverb Level"), NEEDS_TRANS("Tremolo Level"), NEEDS_TRANS("Chorus Level"), NEEDS_TRANS("Celeste Level"), - NEEDS_TRANS("Phaser Level"), NEEDS_TRANS("Data Button increment"), NEEDS_TRANS("Data Button decrement"), NEEDS_TRANS("Non-registered Parameter (fine)"), - NEEDS_TRANS("Non-registered Parameter (coarse)"), NEEDS_TRANS("Registered Parameter (fine)"), NEEDS_TRANS("Registered Parameter (coarse)"), - nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, - nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, - NEEDS_TRANS("All Sound Off"), NEEDS_TRANS("All Controllers Off"), NEEDS_TRANS("Local Keyboard (on/off)"), NEEDS_TRANS("All Notes Off"), - NEEDS_TRANS("Omni Mode Off"), NEEDS_TRANS("Omni Mode On"), NEEDS_TRANS("Mono Operation"), NEEDS_TRANS("Poly Operation") - }; - - return isPositiveAndBelow (n, numElementsInArray (names)) ? names[n] : nullptr; -} - -} // namespace juce +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2017 - ROLI Ltd. + + JUCE is an open source library subject to commercial or open-source + licensing. + + The code included in this file is provided under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license. Permission + To use, copy, modify, and/or distribute this software for any purpose with or + without fee is hereby granted provided that the above copyright notice and + this permission notice appear in all copies. + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +namespace juce +{ + +namespace MidiHelpers +{ + inline uint8 initialByte (const int type, const int channel) noexcept + { + return (uint8) (type | jlimit (0, 15, channel - 1)); + } + + inline uint8 validVelocity (const int v) noexcept + { + return (uint8) jlimit (0, 127, v); + } +} + +//============================================================================== +uint8 MidiMessage::floatValueToMidiByte (const float v) noexcept +{ + jassert (v >= 0 && v <= 1.0f); // if your value is > 1, maybe you're passing an + // integer value to a float method by mistake? + + return MidiHelpers::validVelocity (roundToInt (v * 127.0f)); +} + +uint16 MidiMessage::pitchbendToPitchwheelPos (const float pitchbend, + const float pitchbendRange) noexcept +{ + // can't translate a pitchbend value that is outside of the given range! + jassert (std::abs (pitchbend) <= pitchbendRange); + + return static_cast (pitchbend > 0.0f + ? jmap (pitchbend, 0.0f, pitchbendRange, 8192.0f, 16383.0f) + : jmap (pitchbend, -pitchbendRange, 0.0f, 0.0f, 8192.0f)); +} + +//============================================================================== +int MidiMessage::readVariableLengthVal (const uint8* data, int& numBytesUsed) noexcept +{ + numBytesUsed = 0; + int v = 0, i; + + do + { + i = (int) *data++; + + if (++numBytesUsed > 6) + break; + + v = (v << 7) + (i & 0x7f); + + } while (i & 0x80); + + return v; +} + +int MidiMessage::getMessageLengthFromFirstByte (const uint8 firstByte) noexcept +{ + // this method only works for valid starting bytes of a short midi message + jassert (firstByte >= 0x80 && firstByte != 0xf0 && firstByte != 0xf7); + + static const char messageLengths[] = + { + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 1, 2, 3, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + }; + + return messageLengths[firstByte & 0x7f]; +} + +//============================================================================== +MidiMessage::MidiMessage() noexcept + : size (2) +{ + packedData.asBytes[0] = 0xf0; + packedData.asBytes[1] = 0xf7; +} + +MidiMessage::MidiMessage (const void* const d, const int dataSize, const double t) + : timeStamp (t), size (dataSize) +{ + jassert (dataSize > 0); + // this checks that the length matches the data.. + jassert (dataSize > 3 || *(uint8*)d >= 0xf0 || getMessageLengthFromFirstByte (*(uint8*)d) == size); + + memcpy (allocateSpace (dataSize), d, (size_t) dataSize); +} + +MidiMessage::MidiMessage (const int byte1, const double t) noexcept + : timeStamp (t), size (1) +{ + packedData.asBytes[0] = (uint8) byte1; + + // check that the length matches the data.. + jassert (byte1 >= 0xf0 || getMessageLengthFromFirstByte ((uint8) byte1) == 1); +} + +MidiMessage::MidiMessage (const int byte1, const int byte2, const double t) noexcept + : timeStamp (t), size (2) +{ + packedData.asBytes[0] = (uint8) byte1; + packedData.asBytes[1] = (uint8) byte2; + + // check that the length matches the data.. + jassert (byte1 >= 0xf0 || getMessageLengthFromFirstByte ((uint8) byte1) == 2); +} + +MidiMessage::MidiMessage (const int byte1, const int byte2, const int byte3, const double t) noexcept + : timeStamp (t), size (3) +{ + packedData.asBytes[0] = (uint8) byte1; + packedData.asBytes[1] = (uint8) byte2; + packedData.asBytes[2] = (uint8) byte3; + + // check that the length matches the data.. + jassert (byte1 >= 0xf0 || getMessageLengthFromFirstByte ((uint8) byte1) == 3); +} + +MidiMessage::MidiMessage (const MidiMessage& other) + : timeStamp (other.timeStamp), size (other.size) +{ + if (isHeapAllocated()) + memcpy (allocateSpace (size), other.getData(), (size_t) size); + else + packedData.allocatedData = other.packedData.allocatedData; +} + +MidiMessage::MidiMessage (const MidiMessage& other, const double newTimeStamp) + : timeStamp (newTimeStamp), size (other.size) +{ + if (isHeapAllocated()) + memcpy (allocateSpace (size), other.getData(), (size_t) size); + else + packedData.allocatedData = other.packedData.allocatedData; +} + +MidiMessage::MidiMessage (const void* srcData, int sz, int& numBytesUsed, const uint8 lastStatusByte, + double t, bool sysexHasEmbeddedLength) + : timeStamp (t) +{ + auto src = static_cast (srcData); + auto byte = (unsigned int) *src; + + if (byte < 0x80) + { + byte = (unsigned int) lastStatusByte; + numBytesUsed = -1; + } + else + { + numBytesUsed = 0; + --sz; + ++src; + } + + if (byte >= 0x80) + { + if (byte == 0xf0) + { + auto d = src; + bool haveReadAllLengthBytes = ! sysexHasEmbeddedLength; + int numVariableLengthSysexBytes = 0; + + while (d < src + sz) + { + if (*d >= 0x80) + { + if (*d == 0xf7) + { + ++d; // include the trailing 0xf7 when we hit it + break; + } + + if (haveReadAllLengthBytes) // if we see a 0x80 bit set after the initial data length + break; // bytes, assume it's the end of the sysex + + ++numVariableLengthSysexBytes; + } + else if (! haveReadAllLengthBytes) + { + haveReadAllLengthBytes = true; + ++numVariableLengthSysexBytes; + } + + ++d; + } + + src += numVariableLengthSysexBytes; + size = 1 + (int) (d - src); + + auto dest = allocateSpace (size); + *dest = (uint8) byte; + memcpy (dest + 1, src, (size_t) (size - 1)); + + numBytesUsed += (numVariableLengthSysexBytes + size); // (these aren't counted in the size) + } + else if (byte == 0xff) + { + if (sz == 1) + { + size = 1; + } + else + { + int n; + const int bytesLeft = readVariableLengthVal (src + 1, n); + size = jmin (sz + 1, n + 2 + bytesLeft); + } + + auto dest = allocateSpace (size); + *dest = (uint8) byte; + memcpy (dest + 1, src, (size_t) size - 1); + + numBytesUsed += size; + } + else + { + size = getMessageLengthFromFirstByte ((uint8) byte); + packedData.asBytes[0] = (uint8) byte; + + if (size > 1) + { + packedData.asBytes[1] = (sz > 0 ? src[0] : 0); + + if (size > 2) + packedData.asBytes[2] = (sz > 1 ? src[1] : 0); + } + + numBytesUsed += jmin (size, sz + 1); + } + } + else + { + packedData.allocatedData = nullptr; + size = 0; + } +} + +MidiMessage& MidiMessage::operator= (const MidiMessage& other) +{ + if (this != &other) + { + if (other.isHeapAllocated()) + { + if (isHeapAllocated()) + packedData.allocatedData = static_cast (std::realloc (packedData.allocatedData, (size_t) other.size)); + else + packedData.allocatedData = static_cast (std::malloc ((size_t) other.size)); + + memcpy (packedData.allocatedData, other.packedData.allocatedData, (size_t) other.size); + } + else + { + if (isHeapAllocated()) + std::free (packedData.allocatedData); + + packedData.allocatedData = other.packedData.allocatedData; + } + + timeStamp = other.timeStamp; + size = other.size; + } + + return *this; +} + +MidiMessage::MidiMessage (MidiMessage&& other) noexcept + : timeStamp (other.timeStamp), size (other.size) +{ + packedData.allocatedData = other.packedData.allocatedData; + other.size = 0; +} + +MidiMessage& MidiMessage::operator= (MidiMessage&& other) noexcept +{ + packedData.allocatedData = other.packedData.allocatedData; + timeStamp = other.timeStamp; + size = other.size; + other.size = 0; + return *this; +} + +MidiMessage::~MidiMessage() noexcept +{ + if (isHeapAllocated()) + std::free (packedData.allocatedData); +} + +uint8* MidiMessage::allocateSpace (int bytes) +{ + if (bytes > (int) sizeof (packedData)) + { + auto d = static_cast (std::malloc ((size_t) bytes)); + packedData.allocatedData = d; + return d; + } + + return packedData.asBytes; +} + +String MidiMessage::getDescription() const +{ + if (isNoteOn()) return "Note on " + MidiMessage::getMidiNoteName (getNoteNumber(), true, true, 3) + " Velocity " + String (getVelocity()) + " Channel " + String (getChannel()); + if (isNoteOff()) return "Note off " + MidiMessage::getMidiNoteName (getNoteNumber(), true, true, 3) + " Velocity " + String (getVelocity()) + " Channel " + String (getChannel()); + if (isProgramChange()) return "Program change " + String (getProgramChangeNumber()) + " Channel " + String (getChannel()); + if (isPitchWheel()) return "Pitch wheel " + String (getPitchWheelValue()) + " Channel " + String (getChannel()); + if (isAftertouch()) return "Aftertouch " + MidiMessage::getMidiNoteName (getNoteNumber(), true, true, 3) + ": " + String (getAfterTouchValue()) + " Channel " + String (getChannel()); + if (isChannelPressure()) return "Channel pressure " + String (getChannelPressureValue()) + " Channel " + String (getChannel()); + if (isAllNotesOff()) return "All notes off Channel " + String (getChannel()); + if (isAllSoundOff()) return "All sound off Channel " + String (getChannel()); + if (isMetaEvent()) return "Meta event"; + + if (isController()) + { + String name (MidiMessage::getControllerName (getControllerNumber())); + + if (name.isEmpty()) + name = String (getControllerNumber()); + + return "Controller " + name + ": " + String (getControllerValue()) + " Channel " + String (getChannel()); + } + + return String::toHexString (getRawData(), getRawDataSize()); +} + +MidiMessage MidiMessage::withTimeStamp (double newTimestamp) const +{ + return { *this, newTimestamp }; +} + +int MidiMessage::getChannel() const noexcept +{ + auto data = getRawData(); + + if ((data[0] & 0xf0) != 0xf0) + return (data[0] & 0xf) + 1; + + return 0; +} + +bool MidiMessage::isForChannel (const int channel) const noexcept +{ + jassert (channel > 0 && channel <= 16); // valid channels are numbered 1 to 16 + + auto data = getRawData(); + + return ((data[0] & 0xf) == channel - 1) + && ((data[0] & 0xf0) != 0xf0); +} + +void MidiMessage::setChannel (const int channel) noexcept +{ + jassert (channel > 0 && channel <= 16); // valid channels are numbered 1 to 16 + + auto data = getData(); + + if ((data[0] & 0xf0) != (uint8) 0xf0) + data[0] = (uint8) ((data[0] & (uint8) 0xf0) + | (uint8)(channel - 1)); +} + +bool MidiMessage::isNoteOn (const bool returnTrueForVelocity0) const noexcept +{ + auto data = getRawData(); + + return ((data[0] & 0xf0) == 0x90) + && (returnTrueForVelocity0 || data[2] != 0); +} + +bool MidiMessage::isNoteOff (const bool returnTrueForNoteOnVelocity0) const noexcept +{ + auto data = getRawData(); + + return ((data[0] & 0xf0) == 0x80) + || (returnTrueForNoteOnVelocity0 && (data[2] == 0) && ((data[0] & 0xf0) == 0x90)); +} + +bool MidiMessage::isNoteOnOrOff() const noexcept +{ + auto d = getRawData()[0] & 0xf0; + return (d == 0x90) || (d == 0x80); +} + +int MidiMessage::getNoteNumber() const noexcept +{ + return getRawData()[1]; +} + +void MidiMessage::setNoteNumber (const int newNoteNumber) noexcept +{ + if (isNoteOnOrOff() || isAftertouch()) + getData()[1] = (uint8) (newNoteNumber & 127); +} + +uint8 MidiMessage::getVelocity() const noexcept +{ + if (isNoteOnOrOff()) + return getRawData()[2]; + + return 0; +} + +float MidiMessage::getFloatVelocity() const noexcept +{ + return getVelocity() * (1.0f / 127.0f); +} + +void MidiMessage::setVelocity (const float newVelocity) noexcept +{ + if (isNoteOnOrOff()) + getData()[2] = floatValueToMidiByte (newVelocity); +} + +void MidiMessage::multiplyVelocity (const float scaleFactor) noexcept +{ + if (isNoteOnOrOff()) + { + auto data = getData(); + data[2] = MidiHelpers::validVelocity (roundToInt (scaleFactor * data[2])); + } +} + +bool MidiMessage::isAftertouch() const noexcept +{ + return (getRawData()[0] & 0xf0) == 0xa0; +} + +int MidiMessage::getAfterTouchValue() const noexcept +{ + jassert (isAftertouch()); + return getRawData()[2]; +} + +MidiMessage MidiMessage::aftertouchChange (const int channel, + const int noteNum, + const int aftertouchValue) noexcept +{ + jassert (channel > 0 && channel <= 16); // valid channels are numbered 1 to 16 + jassert (isPositiveAndBelow (noteNum, 128)); + jassert (isPositiveAndBelow (aftertouchValue, 128)); + + return MidiMessage (MidiHelpers::initialByte (0xa0, channel), + noteNum & 0x7f, + aftertouchValue & 0x7f); +} + +bool MidiMessage::isChannelPressure() const noexcept +{ + return (getRawData()[0] & 0xf0) == 0xd0; +} + +int MidiMessage::getChannelPressureValue() const noexcept +{ + jassert (isChannelPressure()); + return getRawData()[1]; +} + +MidiMessage MidiMessage::channelPressureChange (const int channel, const int pressure) noexcept +{ + jassert (channel > 0 && channel <= 16); // valid channels are numbered 1 to 16 + jassert (isPositiveAndBelow (pressure, 128)); + + return MidiMessage (MidiHelpers::initialByte (0xd0, channel), pressure & 0x7f); +} + +bool MidiMessage::isSustainPedalOn() const noexcept { return isControllerOfType (0x40) && getRawData()[2] >= 64; } +bool MidiMessage::isSustainPedalOff() const noexcept { return isControllerOfType (0x40) && getRawData()[2] < 64; } + +bool MidiMessage::isSostenutoPedalOn() const noexcept { return isControllerOfType (0x42) && getRawData()[2] >= 64; } +bool MidiMessage::isSostenutoPedalOff() const noexcept { return isControllerOfType (0x42) && getRawData()[2] < 64; } + +bool MidiMessage::isSoftPedalOn() const noexcept { return isControllerOfType (0x43) && getRawData()[2] >= 64; } +bool MidiMessage::isSoftPedalOff() const noexcept { return isControllerOfType (0x43) && getRawData()[2] < 64; } + + +bool MidiMessage::isProgramChange() const noexcept +{ + return (getRawData()[0] & 0xf0) == 0xc0; +} + +int MidiMessage::getProgramChangeNumber() const noexcept +{ + jassert (isProgramChange()); + return getRawData()[1]; +} + +MidiMessage MidiMessage::programChange (const int channel, const int programNumber) noexcept +{ + jassert (channel > 0 && channel <= 16); // valid channels are numbered 1 to 16 + + return MidiMessage (MidiHelpers::initialByte (0xc0, channel), programNumber & 0x7f); +} + +bool MidiMessage::isPitchWheel() const noexcept +{ + return (getRawData()[0] & 0xf0) == 0xe0; +} + +int MidiMessage::getPitchWheelValue() const noexcept +{ + jassert (isPitchWheel()); + auto data = getRawData(); + return data[1] | (data[2] << 7); +} + +MidiMessage MidiMessage::pitchWheel (const int channel, const int position) noexcept +{ + jassert (channel > 0 && channel <= 16); // valid channels are numbered 1 to 16 + jassert (isPositiveAndBelow (position, 0x4000)); + + return MidiMessage (MidiHelpers::initialByte (0xe0, channel), + position & 127, (position >> 7) & 127); +} + +bool MidiMessage::isController() const noexcept +{ + return (getRawData()[0] & 0xf0) == 0xb0; +} + +bool MidiMessage::isControllerOfType (const int controllerType) const noexcept +{ + auto data = getRawData(); + return (data[0] & 0xf0) == 0xb0 && data[1] == controllerType; +} + +int MidiMessage::getControllerNumber() const noexcept +{ + jassert (isController()); + return getRawData()[1]; +} + +int MidiMessage::getControllerValue() const noexcept +{ + jassert (isController()); + return getRawData()[2]; +} + +MidiMessage MidiMessage::controllerEvent (const int channel, const int controllerType, const int value) noexcept +{ + // the channel must be between 1 and 16 inclusive + jassert (channel > 0 && channel <= 16); + + return MidiMessage (MidiHelpers::initialByte (0xb0, channel), + controllerType & 127, value & 127); +} + +MidiMessage MidiMessage::noteOn (const int channel, const int noteNumber, const uint8 velocity) noexcept +{ + jassert (channel > 0 && channel <= 16); + jassert (isPositiveAndBelow (noteNumber, 128)); + + return MidiMessage (MidiHelpers::initialByte (0x90, channel), + noteNumber & 127, MidiHelpers::validVelocity (velocity)); +} + +MidiMessage MidiMessage::noteOn (const int channel, const int noteNumber, const float velocity) noexcept +{ + return noteOn (channel, noteNumber, floatValueToMidiByte (velocity)); +} + +MidiMessage MidiMessage::noteOff (const int channel, const int noteNumber, uint8 velocity) noexcept +{ + jassert (channel > 0 && channel <= 16); + jassert (isPositiveAndBelow (noteNumber, 128)); + + return MidiMessage (MidiHelpers::initialByte (0x80, channel), + noteNumber & 127, MidiHelpers::validVelocity (velocity)); +} + +MidiMessage MidiMessage::noteOff (const int channel, const int noteNumber, float velocity) noexcept +{ + return noteOff (channel, noteNumber, floatValueToMidiByte (velocity)); +} + +MidiMessage MidiMessage::noteOff (const int channel, const int noteNumber) noexcept +{ + jassert (channel > 0 && channel <= 16); + jassert (isPositiveAndBelow (noteNumber, 128)); + + return MidiMessage (MidiHelpers::initialByte (0x80, channel), noteNumber & 127, 0); +} + +MidiMessage MidiMessage::allNotesOff (const int channel) noexcept +{ + return controllerEvent (channel, 123, 0); +} + +bool MidiMessage::isAllNotesOff() const noexcept +{ + auto data = getRawData(); + return (data[0] & 0xf0) == 0xb0 && data[1] == 123; +} + +MidiMessage MidiMessage::allSoundOff (const int channel) noexcept +{ + return controllerEvent (channel, 120, 0); +} + +bool MidiMessage::isAllSoundOff() const noexcept +{ + auto data = getRawData(); + return data[1] == 120 && (data[0] & 0xf0) == 0xb0; +} + +bool MidiMessage::isResetAllControllers() const noexcept +{ + auto data = getRawData(); + return (data[0] & 0xf0) == 0xb0 && data[1] == 121; +} + +MidiMessage MidiMessage::allControllersOff (const int channel) noexcept +{ + return controllerEvent (channel, 121, 0); +} + +MidiMessage MidiMessage::masterVolume (const float volume) +{ + auto vol = jlimit (0, 0x3fff, roundToInt (volume * 0x4000)); + + return { 0xf0, 0x7f, 0x7f, 0x04, 0x01, vol & 0x7f, vol >> 7, 0xf7 }; +} + +//============================================================================== +bool MidiMessage::isSysEx() const noexcept +{ + return *getRawData() == 0xf0; +} + +MidiMessage MidiMessage::createSysExMessage (const void* sysexData, const int dataSize) +{ + HeapBlock m (dataSize + 2); + + m[0] = 0xf0; + memcpy (m + 1, sysexData, (size_t) dataSize); + m[dataSize + 1] = 0xf7; + + return MidiMessage (m, dataSize + 2); +} + +const uint8* MidiMessage::getSysExData() const noexcept +{ + return isSysEx() ? getRawData() + 1 : nullptr; +} + +int MidiMessage::getSysExDataSize() const noexcept +{ + return isSysEx() ? size - 2 : 0; +} + +//============================================================================== +bool MidiMessage::isMetaEvent() const noexcept { return *getRawData() == 0xff; } +bool MidiMessage::isActiveSense() const noexcept { return *getRawData() == 0xfe; } + +int MidiMessage::getMetaEventType() const noexcept +{ + auto data = getRawData(); + return *data != 0xff ? -1 : data[1]; +} + +int MidiMessage::getMetaEventLength() const noexcept +{ + auto data = getRawData(); + + if (*data == 0xff) + { + int n; + return jmin (size - 2, readVariableLengthVal (data + 2, n)); + } + + return 0; +} + +const uint8* MidiMessage::getMetaEventData() const noexcept +{ + jassert (isMetaEvent()); + + int n; + auto d = getRawData() + 2; + readVariableLengthVal (d, n); + return d + n; +} + +bool MidiMessage::isTrackMetaEvent() const noexcept { return getMetaEventType() == 0; } +bool MidiMessage::isEndOfTrackMetaEvent() const noexcept { return getMetaEventType() == 47; } + +bool MidiMessage::isTextMetaEvent() const noexcept +{ + auto t = getMetaEventType(); + return t > 0 && t < 16; +} + +String MidiMessage::getTextFromTextMetaEvent() const +{ + auto textData = reinterpret_cast (getMetaEventData()); + + return String (CharPointer_UTF8 (textData), + CharPointer_UTF8 (textData + getMetaEventLength())); +} + +MidiMessage MidiMessage::textMetaEvent (int type, StringRef text) +{ + jassert (type > 0 && type < 16); + + MidiMessage result; + + const size_t textSize = text.text.sizeInBytes() - 1; + + uint8 header[8]; + size_t n = sizeof (header); + + header[--n] = (uint8) (textSize & 0x7f); + + for (size_t i = textSize; (i >>= 7) != 0;) + header[--n] = (uint8) ((i & 0x7f) | 0x80); + + header[--n] = (uint8) type; + header[--n] = 0xff; + + const size_t headerLen = sizeof (header) - n; + const int totalSize = (int) (headerLen + textSize); + + auto dest = result.allocateSpace (totalSize); + result.size = totalSize; + + memcpy (dest, header + n, headerLen); + memcpy (dest + headerLen, text.text.getAddress(), textSize); + + return result; +} + +bool MidiMessage::isTrackNameEvent() const noexcept { auto data = getRawData(); return (data[1] == 3) && (*data == 0xff); } +bool MidiMessage::isTempoMetaEvent() const noexcept { auto data = getRawData(); return (data[1] == 81) && (*data == 0xff); } +bool MidiMessage::isMidiChannelMetaEvent() const noexcept { auto data = getRawData(); return (data[1] == 0x20) && (*data == 0xff) && (data[2] == 1); } + +int MidiMessage::getMidiChannelMetaEventChannel() const noexcept +{ + jassert (isMidiChannelMetaEvent()); + return getRawData()[3] + 1; +} + +double MidiMessage::getTempoSecondsPerQuarterNote() const noexcept +{ + if (! isTempoMetaEvent()) + return 0.0; + + auto d = getMetaEventData(); + + return (((unsigned int) d[0] << 16) + | ((unsigned int) d[1] << 8) + | d[2]) + / 1000000.0; +} + +double MidiMessage::getTempoMetaEventTickLength (const short timeFormat) const noexcept +{ + if (timeFormat > 0) + { + if (! isTempoMetaEvent()) + return 0.5 / timeFormat; + + return getTempoSecondsPerQuarterNote() / timeFormat; + } + + const int frameCode = (-timeFormat) >> 8; + double framesPerSecond; + + switch (frameCode) + { + case 24: framesPerSecond = 24.0; break; + case 25: framesPerSecond = 25.0; break; + case 29: framesPerSecond = 30.0 * 1000.0 / 1001.0; break; + case 30: framesPerSecond = 30.0; break; + default: framesPerSecond = 30.0; break; + } + + return (1.0 / framesPerSecond) / (timeFormat & 0xff); +} + +MidiMessage MidiMessage::tempoMetaEvent (int microsecondsPerQuarterNote) noexcept +{ + return { 0xff, 81, 3, + (uint8) (microsecondsPerQuarterNote >> 16), + (uint8) (microsecondsPerQuarterNote >> 8), + (uint8) microsecondsPerQuarterNote }; +} + +bool MidiMessage::isTimeSignatureMetaEvent() const noexcept +{ + auto data = getRawData(); + return (data[1] == 0x58) && (*data == (uint8) 0xff); +} + +void MidiMessage::getTimeSignatureInfo (int& numerator, int& denominator) const noexcept +{ + if (isTimeSignatureMetaEvent()) + { + auto d = getMetaEventData(); + numerator = d[0]; + denominator = 1 << d[1]; + } + else + { + numerator = 4; + denominator = 4; + } +} + +MidiMessage MidiMessage::timeSignatureMetaEvent (const int numerator, const int denominator) +{ + int n = 1; + int powerOfTwo = 0; + + while (n < denominator) + { + n <<= 1; + ++powerOfTwo; + } + + return { 0xff, 0x58, 0x04, numerator, powerOfTwo, 1, 96 }; +} + +MidiMessage MidiMessage::midiChannelMetaEvent (const int channel) noexcept +{ + return { 0xff, 0x20, 0x01, jlimit (0, 0xff, channel - 1) }; +} + +bool MidiMessage::isKeySignatureMetaEvent() const noexcept +{ + return getMetaEventType() == 0x59; +} + +int MidiMessage::getKeySignatureNumberOfSharpsOrFlats() const noexcept +{ + return (int) (int8) getMetaEventData()[0]; +} + +bool MidiMessage::isKeySignatureMajorKey() const noexcept +{ + return getMetaEventData()[1] == 0; +} + +MidiMessage MidiMessage::keySignatureMetaEvent (int numberOfSharpsOrFlats, bool isMinorKey) +{ + jassert (numberOfSharpsOrFlats >= -7 && numberOfSharpsOrFlats <= 7); + + return { 0xff, 0x59, 0x02, numberOfSharpsOrFlats, isMinorKey ? 1 : 0 }; +} + +MidiMessage MidiMessage::endOfTrack() noexcept +{ + return { 0xff, 0x2f, 0x00 }; +} + +//============================================================================== +bool MidiMessage::isSongPositionPointer() const noexcept { return *getRawData() == 0xf2; } +int MidiMessage::getSongPositionPointerMidiBeat() const noexcept { auto data = getRawData(); return data[1] | (data[2] << 7); } + +MidiMessage MidiMessage::songPositionPointer (const int positionInMidiBeats) noexcept +{ + return { 0xf2, + positionInMidiBeats & 127, + (positionInMidiBeats >> 7) & 127 }; +} + +bool MidiMessage::isMidiStart() const noexcept { return *getRawData() == 0xfa; } +MidiMessage MidiMessage::midiStart() noexcept { return MidiMessage (0xfa); } + +bool MidiMessage::isMidiContinue() const noexcept { return *getRawData() == 0xfb; } +MidiMessage MidiMessage::midiContinue() noexcept { return MidiMessage (0xfb); } + +bool MidiMessage::isMidiStop() const noexcept { return *getRawData() == 0xfc; } +MidiMessage MidiMessage::midiStop() noexcept { return MidiMessage (0xfc); } + +bool MidiMessage::isMidiClock() const noexcept { return *getRawData() == 0xf8; } +MidiMessage MidiMessage::midiClock() noexcept { return MidiMessage (0xf8); } + +bool MidiMessage::isQuarterFrame() const noexcept { return *getRawData() == 0xf1; } +int MidiMessage::getQuarterFrameSequenceNumber() const noexcept { return ((int) getRawData()[1]) >> 4; } +int MidiMessage::getQuarterFrameValue() const noexcept { return ((int) getRawData()[1]) & 0x0f; } + +MidiMessage MidiMessage::quarterFrame (const int sequenceNumber, const int value) noexcept +{ + return MidiMessage (0xf1, (sequenceNumber << 4) | value); +} + +bool MidiMessage::isFullFrame() const noexcept +{ + auto data = getRawData(); + + return data[0] == 0xf0 + && data[1] == 0x7f + && size >= 10 + && data[3] == 0x01 + && data[4] == 0x01; +} + +void MidiMessage::getFullFrameParameters (int& hours, int& minutes, int& seconds, int& frames, + MidiMessage::SmpteTimecodeType& timecodeType) const noexcept +{ + jassert (isFullFrame()); + + auto data = getRawData(); + timecodeType = (SmpteTimecodeType) (data[5] >> 5); + hours = data[5] & 0x1f; + minutes = data[6]; + seconds = data[7]; + frames = data[8]; +} + +MidiMessage MidiMessage::fullFrame (int hours, int minutes, int seconds, int frames, + MidiMessage::SmpteTimecodeType timecodeType) +{ + return { 0xf0, 0x7f, 0x7f, 0x01, 0x01, + (hours & 0x01f) | (timecodeType << 5), + minutes, seconds, frames, + 0xf7 }; +} + +bool MidiMessage::isMidiMachineControlMessage() const noexcept +{ + auto data = getRawData(); + + return data[0] == 0xf0 + && data[1] == 0x7f + && data[3] == 0x06 + && size > 5; +} + +MidiMessage::MidiMachineControlCommand MidiMessage::getMidiMachineControlCommand() const noexcept +{ + jassert (isMidiMachineControlMessage()); + + return (MidiMachineControlCommand) getRawData()[4]; +} + +MidiMessage MidiMessage::midiMachineControlCommand (MidiMessage::MidiMachineControlCommand command) +{ + return { 0xf0, 0x7f, 0, 6, command, 0xf7 }; +} + +//============================================================================== +bool MidiMessage::isMidiMachineControlGoto (int& hours, int& minutes, int& seconds, int& frames) const noexcept +{ + auto data = getRawData(); + + if (size >= 12 + && data[0] == 0xf0 + && data[1] == 0x7f + && data[3] == 0x06 + && data[4] == 0x44 + && data[5] == 0x06 + && data[6] == 0x01) + { + hours = data[7] % 24; // (that some machines send out hours > 24) + minutes = data[8]; + seconds = data[9]; + frames = data[10]; + + return true; + } + + return false; +} + +MidiMessage MidiMessage::midiMachineControlGoto (int hours, int minutes, int seconds, int frames) +{ + return { 0xf0, 0x7f, 0, 6, 0x44, 6, 1, hours, minutes, seconds, frames, 0xf7 }; +} + +//============================================================================== +String MidiMessage::getMidiNoteName (int note, bool useSharps, bool includeOctaveNumber, int octaveNumForMiddleC) +{ + static const char* const sharpNoteNames[] = { "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B" }; + static const char* const flatNoteNames[] = { "C", "Db", "D", "Eb", "E", "F", "Gb", "G", "Ab", "A", "Bb", "B" }; + + if (isPositiveAndBelow (note, 128)) + { + String s (useSharps ? sharpNoteNames[note % 12] + : flatNoteNames [note % 12]); + + if (includeOctaveNumber) + s << (note / 12 + (octaveNumForMiddleC - 5)); + + return s; + } + + return {}; +} + +double MidiMessage::getMidiNoteInHertz (const int noteNumber, const double frequencyOfA) noexcept +{ + return frequencyOfA * std::pow (2.0, (noteNumber - 69) / 12.0); +} + +bool MidiMessage::isMidiNoteBlack (int noteNumber) noexcept +{ + return ((1 << (noteNumber % 12)) & 0x054a) != 0; +} + +const char* MidiMessage::getGMInstrumentName (const int n) +{ + static const char* names[] = + { + NEEDS_TRANS("Acoustic Grand Piano"), NEEDS_TRANS("Bright Acoustic Piano"), NEEDS_TRANS("Electric Grand Piano"), NEEDS_TRANS("Honky-tonk Piano"), + NEEDS_TRANS("Electric Piano 1"), NEEDS_TRANS("Electric Piano 2"), NEEDS_TRANS("Harpsichord"), NEEDS_TRANS("Clavinet"), + NEEDS_TRANS("Celesta"), NEEDS_TRANS("Glockenspiel"), NEEDS_TRANS("Music Box"), NEEDS_TRANS("Vibraphone"), + NEEDS_TRANS("Marimba"), NEEDS_TRANS("Xylophone"), NEEDS_TRANS("Tubular Bells"), NEEDS_TRANS("Dulcimer"), + NEEDS_TRANS("Drawbar Organ"), NEEDS_TRANS("Percussive Organ"), NEEDS_TRANS("Rock Organ"), NEEDS_TRANS("Church Organ"), + NEEDS_TRANS("Reed Organ"), NEEDS_TRANS("Accordion"), NEEDS_TRANS("Harmonica"), NEEDS_TRANS("Tango Accordion"), + NEEDS_TRANS("Acoustic Guitar (nylon)"), NEEDS_TRANS("Acoustic Guitar (steel)"), NEEDS_TRANS("Electric Guitar (jazz)"), NEEDS_TRANS("Electric Guitar (clean)"), + NEEDS_TRANS("Electric Guitar (mute)"), NEEDS_TRANS("Overdriven Guitar"), NEEDS_TRANS("Distortion Guitar"), NEEDS_TRANS("Guitar Harmonics"), + NEEDS_TRANS("Acoustic Bass"), NEEDS_TRANS("Electric Bass (finger)"), NEEDS_TRANS("Electric Bass (pick)"), NEEDS_TRANS("Fretless Bass"), + NEEDS_TRANS("Slap Bass 1"), NEEDS_TRANS("Slap Bass 2"), NEEDS_TRANS("Synth Bass 1"), NEEDS_TRANS("Synth Bass 2"), + NEEDS_TRANS("Violin"), NEEDS_TRANS("Viola"), NEEDS_TRANS("Cello"), NEEDS_TRANS("Contrabass"), + NEEDS_TRANS("Tremolo Strings"), NEEDS_TRANS("Pizzicato Strings"), NEEDS_TRANS("Orchestral Harp"), NEEDS_TRANS("Timpani"), + NEEDS_TRANS("String Ensemble 1"), NEEDS_TRANS("String Ensemble 2"), NEEDS_TRANS("SynthStrings 1"), NEEDS_TRANS("SynthStrings 2"), + NEEDS_TRANS("Choir Aahs"), NEEDS_TRANS("Voice Oohs"), NEEDS_TRANS("Synth Voice"), NEEDS_TRANS("Orchestra Hit"), + NEEDS_TRANS("Trumpet"), NEEDS_TRANS("Trombone"), NEEDS_TRANS("Tuba"), NEEDS_TRANS("Muted Trumpet"), + NEEDS_TRANS("French Horn"), NEEDS_TRANS("Brass Section"), NEEDS_TRANS("SynthBrass 1"), NEEDS_TRANS("SynthBrass 2"), + NEEDS_TRANS("Soprano Sax"), NEEDS_TRANS("Alto Sax"), NEEDS_TRANS("Tenor Sax"), NEEDS_TRANS("Baritone Sax"), + NEEDS_TRANS("Oboe"), NEEDS_TRANS("English Horn"), NEEDS_TRANS("Bassoon"), NEEDS_TRANS("Clarinet"), + NEEDS_TRANS("Piccolo"), NEEDS_TRANS("Flute"), NEEDS_TRANS("Recorder"), NEEDS_TRANS("Pan Flute"), + NEEDS_TRANS("Blown Bottle"), NEEDS_TRANS("Shakuhachi"), NEEDS_TRANS("Whistle"), NEEDS_TRANS("Ocarina"), + NEEDS_TRANS("Lead 1 (square)"), NEEDS_TRANS("Lead 2 (sawtooth)"), NEEDS_TRANS("Lead 3 (calliope)"), NEEDS_TRANS("Lead 4 (chiff)"), + NEEDS_TRANS("Lead 5 (charang)"), NEEDS_TRANS("Lead 6 (voice)"), NEEDS_TRANS("Lead 7 (fifths)"), NEEDS_TRANS("Lead 8 (bass+lead)"), + NEEDS_TRANS("Pad 1 (new age)"), NEEDS_TRANS("Pad 2 (warm)"), NEEDS_TRANS("Pad 3 (polysynth)"), NEEDS_TRANS("Pad 4 (choir)"), + NEEDS_TRANS("Pad 5 (bowed)"), NEEDS_TRANS("Pad 6 (metallic)"), NEEDS_TRANS("Pad 7 (halo)"), NEEDS_TRANS("Pad 8 (sweep)"), + NEEDS_TRANS("FX 1 (rain)"), NEEDS_TRANS("FX 2 (soundtrack)"), NEEDS_TRANS("FX 3 (crystal)"), NEEDS_TRANS("FX 4 (atmosphere)"), + NEEDS_TRANS("FX 5 (brightness)"), NEEDS_TRANS("FX 6 (goblins)"), NEEDS_TRANS("FX 7 (echoes)"), NEEDS_TRANS("FX 8 (sci-fi)"), + NEEDS_TRANS("Sitar"), NEEDS_TRANS("Banjo"), NEEDS_TRANS("Shamisen"), NEEDS_TRANS("Koto"), + NEEDS_TRANS("Kalimba"), NEEDS_TRANS("Bag pipe"), NEEDS_TRANS("Fiddle"), NEEDS_TRANS("Shanai"), + NEEDS_TRANS("Tinkle Bell"), NEEDS_TRANS("Agogo"), NEEDS_TRANS("Steel Drums"), NEEDS_TRANS("Woodblock"), + NEEDS_TRANS("Taiko Drum"), NEEDS_TRANS("Melodic Tom"), NEEDS_TRANS("Synth Drum"), NEEDS_TRANS("Reverse Cymbal"), + NEEDS_TRANS("Guitar Fret Noise"), NEEDS_TRANS("Breath Noise"), NEEDS_TRANS("Seashore"), NEEDS_TRANS("Bird Tweet"), + NEEDS_TRANS("Telephone Ring"), NEEDS_TRANS("Helicopter"), NEEDS_TRANS("Applause"), NEEDS_TRANS("Gunshot") + }; + + return isPositiveAndBelow (n, numElementsInArray (names)) ? names[n] : nullptr; +} + +const char* MidiMessage::getGMInstrumentBankName (const int n) +{ + static const char* names[] = + { + NEEDS_TRANS("Piano"), NEEDS_TRANS("Chromatic Percussion"), NEEDS_TRANS("Organ"), NEEDS_TRANS("Guitar"), + NEEDS_TRANS("Bass"), NEEDS_TRANS("Strings"), NEEDS_TRANS("Ensemble"), NEEDS_TRANS("Brass"), + NEEDS_TRANS("Reed"), NEEDS_TRANS("Pipe"), NEEDS_TRANS("Synth Lead"), NEEDS_TRANS("Synth Pad"), + NEEDS_TRANS("Synth Effects"), NEEDS_TRANS("Ethnic"), NEEDS_TRANS("Percussive"), NEEDS_TRANS("Sound Effects") + }; + + return isPositiveAndBelow (n, numElementsInArray (names)) ? names[n] : nullptr; +} + +const char* MidiMessage::getRhythmInstrumentName (const int n) +{ + static const char* names[] = + { + NEEDS_TRANS("Acoustic Bass Drum"), NEEDS_TRANS("Bass Drum 1"), NEEDS_TRANS("Side Stick"), NEEDS_TRANS("Acoustic Snare"), + NEEDS_TRANS("Hand Clap"), NEEDS_TRANS("Electric Snare"), NEEDS_TRANS("Low Floor Tom"), NEEDS_TRANS("Closed Hi-Hat"), + NEEDS_TRANS("High Floor Tom"), NEEDS_TRANS("Pedal Hi-Hat"), NEEDS_TRANS("Low Tom"), NEEDS_TRANS("Open Hi-Hat"), + NEEDS_TRANS("Low-Mid Tom"), NEEDS_TRANS("Hi-Mid Tom"), NEEDS_TRANS("Crash Cymbal 1"), NEEDS_TRANS("High Tom"), + NEEDS_TRANS("Ride Cymbal 1"), NEEDS_TRANS("Chinese Cymbal"), NEEDS_TRANS("Ride Bell"), NEEDS_TRANS("Tambourine"), + NEEDS_TRANS("Splash Cymbal"), NEEDS_TRANS("Cowbell"), NEEDS_TRANS("Crash Cymbal 2"), NEEDS_TRANS("Vibraslap"), + NEEDS_TRANS("Ride Cymbal 2"), NEEDS_TRANS("Hi Bongo"), NEEDS_TRANS("Low Bongo"), NEEDS_TRANS("Mute Hi Conga"), + NEEDS_TRANS("Open Hi Conga"), NEEDS_TRANS("Low Conga"), NEEDS_TRANS("High Timbale"), NEEDS_TRANS("Low Timbale"), + NEEDS_TRANS("High Agogo"), NEEDS_TRANS("Low Agogo"), NEEDS_TRANS("Cabasa"), NEEDS_TRANS("Maracas"), + NEEDS_TRANS("Short Whistle"), NEEDS_TRANS("Long Whistle"), NEEDS_TRANS("Short Guiro"), NEEDS_TRANS("Long Guiro"), + NEEDS_TRANS("Claves"), NEEDS_TRANS("Hi Wood Block"), NEEDS_TRANS("Low Wood Block"), NEEDS_TRANS("Mute Cuica"), + NEEDS_TRANS("Open Cuica"), NEEDS_TRANS("Mute Triangle"), NEEDS_TRANS("Open Triangle") + }; + + return (n >= 35 && n <= 81) ? names[n - 35] : nullptr; +} + +const char* MidiMessage::getControllerName (const int n) +{ + static const char* names[] = + { + NEEDS_TRANS("Bank Select"), NEEDS_TRANS("Modulation Wheel (coarse)"), NEEDS_TRANS("Breath controller (coarse)"), + nullptr, + NEEDS_TRANS("Foot Pedal (coarse)"), NEEDS_TRANS("Portamento Time (coarse)"), NEEDS_TRANS("Data Entry (coarse)"), + NEEDS_TRANS("Volume (coarse)"), NEEDS_TRANS("Balance (coarse)"), + nullptr, + NEEDS_TRANS("Pan position (coarse)"), NEEDS_TRANS("Expression (coarse)"), NEEDS_TRANS("Effect Control 1 (coarse)"), + NEEDS_TRANS("Effect Control 2 (coarse)"), + nullptr, nullptr, + NEEDS_TRANS("General Purpose Slider 1"), NEEDS_TRANS("General Purpose Slider 2"), + NEEDS_TRANS("General Purpose Slider 3"), NEEDS_TRANS("General Purpose Slider 4"), + nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, + NEEDS_TRANS("Bank Select (fine)"), NEEDS_TRANS("Modulation Wheel (fine)"), NEEDS_TRANS("Breath controller (fine)"), + nullptr, + NEEDS_TRANS("Foot Pedal (fine)"), NEEDS_TRANS("Portamento Time (fine)"), NEEDS_TRANS("Data Entry (fine)"), NEEDS_TRANS("Volume (fine)"), + NEEDS_TRANS("Balance (fine)"), nullptr, NEEDS_TRANS("Pan position (fine)"), NEEDS_TRANS("Expression (fine)"), + NEEDS_TRANS("Effect Control 1 (fine)"), NEEDS_TRANS("Effect Control 2 (fine)"), + nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, + nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, + NEEDS_TRANS("Hold Pedal (on/off)"), NEEDS_TRANS("Portamento (on/off)"), NEEDS_TRANS("Sustenuto Pedal (on/off)"), NEEDS_TRANS("Soft Pedal (on/off)"), + NEEDS_TRANS("Legato Pedal (on/off)"), NEEDS_TRANS("Hold 2 Pedal (on/off)"), NEEDS_TRANS("Sound Variation"), NEEDS_TRANS("Sound Timbre"), + NEEDS_TRANS("Sound Release Time"), NEEDS_TRANS("Sound Attack Time"), NEEDS_TRANS("Sound Brightness"), NEEDS_TRANS("Sound Control 6"), + NEEDS_TRANS("Sound Control 7"), NEEDS_TRANS("Sound Control 8"), NEEDS_TRANS("Sound Control 9"), NEEDS_TRANS("Sound Control 10"), + NEEDS_TRANS("General Purpose Button 1 (on/off)"), NEEDS_TRANS("General Purpose Button 2 (on/off)"), + NEEDS_TRANS("General Purpose Button 3 (on/off)"), NEEDS_TRANS("General Purpose Button 4 (on/off)"), + nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, + NEEDS_TRANS("Reverb Level"), NEEDS_TRANS("Tremolo Level"), NEEDS_TRANS("Chorus Level"), NEEDS_TRANS("Celeste Level"), + NEEDS_TRANS("Phaser Level"), NEEDS_TRANS("Data Button increment"), NEEDS_TRANS("Data Button decrement"), NEEDS_TRANS("Non-registered Parameter (fine)"), + NEEDS_TRANS("Non-registered Parameter (coarse)"), NEEDS_TRANS("Registered Parameter (fine)"), NEEDS_TRANS("Registered Parameter (coarse)"), + nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, + nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, + NEEDS_TRANS("All Sound Off"), NEEDS_TRANS("All Controllers Off"), NEEDS_TRANS("Local Keyboard (on/off)"), NEEDS_TRANS("All Notes Off"), + NEEDS_TRANS("Omni Mode Off"), NEEDS_TRANS("Omni Mode On"), NEEDS_TRANS("Mono Operation"), NEEDS_TRANS("Poly Operation") + }; + + return isPositiveAndBelow (n, numElementsInArray (names)) ? names[n] : nullptr; +} + +} // namespace juce diff --git a/JuceLibraryCode/modules/juce_audio_basics/midi/juce_MidiMessage.h b/JuceLibraryCode/modules/juce_audio_basics/midi/juce_MidiMessage.h index 5628c3a..c0fc47a 100644 --- a/JuceLibraryCode/modules/juce_audio_basics/midi/juce_MidiMessage.h +++ b/JuceLibraryCode/modules/juce_audio_basics/midi/juce_MidiMessage.h @@ -1,952 +1,952 @@ -/* - ============================================================================== - - This file is part of the JUCE library. - Copyright (c) 2017 - ROLI Ltd. - - JUCE is an open source library subject to commercial or open-source - licensing. - - The code included in this file is provided under the terms of the ISC license - http://www.isc.org/downloads/software-support-policy/isc-license. Permission - To use, copy, modify, and/or distribute this software for any purpose with or - without fee is hereby granted provided that the above copyright notice and - this permission notice appear in all copies. - - JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER - EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE - DISCLAIMED. - - ============================================================================== -*/ - -namespace juce -{ - -//============================================================================== -/** - Encapsulates a MIDI message. - - @see MidiMessageSequence, MidiOutput, MidiInput - - @tags{Audio} -*/ -class JUCE_API MidiMessage -{ -public: - //============================================================================== - /** Creates a 3-byte short midi message. - - @param byte1 message byte 1 - @param byte2 message byte 2 - @param byte3 message byte 3 - @param timeStamp the time to give the midi message - this value doesn't - use any particular units, so will be application-specific - */ - MidiMessage (int byte1, int byte2, int byte3, double timeStamp = 0) noexcept; - - /** Creates a 2-byte short midi message. - - @param byte1 message byte 1 - @param byte2 message byte 2 - @param timeStamp the time to give the midi message - this value doesn't - use any particular units, so will be application-specific - */ - MidiMessage (int byte1, int byte2, double timeStamp = 0) noexcept; - - /** Creates a 1-byte short midi message. - - @param byte1 message byte 1 - @param timeStamp the time to give the midi message - this value doesn't - use any particular units, so will be application-specific - */ - MidiMessage (int byte1, double timeStamp = 0) noexcept; - - /** Creates a midi message from a list of bytes. */ - template - MidiMessage (int byte1, int byte2, int byte3, Data... otherBytes) : size (3 + sizeof... (otherBytes)) - { - // this checks that the length matches the data.. - jassert (size > 3 || byte1 >= 0xf0 || getMessageLengthFromFirstByte ((uint8) byte1) == size); - - const uint8 data[] = { (uint8) byte1, (uint8) byte2, (uint8) byte3, static_cast (otherBytes)... }; - memcpy (allocateSpace (size), data, (size_t) size); - } - - - /** Creates a midi message from a block of data. */ - MidiMessage (const void* data, int numBytes, double timeStamp = 0); - - /** Reads the next midi message from some data. - - This will read as many bytes from a data stream as it needs to make a - complete message, and will return the number of bytes it used. This lets - you read a sequence of midi messages from a file or stream. - - @param data the data to read from - @param maxBytesToUse the maximum number of bytes it's allowed to read - @param numBytesUsed returns the number of bytes that were actually needed - @param lastStatusByte in a sequence of midi messages, the initial byte - can be dropped from a message if it's the same as the - first byte of the previous message, so this lets you - supply the byte to use if the first byte of the message - has in fact been dropped. - @param timeStamp the time to give the midi message - this value doesn't - use any particular units, so will be application-specific - @param sysexHasEmbeddedLength when reading sysexes, this flag indicates whether - to expect the data to begin with a variable-length - field indicating its size - */ - MidiMessage (const void* data, int maxBytesToUse, - int& numBytesUsed, uint8 lastStatusByte, - double timeStamp = 0, - bool sysexHasEmbeddedLength = true); - - /** Creates an active-sense message. - Since the MidiMessage has to contain a valid message, this default constructor - just initialises it with an empty sysex message. - */ - MidiMessage() noexcept; - - /** Creates a copy of another midi message. */ - MidiMessage (const MidiMessage&); - - /** Creates a copy of another midi message, with a different timestamp. */ - MidiMessage (const MidiMessage&, double newTimeStamp); - - /** Destructor. */ - ~MidiMessage() noexcept; - - /** Copies this message from another one. */ - MidiMessage& operator= (const MidiMessage& other); - - /** Move constructor */ - MidiMessage (MidiMessage&&) noexcept; - - /** Move assignment operator */ - MidiMessage& operator= (MidiMessage&&) noexcept; - - //============================================================================== - /** Returns a pointer to the raw midi data. - @see getRawDataSize - */ - const uint8* getRawData() const noexcept { return getData(); } - - /** Returns the number of bytes of data in the message. - @see getRawData - */ - int getRawDataSize() const noexcept { return size; } - - //============================================================================== - /** Returns a human-readable description of the midi message as a string, - for example "Note On C#3 Velocity 120 Channel 1". - */ - String getDescription() const; - - //============================================================================== - /** Returns the timestamp associated with this message. - - The exact meaning of this time and its units will vary, as messages are used in - a variety of different contexts. - - If you're getting the message from a midi file, this could be a time in seconds, or - a number of ticks - see MidiFile::convertTimestampTicksToSeconds(). - - If the message is being used in a MidiBuffer, it might indicate the number of - audio samples from the start of the buffer. - - If the message was created by a MidiInput, see MidiInputCallback::handleIncomingMidiMessage() - for details of the way that it initialises this value. - - @see setTimeStamp, addToTimeStamp - */ - double getTimeStamp() const noexcept { return timeStamp; } - - /** Changes the message's associated timestamp. - The units for the timestamp will be application-specific - see the notes for getTimeStamp(). - @see addToTimeStamp, getTimeStamp - */ - void setTimeStamp (double newTimestamp) noexcept { timeStamp = newTimestamp; } - - /** Adds a value to the message's timestamp. - The units for the timestamp will be application-specific. - */ - void addToTimeStamp (double delta) noexcept { timeStamp += delta; } - - /** Return a copy of this message with a new timestamp. - The units for the timestamp will be application-specific - see the notes for getTimeStamp(). - */ - MidiMessage withTimeStamp (double newTimestamp) const; - - //============================================================================== - /** Returns the midi channel associated with the message. - - @returns a value 1 to 16 if the message has a channel, or 0 if it hasn't (e.g. - if it's a sysex) - @see isForChannel, setChannel - */ - int getChannel() const noexcept; - - /** Returns true if the message applies to the given midi channel. - - @param channelNumber the channel number to look for, in the range 1 to 16 - @see getChannel, setChannel - */ - bool isForChannel (int channelNumber) const noexcept; - - /** Changes the message's midi channel. - This won't do anything for non-channel messages like sysexes. - @param newChannelNumber the channel number to change it to, in the range 1 to 16 - */ - void setChannel (int newChannelNumber) noexcept; - - //============================================================================== - /** Returns true if this is a system-exclusive message. - */ - bool isSysEx() const noexcept; - - /** Returns a pointer to the sysex data inside the message. - If this event isn't a sysex event, it'll return 0. - @see getSysExDataSize - */ - const uint8* getSysExData() const noexcept; - - /** Returns the size of the sysex data. - This value excludes the 0xf0 header byte and the 0xf7 at the end. - @see getSysExData - */ - int getSysExDataSize() const noexcept; - - //============================================================================== - /** Returns true if this message is a 'key-down' event. - - @param returnTrueForVelocity0 if true, then if this event is a note-on with - velocity 0, it will still be considered to be a note-on and the - method will return true. If returnTrueForVelocity0 is false, then - if this is a note-on event with velocity 0, it'll be regarded as - a note-off, and the method will return false - - @see isNoteOff, getNoteNumber, getVelocity, noteOn - */ - bool isNoteOn (bool returnTrueForVelocity0 = false) const noexcept; - - /** Creates a key-down message (using a floating-point velocity). - - @param channel the midi channel, in the range 1 to 16 - @param noteNumber the key number, 0 to 127 - @param velocity in the range 0 to 1.0 - @see isNoteOn - */ - static MidiMessage noteOn (int channel, int noteNumber, float velocity) noexcept; - - /** Creates a key-down message (using an integer velocity). - - @param channel the midi channel, in the range 1 to 16 - @param noteNumber the key number, 0 to 127 - @param velocity in the range 0 to 127 - @see isNoteOn - */ - static MidiMessage noteOn (int channel, int noteNumber, uint8 velocity) noexcept; - - /** Returns true if this message is a 'key-up' event. - - If returnTrueForNoteOnVelocity0 is true, then his will also return true - for a note-on event with a velocity of 0. - - @see isNoteOn, getNoteNumber, getVelocity, noteOff - */ - bool isNoteOff (bool returnTrueForNoteOnVelocity0 = true) const noexcept; - - /** Creates a key-up message. - - @param channel the midi channel, in the range 1 to 16 - @param noteNumber the key number, 0 to 127 - @param velocity in the range 0 to 1.0 - @see isNoteOff - */ - static MidiMessage noteOff (int channel, int noteNumber, float velocity) noexcept; - - /** Creates a key-up message. - - @param channel the midi channel, in the range 1 to 16 - @param noteNumber the key number, 0 to 127 - @param velocity in the range 0 to 127 - @see isNoteOff - */ - static MidiMessage noteOff (int channel, int noteNumber, uint8 velocity) noexcept; - - /** Creates a key-up message. - - @param channel the midi channel, in the range 1 to 16 - @param noteNumber the key number, 0 to 127 - @see isNoteOff - */ - static MidiMessage noteOff (int channel, int noteNumber) noexcept; - - /** Returns true if this message is a 'key-down' or 'key-up' event. - - @see isNoteOn, isNoteOff - */ - bool isNoteOnOrOff() const noexcept; - - /** Returns the midi note number for note-on and note-off messages. - If the message isn't a note-on or off, the value returned is undefined. - @see isNoteOff, getMidiNoteName, getMidiNoteInHertz, setNoteNumber - */ - int getNoteNumber() const noexcept; - - /** Changes the midi note number of a note-on or note-off message. - If the message isn't a note on or off, this will do nothing. - */ - void setNoteNumber (int newNoteNumber) noexcept; - - //============================================================================== - /** Returns the velocity of a note-on or note-off message. - - The value returned will be in the range 0 to 127. - If the message isn't a note-on or off event, it will return 0. - - @see getFloatVelocity - */ - uint8 getVelocity() const noexcept; - - /** Returns the velocity of a note-on or note-off message. - - The value returned will be in the range 0 to 1.0 - If the message isn't a note-on or off event, it will return 0. - - @see getVelocity, setVelocity - */ - float getFloatVelocity() const noexcept; - - /** Changes the velocity of a note-on or note-off message. - - If the message isn't a note on or off, this will do nothing. - - @param newVelocity the new velocity, in the range 0 to 1.0 - @see getFloatVelocity, multiplyVelocity - */ - void setVelocity (float newVelocity) noexcept; - - /** Multiplies the velocity of a note-on or note-off message by a given amount. - - If the message isn't a note on or off, this will do nothing. - - @param scaleFactor the value by which to multiply the velocity - @see setVelocity - */ - void multiplyVelocity (float scaleFactor) noexcept; - - //============================================================================== - /** Returns true if this message is a 'sustain pedal down' controller message. */ - bool isSustainPedalOn() const noexcept; - /** Returns true if this message is a 'sustain pedal up' controller message. */ - bool isSustainPedalOff() const noexcept; - - /** Returns true if this message is a 'sostenuto pedal down' controller message. */ - bool isSostenutoPedalOn() const noexcept; - /** Returns true if this message is a 'sostenuto pedal up' controller message. */ - bool isSostenutoPedalOff() const noexcept; - - /** Returns true if this message is a 'soft pedal down' controller message. */ - bool isSoftPedalOn() const noexcept; - /** Returns true if this message is a 'soft pedal up' controller message. */ - bool isSoftPedalOff() const noexcept; - - //============================================================================== - /** Returns true if the message is a program (patch) change message. - @see getProgramChangeNumber, getGMInstrumentName - */ - bool isProgramChange() const noexcept; - - /** Returns the new program number of a program change message. - If the message isn't a program change, the value returned is undefined. - @see isProgramChange, getGMInstrumentName - */ - int getProgramChangeNumber() const noexcept; - - /** Creates a program-change message. - - @param channel the midi channel, in the range 1 to 16 - @param programNumber the midi program number, 0 to 127 - @see isProgramChange, getGMInstrumentName - */ - static MidiMessage programChange (int channel, int programNumber) noexcept; - - //============================================================================== - /** Returns true if the message is a pitch-wheel move. - @see getPitchWheelValue, pitchWheel - */ - bool isPitchWheel() const noexcept; - - /** Returns the pitch wheel position from a pitch-wheel move message. - - The value returned is a 14-bit number from 0 to 0x3fff, indicating the wheel position. - If called for messages which aren't pitch wheel events, the number returned will be - nonsense. - - @see isPitchWheel - */ - int getPitchWheelValue() const noexcept; - - /** Creates a pitch-wheel move message. - - @param channel the midi channel, in the range 1 to 16 - @param position the wheel position, in the range 0 to 16383 - @see isPitchWheel - */ - static MidiMessage pitchWheel (int channel, int position) noexcept; - - //============================================================================== - /** Returns true if the message is an aftertouch event. - - For aftertouch events, use the getNoteNumber() method to find out the key - that it applies to, and getAftertouchValue() to find out the amount. Use - getChannel() to find out the channel. - - @see getAftertouchValue, getNoteNumber - */ - bool isAftertouch() const noexcept; - - /** Returns the amount of aftertouch from an aftertouch messages. - - The value returned is in the range 0 to 127, and will be nonsense for messages - other than aftertouch messages. - - @see isAftertouch - */ - int getAfterTouchValue() const noexcept; - - /** Creates an aftertouch message. - - @param channel the midi channel, in the range 1 to 16 - @param noteNumber the key number, 0 to 127 - @param aftertouchAmount the amount of aftertouch, 0 to 127 - @see isAftertouch - */ - static MidiMessage aftertouchChange (int channel, - int noteNumber, - int aftertouchAmount) noexcept; - - /** Returns true if the message is a channel-pressure change event. - - This is like aftertouch, but common to the whole channel rather than a specific - note. Use getChannelPressureValue() to find out the pressure, and getChannel() - to find out the channel. - - @see channelPressureChange - */ - bool isChannelPressure() const noexcept; - - /** Returns the pressure from a channel pressure change message. - - @returns the pressure, in the range 0 to 127 - @see isChannelPressure, channelPressureChange - */ - int getChannelPressureValue() const noexcept; - - /** Creates a channel-pressure change event. - - @param channel the midi channel: 1 to 16 - @param pressure the pressure, 0 to 127 - @see isChannelPressure - */ - static MidiMessage channelPressureChange (int channel, int pressure) noexcept; - - //============================================================================== - /** Returns true if this is a midi controller message. - - @see getControllerNumber, getControllerValue, controllerEvent - */ - bool isController() const noexcept; - - /** Returns the controller number of a controller message. - - The name of the controller can be looked up using the getControllerName() method. - Note that the value returned is invalid for messages that aren't controller changes. - - @see isController, getControllerName, getControllerValue - */ - int getControllerNumber() const noexcept; - - /** Returns the controller value from a controller message. - - A value 0 to 127 is returned to indicate the new controller position. - Note that the value returned is invalid for messages that aren't controller changes. - - @see isController, getControllerNumber - */ - int getControllerValue() const noexcept; - - /** Returns true if this message is a controller message and if it has the specified - controller type. - */ - bool isControllerOfType (int controllerType) const noexcept; - - /** Creates a controller message. - @param channel the midi channel, in the range 1 to 16 - @param controllerType the type of controller - @param value the controller value - @see isController - */ - static MidiMessage controllerEvent (int channel, - int controllerType, - int value) noexcept; - - /** Checks whether this message is an all-notes-off message. - @see allNotesOff - */ - bool isAllNotesOff() const noexcept; - - /** Checks whether this message is an all-sound-off message. - @see allSoundOff - */ - bool isAllSoundOff() const noexcept; - - /** Checks whether this message is a reset all controllers message. - @see allControllerOff - */ - bool isResetAllControllers() const noexcept; - - /** Creates an all-notes-off message. - @param channel the midi channel, in the range 1 to 16 - @see isAllNotesOff - */ - static MidiMessage allNotesOff (int channel) noexcept; - - /** Creates an all-sound-off message. - @param channel the midi channel, in the range 1 to 16 - @see isAllSoundOff - */ - static MidiMessage allSoundOff (int channel) noexcept; - - /** Creates an all-controllers-off message. - @param channel the midi channel, in the range 1 to 16 - */ - static MidiMessage allControllersOff (int channel) noexcept; - - //============================================================================== - /** Returns true if this event is a meta-event. - - Meta-events are things like tempo changes, track names, etc. - - @see getMetaEventType, isTrackMetaEvent, isEndOfTrackMetaEvent, - isTextMetaEvent, isTrackNameEvent, isTempoMetaEvent, isTimeSignatureMetaEvent, - isKeySignatureMetaEvent, isMidiChannelMetaEvent - */ - bool isMetaEvent() const noexcept; - - /** Returns a meta-event's type number. - - If the message isn't a meta-event, this will return -1. - - @see isMetaEvent, isTrackMetaEvent, isEndOfTrackMetaEvent, - isTextMetaEvent, isTrackNameEvent, isTempoMetaEvent, isTimeSignatureMetaEvent, - isKeySignatureMetaEvent, isMidiChannelMetaEvent - */ - int getMetaEventType() const noexcept; - - /** Returns a pointer to the data in a meta-event. - @see isMetaEvent, getMetaEventLength - */ - const uint8* getMetaEventData() const noexcept; - - /** Returns the length of the data for a meta-event. - @see isMetaEvent, getMetaEventData - */ - int getMetaEventLength() const noexcept; - - //============================================================================== - /** Returns true if this is a 'track' meta-event. */ - bool isTrackMetaEvent() const noexcept; - - /** Returns true if this is an 'end-of-track' meta-event. */ - bool isEndOfTrackMetaEvent() const noexcept; - - /** Creates an end-of-track meta-event. - @see isEndOfTrackMetaEvent - */ - static MidiMessage endOfTrack() noexcept; - - /** Returns true if this is an 'track name' meta-event. - You can use the getTextFromTextMetaEvent() method to get the track's name. - */ - bool isTrackNameEvent() const noexcept; - - /** Returns true if this is a 'text' meta-event. - @see getTextFromTextMetaEvent - */ - bool isTextMetaEvent() const noexcept; - - /** Returns the text from a text meta-event. - @see isTextMetaEvent - */ - String getTextFromTextMetaEvent() const; - - /** Creates a text meta-event. */ - static MidiMessage textMetaEvent (int type, StringRef text); - - //============================================================================== - /** Returns true if this is a 'tempo' meta-event. - @see getTempoMetaEventTickLength, getTempoSecondsPerQuarterNote - */ - bool isTempoMetaEvent() const noexcept; - - /** Returns the tick length from a tempo meta-event. - - @param timeFormat the 16-bit time format value from the midi file's header. - @returns the tick length (in seconds). - @see isTempoMetaEvent - */ - double getTempoMetaEventTickLength (short timeFormat) const noexcept; - - /** Calculates the seconds-per-quarter-note from a tempo meta-event. - @see isTempoMetaEvent, getTempoMetaEventTickLength - */ - double getTempoSecondsPerQuarterNote() const noexcept; - - /** Creates a tempo meta-event. - @see isTempoMetaEvent - */ - static MidiMessage tempoMetaEvent (int microsecondsPerQuarterNote) noexcept; - - //============================================================================== - /** Returns true if this is a 'time-signature' meta-event. - @see getTimeSignatureInfo - */ - bool isTimeSignatureMetaEvent() const noexcept; - - /** Returns the time-signature values from a time-signature meta-event. - @see isTimeSignatureMetaEvent - */ - void getTimeSignatureInfo (int& numerator, int& denominator) const noexcept; - - /** Creates a time-signature meta-event. - @see isTimeSignatureMetaEvent - */ - static MidiMessage timeSignatureMetaEvent (int numerator, int denominator); - - //============================================================================== - /** Returns true if this is a 'key-signature' meta-event. - @see getKeySignatureNumberOfSharpsOrFlats, isKeySignatureMajorKey - */ - bool isKeySignatureMetaEvent() const noexcept; - - /** Returns the key from a key-signature meta-event. - This method must only be called if isKeySignatureMetaEvent() is true. - A positive number here indicates the number of sharps in the key signature, - and a negative number indicates a number of flats. So e.g. 3 = F# + C# + G#, - -2 = Bb + Eb - @see isKeySignatureMetaEvent, isKeySignatureMajorKey - */ - int getKeySignatureNumberOfSharpsOrFlats() const noexcept; - - /** Returns true if this key-signature event is major, or false if it's minor. - This method must only be called if isKeySignatureMetaEvent() is true. - */ - bool isKeySignatureMajorKey() const noexcept; - - /** Creates a key-signature meta-event. - @param numberOfSharpsOrFlats if positive, this indicates the number of sharps - in the key; if negative, the number of flats - @param isMinorKey if true, the key is minor; if false, it is major - @see isKeySignatureMetaEvent - */ - static MidiMessage keySignatureMetaEvent (int numberOfSharpsOrFlats, bool isMinorKey); - - //============================================================================== - /** Returns true if this is a 'channel' meta-event. - - A channel meta-event specifies the midi channel that should be used - for subsequent meta-events. - - @see getMidiChannelMetaEventChannel - */ - bool isMidiChannelMetaEvent() const noexcept; - - /** Returns the channel number from a channel meta-event. - - @returns the channel, in the range 1 to 16. - @see isMidiChannelMetaEvent - */ - int getMidiChannelMetaEventChannel() const noexcept; - - /** Creates a midi channel meta-event. - - @param channel the midi channel, in the range 1 to 16 - @see isMidiChannelMetaEvent - */ - static MidiMessage midiChannelMetaEvent (int channel) noexcept; - - //============================================================================== - /** Returns true if this is an active-sense message. */ - bool isActiveSense() const noexcept; - - //============================================================================== - /** Returns true if this is a midi start event. - @see midiStart - */ - bool isMidiStart() const noexcept; - - /** Creates a midi start event. */ - static MidiMessage midiStart() noexcept; - - /** Returns true if this is a midi continue event. - @see midiContinue - */ - bool isMidiContinue() const noexcept; - - /** Creates a midi continue event. */ - static MidiMessage midiContinue() noexcept; - - /** Returns true if this is a midi stop event. - @see midiStop - */ - bool isMidiStop() const noexcept; - - /** Creates a midi stop event. */ - static MidiMessage midiStop() noexcept; - - /** Returns true if this is a midi clock event. - @see midiClock, songPositionPointer - */ - bool isMidiClock() const noexcept; - - /** Creates a midi clock event. */ - static MidiMessage midiClock() noexcept; - - /** Returns true if this is a song-position-pointer message. - @see getSongPositionPointerMidiBeat, songPositionPointer - */ - bool isSongPositionPointer() const noexcept; - - /** Returns the midi beat-number of a song-position-pointer message. - @see isSongPositionPointer, songPositionPointer - */ - int getSongPositionPointerMidiBeat() const noexcept; - - /** Creates a song-position-pointer message. - - The position is a number of midi beats from the start of the song, where 1 midi - beat is 6 midi clocks, and there are 24 midi clocks in a quarter-note. So there - are 4 midi beats in a quarter-note. - - @see isSongPositionPointer, getSongPositionPointerMidiBeat - */ - static MidiMessage songPositionPointer (int positionInMidiBeats) noexcept; - - //============================================================================== - /** Returns true if this is a quarter-frame midi timecode message. - @see quarterFrame, getQuarterFrameSequenceNumber, getQuarterFrameValue - */ - bool isQuarterFrame() const noexcept; - - /** Returns the sequence number of a quarter-frame midi timecode message. - This will be a value between 0 and 7. - @see isQuarterFrame, getQuarterFrameValue, quarterFrame - */ - int getQuarterFrameSequenceNumber() const noexcept; - - /** Returns the value from a quarter-frame message. - This will be the lower nybble of the message's data-byte, a value between 0 and 15 - */ - int getQuarterFrameValue() const noexcept; - - /** Creates a quarter-frame MTC message. - - @param sequenceNumber a value 0 to 7 for the upper nybble of the message's data byte - @param value a value 0 to 15 for the lower nybble of the message's data byte - */ - static MidiMessage quarterFrame (int sequenceNumber, int value) noexcept; - - /** SMPTE timecode types. - Used by the getFullFrameParameters() and fullFrame() methods. - */ - enum SmpteTimecodeType - { - fps24 = 0, - fps25 = 1, - fps30drop = 2, - fps30 = 3 - }; - - /** Returns true if this is a full-frame midi timecode message. */ - bool isFullFrame() const noexcept; - - /** Extracts the timecode information from a full-frame midi timecode message. - - You should only call this on messages where you've used isFullFrame() to - check that they're the right kind. - */ - void getFullFrameParameters (int& hours, - int& minutes, - int& seconds, - int& frames, - SmpteTimecodeType& timecodeType) const noexcept; - - /** Creates a full-frame MTC message. */ - static MidiMessage fullFrame (int hours, - int minutes, - int seconds, - int frames, - SmpteTimecodeType timecodeType); - - //============================================================================== - /** Types of MMC command. - - @see isMidiMachineControlMessage, getMidiMachineControlCommand, midiMachineControlCommand - */ - enum MidiMachineControlCommand - { - mmc_stop = 1, - mmc_play = 2, - mmc_deferredplay = 3, - mmc_fastforward = 4, - mmc_rewind = 5, - mmc_recordStart = 6, - mmc_recordStop = 7, - mmc_pause = 9 - }; - - /** Checks whether this is an MMC message. - If it is, you can use the getMidiMachineControlCommand() to find out its type. - */ - bool isMidiMachineControlMessage() const noexcept; - - /** For an MMC message, this returns its type. - - Make sure it's actually an MMC message with isMidiMachineControlMessage() before - calling this method. - */ - MidiMachineControlCommand getMidiMachineControlCommand() const noexcept; - - /** Creates an MMC message. */ - static MidiMessage midiMachineControlCommand (MidiMachineControlCommand command); - - /** Checks whether this is an MMC "goto" message. - If it is, the parameters passed-in are set to the time that the message contains. - @see midiMachineControlGoto - */ - bool isMidiMachineControlGoto (int& hours, - int& minutes, - int& seconds, - int& frames) const noexcept; - - /** Creates an MMC "goto" message. - This messages tells the device to go to a specific frame. - @see isMidiMachineControlGoto - */ - static MidiMessage midiMachineControlGoto (int hours, - int minutes, - int seconds, - int frames); - - //============================================================================== - /** Creates a master-volume change message. - @param volume the volume, 0 to 1.0 - */ - static MidiMessage masterVolume (float volume); - - //============================================================================== - /** Creates a system-exclusive message. - The data passed in is wrapped with header and tail bytes of 0xf0 and 0xf7. - */ - static MidiMessage createSysExMessage (const void* sysexData, - int dataSize); - - - //============================================================================== - /** Reads a midi variable-length integer. - - @param data the data to read the number from - @param numBytesUsed on return, this will be set to the number of bytes that were read - */ - static int readVariableLengthVal (const uint8* data, - int& numBytesUsed) noexcept; - - /** Based on the first byte of a short midi message, this uses a lookup table - to return the message length (either 1, 2, or 3 bytes). - - The value passed in must be 0x80 or higher. - */ - static int getMessageLengthFromFirstByte (uint8 firstByte) noexcept; - - //============================================================================== - /** Returns the name of a midi note number. - - E.g "C", "D#", etc. - - @param noteNumber the midi note number, 0 to 127 - @param useSharps if true, sharpened notes are used, e.g. "C#", otherwise - they'll be flattened, e.g. "Db" - @param includeOctaveNumber if true, the octave number will be appended to the string, - e.g. "C#4" - @param octaveNumForMiddleC if an octave number is being appended, this indicates the - number that will be used for middle C's octave - - @see getMidiNoteInHertz - */ - static String getMidiNoteName (int noteNumber, - bool useSharps, - bool includeOctaveNumber, - int octaveNumForMiddleC); - - /** Returns the frequency of a midi note number. - - The frequencyOfA parameter is an optional frequency for 'A', normally 440-444Hz for concert pitch. - @see getMidiNoteName - */ - static double getMidiNoteInHertz (int noteNumber, double frequencyOfA = 440.0) noexcept; - - /** Returns true if the given midi note number is a black key. */ - static bool isMidiNoteBlack (int noteNumber) noexcept; - - /** Returns the standard name of a GM instrument, or nullptr if unknown for this index. - - @param midiInstrumentNumber the program number 0 to 127 - @see getProgramChangeNumber - */ - static const char* getGMInstrumentName (int midiInstrumentNumber); - - /** Returns the name of a bank of GM instruments, or nullptr if unknown for this bank number. - @param midiBankNumber the bank, 0 to 15 - */ - static const char* getGMInstrumentBankName (int midiBankNumber); - - /** Returns the standard name of a channel 10 percussion sound, or nullptr if unknown for this note number. - @param midiNoteNumber the key number, 35 to 81 - */ - static const char* getRhythmInstrumentName (int midiNoteNumber); - - /** Returns the name of a controller type number, or nullptr if unknown for this controller number. - @see getControllerNumber - */ - static const char* getControllerName (int controllerNumber); - - /** Converts a floating-point value between 0 and 1 to a MIDI 7-bit value between 0 and 127. */ - static uint8 floatValueToMidiByte (float valueBetween0and1) noexcept; - - /** Converts a pitchbend value in semitones to a MIDI 14-bit pitchwheel position value. */ - static uint16 pitchbendToPitchwheelPos (float pitchbendInSemitones, - float pitchbendRangeInSemitones) noexcept; - -private: - //============================================================================== - #ifndef DOXYGEN - union PackedData - { - uint8* allocatedData; - uint8 asBytes[sizeof (uint8*)]; - }; - - PackedData packedData; - double timeStamp = 0; - int size; - #endif - - inline bool isHeapAllocated() const noexcept { return size > (int) sizeof (packedData); } - inline uint8* getData() const noexcept { return isHeapAllocated() ? packedData.allocatedData : (uint8*) packedData.asBytes; } - uint8* allocateSpace (int); -}; - -} // namespace juce +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2017 - ROLI Ltd. + + JUCE is an open source library subject to commercial or open-source + licensing. + + The code included in this file is provided under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license. Permission + To use, copy, modify, and/or distribute this software for any purpose with or + without fee is hereby granted provided that the above copyright notice and + this permission notice appear in all copies. + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +namespace juce +{ + +//============================================================================== +/** + Encapsulates a MIDI message. + + @see MidiMessageSequence, MidiOutput, MidiInput + + @tags{Audio} +*/ +class JUCE_API MidiMessage +{ +public: + //============================================================================== + /** Creates a 3-byte short midi message. + + @param byte1 message byte 1 + @param byte2 message byte 2 + @param byte3 message byte 3 + @param timeStamp the time to give the midi message - this value doesn't + use any particular units, so will be application-specific + */ + MidiMessage (int byte1, int byte2, int byte3, double timeStamp = 0) noexcept; + + /** Creates a 2-byte short midi message. + + @param byte1 message byte 1 + @param byte2 message byte 2 + @param timeStamp the time to give the midi message - this value doesn't + use any particular units, so will be application-specific + */ + MidiMessage (int byte1, int byte2, double timeStamp = 0) noexcept; + + /** Creates a 1-byte short midi message. + + @param byte1 message byte 1 + @param timeStamp the time to give the midi message - this value doesn't + use any particular units, so will be application-specific + */ + MidiMessage (int byte1, double timeStamp = 0) noexcept; + + /** Creates a midi message from a list of bytes. */ + template + MidiMessage (int byte1, int byte2, int byte3, Data... otherBytes) : size (3 + sizeof... (otherBytes)) + { + // this checks that the length matches the data.. + jassert (size > 3 || byte1 >= 0xf0 || getMessageLengthFromFirstByte ((uint8) byte1) == size); + + const uint8 data[] = { (uint8) byte1, (uint8) byte2, (uint8) byte3, static_cast (otherBytes)... }; + memcpy (allocateSpace (size), data, (size_t) size); + } + + + /** Creates a midi message from a block of data. */ + MidiMessage (const void* data, int numBytes, double timeStamp = 0); + + /** Reads the next midi message from some data. + + This will read as many bytes from a data stream as it needs to make a + complete message, and will return the number of bytes it used. This lets + you read a sequence of midi messages from a file or stream. + + @param data the data to read from + @param maxBytesToUse the maximum number of bytes it's allowed to read + @param numBytesUsed returns the number of bytes that were actually needed + @param lastStatusByte in a sequence of midi messages, the initial byte + can be dropped from a message if it's the same as the + first byte of the previous message, so this lets you + supply the byte to use if the first byte of the message + has in fact been dropped. + @param timeStamp the time to give the midi message - this value doesn't + use any particular units, so will be application-specific + @param sysexHasEmbeddedLength when reading sysexes, this flag indicates whether + to expect the data to begin with a variable-length + field indicating its size + */ + MidiMessage (const void* data, int maxBytesToUse, + int& numBytesUsed, uint8 lastStatusByte, + double timeStamp = 0, + bool sysexHasEmbeddedLength = true); + + /** Creates an active-sense message. + Since the MidiMessage has to contain a valid message, this default constructor + just initialises it with an empty sysex message. + */ + MidiMessage() noexcept; + + /** Creates a copy of another midi message. */ + MidiMessage (const MidiMessage&); + + /** Creates a copy of another midi message, with a different timestamp. */ + MidiMessage (const MidiMessage&, double newTimeStamp); + + /** Destructor. */ + ~MidiMessage() noexcept; + + /** Copies this message from another one. */ + MidiMessage& operator= (const MidiMessage& other); + + /** Move constructor */ + MidiMessage (MidiMessage&&) noexcept; + + /** Move assignment operator */ + MidiMessage& operator= (MidiMessage&&) noexcept; + + //============================================================================== + /** Returns a pointer to the raw midi data. + @see getRawDataSize + */ + const uint8* getRawData() const noexcept { return getData(); } + + /** Returns the number of bytes of data in the message. + @see getRawData + */ + int getRawDataSize() const noexcept { return size; } + + //============================================================================== + /** Returns a human-readable description of the midi message as a string, + for example "Note On C#3 Velocity 120 Channel 1". + */ + String getDescription() const; + + //============================================================================== + /** Returns the timestamp associated with this message. + + The exact meaning of this time and its units will vary, as messages are used in + a variety of different contexts. + + If you're getting the message from a midi file, this could be a time in seconds, or + a number of ticks - see MidiFile::convertTimestampTicksToSeconds(). + + If the message is being used in a MidiBuffer, it might indicate the number of + audio samples from the start of the buffer. + + If the message was created by a MidiInput, see MidiInputCallback::handleIncomingMidiMessage() + for details of the way that it initialises this value. + + @see setTimeStamp, addToTimeStamp + */ + double getTimeStamp() const noexcept { return timeStamp; } + + /** Changes the message's associated timestamp. + The units for the timestamp will be application-specific - see the notes for getTimeStamp(). + @see addToTimeStamp, getTimeStamp + */ + void setTimeStamp (double newTimestamp) noexcept { timeStamp = newTimestamp; } + + /** Adds a value to the message's timestamp. + The units for the timestamp will be application-specific. + */ + void addToTimeStamp (double delta) noexcept { timeStamp += delta; } + + /** Return a copy of this message with a new timestamp. + The units for the timestamp will be application-specific - see the notes for getTimeStamp(). + */ + MidiMessage withTimeStamp (double newTimestamp) const; + + //============================================================================== + /** Returns the midi channel associated with the message. + + @returns a value 1 to 16 if the message has a channel, or 0 if it hasn't (e.g. + if it's a sysex) + @see isForChannel, setChannel + */ + int getChannel() const noexcept; + + /** Returns true if the message applies to the given midi channel. + + @param channelNumber the channel number to look for, in the range 1 to 16 + @see getChannel, setChannel + */ + bool isForChannel (int channelNumber) const noexcept; + + /** Changes the message's midi channel. + This won't do anything for non-channel messages like sysexes. + @param newChannelNumber the channel number to change it to, in the range 1 to 16 + */ + void setChannel (int newChannelNumber) noexcept; + + //============================================================================== + /** Returns true if this is a system-exclusive message. + */ + bool isSysEx() const noexcept; + + /** Returns a pointer to the sysex data inside the message. + If this event isn't a sysex event, it'll return 0. + @see getSysExDataSize + */ + const uint8* getSysExData() const noexcept; + + /** Returns the size of the sysex data. + This value excludes the 0xf0 header byte and the 0xf7 at the end. + @see getSysExData + */ + int getSysExDataSize() const noexcept; + + //============================================================================== + /** Returns true if this message is a 'key-down' event. + + @param returnTrueForVelocity0 if true, then if this event is a note-on with + velocity 0, it will still be considered to be a note-on and the + method will return true. If returnTrueForVelocity0 is false, then + if this is a note-on event with velocity 0, it'll be regarded as + a note-off, and the method will return false + + @see isNoteOff, getNoteNumber, getVelocity, noteOn + */ + bool isNoteOn (bool returnTrueForVelocity0 = false) const noexcept; + + /** Creates a key-down message (using a floating-point velocity). + + @param channel the midi channel, in the range 1 to 16 + @param noteNumber the key number, 0 to 127 + @param velocity in the range 0 to 1.0 + @see isNoteOn + */ + static MidiMessage noteOn (int channel, int noteNumber, float velocity) noexcept; + + /** Creates a key-down message (using an integer velocity). + + @param channel the midi channel, in the range 1 to 16 + @param noteNumber the key number, 0 to 127 + @param velocity in the range 0 to 127 + @see isNoteOn + */ + static MidiMessage noteOn (int channel, int noteNumber, uint8 velocity) noexcept; + + /** Returns true if this message is a 'key-up' event. + + If returnTrueForNoteOnVelocity0 is true, then his will also return true + for a note-on event with a velocity of 0. + + @see isNoteOn, getNoteNumber, getVelocity, noteOff + */ + bool isNoteOff (bool returnTrueForNoteOnVelocity0 = true) const noexcept; + + /** Creates a key-up message. + + @param channel the midi channel, in the range 1 to 16 + @param noteNumber the key number, 0 to 127 + @param velocity in the range 0 to 1.0 + @see isNoteOff + */ + static MidiMessage noteOff (int channel, int noteNumber, float velocity) noexcept; + + /** Creates a key-up message. + + @param channel the midi channel, in the range 1 to 16 + @param noteNumber the key number, 0 to 127 + @param velocity in the range 0 to 127 + @see isNoteOff + */ + static MidiMessage noteOff (int channel, int noteNumber, uint8 velocity) noexcept; + + /** Creates a key-up message. + + @param channel the midi channel, in the range 1 to 16 + @param noteNumber the key number, 0 to 127 + @see isNoteOff + */ + static MidiMessage noteOff (int channel, int noteNumber) noexcept; + + /** Returns true if this message is a 'key-down' or 'key-up' event. + + @see isNoteOn, isNoteOff + */ + bool isNoteOnOrOff() const noexcept; + + /** Returns the midi note number for note-on and note-off messages. + If the message isn't a note-on or off, the value returned is undefined. + @see isNoteOff, getMidiNoteName, getMidiNoteInHertz, setNoteNumber + */ + int getNoteNumber() const noexcept; + + /** Changes the midi note number of a note-on or note-off message. + If the message isn't a note on or off, this will do nothing. + */ + void setNoteNumber (int newNoteNumber) noexcept; + + //============================================================================== + /** Returns the velocity of a note-on or note-off message. + + The value returned will be in the range 0 to 127. + If the message isn't a note-on or off event, it will return 0. + + @see getFloatVelocity + */ + uint8 getVelocity() const noexcept; + + /** Returns the velocity of a note-on or note-off message. + + The value returned will be in the range 0 to 1.0 + If the message isn't a note-on or off event, it will return 0. + + @see getVelocity, setVelocity + */ + float getFloatVelocity() const noexcept; + + /** Changes the velocity of a note-on or note-off message. + + If the message isn't a note on or off, this will do nothing. + + @param newVelocity the new velocity, in the range 0 to 1.0 + @see getFloatVelocity, multiplyVelocity + */ + void setVelocity (float newVelocity) noexcept; + + /** Multiplies the velocity of a note-on or note-off message by a given amount. + + If the message isn't a note on or off, this will do nothing. + + @param scaleFactor the value by which to multiply the velocity + @see setVelocity + */ + void multiplyVelocity (float scaleFactor) noexcept; + + //============================================================================== + /** Returns true if this message is a 'sustain pedal down' controller message. */ + bool isSustainPedalOn() const noexcept; + /** Returns true if this message is a 'sustain pedal up' controller message. */ + bool isSustainPedalOff() const noexcept; + + /** Returns true if this message is a 'sostenuto pedal down' controller message. */ + bool isSostenutoPedalOn() const noexcept; + /** Returns true if this message is a 'sostenuto pedal up' controller message. */ + bool isSostenutoPedalOff() const noexcept; + + /** Returns true if this message is a 'soft pedal down' controller message. */ + bool isSoftPedalOn() const noexcept; + /** Returns true if this message is a 'soft pedal up' controller message. */ + bool isSoftPedalOff() const noexcept; + + //============================================================================== + /** Returns true if the message is a program (patch) change message. + @see getProgramChangeNumber, getGMInstrumentName + */ + bool isProgramChange() const noexcept; + + /** Returns the new program number of a program change message. + If the message isn't a program change, the value returned is undefined. + @see isProgramChange, getGMInstrumentName + */ + int getProgramChangeNumber() const noexcept; + + /** Creates a program-change message. + + @param channel the midi channel, in the range 1 to 16 + @param programNumber the midi program number, 0 to 127 + @see isProgramChange, getGMInstrumentName + */ + static MidiMessage programChange (int channel, int programNumber) noexcept; + + //============================================================================== + /** Returns true if the message is a pitch-wheel move. + @see getPitchWheelValue, pitchWheel + */ + bool isPitchWheel() const noexcept; + + /** Returns the pitch wheel position from a pitch-wheel move message. + + The value returned is a 14-bit number from 0 to 0x3fff, indicating the wheel position. + If called for messages which aren't pitch wheel events, the number returned will be + nonsense. + + @see isPitchWheel + */ + int getPitchWheelValue() const noexcept; + + /** Creates a pitch-wheel move message. + + @param channel the midi channel, in the range 1 to 16 + @param position the wheel position, in the range 0 to 16383 + @see isPitchWheel + */ + static MidiMessage pitchWheel (int channel, int position) noexcept; + + //============================================================================== + /** Returns true if the message is an aftertouch event. + + For aftertouch events, use the getNoteNumber() method to find out the key + that it applies to, and getAftertouchValue() to find out the amount. Use + getChannel() to find out the channel. + + @see getAftertouchValue, getNoteNumber + */ + bool isAftertouch() const noexcept; + + /** Returns the amount of aftertouch from an aftertouch messages. + + The value returned is in the range 0 to 127, and will be nonsense for messages + other than aftertouch messages. + + @see isAftertouch + */ + int getAfterTouchValue() const noexcept; + + /** Creates an aftertouch message. + + @param channel the midi channel, in the range 1 to 16 + @param noteNumber the key number, 0 to 127 + @param aftertouchAmount the amount of aftertouch, 0 to 127 + @see isAftertouch + */ + static MidiMessage aftertouchChange (int channel, + int noteNumber, + int aftertouchAmount) noexcept; + + /** Returns true if the message is a channel-pressure change event. + + This is like aftertouch, but common to the whole channel rather than a specific + note. Use getChannelPressureValue() to find out the pressure, and getChannel() + to find out the channel. + + @see channelPressureChange + */ + bool isChannelPressure() const noexcept; + + /** Returns the pressure from a channel pressure change message. + + @returns the pressure, in the range 0 to 127 + @see isChannelPressure, channelPressureChange + */ + int getChannelPressureValue() const noexcept; + + /** Creates a channel-pressure change event. + + @param channel the midi channel: 1 to 16 + @param pressure the pressure, 0 to 127 + @see isChannelPressure + */ + static MidiMessage channelPressureChange (int channel, int pressure) noexcept; + + //============================================================================== + /** Returns true if this is a midi controller message. + + @see getControllerNumber, getControllerValue, controllerEvent + */ + bool isController() const noexcept; + + /** Returns the controller number of a controller message. + + The name of the controller can be looked up using the getControllerName() method. + Note that the value returned is invalid for messages that aren't controller changes. + + @see isController, getControllerName, getControllerValue + */ + int getControllerNumber() const noexcept; + + /** Returns the controller value from a controller message. + + A value 0 to 127 is returned to indicate the new controller position. + Note that the value returned is invalid for messages that aren't controller changes. + + @see isController, getControllerNumber + */ + int getControllerValue() const noexcept; + + /** Returns true if this message is a controller message and if it has the specified + controller type. + */ + bool isControllerOfType (int controllerType) const noexcept; + + /** Creates a controller message. + @param channel the midi channel, in the range 1 to 16 + @param controllerType the type of controller + @param value the controller value + @see isController + */ + static MidiMessage controllerEvent (int channel, + int controllerType, + int value) noexcept; + + /** Checks whether this message is an all-notes-off message. + @see allNotesOff + */ + bool isAllNotesOff() const noexcept; + + /** Checks whether this message is an all-sound-off message. + @see allSoundOff + */ + bool isAllSoundOff() const noexcept; + + /** Checks whether this message is a reset all controllers message. + @see allControllerOff + */ + bool isResetAllControllers() const noexcept; + + /** Creates an all-notes-off message. + @param channel the midi channel, in the range 1 to 16 + @see isAllNotesOff + */ + static MidiMessage allNotesOff (int channel) noexcept; + + /** Creates an all-sound-off message. + @param channel the midi channel, in the range 1 to 16 + @see isAllSoundOff + */ + static MidiMessage allSoundOff (int channel) noexcept; + + /** Creates an all-controllers-off message. + @param channel the midi channel, in the range 1 to 16 + */ + static MidiMessage allControllersOff (int channel) noexcept; + + //============================================================================== + /** Returns true if this event is a meta-event. + + Meta-events are things like tempo changes, track names, etc. + + @see getMetaEventType, isTrackMetaEvent, isEndOfTrackMetaEvent, + isTextMetaEvent, isTrackNameEvent, isTempoMetaEvent, isTimeSignatureMetaEvent, + isKeySignatureMetaEvent, isMidiChannelMetaEvent + */ + bool isMetaEvent() const noexcept; + + /** Returns a meta-event's type number. + + If the message isn't a meta-event, this will return -1. + + @see isMetaEvent, isTrackMetaEvent, isEndOfTrackMetaEvent, + isTextMetaEvent, isTrackNameEvent, isTempoMetaEvent, isTimeSignatureMetaEvent, + isKeySignatureMetaEvent, isMidiChannelMetaEvent + */ + int getMetaEventType() const noexcept; + + /** Returns a pointer to the data in a meta-event. + @see isMetaEvent, getMetaEventLength + */ + const uint8* getMetaEventData() const noexcept; + + /** Returns the length of the data for a meta-event. + @see isMetaEvent, getMetaEventData + */ + int getMetaEventLength() const noexcept; + + //============================================================================== + /** Returns true if this is a 'track' meta-event. */ + bool isTrackMetaEvent() const noexcept; + + /** Returns true if this is an 'end-of-track' meta-event. */ + bool isEndOfTrackMetaEvent() const noexcept; + + /** Creates an end-of-track meta-event. + @see isEndOfTrackMetaEvent + */ + static MidiMessage endOfTrack() noexcept; + + /** Returns true if this is an 'track name' meta-event. + You can use the getTextFromTextMetaEvent() method to get the track's name. + */ + bool isTrackNameEvent() const noexcept; + + /** Returns true if this is a 'text' meta-event. + @see getTextFromTextMetaEvent + */ + bool isTextMetaEvent() const noexcept; + + /** Returns the text from a text meta-event. + @see isTextMetaEvent + */ + String getTextFromTextMetaEvent() const; + + /** Creates a text meta-event. */ + static MidiMessage textMetaEvent (int type, StringRef text); + + //============================================================================== + /** Returns true if this is a 'tempo' meta-event. + @see getTempoMetaEventTickLength, getTempoSecondsPerQuarterNote + */ + bool isTempoMetaEvent() const noexcept; + + /** Returns the tick length from a tempo meta-event. + + @param timeFormat the 16-bit time format value from the midi file's header. + @returns the tick length (in seconds). + @see isTempoMetaEvent + */ + double getTempoMetaEventTickLength (short timeFormat) const noexcept; + + /** Calculates the seconds-per-quarter-note from a tempo meta-event. + @see isTempoMetaEvent, getTempoMetaEventTickLength + */ + double getTempoSecondsPerQuarterNote() const noexcept; + + /** Creates a tempo meta-event. + @see isTempoMetaEvent + */ + static MidiMessage tempoMetaEvent (int microsecondsPerQuarterNote) noexcept; + + //============================================================================== + /** Returns true if this is a 'time-signature' meta-event. + @see getTimeSignatureInfo + */ + bool isTimeSignatureMetaEvent() const noexcept; + + /** Returns the time-signature values from a time-signature meta-event. + @see isTimeSignatureMetaEvent + */ + void getTimeSignatureInfo (int& numerator, int& denominator) const noexcept; + + /** Creates a time-signature meta-event. + @see isTimeSignatureMetaEvent + */ + static MidiMessage timeSignatureMetaEvent (int numerator, int denominator); + + //============================================================================== + /** Returns true if this is a 'key-signature' meta-event. + @see getKeySignatureNumberOfSharpsOrFlats, isKeySignatureMajorKey + */ + bool isKeySignatureMetaEvent() const noexcept; + + /** Returns the key from a key-signature meta-event. + This method must only be called if isKeySignatureMetaEvent() is true. + A positive number here indicates the number of sharps in the key signature, + and a negative number indicates a number of flats. So e.g. 3 = F# + C# + G#, + -2 = Bb + Eb + @see isKeySignatureMetaEvent, isKeySignatureMajorKey + */ + int getKeySignatureNumberOfSharpsOrFlats() const noexcept; + + /** Returns true if this key-signature event is major, or false if it's minor. + This method must only be called if isKeySignatureMetaEvent() is true. + */ + bool isKeySignatureMajorKey() const noexcept; + + /** Creates a key-signature meta-event. + @param numberOfSharpsOrFlats if positive, this indicates the number of sharps + in the key; if negative, the number of flats + @param isMinorKey if true, the key is minor; if false, it is major + @see isKeySignatureMetaEvent + */ + static MidiMessage keySignatureMetaEvent (int numberOfSharpsOrFlats, bool isMinorKey); + + //============================================================================== + /** Returns true if this is a 'channel' meta-event. + + A channel meta-event specifies the midi channel that should be used + for subsequent meta-events. + + @see getMidiChannelMetaEventChannel + */ + bool isMidiChannelMetaEvent() const noexcept; + + /** Returns the channel number from a channel meta-event. + + @returns the channel, in the range 1 to 16. + @see isMidiChannelMetaEvent + */ + int getMidiChannelMetaEventChannel() const noexcept; + + /** Creates a midi channel meta-event. + + @param channel the midi channel, in the range 1 to 16 + @see isMidiChannelMetaEvent + */ + static MidiMessage midiChannelMetaEvent (int channel) noexcept; + + //============================================================================== + /** Returns true if this is an active-sense message. */ + bool isActiveSense() const noexcept; + + //============================================================================== + /** Returns true if this is a midi start event. + @see midiStart + */ + bool isMidiStart() const noexcept; + + /** Creates a midi start event. */ + static MidiMessage midiStart() noexcept; + + /** Returns true if this is a midi continue event. + @see midiContinue + */ + bool isMidiContinue() const noexcept; + + /** Creates a midi continue event. */ + static MidiMessage midiContinue() noexcept; + + /** Returns true if this is a midi stop event. + @see midiStop + */ + bool isMidiStop() const noexcept; + + /** Creates a midi stop event. */ + static MidiMessage midiStop() noexcept; + + /** Returns true if this is a midi clock event. + @see midiClock, songPositionPointer + */ + bool isMidiClock() const noexcept; + + /** Creates a midi clock event. */ + static MidiMessage midiClock() noexcept; + + /** Returns true if this is a song-position-pointer message. + @see getSongPositionPointerMidiBeat, songPositionPointer + */ + bool isSongPositionPointer() const noexcept; + + /** Returns the midi beat-number of a song-position-pointer message. + @see isSongPositionPointer, songPositionPointer + */ + int getSongPositionPointerMidiBeat() const noexcept; + + /** Creates a song-position-pointer message. + + The position is a number of midi beats from the start of the song, where 1 midi + beat is 6 midi clocks, and there are 24 midi clocks in a quarter-note. So there + are 4 midi beats in a quarter-note. + + @see isSongPositionPointer, getSongPositionPointerMidiBeat + */ + static MidiMessage songPositionPointer (int positionInMidiBeats) noexcept; + + //============================================================================== + /** Returns true if this is a quarter-frame midi timecode message. + @see quarterFrame, getQuarterFrameSequenceNumber, getQuarterFrameValue + */ + bool isQuarterFrame() const noexcept; + + /** Returns the sequence number of a quarter-frame midi timecode message. + This will be a value between 0 and 7. + @see isQuarterFrame, getQuarterFrameValue, quarterFrame + */ + int getQuarterFrameSequenceNumber() const noexcept; + + /** Returns the value from a quarter-frame message. + This will be the lower nybble of the message's data-byte, a value between 0 and 15 + */ + int getQuarterFrameValue() const noexcept; + + /** Creates a quarter-frame MTC message. + + @param sequenceNumber a value 0 to 7 for the upper nybble of the message's data byte + @param value a value 0 to 15 for the lower nybble of the message's data byte + */ + static MidiMessage quarterFrame (int sequenceNumber, int value) noexcept; + + /** SMPTE timecode types. + Used by the getFullFrameParameters() and fullFrame() methods. + */ + enum SmpteTimecodeType + { + fps24 = 0, + fps25 = 1, + fps30drop = 2, + fps30 = 3 + }; + + /** Returns true if this is a full-frame midi timecode message. */ + bool isFullFrame() const noexcept; + + /** Extracts the timecode information from a full-frame midi timecode message. + + You should only call this on messages where you've used isFullFrame() to + check that they're the right kind. + */ + void getFullFrameParameters (int& hours, + int& minutes, + int& seconds, + int& frames, + SmpteTimecodeType& timecodeType) const noexcept; + + /** Creates a full-frame MTC message. */ + static MidiMessage fullFrame (int hours, + int minutes, + int seconds, + int frames, + SmpteTimecodeType timecodeType); + + //============================================================================== + /** Types of MMC command. + + @see isMidiMachineControlMessage, getMidiMachineControlCommand, midiMachineControlCommand + */ + enum MidiMachineControlCommand + { + mmc_stop = 1, + mmc_play = 2, + mmc_deferredplay = 3, + mmc_fastforward = 4, + mmc_rewind = 5, + mmc_recordStart = 6, + mmc_recordStop = 7, + mmc_pause = 9 + }; + + /** Checks whether this is an MMC message. + If it is, you can use the getMidiMachineControlCommand() to find out its type. + */ + bool isMidiMachineControlMessage() const noexcept; + + /** For an MMC message, this returns its type. + + Make sure it's actually an MMC message with isMidiMachineControlMessage() before + calling this method. + */ + MidiMachineControlCommand getMidiMachineControlCommand() const noexcept; + + /** Creates an MMC message. */ + static MidiMessage midiMachineControlCommand (MidiMachineControlCommand command); + + /** Checks whether this is an MMC "goto" message. + If it is, the parameters passed-in are set to the time that the message contains. + @see midiMachineControlGoto + */ + bool isMidiMachineControlGoto (int& hours, + int& minutes, + int& seconds, + int& frames) const noexcept; + + /** Creates an MMC "goto" message. + This messages tells the device to go to a specific frame. + @see isMidiMachineControlGoto + */ + static MidiMessage midiMachineControlGoto (int hours, + int minutes, + int seconds, + int frames); + + //============================================================================== + /** Creates a master-volume change message. + @param volume the volume, 0 to 1.0 + */ + static MidiMessage masterVolume (float volume); + + //============================================================================== + /** Creates a system-exclusive message. + The data passed in is wrapped with header and tail bytes of 0xf0 and 0xf7. + */ + static MidiMessage createSysExMessage (const void* sysexData, + int dataSize); + + + //============================================================================== + /** Reads a midi variable-length integer. + + @param data the data to read the number from + @param numBytesUsed on return, this will be set to the number of bytes that were read + */ + static int readVariableLengthVal (const uint8* data, + int& numBytesUsed) noexcept; + + /** Based on the first byte of a short midi message, this uses a lookup table + to return the message length (either 1, 2, or 3 bytes). + + The value passed in must be 0x80 or higher. + */ + static int getMessageLengthFromFirstByte (uint8 firstByte) noexcept; + + //============================================================================== + /** Returns the name of a midi note number. + + E.g "C", "D#", etc. + + @param noteNumber the midi note number, 0 to 127 + @param useSharps if true, sharpened notes are used, e.g. "C#", otherwise + they'll be flattened, e.g. "Db" + @param includeOctaveNumber if true, the octave number will be appended to the string, + e.g. "C#4" + @param octaveNumForMiddleC if an octave number is being appended, this indicates the + number that will be used for middle C's octave + + @see getMidiNoteInHertz + */ + static String getMidiNoteName (int noteNumber, + bool useSharps, + bool includeOctaveNumber, + int octaveNumForMiddleC); + + /** Returns the frequency of a midi note number. + + The frequencyOfA parameter is an optional frequency for 'A', normally 440-444Hz for concert pitch. + @see getMidiNoteName + */ + static double getMidiNoteInHertz (int noteNumber, double frequencyOfA = 440.0) noexcept; + + /** Returns true if the given midi note number is a black key. */ + static bool isMidiNoteBlack (int noteNumber) noexcept; + + /** Returns the standard name of a GM instrument, or nullptr if unknown for this index. + + @param midiInstrumentNumber the program number 0 to 127 + @see getProgramChangeNumber + */ + static const char* getGMInstrumentName (int midiInstrumentNumber); + + /** Returns the name of a bank of GM instruments, or nullptr if unknown for this bank number. + @param midiBankNumber the bank, 0 to 15 + */ + static const char* getGMInstrumentBankName (int midiBankNumber); + + /** Returns the standard name of a channel 10 percussion sound, or nullptr if unknown for this note number. + @param midiNoteNumber the key number, 35 to 81 + */ + static const char* getRhythmInstrumentName (int midiNoteNumber); + + /** Returns the name of a controller type number, or nullptr if unknown for this controller number. + @see getControllerNumber + */ + static const char* getControllerName (int controllerNumber); + + /** Converts a floating-point value between 0 and 1 to a MIDI 7-bit value between 0 and 127. */ + static uint8 floatValueToMidiByte (float valueBetween0and1) noexcept; + + /** Converts a pitchbend value in semitones to a MIDI 14-bit pitchwheel position value. */ + static uint16 pitchbendToPitchwheelPos (float pitchbendInSemitones, + float pitchbendRangeInSemitones) noexcept; + +private: + //============================================================================== + #ifndef DOXYGEN + union PackedData + { + uint8* allocatedData; + uint8 asBytes[sizeof (uint8*)]; + }; + + PackedData packedData; + double timeStamp = 0; + int size; + #endif + + inline bool isHeapAllocated() const noexcept { return size > (int) sizeof (packedData); } + inline uint8* getData() const noexcept { return isHeapAllocated() ? packedData.allocatedData : (uint8*) packedData.asBytes; } + uint8* allocateSpace (int); +}; + +} // namespace juce diff --git a/JuceLibraryCode/modules/juce_audio_basics/midi/juce_MidiMessageSequence.cpp b/JuceLibraryCode/modules/juce_audio_basics/midi/juce_MidiMessageSequence.cpp index d74aa58..41d55d5 100644 --- a/JuceLibraryCode/modules/juce_audio_basics/midi/juce_MidiMessageSequence.cpp +++ b/JuceLibraryCode/modules/juce_audio_basics/midi/juce_MidiMessageSequence.cpp @@ -1,412 +1,412 @@ -/* - ============================================================================== - - This file is part of the JUCE library. - Copyright (c) 2017 - ROLI Ltd. - - JUCE is an open source library subject to commercial or open-source - licensing. - - The code included in this file is provided under the terms of the ISC license - http://www.isc.org/downloads/software-support-policy/isc-license. Permission - To use, copy, modify, and/or distribute this software for any purpose with or - without fee is hereby granted provided that the above copyright notice and - this permission notice appear in all copies. - - JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER - EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE - DISCLAIMED. - - ============================================================================== -*/ - -namespace juce -{ - -MidiMessageSequence::MidiEventHolder::MidiEventHolder (const MidiMessage& mm) : message (mm) {} -MidiMessageSequence::MidiEventHolder::MidiEventHolder (MidiMessage&& mm) : message (std::move (mm)) {} -MidiMessageSequence::MidiEventHolder::~MidiEventHolder() {} - -//============================================================================== -MidiMessageSequence::MidiMessageSequence() -{ -} - -MidiMessageSequence::MidiMessageSequence (const MidiMessageSequence& other) -{ - list.addCopiesOf (other.list); - - for (int i = 0; i < list.size(); ++i) - { - auto noteOffIndex = other.getIndexOfMatchingKeyUp (i); - - if (noteOffIndex >= 0) - list.getUnchecked(i)->noteOffObject = list.getUnchecked (noteOffIndex); - } -} - -MidiMessageSequence& MidiMessageSequence::operator= (const MidiMessageSequence& other) -{ - MidiMessageSequence otherCopy (other); - swapWith (otherCopy); - return *this; -} - -MidiMessageSequence::MidiMessageSequence (MidiMessageSequence&& other) noexcept - : list (std::move (other.list)) -{ -} - -MidiMessageSequence& MidiMessageSequence::operator= (MidiMessageSequence&& other) noexcept -{ - list = std::move (other.list); - return *this; -} - -MidiMessageSequence::~MidiMessageSequence() -{ -} - -void MidiMessageSequence::swapWith (MidiMessageSequence& other) noexcept -{ - list.swapWith (other.list); -} - -void MidiMessageSequence::clear() -{ - list.clear(); -} - -int MidiMessageSequence::getNumEvents() const noexcept -{ - return list.size(); -} - -MidiMessageSequence::MidiEventHolder* MidiMessageSequence::getEventPointer (int index) const noexcept -{ - return list[index]; -} - -MidiMessageSequence::MidiEventHolder** MidiMessageSequence::begin() noexcept { return list.begin(); } -MidiMessageSequence::MidiEventHolder* const* MidiMessageSequence::begin() const noexcept { return list.begin(); } -MidiMessageSequence::MidiEventHolder** MidiMessageSequence::end() noexcept { return list.end(); } -MidiMessageSequence::MidiEventHolder* const* MidiMessageSequence::end() const noexcept { return list.end(); } - -double MidiMessageSequence::getTimeOfMatchingKeyUp (int index) const noexcept -{ - if (auto* meh = list[index]) - if (auto* noteOff = meh->noteOffObject) - return noteOff->message.getTimeStamp(); - - return 0; -} - -int MidiMessageSequence::getIndexOfMatchingKeyUp (int index) const noexcept -{ - if (auto* meh = list[index]) - { - if (auto* noteOff = meh->noteOffObject) - { - for (int i = index; i < list.size(); ++i) - if (list.getUnchecked(i) == noteOff) - return i; - - jassertfalse; // we've somehow got a pointer to a note-off object that isn't in the sequence - } - } - - return -1; -} - -int MidiMessageSequence::getIndexOf (const MidiEventHolder* event) const noexcept -{ - return list.indexOf (event); -} - -int MidiMessageSequence::getNextIndexAtTime (double timeStamp) const noexcept -{ - auto numEvents = list.size(); - int i; - - for (i = 0; i < numEvents; ++i) - if (list.getUnchecked(i)->message.getTimeStamp() >= timeStamp) - break; - - return i; -} - -//============================================================================== -double MidiMessageSequence::getStartTime() const noexcept -{ - return getEventTime (0); -} - -double MidiMessageSequence::getEndTime() const noexcept -{ - return getEventTime (list.size() - 1); -} - -double MidiMessageSequence::getEventTime (const int index) const noexcept -{ - if (auto* meh = list[index]) - return meh->message.getTimeStamp(); - - return 0; -} - -//============================================================================== -MidiMessageSequence::MidiEventHolder* MidiMessageSequence::addEvent (MidiEventHolder* newEvent, double timeAdjustment) -{ - newEvent->message.addToTimeStamp (timeAdjustment); - auto time = newEvent->message.getTimeStamp(); - int i; - - for (i = list.size(); --i >= 0;) - if (list.getUnchecked(i)->message.getTimeStamp() <= time) - break; - - list.insert (i + 1, newEvent); - return newEvent; -} - -MidiMessageSequence::MidiEventHolder* MidiMessageSequence::addEvent (const MidiMessage& newMessage, double timeAdjustment) -{ - return addEvent (new MidiEventHolder (newMessage), timeAdjustment); -} - -MidiMessageSequence::MidiEventHolder* MidiMessageSequence::addEvent (MidiMessage&& newMessage, double timeAdjustment) -{ - return addEvent (new MidiEventHolder (std::move (newMessage)), timeAdjustment); -} - -void MidiMessageSequence::deleteEvent (int index, bool deleteMatchingNoteUp) -{ - if (isPositiveAndBelow (index, list.size())) - { - if (deleteMatchingNoteUp) - deleteEvent (getIndexOfMatchingKeyUp (index), false); - - list.remove (index); - } -} - -void MidiMessageSequence::addSequence (const MidiMessageSequence& other, double timeAdjustment) -{ - for (auto* m : other) - { - auto newOne = new MidiEventHolder (m->message); - newOne->message.addToTimeStamp (timeAdjustment); - list.add (newOne); - } - - sort(); -} - -void MidiMessageSequence::addSequence (const MidiMessageSequence& other, - double timeAdjustment, - double firstAllowableTime, - double endOfAllowableDestTimes) -{ - for (auto* m : other) - { - auto t = m->message.getTimeStamp() + timeAdjustment; - - if (t >= firstAllowableTime && t < endOfAllowableDestTimes) - { - auto newOne = new MidiEventHolder (m->message); - newOne->message.setTimeStamp (t); - list.add (newOne); - } - } - - sort(); -} - -void MidiMessageSequence::sort() noexcept -{ - std::stable_sort (list.begin(), list.end(), - [] (const MidiEventHolder* a, const MidiEventHolder* b) { return a->message.getTimeStamp() < b->message.getTimeStamp(); }); -} - -void MidiMessageSequence::updateMatchedPairs() noexcept -{ - for (int i = 0; i < list.size(); ++i) - { - auto* meh = list.getUnchecked(i); - auto& m1 = meh->message; - - if (m1.isNoteOn()) - { - meh->noteOffObject = nullptr; - auto note = m1.getNoteNumber(); - auto chan = m1.getChannel(); - auto len = list.size(); - - for (int j = i + 1; j < len; ++j) - { - auto* meh2 = list.getUnchecked(j); - auto& m = meh2->message; - - if (m.getNoteNumber() == note && m.getChannel() == chan) - { - if (m.isNoteOff()) - { - meh->noteOffObject = meh2; - break; - } - - if (m.isNoteOn()) - { - auto newEvent = new MidiEventHolder (MidiMessage::noteOff (chan, note)); - list.insert (j, newEvent); - newEvent->message.setTimeStamp (m.getTimeStamp()); - meh->noteOffObject = newEvent; - break; - } - } - } - } - } -} - -void MidiMessageSequence::addTimeToMessages (double delta) noexcept -{ - if (delta != 0) - for (auto* m : list) - m->message.addToTimeStamp (delta); -} - -//============================================================================== -void MidiMessageSequence::extractMidiChannelMessages (const int channelNumberToExtract, - MidiMessageSequence& destSequence, - const bool alsoIncludeMetaEvents) const -{ - for (auto* meh : list) - if (meh->message.isForChannel (channelNumberToExtract) - || (alsoIncludeMetaEvents && meh->message.isMetaEvent())) - destSequence.addEvent (meh->message); -} - -void MidiMessageSequence::extractSysExMessages (MidiMessageSequence& destSequence) const -{ - for (auto* meh : list) - if (meh->message.isSysEx()) - destSequence.addEvent (meh->message); -} - -void MidiMessageSequence::deleteMidiChannelMessages (const int channelNumberToRemove) -{ - for (int i = list.size(); --i >= 0;) - if (list.getUnchecked(i)->message.isForChannel (channelNumberToRemove)) - list.remove(i); -} - -void MidiMessageSequence::deleteSysExMessages() -{ - for (int i = list.size(); --i >= 0;) - if (list.getUnchecked(i)->message.isSysEx()) - list.remove(i); -} - -//============================================================================== -void MidiMessageSequence::createControllerUpdatesForTime (int channelNumber, double time, Array& dest) -{ - bool doneProg = false; - bool donePitchWheel = false; - bool doneControllers[128] = {}; - - for (int i = list.size(); --i >= 0;) - { - auto& mm = list.getUnchecked(i)->message; - - if (mm.isForChannel (channelNumber) && mm.getTimeStamp() <= time) - { - if (mm.isProgramChange() && ! doneProg) - { - doneProg = true; - dest.add (MidiMessage (mm, 0.0)); - } - else if (mm.isPitchWheel() && ! donePitchWheel) - { - donePitchWheel = true; - dest.add (MidiMessage (mm, 0.0)); - } - else if (mm.isController()) - { - auto controllerNumber = mm.getControllerNumber(); - jassert (isPositiveAndBelow (controllerNumber, 128)); - - if (! doneControllers[controllerNumber]) - { - doneControllers[controllerNumber] = true; - dest.add (MidiMessage (mm, 0.0)); - } - } - } - } -} - - -//============================================================================== -//============================================================================== -#if JUCE_UNIT_TESTS - -struct MidiMessageSequenceTest : public UnitTest -{ - MidiMessageSequenceTest() - : UnitTest ("MidiMessageSequence", UnitTestCategories::midi) - {} - - void runTest() override - { - MidiMessageSequence s; - - s.addEvent (MidiMessage::noteOn (1, 60, 0.5f).withTimeStamp (0.0)); - s.addEvent (MidiMessage::noteOff (1, 60, 0.5f).withTimeStamp (4.0)); - s.addEvent (MidiMessage::noteOn (1, 30, 0.5f).withTimeStamp (2.0)); - s.addEvent (MidiMessage::noteOff (1, 30, 0.5f).withTimeStamp (8.0)); - - beginTest ("Start & end time"); - expectEquals (s.getStartTime(), 0.0); - expectEquals (s.getEndTime(), 8.0); - expectEquals (s.getEventTime (1), 2.0); - - beginTest ("Matching note off & ons"); - s.updateMatchedPairs(); - expectEquals (s.getTimeOfMatchingKeyUp (0), 4.0); - expectEquals (s.getTimeOfMatchingKeyUp (1), 8.0); - expectEquals (s.getIndexOfMatchingKeyUp (0), 2); - expectEquals (s.getIndexOfMatchingKeyUp (1), 3); - - beginTest ("Time & indices"); - expectEquals (s.getNextIndexAtTime (0.5), 1); - expectEquals (s.getNextIndexAtTime (2.5), 2); - expectEquals (s.getNextIndexAtTime (9.0), 4); - - beginTest ("Deleting events"); - s.deleteEvent (0, true); - expectEquals (s.getNumEvents(), 2); - - beginTest ("Merging sequences"); - MidiMessageSequence s2; - s2.addEvent (MidiMessage::noteOn (2, 25, 0.5f).withTimeStamp (0.0)); - s2.addEvent (MidiMessage::noteOn (2, 40, 0.5f).withTimeStamp (1.0)); - s2.addEvent (MidiMessage::noteOff (2, 40, 0.5f).withTimeStamp (5.0)); - s2.addEvent (MidiMessage::noteOn (2, 80, 0.5f).withTimeStamp (3.0)); - s2.addEvent (MidiMessage::noteOff (2, 80, 0.5f).withTimeStamp (7.0)); - s2.addEvent (MidiMessage::noteOff (2, 25, 0.5f).withTimeStamp (9.0)); - - s.addSequence (s2, 0.0, 0.0, 8.0); // Intentionally cut off the last note off - s.updateMatchedPairs(); - - expectEquals (s.getNumEvents(), 7); - expectEquals (s.getIndexOfMatchingKeyUp (0), -1); // Truncated note, should be no note off - expectEquals (s.getTimeOfMatchingKeyUp (1), 5.0); - } -}; - -static MidiMessageSequenceTest midiMessageSequenceTests; - -#endif - -} // namespace juce +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2017 - ROLI Ltd. + + JUCE is an open source library subject to commercial or open-source + licensing. + + The code included in this file is provided under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license. Permission + To use, copy, modify, and/or distribute this software for any purpose with or + without fee is hereby granted provided that the above copyright notice and + this permission notice appear in all copies. + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +namespace juce +{ + +MidiMessageSequence::MidiEventHolder::MidiEventHolder (const MidiMessage& mm) : message (mm) {} +MidiMessageSequence::MidiEventHolder::MidiEventHolder (MidiMessage&& mm) : message (std::move (mm)) {} +MidiMessageSequence::MidiEventHolder::~MidiEventHolder() {} + +//============================================================================== +MidiMessageSequence::MidiMessageSequence() +{ +} + +MidiMessageSequence::MidiMessageSequence (const MidiMessageSequence& other) +{ + list.addCopiesOf (other.list); + + for (int i = 0; i < list.size(); ++i) + { + auto noteOffIndex = other.getIndexOfMatchingKeyUp (i); + + if (noteOffIndex >= 0) + list.getUnchecked(i)->noteOffObject = list.getUnchecked (noteOffIndex); + } +} + +MidiMessageSequence& MidiMessageSequence::operator= (const MidiMessageSequence& other) +{ + MidiMessageSequence otherCopy (other); + swapWith (otherCopy); + return *this; +} + +MidiMessageSequence::MidiMessageSequence (MidiMessageSequence&& other) noexcept + : list (std::move (other.list)) +{ +} + +MidiMessageSequence& MidiMessageSequence::operator= (MidiMessageSequence&& other) noexcept +{ + list = std::move (other.list); + return *this; +} + +MidiMessageSequence::~MidiMessageSequence() +{ +} + +void MidiMessageSequence::swapWith (MidiMessageSequence& other) noexcept +{ + list.swapWith (other.list); +} + +void MidiMessageSequence::clear() +{ + list.clear(); +} + +int MidiMessageSequence::getNumEvents() const noexcept +{ + return list.size(); +} + +MidiMessageSequence::MidiEventHolder* MidiMessageSequence::getEventPointer (int index) const noexcept +{ + return list[index]; +} + +MidiMessageSequence::MidiEventHolder** MidiMessageSequence::begin() noexcept { return list.begin(); } +MidiMessageSequence::MidiEventHolder* const* MidiMessageSequence::begin() const noexcept { return list.begin(); } +MidiMessageSequence::MidiEventHolder** MidiMessageSequence::end() noexcept { return list.end(); } +MidiMessageSequence::MidiEventHolder* const* MidiMessageSequence::end() const noexcept { return list.end(); } + +double MidiMessageSequence::getTimeOfMatchingKeyUp (int index) const noexcept +{ + if (auto* meh = list[index]) + if (auto* noteOff = meh->noteOffObject) + return noteOff->message.getTimeStamp(); + + return 0; +} + +int MidiMessageSequence::getIndexOfMatchingKeyUp (int index) const noexcept +{ + if (auto* meh = list[index]) + { + if (auto* noteOff = meh->noteOffObject) + { + for (int i = index; i < list.size(); ++i) + if (list.getUnchecked(i) == noteOff) + return i; + + jassertfalse; // we've somehow got a pointer to a note-off object that isn't in the sequence + } + } + + return -1; +} + +int MidiMessageSequence::getIndexOf (const MidiEventHolder* event) const noexcept +{ + return list.indexOf (event); +} + +int MidiMessageSequence::getNextIndexAtTime (double timeStamp) const noexcept +{ + auto numEvents = list.size(); + int i; + + for (i = 0; i < numEvents; ++i) + if (list.getUnchecked(i)->message.getTimeStamp() >= timeStamp) + break; + + return i; +} + +//============================================================================== +double MidiMessageSequence::getStartTime() const noexcept +{ + return getEventTime (0); +} + +double MidiMessageSequence::getEndTime() const noexcept +{ + return getEventTime (list.size() - 1); +} + +double MidiMessageSequence::getEventTime (const int index) const noexcept +{ + if (auto* meh = list[index]) + return meh->message.getTimeStamp(); + + return 0; +} + +//============================================================================== +MidiMessageSequence::MidiEventHolder* MidiMessageSequence::addEvent (MidiEventHolder* newEvent, double timeAdjustment) +{ + newEvent->message.addToTimeStamp (timeAdjustment); + auto time = newEvent->message.getTimeStamp(); + int i; + + for (i = list.size(); --i >= 0;) + if (list.getUnchecked(i)->message.getTimeStamp() <= time) + break; + + list.insert (i + 1, newEvent); + return newEvent; +} + +MidiMessageSequence::MidiEventHolder* MidiMessageSequence::addEvent (const MidiMessage& newMessage, double timeAdjustment) +{ + return addEvent (new MidiEventHolder (newMessage), timeAdjustment); +} + +MidiMessageSequence::MidiEventHolder* MidiMessageSequence::addEvent (MidiMessage&& newMessage, double timeAdjustment) +{ + return addEvent (new MidiEventHolder (std::move (newMessage)), timeAdjustment); +} + +void MidiMessageSequence::deleteEvent (int index, bool deleteMatchingNoteUp) +{ + if (isPositiveAndBelow (index, list.size())) + { + if (deleteMatchingNoteUp) + deleteEvent (getIndexOfMatchingKeyUp (index), false); + + list.remove (index); + } +} + +void MidiMessageSequence::addSequence (const MidiMessageSequence& other, double timeAdjustment) +{ + for (auto* m : other) + { + auto newOne = new MidiEventHolder (m->message); + newOne->message.addToTimeStamp (timeAdjustment); + list.add (newOne); + } + + sort(); +} + +void MidiMessageSequence::addSequence (const MidiMessageSequence& other, + double timeAdjustment, + double firstAllowableTime, + double endOfAllowableDestTimes) +{ + for (auto* m : other) + { + auto t = m->message.getTimeStamp() + timeAdjustment; + + if (t >= firstAllowableTime && t < endOfAllowableDestTimes) + { + auto newOne = new MidiEventHolder (m->message); + newOne->message.setTimeStamp (t); + list.add (newOne); + } + } + + sort(); +} + +void MidiMessageSequence::sort() noexcept +{ + std::stable_sort (list.begin(), list.end(), + [] (const MidiEventHolder* a, const MidiEventHolder* b) { return a->message.getTimeStamp() < b->message.getTimeStamp(); }); +} + +void MidiMessageSequence::updateMatchedPairs() noexcept +{ + for (int i = 0; i < list.size(); ++i) + { + auto* meh = list.getUnchecked(i); + auto& m1 = meh->message; + + if (m1.isNoteOn()) + { + meh->noteOffObject = nullptr; + auto note = m1.getNoteNumber(); + auto chan = m1.getChannel(); + auto len = list.size(); + + for (int j = i + 1; j < len; ++j) + { + auto* meh2 = list.getUnchecked(j); + auto& m = meh2->message; + + if (m.getNoteNumber() == note && m.getChannel() == chan) + { + if (m.isNoteOff()) + { + meh->noteOffObject = meh2; + break; + } + + if (m.isNoteOn()) + { + auto newEvent = new MidiEventHolder (MidiMessage::noteOff (chan, note)); + list.insert (j, newEvent); + newEvent->message.setTimeStamp (m.getTimeStamp()); + meh->noteOffObject = newEvent; + break; + } + } + } + } + } +} + +void MidiMessageSequence::addTimeToMessages (double delta) noexcept +{ + if (delta != 0) + for (auto* m : list) + m->message.addToTimeStamp (delta); +} + +//============================================================================== +void MidiMessageSequence::extractMidiChannelMessages (const int channelNumberToExtract, + MidiMessageSequence& destSequence, + const bool alsoIncludeMetaEvents) const +{ + for (auto* meh : list) + if (meh->message.isForChannel (channelNumberToExtract) + || (alsoIncludeMetaEvents && meh->message.isMetaEvent())) + destSequence.addEvent (meh->message); +} + +void MidiMessageSequence::extractSysExMessages (MidiMessageSequence& destSequence) const +{ + for (auto* meh : list) + if (meh->message.isSysEx()) + destSequence.addEvent (meh->message); +} + +void MidiMessageSequence::deleteMidiChannelMessages (const int channelNumberToRemove) +{ + for (int i = list.size(); --i >= 0;) + if (list.getUnchecked(i)->message.isForChannel (channelNumberToRemove)) + list.remove(i); +} + +void MidiMessageSequence::deleteSysExMessages() +{ + for (int i = list.size(); --i >= 0;) + if (list.getUnchecked(i)->message.isSysEx()) + list.remove(i); +} + +//============================================================================== +void MidiMessageSequence::createControllerUpdatesForTime (int channelNumber, double time, Array& dest) +{ + bool doneProg = false; + bool donePitchWheel = false; + bool doneControllers[128] = {}; + + for (int i = list.size(); --i >= 0;) + { + auto& mm = list.getUnchecked(i)->message; + + if (mm.isForChannel (channelNumber) && mm.getTimeStamp() <= time) + { + if (mm.isProgramChange() && ! doneProg) + { + doneProg = true; + dest.add (MidiMessage (mm, 0.0)); + } + else if (mm.isPitchWheel() && ! donePitchWheel) + { + donePitchWheel = true; + dest.add (MidiMessage (mm, 0.0)); + } + else if (mm.isController()) + { + auto controllerNumber = mm.getControllerNumber(); + jassert (isPositiveAndBelow (controllerNumber, 128)); + + if (! doneControllers[controllerNumber]) + { + doneControllers[controllerNumber] = true; + dest.add (MidiMessage (mm, 0.0)); + } + } + } + } +} + + +//============================================================================== +//============================================================================== +#if JUCE_UNIT_TESTS + +struct MidiMessageSequenceTest : public UnitTest +{ + MidiMessageSequenceTest() + : UnitTest ("MidiMessageSequence", UnitTestCategories::midi) + {} + + void runTest() override + { + MidiMessageSequence s; + + s.addEvent (MidiMessage::noteOn (1, 60, 0.5f).withTimeStamp (0.0)); + s.addEvent (MidiMessage::noteOff (1, 60, 0.5f).withTimeStamp (4.0)); + s.addEvent (MidiMessage::noteOn (1, 30, 0.5f).withTimeStamp (2.0)); + s.addEvent (MidiMessage::noteOff (1, 30, 0.5f).withTimeStamp (8.0)); + + beginTest ("Start & end time"); + expectEquals (s.getStartTime(), 0.0); + expectEquals (s.getEndTime(), 8.0); + expectEquals (s.getEventTime (1), 2.0); + + beginTest ("Matching note off & ons"); + s.updateMatchedPairs(); + expectEquals (s.getTimeOfMatchingKeyUp (0), 4.0); + expectEquals (s.getTimeOfMatchingKeyUp (1), 8.0); + expectEquals (s.getIndexOfMatchingKeyUp (0), 2); + expectEquals (s.getIndexOfMatchingKeyUp (1), 3); + + beginTest ("Time & indices"); + expectEquals (s.getNextIndexAtTime (0.5), 1); + expectEquals (s.getNextIndexAtTime (2.5), 2); + expectEquals (s.getNextIndexAtTime (9.0), 4); + + beginTest ("Deleting events"); + s.deleteEvent (0, true); + expectEquals (s.getNumEvents(), 2); + + beginTest ("Merging sequences"); + MidiMessageSequence s2; + s2.addEvent (MidiMessage::noteOn (2, 25, 0.5f).withTimeStamp (0.0)); + s2.addEvent (MidiMessage::noteOn (2, 40, 0.5f).withTimeStamp (1.0)); + s2.addEvent (MidiMessage::noteOff (2, 40, 0.5f).withTimeStamp (5.0)); + s2.addEvent (MidiMessage::noteOn (2, 80, 0.5f).withTimeStamp (3.0)); + s2.addEvent (MidiMessage::noteOff (2, 80, 0.5f).withTimeStamp (7.0)); + s2.addEvent (MidiMessage::noteOff (2, 25, 0.5f).withTimeStamp (9.0)); + + s.addSequence (s2, 0.0, 0.0, 8.0); // Intentionally cut off the last note off + s.updateMatchedPairs(); + + expectEquals (s.getNumEvents(), 7); + expectEquals (s.getIndexOfMatchingKeyUp (0), -1); // Truncated note, should be no note off + expectEquals (s.getTimeOfMatchingKeyUp (1), 5.0); + } +}; + +static MidiMessageSequenceTest midiMessageSequenceTests; + +#endif + +} // namespace juce diff --git a/JuceLibraryCode/modules/juce_audio_basics/midi/juce_MidiMessageSequence.h b/JuceLibraryCode/modules/juce_audio_basics/midi/juce_MidiMessageSequence.h index 5d4187e..9068ce1 100644 --- a/JuceLibraryCode/modules/juce_audio_basics/midi/juce_MidiMessageSequence.h +++ b/JuceLibraryCode/modules/juce_audio_basics/midi/juce_MidiMessageSequence.h @@ -1,306 +1,306 @@ -/* - ============================================================================== - - This file is part of the JUCE library. - Copyright (c) 2017 - ROLI Ltd. - - JUCE is an open source library subject to commercial or open-source - licensing. - - The code included in this file is provided under the terms of the ISC license - http://www.isc.org/downloads/software-support-policy/isc-license. Permission - To use, copy, modify, and/or distribute this software for any purpose with or - without fee is hereby granted provided that the above copyright notice and - this permission notice appear in all copies. - - JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER - EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE - DISCLAIMED. - - ============================================================================== -*/ - -namespace juce -{ - -//============================================================================== -/** - A sequence of timestamped midi messages. - - This allows the sequence to be manipulated, and also to be read from and - written to a standard midi file. - - @see MidiMessage, MidiFile - - @tags{Audio} -*/ -class JUCE_API MidiMessageSequence -{ -public: - //============================================================================== - /** Creates an empty midi sequence object. */ - MidiMessageSequence(); - - /** Creates a copy of another sequence. */ - MidiMessageSequence (const MidiMessageSequence&); - - /** Replaces this sequence with another one. */ - MidiMessageSequence& operator= (const MidiMessageSequence&); - - /** Move constructor */ - MidiMessageSequence (MidiMessageSequence&&) noexcept; - - /** Move assignment operator */ - MidiMessageSequence& operator= (MidiMessageSequence&&) noexcept; - - /** Destructor. */ - ~MidiMessageSequence(); - - //============================================================================== - /** Structure used to hold midi events in the sequence. - - These structures act as 'handles' on the events as they are moved about in - the list, and make it quick to find the matching note-offs for note-on events. - - @see MidiMessageSequence::getEventPointer - */ - class MidiEventHolder - { - public: - //============================================================================== - /** Destructor. */ - ~MidiEventHolder(); - - /** The message itself, whose timestamp is used to specify the event's time. */ - MidiMessage message; - - /** The matching note-off event (if this is a note-on event). - - If this isn't a note-on, this pointer will be nullptr. - - Use the MidiMessageSequence::updateMatchedPairs() method to keep these - note-offs up-to-date after events have been moved around in the sequence - or deleted. - */ - MidiEventHolder* noteOffObject = nullptr; - - private: - //============================================================================== - friend class MidiMessageSequence; - MidiEventHolder (const MidiMessage&); - MidiEventHolder (MidiMessage&&); - JUCE_LEAK_DETECTOR (MidiEventHolder) - }; - - //============================================================================== - /** Clears the sequence. */ - void clear(); - - /** Returns the number of events in the sequence. */ - int getNumEvents() const noexcept; - - /** Returns a pointer to one of the events. */ - MidiEventHolder* getEventPointer (int index) const noexcept; - - /** Iterator for the list of MidiEventHolders */ - MidiEventHolder** begin() noexcept; - - /** Iterator for the list of MidiEventHolders */ - MidiEventHolder* const* begin() const noexcept; - - /** Iterator for the list of MidiEventHolders */ - MidiEventHolder** end() noexcept; - - /** Iterator for the list of MidiEventHolders */ - MidiEventHolder* const* end() const noexcept; - - /** Returns the time of the note-up that matches the note-on at this index. - If the event at this index isn't a note-on, it'll just return 0. - @see MidiMessageSequence::MidiEventHolder::noteOffObject - */ - double getTimeOfMatchingKeyUp (int index) const noexcept; - - /** Returns the index of the note-up that matches the note-on at this index. - If the event at this index isn't a note-on, it'll just return -1. - @see MidiMessageSequence::MidiEventHolder::noteOffObject - */ - int getIndexOfMatchingKeyUp (int index) const noexcept; - - /** Returns the index of an event. */ - int getIndexOf (const MidiEventHolder* event) const noexcept; - - /** Returns the index of the first event on or after the given timestamp. - If the time is beyond the end of the sequence, this will return the - number of events. - */ - int getNextIndexAtTime (double timeStamp) const noexcept; - - //============================================================================== - /** Returns the timestamp of the first event in the sequence. - @see getEndTime - */ - double getStartTime() const noexcept; - - /** Returns the timestamp of the last event in the sequence. - @see getStartTime - */ - double getEndTime() const noexcept; - - /** Returns the timestamp of the event at a given index. - If the index is out-of-range, this will return 0.0 - */ - double getEventTime (int index) const noexcept; - - //============================================================================== - /** Inserts a midi message into the sequence. - - The index at which the new message gets inserted will depend on its timestamp, - because the sequence is kept sorted. - - Remember to call updateMatchedPairs() after adding note-on events. - - @param newMessage the new message to add (an internal copy will be made) - @param timeAdjustment an optional value to add to the timestamp of the message - that will be inserted - @see updateMatchedPairs - */ - MidiEventHolder* addEvent (const MidiMessage& newMessage, double timeAdjustment = 0); - - /** Inserts a midi message into the sequence. - - The index at which the new message gets inserted will depend on its timestamp, - because the sequence is kept sorted. - - Remember to call updateMatchedPairs() after adding note-on events. - - @param newMessage the new message to add (an internal copy will be made) - @param timeAdjustment an optional value to add to the timestamp of the message - that will be inserted - @see updateMatchedPairs - */ - MidiEventHolder* addEvent (MidiMessage&& newMessage, double timeAdjustment = 0); - - /** Deletes one of the events in the sequence. - - Remember to call updateMatchedPairs() after removing events. - - @param index the index of the event to delete - @param deleteMatchingNoteUp whether to also remove the matching note-off - if the event you're removing is a note-on - */ - void deleteEvent (int index, bool deleteMatchingNoteUp); - - /** Merges another sequence into this one. - Remember to call updateMatchedPairs() after using this method. - - @param other the sequence to add from - @param timeAdjustmentDelta an amount to add to the timestamps of the midi events - as they are read from the other sequence - @param firstAllowableDestTime events will not be added if their time is earlier - than this time. (This is after their time has been adjusted - by the timeAdjustmentDelta) - @param endOfAllowableDestTimes events will not be added if their time is equal to - or greater than this time. (This is after their time has - been adjusted by the timeAdjustmentDelta) - */ - void addSequence (const MidiMessageSequence& other, - double timeAdjustmentDelta, - double firstAllowableDestTime, - double endOfAllowableDestTimes); - - /** Merges another sequence into this one. - Remember to call updateMatchedPairs() after using this method. - - @param other the sequence to add from - @param timeAdjustmentDelta an amount to add to the timestamps of the midi events - as they are read from the other sequence - */ - void addSequence (const MidiMessageSequence& other, - double timeAdjustmentDelta); - - //============================================================================== - /** Makes sure all the note-on and note-off pairs are up-to-date. - - Call this after re-ordering messages or deleting/adding messages, and it - will scan the list and make sure all the note-offs in the MidiEventHolder - structures are pointing at the correct ones. - */ - void updateMatchedPairs() noexcept; - - /** Forces a sort of the sequence. - You may need to call this if you've manually modified the timestamps of some - events such that the overall order now needs updating. - */ - void sort() noexcept; - - //============================================================================== - /** Copies all the messages for a particular midi channel to another sequence. - - @param channelNumberToExtract the midi channel to look for, in the range 1 to 16 - @param destSequence the sequence that the chosen events should be copied to - @param alsoIncludeMetaEvents if true, any meta-events (which don't apply to a specific - channel) will also be copied across. - @see extractSysExMessages - */ - void extractMidiChannelMessages (int channelNumberToExtract, - MidiMessageSequence& destSequence, - bool alsoIncludeMetaEvents) const; - - /** Copies all midi sys-ex messages to another sequence. - @param destSequence this is the sequence to which any sys-exes in this sequence - will be added - @see extractMidiChannelMessages - */ - void extractSysExMessages (MidiMessageSequence& destSequence) const; - - /** Removes any messages in this sequence that have a specific midi channel. - @param channelNumberToRemove the midi channel to look for, in the range 1 to 16 - */ - void deleteMidiChannelMessages (int channelNumberToRemove); - - /** Removes any sys-ex messages from this sequence. */ - void deleteSysExMessages(); - - /** Adds an offset to the timestamps of all events in the sequence. - @param deltaTime the amount to add to each timestamp. - */ - void addTimeToMessages (double deltaTime) noexcept; - - //============================================================================== - /** Scans through the sequence to determine the state of any midi controllers at - a given time. - - This will create a sequence of midi controller changes that can be - used to set all midi controllers to the state they would be in at the - specified time within this sequence. - - As well as controllers, it will also recreate the midi program number - and pitch bend position. - - @param channelNumber the midi channel to look for, in the range 1 to 16. Controllers - for other channels will be ignored. - @param time the time at which you want to find out the state - there are - no explicit units for this time measurement, it's the same units - as used for the timestamps of the messages - @param resultMessages an array to which midi controller-change messages will be added. This - will be the minimum number of controller changes to recreate the - state at the required time. - */ - void createControllerUpdatesForTime (int channelNumber, double time, - Array& resultMessages); - - //============================================================================== - /** Swaps this sequence with another one. */ - void swapWith (MidiMessageSequence&) noexcept; - -private: - //============================================================================== - friend class MidiFile; - OwnedArray list; - - MidiEventHolder* addEvent (MidiEventHolder*, double); - - JUCE_LEAK_DETECTOR (MidiMessageSequence) -}; - -} // namespace juce +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2017 - ROLI Ltd. + + JUCE is an open source library subject to commercial or open-source + licensing. + + The code included in this file is provided under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license. Permission + To use, copy, modify, and/or distribute this software for any purpose with or + without fee is hereby granted provided that the above copyright notice and + this permission notice appear in all copies. + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +namespace juce +{ + +//============================================================================== +/** + A sequence of timestamped midi messages. + + This allows the sequence to be manipulated, and also to be read from and + written to a standard midi file. + + @see MidiMessage, MidiFile + + @tags{Audio} +*/ +class JUCE_API MidiMessageSequence +{ +public: + //============================================================================== + /** Creates an empty midi sequence object. */ + MidiMessageSequence(); + + /** Creates a copy of another sequence. */ + MidiMessageSequence (const MidiMessageSequence&); + + /** Replaces this sequence with another one. */ + MidiMessageSequence& operator= (const MidiMessageSequence&); + + /** Move constructor */ + MidiMessageSequence (MidiMessageSequence&&) noexcept; + + /** Move assignment operator */ + MidiMessageSequence& operator= (MidiMessageSequence&&) noexcept; + + /** Destructor. */ + ~MidiMessageSequence(); + + //============================================================================== + /** Structure used to hold midi events in the sequence. + + These structures act as 'handles' on the events as they are moved about in + the list, and make it quick to find the matching note-offs for note-on events. + + @see MidiMessageSequence::getEventPointer + */ + class MidiEventHolder + { + public: + //============================================================================== + /** Destructor. */ + ~MidiEventHolder(); + + /** The message itself, whose timestamp is used to specify the event's time. */ + MidiMessage message; + + /** The matching note-off event (if this is a note-on event). + + If this isn't a note-on, this pointer will be nullptr. + + Use the MidiMessageSequence::updateMatchedPairs() method to keep these + note-offs up-to-date after events have been moved around in the sequence + or deleted. + */ + MidiEventHolder* noteOffObject = nullptr; + + private: + //============================================================================== + friend class MidiMessageSequence; + MidiEventHolder (const MidiMessage&); + MidiEventHolder (MidiMessage&&); + JUCE_LEAK_DETECTOR (MidiEventHolder) + }; + + //============================================================================== + /** Clears the sequence. */ + void clear(); + + /** Returns the number of events in the sequence. */ + int getNumEvents() const noexcept; + + /** Returns a pointer to one of the events. */ + MidiEventHolder* getEventPointer (int index) const noexcept; + + /** Iterator for the list of MidiEventHolders */ + MidiEventHolder** begin() noexcept; + + /** Iterator for the list of MidiEventHolders */ + MidiEventHolder* const* begin() const noexcept; + + /** Iterator for the list of MidiEventHolders */ + MidiEventHolder** end() noexcept; + + /** Iterator for the list of MidiEventHolders */ + MidiEventHolder* const* end() const noexcept; + + /** Returns the time of the note-up that matches the note-on at this index. + If the event at this index isn't a note-on, it'll just return 0. + @see MidiMessageSequence::MidiEventHolder::noteOffObject + */ + double getTimeOfMatchingKeyUp (int index) const noexcept; + + /** Returns the index of the note-up that matches the note-on at this index. + If the event at this index isn't a note-on, it'll just return -1. + @see MidiMessageSequence::MidiEventHolder::noteOffObject + */ + int getIndexOfMatchingKeyUp (int index) const noexcept; + + /** Returns the index of an event. */ + int getIndexOf (const MidiEventHolder* event) const noexcept; + + /** Returns the index of the first event on or after the given timestamp. + If the time is beyond the end of the sequence, this will return the + number of events. + */ + int getNextIndexAtTime (double timeStamp) const noexcept; + + //============================================================================== + /** Returns the timestamp of the first event in the sequence. + @see getEndTime + */ + double getStartTime() const noexcept; + + /** Returns the timestamp of the last event in the sequence. + @see getStartTime + */ + double getEndTime() const noexcept; + + /** Returns the timestamp of the event at a given index. + If the index is out-of-range, this will return 0.0 + */ + double getEventTime (int index) const noexcept; + + //============================================================================== + /** Inserts a midi message into the sequence. + + The index at which the new message gets inserted will depend on its timestamp, + because the sequence is kept sorted. + + Remember to call updateMatchedPairs() after adding note-on events. + + @param newMessage the new message to add (an internal copy will be made) + @param timeAdjustment an optional value to add to the timestamp of the message + that will be inserted + @see updateMatchedPairs + */ + MidiEventHolder* addEvent (const MidiMessage& newMessage, double timeAdjustment = 0); + + /** Inserts a midi message into the sequence. + + The index at which the new message gets inserted will depend on its timestamp, + because the sequence is kept sorted. + + Remember to call updateMatchedPairs() after adding note-on events. + + @param newMessage the new message to add (an internal copy will be made) + @param timeAdjustment an optional value to add to the timestamp of the message + that will be inserted + @see updateMatchedPairs + */ + MidiEventHolder* addEvent (MidiMessage&& newMessage, double timeAdjustment = 0); + + /** Deletes one of the events in the sequence. + + Remember to call updateMatchedPairs() after removing events. + + @param index the index of the event to delete + @param deleteMatchingNoteUp whether to also remove the matching note-off + if the event you're removing is a note-on + */ + void deleteEvent (int index, bool deleteMatchingNoteUp); + + /** Merges another sequence into this one. + Remember to call updateMatchedPairs() after using this method. + + @param other the sequence to add from + @param timeAdjustmentDelta an amount to add to the timestamps of the midi events + as they are read from the other sequence + @param firstAllowableDestTime events will not be added if their time is earlier + than this time. (This is after their time has been adjusted + by the timeAdjustmentDelta) + @param endOfAllowableDestTimes events will not be added if their time is equal to + or greater than this time. (This is after their time has + been adjusted by the timeAdjustmentDelta) + */ + void addSequence (const MidiMessageSequence& other, + double timeAdjustmentDelta, + double firstAllowableDestTime, + double endOfAllowableDestTimes); + + /** Merges another sequence into this one. + Remember to call updateMatchedPairs() after using this method. + + @param other the sequence to add from + @param timeAdjustmentDelta an amount to add to the timestamps of the midi events + as they are read from the other sequence + */ + void addSequence (const MidiMessageSequence& other, + double timeAdjustmentDelta); + + //============================================================================== + /** Makes sure all the note-on and note-off pairs are up-to-date. + + Call this after re-ordering messages or deleting/adding messages, and it + will scan the list and make sure all the note-offs in the MidiEventHolder + structures are pointing at the correct ones. + */ + void updateMatchedPairs() noexcept; + + /** Forces a sort of the sequence. + You may need to call this if you've manually modified the timestamps of some + events such that the overall order now needs updating. + */ + void sort() noexcept; + + //============================================================================== + /** Copies all the messages for a particular midi channel to another sequence. + + @param channelNumberToExtract the midi channel to look for, in the range 1 to 16 + @param destSequence the sequence that the chosen events should be copied to + @param alsoIncludeMetaEvents if true, any meta-events (which don't apply to a specific + channel) will also be copied across. + @see extractSysExMessages + */ + void extractMidiChannelMessages (int channelNumberToExtract, + MidiMessageSequence& destSequence, + bool alsoIncludeMetaEvents) const; + + /** Copies all midi sys-ex messages to another sequence. + @param destSequence this is the sequence to which any sys-exes in this sequence + will be added + @see extractMidiChannelMessages + */ + void extractSysExMessages (MidiMessageSequence& destSequence) const; + + /** Removes any messages in this sequence that have a specific midi channel. + @param channelNumberToRemove the midi channel to look for, in the range 1 to 16 + */ + void deleteMidiChannelMessages (int channelNumberToRemove); + + /** Removes any sys-ex messages from this sequence. */ + void deleteSysExMessages(); + + /** Adds an offset to the timestamps of all events in the sequence. + @param deltaTime the amount to add to each timestamp. + */ + void addTimeToMessages (double deltaTime) noexcept; + + //============================================================================== + /** Scans through the sequence to determine the state of any midi controllers at + a given time. + + This will create a sequence of midi controller changes that can be + used to set all midi controllers to the state they would be in at the + specified time within this sequence. + + As well as controllers, it will also recreate the midi program number + and pitch bend position. + + @param channelNumber the midi channel to look for, in the range 1 to 16. Controllers + for other channels will be ignored. + @param time the time at which you want to find out the state - there are + no explicit units for this time measurement, it's the same units + as used for the timestamps of the messages + @param resultMessages an array to which midi controller-change messages will be added. This + will be the minimum number of controller changes to recreate the + state at the required time. + */ + void createControllerUpdatesForTime (int channelNumber, double time, + Array& resultMessages); + + //============================================================================== + /** Swaps this sequence with another one. */ + void swapWith (MidiMessageSequence&) noexcept; + +private: + //============================================================================== + friend class MidiFile; + OwnedArray list; + + MidiEventHolder* addEvent (MidiEventHolder*, double); + + JUCE_LEAK_DETECTOR (MidiMessageSequence) +}; + +} // namespace juce diff --git a/JuceLibraryCode/modules/juce_audio_basics/midi/juce_MidiRPN.cpp b/JuceLibraryCode/modules/juce_audio_basics/midi/juce_MidiRPN.cpp index 10e8e03..586e7db 100644 --- a/JuceLibraryCode/modules/juce_audio_basics/midi/juce_MidiRPN.cpp +++ b/JuceLibraryCode/modules/juce_audio_basics/midi/juce_MidiRPN.cpp @@ -1,381 +1,381 @@ -/* - ============================================================================== - - This file is part of the JUCE library. - Copyright (c) 2017 - ROLI Ltd. - - JUCE is an open source library subject to commercial or open-source - licensing. - - The code included in this file is provided under the terms of the ISC license - http://www.isc.org/downloads/software-support-policy/isc-license. Permission - To use, copy, modify, and/or distribute this software for any purpose with or - without fee is hereby granted provided that the above copyright notice and - this permission notice appear in all copies. - - JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER - EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE - DISCLAIMED. - - ============================================================================== -*/ - -namespace juce -{ - -MidiRPNDetector::MidiRPNDetector() noexcept -{ -} - -MidiRPNDetector::~MidiRPNDetector() noexcept -{ -} - -bool MidiRPNDetector::parseControllerMessage (int midiChannel, - int controllerNumber, - int controllerValue, - MidiRPNMessage& result) noexcept -{ - jassert (midiChannel >= 1 && midiChannel <= 16); - jassert (controllerNumber >= 0 && controllerNumber < 128); - jassert (controllerValue >= 0 && controllerValue < 128); - - return states[midiChannel - 1].handleController (midiChannel, controllerNumber, controllerValue, result); -} - -void MidiRPNDetector::reset() noexcept -{ - for (int i = 0; i < 16; ++i) - { - states[i].parameterMSB = 0xff; - states[i].parameterLSB = 0xff; - states[i].resetValue(); - states[i].isNRPN = false; - } -} - -//============================================================================== -MidiRPNDetector::ChannelState::ChannelState() noexcept - : parameterMSB (0xff), parameterLSB (0xff), valueMSB (0xff), valueLSB (0xff), isNRPN (false) -{ -} - -bool MidiRPNDetector::ChannelState::handleController (int channel, - int controllerNumber, - int value, - MidiRPNMessage& result) noexcept -{ - switch (controllerNumber) - { - case 0x62: parameterLSB = uint8 (value); resetValue(); isNRPN = true; break; - case 0x63: parameterMSB = uint8 (value); resetValue(); isNRPN = true; break; - - case 0x64: parameterLSB = uint8 (value); resetValue(); isNRPN = false; break; - case 0x65: parameterMSB = uint8 (value); resetValue(); isNRPN = false; break; - - case 0x06: valueMSB = uint8 (value); return sendIfReady (channel, result); - case 0x26: valueLSB = uint8 (value); break; - - default: break; - } - - return false; -} - -void MidiRPNDetector::ChannelState::resetValue() noexcept -{ - valueMSB = 0xff; - valueLSB = 0xff; -} - -//============================================================================== -bool MidiRPNDetector::ChannelState::sendIfReady (int channel, MidiRPNMessage& result) noexcept -{ - if (parameterMSB < 0x80 && parameterLSB < 0x80) - { - if (valueMSB < 0x80) - { - result.channel = channel; - result.parameterNumber = (parameterMSB << 7) + parameterLSB; - result.isNRPN = isNRPN; - - if (valueLSB < 0x80) - { - result.value = (valueMSB << 7) + valueLSB; - result.is14BitValue = true; - } - else - { - result.value = valueMSB; - result.is14BitValue = false; - } - - return true; - } - } - - return false; -} - -//============================================================================== -MidiBuffer MidiRPNGenerator::generate (MidiRPNMessage message) -{ - return generate (message.channel, - message.parameterNumber, - message.value, - message.isNRPN, - message.is14BitValue); -} - -MidiBuffer MidiRPNGenerator::generate (int midiChannel, - int parameterNumber, - int value, - bool isNRPN, - bool use14BitValue) -{ - jassert (midiChannel > 0 && midiChannel <= 16); - jassert (parameterNumber >= 0 && parameterNumber < 16384); - jassert (value >= 0 && value < (use14BitValue ? 16384 : 128)); - - uint8 parameterLSB = uint8 (parameterNumber & 0x0000007f); - uint8 parameterMSB = uint8 (parameterNumber >> 7); - - uint8 valueLSB = use14BitValue ? uint8 (value & 0x0000007f) : 0x00; - uint8 valueMSB = use14BitValue ? uint8 (value >> 7) : uint8 (value); - - uint8 channelByte = uint8 (0xb0 + midiChannel - 1); - - MidiBuffer buffer; - - buffer.addEvent (MidiMessage (channelByte, isNRPN ? 0x62 : 0x64, parameterLSB), 0); - buffer.addEvent (MidiMessage (channelByte, isNRPN ? 0x63 : 0x65, parameterMSB), 0); - - // sending the value LSB is optional, but must come before sending the value MSB: - if (use14BitValue) - buffer.addEvent (MidiMessage (channelByte, 0x26, valueLSB), 0); - - buffer.addEvent (MidiMessage (channelByte, 0x06, valueMSB), 0); - - return buffer; -} - - -//============================================================================== -//============================================================================== -#if JUCE_UNIT_TESTS - -class MidiRPNDetectorTests : public UnitTest -{ -public: - MidiRPNDetectorTests() - : UnitTest ("MidiRPNDetector class", UnitTestCategories::midi) - {} - - void runTest() override - { - beginTest ("7-bit RPN"); - { - MidiRPNDetector detector; - MidiRPNMessage rpn; - expect (! detector.parseControllerMessage (2, 101, 0, rpn)); - expect (! detector.parseControllerMessage (2, 100, 7, rpn)); - expect (detector.parseControllerMessage (2, 6, 42, rpn)); - - expectEquals (rpn.channel, 2); - expectEquals (rpn.parameterNumber, 7); - expectEquals (rpn.value, 42); - expect (! rpn.isNRPN); - expect (! rpn.is14BitValue); - } - - beginTest ("14-bit RPN"); - { - MidiRPNDetector detector; - MidiRPNMessage rpn; - expect (! detector.parseControllerMessage (1, 100, 44, rpn)); - expect (! detector.parseControllerMessage (1, 101, 2, rpn)); - expect (! detector.parseControllerMessage (1, 38, 94, rpn)); - expect (detector.parseControllerMessage (1, 6, 1, rpn)); - - expectEquals (rpn.channel, 1); - expectEquals (rpn.parameterNumber, 300); - expectEquals (rpn.value, 222); - expect (! rpn.isNRPN); - expect (rpn.is14BitValue); - } - - beginTest ("RPNs on multiple channels simultaneously"); - { - MidiRPNDetector detector; - MidiRPNMessage rpn; - expect (! detector.parseControllerMessage (1, 100, 44, rpn)); - expect (! detector.parseControllerMessage (2, 101, 0, rpn)); - expect (! detector.parseControllerMessage (1, 101, 2, rpn)); - expect (! detector.parseControllerMessage (2, 100, 7, rpn)); - expect (! detector.parseControllerMessage (1, 38, 94, rpn)); - expect (detector.parseControllerMessage (2, 6, 42, rpn)); - - expectEquals (rpn.channel, 2); - expectEquals (rpn.parameterNumber, 7); - expectEquals (rpn.value, 42); - expect (! rpn.isNRPN); - expect (! rpn.is14BitValue); - - expect (detector.parseControllerMessage (1, 6, 1, rpn)); - - expectEquals (rpn.channel, 1); - expectEquals (rpn.parameterNumber, 300); - expectEquals (rpn.value, 222); - expect (! rpn.isNRPN); - expect (rpn.is14BitValue); - } - - beginTest ("14-bit RPN with value within 7-bit range"); - { - MidiRPNDetector detector; - MidiRPNMessage rpn; - expect (! detector.parseControllerMessage (16, 100, 0 , rpn)); - expect (! detector.parseControllerMessage (16, 101, 0, rpn)); - expect (! detector.parseControllerMessage (16, 38, 3, rpn)); - expect (detector.parseControllerMessage (16, 6, 0, rpn)); - - expectEquals (rpn.channel, 16); - expectEquals (rpn.parameterNumber, 0); - expectEquals (rpn.value, 3); - expect (! rpn.isNRPN); - expect (rpn.is14BitValue); - } - - beginTest ("invalid RPN (wrong order)"); - { - MidiRPNDetector detector; - MidiRPNMessage rpn; - expect (! detector.parseControllerMessage (2, 6, 42, rpn)); - expect (! detector.parseControllerMessage (2, 101, 0, rpn)); - expect (! detector.parseControllerMessage (2, 100, 7, rpn)); - } - - beginTest ("14-bit RPN interspersed with unrelated CC messages"); - { - MidiRPNDetector detector; - MidiRPNMessage rpn; - expect (! detector.parseControllerMessage (16, 3, 80, rpn)); - expect (! detector.parseControllerMessage (16, 100, 0 , rpn)); - expect (! detector.parseControllerMessage (16, 4, 81, rpn)); - expect (! detector.parseControllerMessage (16, 101, 0, rpn)); - expect (! detector.parseControllerMessage (16, 5, 82, rpn)); - expect (! detector.parseControllerMessage (16, 5, 83, rpn)); - expect (! detector.parseControllerMessage (16, 38, 3, rpn)); - expect (! detector.parseControllerMessage (16, 4, 84, rpn)); - expect (! detector.parseControllerMessage (16, 3, 85, rpn)); - expect (detector.parseControllerMessage (16, 6, 0, rpn)); - - expectEquals (rpn.channel, 16); - expectEquals (rpn.parameterNumber, 0); - expectEquals (rpn.value, 3); - expect (! rpn.isNRPN); - expect (rpn.is14BitValue); - } - - beginTest ("14-bit NRPN"); - { - MidiRPNDetector detector; - MidiRPNMessage rpn; - expect (! detector.parseControllerMessage (1, 98, 44, rpn)); - expect (! detector.parseControllerMessage (1, 99 , 2, rpn)); - expect (! detector.parseControllerMessage (1, 38, 94, rpn)); - expect (detector.parseControllerMessage (1, 6, 1, rpn)); - - expectEquals (rpn.channel, 1); - expectEquals (rpn.parameterNumber, 300); - expectEquals (rpn.value, 222); - expect (rpn.isNRPN); - expect (rpn.is14BitValue); - } - - beginTest ("reset"); - { - MidiRPNDetector detector; - MidiRPNMessage rpn; - expect (! detector.parseControllerMessage (2, 101, 0, rpn)); - detector.reset(); - expect (! detector.parseControllerMessage (2, 100, 7, rpn)); - expect (! detector.parseControllerMessage (2, 6, 42, rpn)); - } - } -}; - -static MidiRPNDetectorTests MidiRPNDetectorUnitTests; - -//============================================================================== -class MidiRPNGeneratorTests : public UnitTest -{ -public: - MidiRPNGeneratorTests() - : UnitTest ("MidiRPNGenerator class", UnitTestCategories::midi) - {} - - void runTest() override - { - beginTest ("generating RPN/NRPN"); - { - { - MidiBuffer buffer = MidiRPNGenerator::generate (1, 23, 1337, true, true); - expectContainsRPN (buffer, 1, 23, 1337, true, true); - } - { - MidiBuffer buffer = MidiRPNGenerator::generate (16, 101, 34, false, false); - expectContainsRPN (buffer, 16, 101, 34, false, false); - } - { - MidiRPNMessage message = { 16, 101, 34, false, false }; - MidiBuffer buffer = MidiRPNGenerator::generate (message); - expectContainsRPN (buffer, message); - } - } - } - -private: - //============================================================================== - void expectContainsRPN (const MidiBuffer& midiBuffer, - int channel, - int parameterNumber, - int value, - bool isNRPN, - bool is14BitValue) - { - MidiRPNMessage expected = { channel, parameterNumber, value, isNRPN, is14BitValue }; - expectContainsRPN (midiBuffer, expected); - } - - //============================================================================== - void expectContainsRPN (const MidiBuffer& midiBuffer, MidiRPNMessage expected) - { - MidiBuffer::Iterator iter (midiBuffer); - MidiMessage midiMessage; - MidiRPNMessage result = MidiRPNMessage(); - MidiRPNDetector detector; - int samplePosition; // not actually used, so no need to initialise. - - while (iter.getNextEvent (midiMessage, samplePosition)) - { - if (detector.parseControllerMessage (midiMessage.getChannel(), - midiMessage.getControllerNumber(), - midiMessage.getControllerValue(), - result)) - break; - } - - expectEquals (result.channel, expected.channel); - expectEquals (result.parameterNumber, expected.parameterNumber); - expectEquals (result.value, expected.value); - expect (result.isNRPN == expected.isNRPN); - expect (result.is14BitValue == expected.is14BitValue); - } -}; - -static MidiRPNGeneratorTests MidiRPNGeneratorUnitTests; - -#endif - -} // namespace juce +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2017 - ROLI Ltd. + + JUCE is an open source library subject to commercial or open-source + licensing. + + The code included in this file is provided under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license. Permission + To use, copy, modify, and/or distribute this software for any purpose with or + without fee is hereby granted provided that the above copyright notice and + this permission notice appear in all copies. + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +namespace juce +{ + +MidiRPNDetector::MidiRPNDetector() noexcept +{ +} + +MidiRPNDetector::~MidiRPNDetector() noexcept +{ +} + +bool MidiRPNDetector::parseControllerMessage (int midiChannel, + int controllerNumber, + int controllerValue, + MidiRPNMessage& result) noexcept +{ + jassert (midiChannel >= 1 && midiChannel <= 16); + jassert (controllerNumber >= 0 && controllerNumber < 128); + jassert (controllerValue >= 0 && controllerValue < 128); + + return states[midiChannel - 1].handleController (midiChannel, controllerNumber, controllerValue, result); +} + +void MidiRPNDetector::reset() noexcept +{ + for (int i = 0; i < 16; ++i) + { + states[i].parameterMSB = 0xff; + states[i].parameterLSB = 0xff; + states[i].resetValue(); + states[i].isNRPN = false; + } +} + +//============================================================================== +MidiRPNDetector::ChannelState::ChannelState() noexcept + : parameterMSB (0xff), parameterLSB (0xff), valueMSB (0xff), valueLSB (0xff), isNRPN (false) +{ +} + +bool MidiRPNDetector::ChannelState::handleController (int channel, + int controllerNumber, + int value, + MidiRPNMessage& result) noexcept +{ + switch (controllerNumber) + { + case 0x62: parameterLSB = uint8 (value); resetValue(); isNRPN = true; break; + case 0x63: parameterMSB = uint8 (value); resetValue(); isNRPN = true; break; + + case 0x64: parameterLSB = uint8 (value); resetValue(); isNRPN = false; break; + case 0x65: parameterMSB = uint8 (value); resetValue(); isNRPN = false; break; + + case 0x06: valueMSB = uint8 (value); return sendIfReady (channel, result); + case 0x26: valueLSB = uint8 (value); break; + + default: break; + } + + return false; +} + +void MidiRPNDetector::ChannelState::resetValue() noexcept +{ + valueMSB = 0xff; + valueLSB = 0xff; +} + +//============================================================================== +bool MidiRPNDetector::ChannelState::sendIfReady (int channel, MidiRPNMessage& result) noexcept +{ + if (parameterMSB < 0x80 && parameterLSB < 0x80) + { + if (valueMSB < 0x80) + { + result.channel = channel; + result.parameterNumber = (parameterMSB << 7) + parameterLSB; + result.isNRPN = isNRPN; + + if (valueLSB < 0x80) + { + result.value = (valueMSB << 7) + valueLSB; + result.is14BitValue = true; + } + else + { + result.value = valueMSB; + result.is14BitValue = false; + } + + return true; + } + } + + return false; +} + +//============================================================================== +MidiBuffer MidiRPNGenerator::generate (MidiRPNMessage message) +{ + return generate (message.channel, + message.parameterNumber, + message.value, + message.isNRPN, + message.is14BitValue); +} + +MidiBuffer MidiRPNGenerator::generate (int midiChannel, + int parameterNumber, + int value, + bool isNRPN, + bool use14BitValue) +{ + jassert (midiChannel > 0 && midiChannel <= 16); + jassert (parameterNumber >= 0 && parameterNumber < 16384); + jassert (value >= 0 && value < (use14BitValue ? 16384 : 128)); + + uint8 parameterLSB = uint8 (parameterNumber & 0x0000007f); + uint8 parameterMSB = uint8 (parameterNumber >> 7); + + uint8 valueLSB = use14BitValue ? uint8 (value & 0x0000007f) : 0x00; + uint8 valueMSB = use14BitValue ? uint8 (value >> 7) : uint8 (value); + + uint8 channelByte = uint8 (0xb0 + midiChannel - 1); + + MidiBuffer buffer; + + buffer.addEvent (MidiMessage (channelByte, isNRPN ? 0x62 : 0x64, parameterLSB), 0); + buffer.addEvent (MidiMessage (channelByte, isNRPN ? 0x63 : 0x65, parameterMSB), 0); + + // sending the value LSB is optional, but must come before sending the value MSB: + if (use14BitValue) + buffer.addEvent (MidiMessage (channelByte, 0x26, valueLSB), 0); + + buffer.addEvent (MidiMessage (channelByte, 0x06, valueMSB), 0); + + return buffer; +} + + +//============================================================================== +//============================================================================== +#if JUCE_UNIT_TESTS + +class MidiRPNDetectorTests : public UnitTest +{ +public: + MidiRPNDetectorTests() + : UnitTest ("MidiRPNDetector class", UnitTestCategories::midi) + {} + + void runTest() override + { + beginTest ("7-bit RPN"); + { + MidiRPNDetector detector; + MidiRPNMessage rpn; + expect (! detector.parseControllerMessage (2, 101, 0, rpn)); + expect (! detector.parseControllerMessage (2, 100, 7, rpn)); + expect (detector.parseControllerMessage (2, 6, 42, rpn)); + + expectEquals (rpn.channel, 2); + expectEquals (rpn.parameterNumber, 7); + expectEquals (rpn.value, 42); + expect (! rpn.isNRPN); + expect (! rpn.is14BitValue); + } + + beginTest ("14-bit RPN"); + { + MidiRPNDetector detector; + MidiRPNMessage rpn; + expect (! detector.parseControllerMessage (1, 100, 44, rpn)); + expect (! detector.parseControllerMessage (1, 101, 2, rpn)); + expect (! detector.parseControllerMessage (1, 38, 94, rpn)); + expect (detector.parseControllerMessage (1, 6, 1, rpn)); + + expectEquals (rpn.channel, 1); + expectEquals (rpn.parameterNumber, 300); + expectEquals (rpn.value, 222); + expect (! rpn.isNRPN); + expect (rpn.is14BitValue); + } + + beginTest ("RPNs on multiple channels simultaneously"); + { + MidiRPNDetector detector; + MidiRPNMessage rpn; + expect (! detector.parseControllerMessage (1, 100, 44, rpn)); + expect (! detector.parseControllerMessage (2, 101, 0, rpn)); + expect (! detector.parseControllerMessage (1, 101, 2, rpn)); + expect (! detector.parseControllerMessage (2, 100, 7, rpn)); + expect (! detector.parseControllerMessage (1, 38, 94, rpn)); + expect (detector.parseControllerMessage (2, 6, 42, rpn)); + + expectEquals (rpn.channel, 2); + expectEquals (rpn.parameterNumber, 7); + expectEquals (rpn.value, 42); + expect (! rpn.isNRPN); + expect (! rpn.is14BitValue); + + expect (detector.parseControllerMessage (1, 6, 1, rpn)); + + expectEquals (rpn.channel, 1); + expectEquals (rpn.parameterNumber, 300); + expectEquals (rpn.value, 222); + expect (! rpn.isNRPN); + expect (rpn.is14BitValue); + } + + beginTest ("14-bit RPN with value within 7-bit range"); + { + MidiRPNDetector detector; + MidiRPNMessage rpn; + expect (! detector.parseControllerMessage (16, 100, 0 , rpn)); + expect (! detector.parseControllerMessage (16, 101, 0, rpn)); + expect (! detector.parseControllerMessage (16, 38, 3, rpn)); + expect (detector.parseControllerMessage (16, 6, 0, rpn)); + + expectEquals (rpn.channel, 16); + expectEquals (rpn.parameterNumber, 0); + expectEquals (rpn.value, 3); + expect (! rpn.isNRPN); + expect (rpn.is14BitValue); + } + + beginTest ("invalid RPN (wrong order)"); + { + MidiRPNDetector detector; + MidiRPNMessage rpn; + expect (! detector.parseControllerMessage (2, 6, 42, rpn)); + expect (! detector.parseControllerMessage (2, 101, 0, rpn)); + expect (! detector.parseControllerMessage (2, 100, 7, rpn)); + } + + beginTest ("14-bit RPN interspersed with unrelated CC messages"); + { + MidiRPNDetector detector; + MidiRPNMessage rpn; + expect (! detector.parseControllerMessage (16, 3, 80, rpn)); + expect (! detector.parseControllerMessage (16, 100, 0 , rpn)); + expect (! detector.parseControllerMessage (16, 4, 81, rpn)); + expect (! detector.parseControllerMessage (16, 101, 0, rpn)); + expect (! detector.parseControllerMessage (16, 5, 82, rpn)); + expect (! detector.parseControllerMessage (16, 5, 83, rpn)); + expect (! detector.parseControllerMessage (16, 38, 3, rpn)); + expect (! detector.parseControllerMessage (16, 4, 84, rpn)); + expect (! detector.parseControllerMessage (16, 3, 85, rpn)); + expect (detector.parseControllerMessage (16, 6, 0, rpn)); + + expectEquals (rpn.channel, 16); + expectEquals (rpn.parameterNumber, 0); + expectEquals (rpn.value, 3); + expect (! rpn.isNRPN); + expect (rpn.is14BitValue); + } + + beginTest ("14-bit NRPN"); + { + MidiRPNDetector detector; + MidiRPNMessage rpn; + expect (! detector.parseControllerMessage (1, 98, 44, rpn)); + expect (! detector.parseControllerMessage (1, 99 , 2, rpn)); + expect (! detector.parseControllerMessage (1, 38, 94, rpn)); + expect (detector.parseControllerMessage (1, 6, 1, rpn)); + + expectEquals (rpn.channel, 1); + expectEquals (rpn.parameterNumber, 300); + expectEquals (rpn.value, 222); + expect (rpn.isNRPN); + expect (rpn.is14BitValue); + } + + beginTest ("reset"); + { + MidiRPNDetector detector; + MidiRPNMessage rpn; + expect (! detector.parseControllerMessage (2, 101, 0, rpn)); + detector.reset(); + expect (! detector.parseControllerMessage (2, 100, 7, rpn)); + expect (! detector.parseControllerMessage (2, 6, 42, rpn)); + } + } +}; + +static MidiRPNDetectorTests MidiRPNDetectorUnitTests; + +//============================================================================== +class MidiRPNGeneratorTests : public UnitTest +{ +public: + MidiRPNGeneratorTests() + : UnitTest ("MidiRPNGenerator class", UnitTestCategories::midi) + {} + + void runTest() override + { + beginTest ("generating RPN/NRPN"); + { + { + MidiBuffer buffer = MidiRPNGenerator::generate (1, 23, 1337, true, true); + expectContainsRPN (buffer, 1, 23, 1337, true, true); + } + { + MidiBuffer buffer = MidiRPNGenerator::generate (16, 101, 34, false, false); + expectContainsRPN (buffer, 16, 101, 34, false, false); + } + { + MidiRPNMessage message = { 16, 101, 34, false, false }; + MidiBuffer buffer = MidiRPNGenerator::generate (message); + expectContainsRPN (buffer, message); + } + } + } + +private: + //============================================================================== + void expectContainsRPN (const MidiBuffer& midiBuffer, + int channel, + int parameterNumber, + int value, + bool isNRPN, + bool is14BitValue) + { + MidiRPNMessage expected = { channel, parameterNumber, value, isNRPN, is14BitValue }; + expectContainsRPN (midiBuffer, expected); + } + + //============================================================================== + void expectContainsRPN (const MidiBuffer& midiBuffer, MidiRPNMessage expected) + { + MidiBuffer::Iterator iter (midiBuffer); + MidiMessage midiMessage; + MidiRPNMessage result = MidiRPNMessage(); + MidiRPNDetector detector; + int samplePosition; // not actually used, so no need to initialise. + + while (iter.getNextEvent (midiMessage, samplePosition)) + { + if (detector.parseControllerMessage (midiMessage.getChannel(), + midiMessage.getControllerNumber(), + midiMessage.getControllerValue(), + result)) + break; + } + + expectEquals (result.channel, expected.channel); + expectEquals (result.parameterNumber, expected.parameterNumber); + expectEquals (result.value, expected.value); + expect (result.isNRPN == expected.isNRPN); + expect (result.is14BitValue == expected.is14BitValue); + } +}; + +static MidiRPNGeneratorTests MidiRPNGeneratorUnitTests; + +#endif + +} // namespace juce diff --git a/JuceLibraryCode/modules/juce_audio_basics/midi/juce_MidiRPN.h b/JuceLibraryCode/modules/juce_audio_basics/midi/juce_MidiRPN.h index 869afac..b22138e 100644 --- a/JuceLibraryCode/modules/juce_audio_basics/midi/juce_MidiRPN.h +++ b/JuceLibraryCode/modules/juce_audio_basics/midi/juce_MidiRPN.h @@ -1,154 +1,154 @@ -/* - ============================================================================== - - This file is part of the JUCE library. - Copyright (c) 2017 - ROLI Ltd. - - JUCE is an open source library subject to commercial or open-source - licensing. - - The code included in this file is provided under the terms of the ISC license - http://www.isc.org/downloads/software-support-policy/isc-license. Permission - To use, copy, modify, and/or distribute this software for any purpose with or - without fee is hereby granted provided that the above copyright notice and - this permission notice appear in all copies. - - JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER - EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE - DISCLAIMED. - - ============================================================================== -*/ - -namespace juce -{ - -//============================================================================== -/** Represents a MIDI RPN (registered parameter number) or NRPN (non-registered - parameter number) message. - - @tags{Audio} -*/ -struct MidiRPNMessage -{ - /** Midi channel of the message, in the range 1 to 16. */ - int channel; - - /** The 14-bit parameter index, in the range 0 to 16383 (0x3fff). */ - int parameterNumber; - - /** The parameter value, in the range 0 to 16383 (0x3fff). - If the message contains no value LSB, the value will be in the range - 0 to 127 (0x7f). - */ - int value; - - /** True if this message is an NRPN; false if it is an RPN. */ - bool isNRPN; - - /** True if the value uses 14-bit resolution (LSB + MSB); false if - the value is 7-bit (MSB only). - */ - bool is14BitValue; -}; - -//============================================================================== -/** - Parses a stream of MIDI data to assemble RPN and NRPN messages from their - constituent MIDI CC messages. - - The detector uses the following parsing rules: the parameter number - LSB/MSB can be sent/received in either order and must both come before the - parameter value; for the parameter value, LSB always has to be sent/received - before the value MSB, otherwise it will be treated as 7-bit (MSB only). - - @tags{Audio} -*/ -class JUCE_API MidiRPNDetector -{ -public: - /** Constructor. */ - MidiRPNDetector() noexcept; - - /** Destructor. */ - ~MidiRPNDetector() noexcept; - - /** Resets the RPN detector's internal state, so that it forgets about - previously received MIDI CC messages. - */ - void reset() noexcept; - - //============================================================================== - /** Takes the next in a stream of incoming MIDI CC messages and returns true - if it forms the last of a sequence that makes an RPN or NPRN. - - If this returns true, then the RPNMessage object supplied will be - filled-out with the message's details. - (If it returns false then the RPNMessage object will be unchanged). - */ - bool parseControllerMessage (int midiChannel, - int controllerNumber, - int controllerValue, - MidiRPNMessage& result) noexcept; - -private: - //============================================================================== - struct ChannelState - { - ChannelState() noexcept; - bool handleController (int channel, int controllerNumber, - int value, MidiRPNMessage&) noexcept; - void resetValue() noexcept; - bool sendIfReady (int channel, MidiRPNMessage&) noexcept; - - uint8 parameterMSB, parameterLSB, valueMSB, valueLSB; - bool isNRPN; - }; - - //============================================================================== - ChannelState states[16]; - - JUCE_LEAK_DETECTOR (MidiRPNDetector) -}; - -//============================================================================== -/** - Generates an appropriate sequence of MIDI CC messages to represent an RPN - or NRPN message. - - This sequence (as a MidiBuffer) can then be directly sent to a MidiOutput. - - @tags{Audio} -*/ -class JUCE_API MidiRPNGenerator -{ -public: - //============================================================================== - /** Generates a MIDI sequence representing the given RPN or NRPN message. */ - static MidiBuffer generate (MidiRPNMessage message); - - //============================================================================== - /** Generates a MIDI sequence representing an RPN or NRPN message with the - given parameters. - - @param channel The MIDI channel of the RPN/NRPN message. - - @param parameterNumber The parameter number, in the range 0 to 16383. - - @param value The parameter value, in the range 0 to 16383, or - in the range 0 to 127 if sendAs14BitValue is false. - - @param isNRPN Whether you need a MIDI RPN or NRPN sequence (RPN is default). - - @param use14BitValue If true (default), the value will have 14-bit precision - (two MIDI bytes). If false, instead the value will have - 7-bit precision (a single MIDI byte). - */ - static MidiBuffer generate (int channel, - int parameterNumber, - int value, - bool isNRPN = false, - bool use14BitValue = true); -}; - -} // namespace juce +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2017 - ROLI Ltd. + + JUCE is an open source library subject to commercial or open-source + licensing. + + The code included in this file is provided under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license. Permission + To use, copy, modify, and/or distribute this software for any purpose with or + without fee is hereby granted provided that the above copyright notice and + this permission notice appear in all copies. + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +namespace juce +{ + +//============================================================================== +/** Represents a MIDI RPN (registered parameter number) or NRPN (non-registered + parameter number) message. + + @tags{Audio} +*/ +struct MidiRPNMessage +{ + /** Midi channel of the message, in the range 1 to 16. */ + int channel; + + /** The 14-bit parameter index, in the range 0 to 16383 (0x3fff). */ + int parameterNumber; + + /** The parameter value, in the range 0 to 16383 (0x3fff). + If the message contains no value LSB, the value will be in the range + 0 to 127 (0x7f). + */ + int value; + + /** True if this message is an NRPN; false if it is an RPN. */ + bool isNRPN; + + /** True if the value uses 14-bit resolution (LSB + MSB); false if + the value is 7-bit (MSB only). + */ + bool is14BitValue; +}; + +//============================================================================== +/** + Parses a stream of MIDI data to assemble RPN and NRPN messages from their + constituent MIDI CC messages. + + The detector uses the following parsing rules: the parameter number + LSB/MSB can be sent/received in either order and must both come before the + parameter value; for the parameter value, LSB always has to be sent/received + before the value MSB, otherwise it will be treated as 7-bit (MSB only). + + @tags{Audio} +*/ +class JUCE_API MidiRPNDetector +{ +public: + /** Constructor. */ + MidiRPNDetector() noexcept; + + /** Destructor. */ + ~MidiRPNDetector() noexcept; + + /** Resets the RPN detector's internal state, so that it forgets about + previously received MIDI CC messages. + */ + void reset() noexcept; + + //============================================================================== + /** Takes the next in a stream of incoming MIDI CC messages and returns true + if it forms the last of a sequence that makes an RPN or NPRN. + + If this returns true, then the RPNMessage object supplied will be + filled-out with the message's details. + (If it returns false then the RPNMessage object will be unchanged). + */ + bool parseControllerMessage (int midiChannel, + int controllerNumber, + int controllerValue, + MidiRPNMessage& result) noexcept; + +private: + //============================================================================== + struct ChannelState + { + ChannelState() noexcept; + bool handleController (int channel, int controllerNumber, + int value, MidiRPNMessage&) noexcept; + void resetValue() noexcept; + bool sendIfReady (int channel, MidiRPNMessage&) noexcept; + + uint8 parameterMSB, parameterLSB, valueMSB, valueLSB; + bool isNRPN; + }; + + //============================================================================== + ChannelState states[16]; + + JUCE_LEAK_DETECTOR (MidiRPNDetector) +}; + +//============================================================================== +/** + Generates an appropriate sequence of MIDI CC messages to represent an RPN + or NRPN message. + + This sequence (as a MidiBuffer) can then be directly sent to a MidiOutput. + + @tags{Audio} +*/ +class JUCE_API MidiRPNGenerator +{ +public: + //============================================================================== + /** Generates a MIDI sequence representing the given RPN or NRPN message. */ + static MidiBuffer generate (MidiRPNMessage message); + + //============================================================================== + /** Generates a MIDI sequence representing an RPN or NRPN message with the + given parameters. + + @param channel The MIDI channel of the RPN/NRPN message. + + @param parameterNumber The parameter number, in the range 0 to 16383. + + @param value The parameter value, in the range 0 to 16383, or + in the range 0 to 127 if sendAs14BitValue is false. + + @param isNRPN Whether you need a MIDI RPN or NRPN sequence (RPN is default). + + @param use14BitValue If true (default), the value will have 14-bit precision + (two MIDI bytes). If false, instead the value will have + 7-bit precision (a single MIDI byte). + */ + static MidiBuffer generate (int channel, + int parameterNumber, + int value, + bool isNRPN = false, + bool use14BitValue = true); +}; + +} // namespace juce diff --git a/JuceLibraryCode/modules/juce_audio_basics/mpe/juce_MPEInstrument.cpp b/JuceLibraryCode/modules/juce_audio_basics/mpe/juce_MPEInstrument.cpp index df2e466..0b86789 100644 --- a/JuceLibraryCode/modules/juce_audio_basics/mpe/juce_MPEInstrument.cpp +++ b/JuceLibraryCode/modules/juce_audio_basics/mpe/juce_MPEInstrument.cpp @@ -1,2292 +1,2292 @@ -/* - ============================================================================== - - This file is part of the JUCE library. - Copyright (c) 2017 - ROLI Ltd. - - JUCE is an open source library subject to commercial or open-source - licensing. - - The code included in this file is provided under the terms of the ISC license - http://www.isc.org/downloads/software-support-policy/isc-license. Permission - To use, copy, modify, and/or distribute this software for any purpose with or - without fee is hereby granted provided that the above copyright notice and - this permission notice appear in all copies. - - JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER - EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE - DISCLAIMED. - - ============================================================================== -*/ - -namespace juce -{ - -namespace -{ - const uint8 noLSBValueReceived = 0xff; - const Range allChannels { 1, 17 }; -} - -//============================================================================== -MPEInstrument::MPEInstrument() noexcept -{ - std::fill_n (lastPressureLowerBitReceivedOnChannel, 16, noLSBValueReceived); - std::fill_n (lastTimbreLowerBitReceivedOnChannel, 16, noLSBValueReceived); - std::fill_n (isMemberChannelSustained, 16, false); - - pitchbendDimension.value = &MPENote::pitchbend; - pressureDimension.value = &MPENote::pressure; - timbreDimension.value = &MPENote::timbre; - - // the default value for pressure is 0, for all other dimension it is centre (= default MPEValue) - std::fill_n (pressureDimension.lastValueReceivedOnChannel, 16, MPEValue::minValue()); - - legacyMode.isEnabled = false; - legacyMode.pitchbendRange = 2; - legacyMode.channelRange = allChannels; -} - -MPEInstrument::~MPEInstrument() -{ -} - -//============================================================================== -MPEZoneLayout MPEInstrument::getZoneLayout() const noexcept -{ - return zoneLayout; -} - -void MPEInstrument::setZoneLayout (MPEZoneLayout newLayout) -{ - releaseAllNotes(); - - const ScopedLock sl (lock); - legacyMode.isEnabled = false; - zoneLayout = newLayout; -} - -//============================================================================== -void MPEInstrument::enableLegacyMode (int pitchbendRange, Range channelRange) -{ - releaseAllNotes(); - - const ScopedLock sl (lock); - legacyMode.isEnabled = true; - legacyMode.pitchbendRange = pitchbendRange; - legacyMode.channelRange = channelRange; - zoneLayout.clearAllZones(); -} - -bool MPEInstrument::isLegacyModeEnabled() const noexcept -{ - return legacyMode.isEnabled; -} - -Range MPEInstrument::getLegacyModeChannelRange() const noexcept -{ - return legacyMode.channelRange; -} - -void MPEInstrument::setLegacyModeChannelRange (Range channelRange) -{ - jassert (allChannels.contains (channelRange)); - - releaseAllNotes(); - const ScopedLock sl (lock); - legacyMode.channelRange = channelRange; -} - -int MPEInstrument::getLegacyModePitchbendRange() const noexcept -{ - return legacyMode.pitchbendRange; -} - -void MPEInstrument::setLegacyModePitchbendRange (int pitchbendRange) -{ - jassert (pitchbendRange >= 0 && pitchbendRange <= 96); - - releaseAllNotes(); - const ScopedLock sl (lock); - legacyMode.pitchbendRange = pitchbendRange; -} - -//============================================================================== -void MPEInstrument::setPressureTrackingMode (TrackingMode modeToUse) -{ - pressureDimension.trackingMode = modeToUse; -} - -void MPEInstrument::setPitchbendTrackingMode (TrackingMode modeToUse) -{ - pitchbendDimension.trackingMode = modeToUse; -} - -void MPEInstrument::setTimbreTrackingMode (TrackingMode modeToUse) -{ - timbreDimension.trackingMode = modeToUse; -} - -//============================================================================== -void MPEInstrument::addListener (Listener* listenerToAdd) -{ - listeners.add (listenerToAdd); -} - -void MPEInstrument::removeListener (Listener* listenerToRemove) -{ - listeners.remove (listenerToRemove); -} - -//============================================================================== -void MPEInstrument::processNextMidiEvent (const MidiMessage& message) -{ - zoneLayout.processNextMidiEvent (message); - - if (message.isNoteOn (true)) processMidiNoteOnMessage (message); - else if (message.isNoteOff (false)) processMidiNoteOffMessage (message); - else if (message.isResetAllControllers() - || message.isAllNotesOff()) processMidiResetAllControllersMessage (message); - else if (message.isPitchWheel()) processMidiPitchWheelMessage (message); - else if (message.isChannelPressure()) processMidiChannelPressureMessage (message); - else if (message.isController()) processMidiControllerMessage (message); - else if (message.isAftertouch()) processMidiAfterTouchMessage (message); -} - -//============================================================================== -void MPEInstrument::processMidiNoteOnMessage (const MidiMessage& message) -{ - // Note: If a note-on with velocity = 0 is used to convey a note-off, - // then the actual note-off velocity is not known. In this case, - // the MPE convention is to use note-off velocity = 64. - - if (message.getVelocity() == 0) - { - noteOff (message.getChannel(), - message.getNoteNumber(), - MPEValue::from7BitInt (64)); - } - else - { - noteOn (message.getChannel(), - message.getNoteNumber(), - MPEValue::from7BitInt (message.getVelocity())); - } -} - -//============================================================================== -void MPEInstrument::processMidiNoteOffMessage (const MidiMessage& message) -{ - noteOff (message.getChannel(), - message.getNoteNumber(), - MPEValue::from7BitInt (message.getVelocity())); -} - -//============================================================================== -void MPEInstrument::processMidiPitchWheelMessage (const MidiMessage& message) -{ - pitchbend (message.getChannel(), - MPEValue::from14BitInt (message.getPitchWheelValue())); -} - -//============================================================================== -void MPEInstrument::processMidiChannelPressureMessage (const MidiMessage& message) -{ - pressure (message.getChannel(), - MPEValue::from7BitInt (message.getChannelPressureValue())); -} - -//============================================================================== -void MPEInstrument::processMidiControllerMessage (const MidiMessage& message) -{ - switch (message.getControllerNumber()) - { - case 64: sustainPedal (message.getChannel(), message.isSustainPedalOn()); break; - case 66: sostenutoPedal (message.getChannel(), message.isSostenutoPedalOn()); break; - case 70: handlePressureMSB (message.getChannel(), message.getControllerValue()); break; - case 74: handleTimbreMSB (message.getChannel(), message.getControllerValue()); break; - case 102: handlePressureLSB (message.getChannel(), message.getControllerValue()); break; - case 106: handleTimbreLSB (message.getChannel(), message.getControllerValue()); break; - default: break; - } -} - -//============================================================================== -void MPEInstrument::processMidiResetAllControllersMessage (const MidiMessage& message) -{ - // in MPE mode, "reset all controllers" is per-zone and expected on the master channel; - // in legacy mode, it is per MIDI channel (within the channel range used). - - if (legacyMode.isEnabled && legacyMode.channelRange.contains (message.getChannel())) - { - for (auto i = notes.size(); --i >= 0;) - { - auto& note = notes.getReference (i); - - if (note.midiChannel == message.getChannel()) - { - note.keyState = MPENote::off; - note.noteOffVelocity = MPEValue::from7BitInt (64); // some reasonable number - listeners.call ([&] (Listener& l) { l.noteReleased (note); }); - notes.remove (i); - } - } - } - else if (isMasterChannel (message.getChannel())) - { - auto zone = (message.getChannel() == 1 ? zoneLayout.getLowerZone() - : zoneLayout.getUpperZone()); - - for (auto i = notes.size(); --i >= 0;) - { - auto& note = notes.getReference (i); - - if (zone.isUsing (note.midiChannel)) - { - note.keyState = MPENote::off; - note.noteOffVelocity = MPEValue::from7BitInt (64); // some reasonable number - listeners.call ([&] (Listener& l) { l.noteReleased (note); }); - notes.remove (i); - } - } - } -} - -void MPEInstrument::processMidiAfterTouchMessage (const MidiMessage& message) -{ - if (! isMasterChannel (message.getChannel())) - return; - - polyAftertouch (message.getChannel(), message.getNoteNumber(), - MPEValue::from7BitInt (message.getAfterTouchValue())); -} - -//============================================================================== -void MPEInstrument::handlePressureMSB (int midiChannel, int value) noexcept -{ - auto lsb = lastPressureLowerBitReceivedOnChannel[midiChannel - 1]; - - pressure (midiChannel, lsb == noLSBValueReceived ? MPEValue::from7BitInt (value) - : MPEValue::from14BitInt (lsb + (value << 7))); -} - -void MPEInstrument::handlePressureLSB (int midiChannel, int value) noexcept -{ - lastPressureLowerBitReceivedOnChannel[midiChannel - 1] = uint8 (value); -} - -void MPEInstrument::handleTimbreMSB (int midiChannel, int value) noexcept -{ - auto lsb = lastTimbreLowerBitReceivedOnChannel[midiChannel - 1]; - - timbre (midiChannel, lsb == noLSBValueReceived ? MPEValue::from7BitInt (value) - : MPEValue::from14BitInt (lsb + (value << 7))); -} - -void MPEInstrument::handleTimbreLSB (int midiChannel, int value) noexcept -{ - lastTimbreLowerBitReceivedOnChannel[midiChannel - 1] = uint8 (value); -} - -//============================================================================== -void MPEInstrument::noteOn (int midiChannel, - int midiNoteNumber, - MPEValue midiNoteOnVelocity) -{ - if (! isUsingChannel (midiChannel)) - return; - - MPENote newNote (midiChannel, - midiNoteNumber, - midiNoteOnVelocity, - getInitialValueForNewNote (midiChannel, pitchbendDimension), - getInitialValueForNewNote (midiChannel, pressureDimension), - getInitialValueForNewNote (midiChannel, timbreDimension), - isMemberChannelSustained[midiChannel - 1] ? MPENote::keyDownAndSustained : MPENote::keyDown); - - const ScopedLock sl (lock); - updateNoteTotalPitchbend (newNote); - - if (auto* alreadyPlayingNote = getNotePtr (midiChannel, midiNoteNumber)) - { - // pathological case: second note-on received for same note -> retrigger it - alreadyPlayingNote->keyState = MPENote::off; - alreadyPlayingNote->noteOffVelocity = MPEValue::from7BitInt (64); // some reasonable number - listeners.call ([=] (Listener& l) { l.noteReleased (*alreadyPlayingNote); }); - notes.remove (alreadyPlayingNote); - } - - notes.add (newNote); - listeners.call ([&] (Listener& l) { l.noteAdded (newNote); }); -} - -//============================================================================== -void MPEInstrument::noteOff (int midiChannel, - int midiNoteNumber, - MPEValue midiNoteOffVelocity) -{ - if (notes.isEmpty() || ! isUsingChannel (midiChannel)) - return; - - const ScopedLock sl (lock); - - if (auto* note = getNotePtr (midiChannel, midiNoteNumber)) - { - note->keyState = (note->keyState == MPENote::keyDownAndSustained) ? MPENote::sustained : MPENote::off; - note->noteOffVelocity = midiNoteOffVelocity; - - // If no more notes are playing on this channel, reset the dimension values - if (getLastNotePlayedPtr (midiChannel) == nullptr) - { - pressureDimension.lastValueReceivedOnChannel[midiChannel - 1] = MPEValue::minValue(); - pitchbendDimension.lastValueReceivedOnChannel[midiChannel - 1] = MPEValue::centreValue(); - timbreDimension.lastValueReceivedOnChannel[midiChannel - 1] = MPEValue::centreValue(); - } - - if (note->keyState == MPENote::off) - { - listeners.call ([=] (Listener& l) { l.noteReleased (*note); }); - notes.remove (note); - } - else - { - listeners.call ([=] (Listener& l) { l.noteKeyStateChanged (*note); }); - } - } -} - -//============================================================================== -void MPEInstrument::pitchbend (int midiChannel, MPEValue value) -{ - const ScopedLock sl (lock); - updateDimension (midiChannel, pitchbendDimension, value); -} - -void MPEInstrument::pressure (int midiChannel, MPEValue value) -{ - const ScopedLock sl (lock); - updateDimension (midiChannel, pressureDimension, value); -} - -void MPEInstrument::timbre (int midiChannel, MPEValue value) -{ - const ScopedLock sl (lock); - updateDimension (midiChannel, timbreDimension, value); -} - -void MPEInstrument::polyAftertouch (int midiChannel, int midiNoteNumber, MPEValue value) -{ - const ScopedLock sl (lock); - - for (auto i = notes.size(); --i >= 0;) - { - auto& note = notes.getReference (i); - - if (note.midiChannel == midiChannel - && note.initialNote == midiNoteNumber - && pressureDimension.getValue (note) != value) - { - pressureDimension.getValue (note) = value; - callListenersDimensionChanged (note, pressureDimension); - } - } -} - -MPEValue MPEInstrument::getInitialValueForNewNote (int midiChannel, MPEDimension& dimension) const -{ - if (getLastNotePlayedPtr (midiChannel) != nullptr) - return &dimension == &pressureDimension ? MPEValue::minValue() : MPEValue::centreValue(); - - return dimension.lastValueReceivedOnChannel[midiChannel - 1]; -} - -//============================================================================== -void MPEInstrument::updateDimension (int midiChannel, MPEDimension& dimension, MPEValue value) -{ - dimension.lastValueReceivedOnChannel[midiChannel - 1] = value; - - if (notes.isEmpty()) - return; - - if (isMemberChannel (midiChannel)) - { - if (dimension.trackingMode == allNotesOnChannel) - { - for (auto i = notes.size(); --i >= 0;) - { - auto& note = notes.getReference (i); - - if (note.midiChannel == midiChannel) - updateDimensionForNote (note, dimension, value); - } - } - else - { - if (auto* note = getNotePtr (midiChannel, dimension.trackingMode)) - updateDimensionForNote (*note, dimension, value); - } - } - else if (isMasterChannel (midiChannel)) - { - updateDimensionMaster (midiChannel == 1, dimension, value); - } -} - -//============================================================================== -void MPEInstrument::updateDimensionMaster (bool isLowerZone, MPEDimension& dimension, MPEValue value) -{ - auto zone = (isLowerZone ? zoneLayout.getLowerZone() - : zoneLayout.getUpperZone()); - - if (! zone.isActive()) - return; - - for (auto i = notes.size(); --i >= 0;) - { - auto& note = notes.getReference (i); - - if (! zone.isUsing (note.midiChannel)) - continue; - - if (&dimension == &pitchbendDimension) - { - // master pitchbend is a special case: we don't change the note's own pitchbend, - // instead we have to update its total (master + note) pitchbend. - updateNoteTotalPitchbend (note); - listeners.call ([&] (Listener& l) { l.notePitchbendChanged (note); }); - } - else if (dimension.getValue (note) != value) - { - dimension.getValue (note) = value; - callListenersDimensionChanged (note, dimension); - } - } -} - -//============================================================================== -void MPEInstrument::updateDimensionForNote (MPENote& note, MPEDimension& dimension, MPEValue value) -{ - if (dimension.getValue (note) != value) - { - dimension.getValue (note) = value; - - if (&dimension == &pitchbendDimension) - updateNoteTotalPitchbend (note); - - callListenersDimensionChanged (note, dimension); - } -} - -//============================================================================== -void MPEInstrument::callListenersDimensionChanged (const MPENote& note, const MPEDimension& dimension) -{ - if (&dimension == &pressureDimension) { listeners.call ([&] (Listener& l) { l.notePressureChanged (note); }); return; } - if (&dimension == &timbreDimension) { listeners.call ([&] (Listener& l) { l.noteTimbreChanged (note); }); return; } - if (&dimension == &pitchbendDimension) { listeners.call ([&] (Listener& l) { l.notePitchbendChanged (note); }); return; } -} - -//============================================================================== -void MPEInstrument::updateNoteTotalPitchbend (MPENote& note) -{ - if (legacyMode.isEnabled) - { - note.totalPitchbendInSemitones = note.pitchbend.asSignedFloat() * legacyMode.pitchbendRange; - } - else - { - auto zone = zoneLayout.getLowerZone(); - - if (! zone.isUsing (note.midiChannel)) - { - if (zoneLayout.getUpperZone().isUsing (note.midiChannel)) - { - zone = zoneLayout.getUpperZone(); - } - else - { - // this note doesn't belong to any zone! - jassertfalse; - return; - } - } - - auto notePitchbendInSemitones = 0.0f; - - if (zone.isUsingChannelAsMemberChannel (note.midiChannel)) - notePitchbendInSemitones = note.pitchbend.asSignedFloat() * zone.perNotePitchbendRange; - - auto masterPitchbendInSemitones = pitchbendDimension.lastValueReceivedOnChannel[zone.getMasterChannel() - 1] - .asSignedFloat() - * zone.masterPitchbendRange; - - note.totalPitchbendInSemitones = notePitchbendInSemitones + masterPitchbendInSemitones; - } -} - -//============================================================================== -void MPEInstrument::sustainPedal (int midiChannel, bool isDown) -{ - const ScopedLock sl (lock); - handleSustainOrSostenuto (midiChannel, isDown, false); -} - -void MPEInstrument::sostenutoPedal (int midiChannel, bool isDown) -{ - const ScopedLock sl (lock); - handleSustainOrSostenuto (midiChannel, isDown, true); -} - -//============================================================================== -void MPEInstrument::handleSustainOrSostenuto (int midiChannel, bool isDown, bool isSostenuto) -{ - // in MPE mode, sustain/sostenuto is per-zone and expected on the master channel; - // in legacy mode, sustain/sostenuto is per MIDI channel (within the channel range used). - - if (legacyMode.isEnabled ? (! legacyMode.channelRange.contains (midiChannel)) : (! isMasterChannel (midiChannel))) - return; - - auto zone = (midiChannel == 1 ? zoneLayout.getLowerZone() - : zoneLayout.getUpperZone()); - - for (auto i = notes.size(); --i >= 0;) - { - auto& note = notes.getReference (i); - - if (legacyMode.isEnabled ? (note.midiChannel == midiChannel) : zone.isUsing (note.midiChannel)) - { - if (note.keyState == MPENote::keyDown && isDown) - note.keyState = MPENote::keyDownAndSustained; - else if (note.keyState == MPENote::sustained && ! isDown) - note.keyState = MPENote::off; - else if (note.keyState == MPENote::keyDownAndSustained && ! isDown) - note.keyState = MPENote::keyDown; - - if (note.keyState == MPENote::off) - { - listeners.call ([&] (Listener& l) { l.noteReleased (note); }); - notes.remove (i); - } - else - { - listeners.call ([&] (Listener& l) { l.noteKeyStateChanged (note); }); - } - } - } - - if (! isSostenuto) - { - if (legacyMode.isEnabled) - { - isMemberChannelSustained[midiChannel - 1] = isDown; - } - else - { - if (zone.isLowerZone()) - for (auto i = zone.getFirstMemberChannel(); i <= zone.getLastMemberChannel(); ++i) - isMemberChannelSustained[i - 1] = isDown; - else - for (auto i = zone.getFirstMemberChannel(); i >= zone.getLastMemberChannel(); --i) - isMemberChannelSustained[i - 1] = isDown; - } - } -} - -//============================================================================== -bool MPEInstrument::isMemberChannel (int midiChannel) const noexcept -{ - if (legacyMode.isEnabled) - return legacyMode.channelRange.contains (midiChannel); - - return zoneLayout.getLowerZone().isUsingChannelAsMemberChannel (midiChannel) - || zoneLayout.getUpperZone().isUsingChannelAsMemberChannel (midiChannel); -} - -bool MPEInstrument::isMasterChannel (int midiChannel) const noexcept -{ - if (legacyMode.isEnabled) - return false; - - const auto lowerZone = zoneLayout.getLowerZone(); - const auto upperZone = zoneLayout.getUpperZone(); - - return (lowerZone.isActive() && midiChannel == lowerZone.getMasterChannel()) - || (upperZone.isActive() && midiChannel == upperZone.getMasterChannel()); -} - -bool MPEInstrument::isUsingChannel (int midiChannel) const noexcept -{ - if (legacyMode.isEnabled) - return legacyMode.channelRange.contains (midiChannel); - - return zoneLayout.getLowerZone().isUsing (midiChannel) - || zoneLayout.getUpperZone().isUsing (midiChannel); -} - -//============================================================================== -int MPEInstrument::getNumPlayingNotes() const noexcept -{ - return notes.size(); -} - -MPENote MPEInstrument::getNote (int midiChannel, int midiNoteNumber) const noexcept -{ - if (auto* note = getNotePtr (midiChannel, midiNoteNumber)) - return *note; - - return {}; -} - -MPENote MPEInstrument::getNote (int index) const noexcept -{ - return notes[index]; -} - -//============================================================================== -MPENote MPEInstrument::getMostRecentNote (int midiChannel) const noexcept -{ - if (auto* note = getLastNotePlayedPtr (midiChannel)) - return *note; - - return {}; -} - -MPENote MPEInstrument::getMostRecentNoteOtherThan (MPENote otherThanThisNote) const noexcept -{ - for (auto i = notes.size(); --i >= 0;) - { - auto& note = notes.getReference (i); - - if (note != otherThanThisNote) - return note; - } - - return {}; -} - -//============================================================================== -const MPENote* MPEInstrument::getNotePtr (int midiChannel, int midiNoteNumber) const noexcept -{ - for (int i = 0; i < notes.size(); ++i) - { - auto& note = notes.getReference (i); - - if (note.midiChannel == midiChannel && note.initialNote == midiNoteNumber) - return ¬e; - } - - return nullptr; -} - -MPENote* MPEInstrument::getNotePtr (int midiChannel, int midiNoteNumber) noexcept -{ - return const_cast (static_cast (*this).getNotePtr (midiChannel, midiNoteNumber)); -} - -//============================================================================== -const MPENote* MPEInstrument::getNotePtr (int midiChannel, TrackingMode mode) const noexcept -{ - // for the "all notes" tracking mode, this method can never possibly - // work because it returns 0 or 1 note but there might be more than one! - jassert (mode != allNotesOnChannel); - - if (mode == lastNotePlayedOnChannel) return getLastNotePlayedPtr (midiChannel); - if (mode == lowestNoteOnChannel) return getLowestNotePtr (midiChannel); - if (mode == highestNoteOnChannel) return getHighestNotePtr (midiChannel); - - return nullptr; -} - -MPENote* MPEInstrument::getNotePtr (int midiChannel, TrackingMode mode) noexcept -{ - return const_cast (static_cast (*this).getNotePtr (midiChannel, mode)); -} - -//============================================================================== -const MPENote* MPEInstrument::getLastNotePlayedPtr (int midiChannel) const noexcept -{ - for (auto i = notes.size(); --i >= 0;) - { - auto& note = notes.getReference (i); - - if (note.midiChannel == midiChannel - && (note.keyState == MPENote::keyDown || note.keyState == MPENote::keyDownAndSustained)) - return ¬e; - } - - return nullptr; -} - -MPENote* MPEInstrument::getLastNotePlayedPtr (int midiChannel) noexcept -{ - return const_cast (static_cast (*this).getLastNotePlayedPtr (midiChannel)); -} - -//============================================================================== -const MPENote* MPEInstrument::getHighestNotePtr (int midiChannel) const noexcept -{ - int initialNoteMax = -1; - const MPENote* result = nullptr; - - for (auto i = notes.size(); --i >= 0;) - { - auto& note = notes.getReference (i); - - if (note.midiChannel == midiChannel - && (note.keyState == MPENote::keyDown || note.keyState == MPENote::keyDownAndSustained) - && note.initialNote > initialNoteMax) - { - result = ¬e; - initialNoteMax = note.initialNote; - } - } - - return result; -} - -MPENote* MPEInstrument::getHighestNotePtr (int midiChannel) noexcept -{ - return const_cast (static_cast (*this).getHighestNotePtr (midiChannel)); -} - -const MPENote* MPEInstrument::getLowestNotePtr (int midiChannel) const noexcept -{ - int initialNoteMin = 128; - const MPENote* result = nullptr; - - for (auto i = notes.size(); --i >= 0;) - { - auto& note = notes.getReference (i); - - if (note.midiChannel == midiChannel - && (note.keyState == MPENote::keyDown || note.keyState == MPENote::keyDownAndSustained) - && note.initialNote < initialNoteMin) - { - result = ¬e; - initialNoteMin = note.initialNote; - } - } - - return result; -} - -MPENote* MPEInstrument::getLowestNotePtr (int midiChannel) noexcept -{ - return const_cast (static_cast (*this).getLowestNotePtr (midiChannel)); -} - -//============================================================================== -void MPEInstrument::releaseAllNotes() -{ - const ScopedLock sl (lock); - - for (auto i = notes.size(); --i >= 0;) - { - auto& note = notes.getReference (i); - note.keyState = MPENote::off; - note.noteOffVelocity = MPEValue::from7BitInt (64); // some reasonable number - listeners.call ([&] (Listener& l) { l.noteReleased (note); }); - } - - notes.clear(); -} - - -//============================================================================== -//============================================================================== -#if JUCE_UNIT_TESTS - -class MPEInstrumentTests : public UnitTest -{ -public: - MPEInstrumentTests() - : UnitTest ("MPEInstrument class", UnitTestCategories::midi) - { - // using lower and upper MPE zones with the following layout for testing - // - // 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 - // * ...................| |........................ * - - testLayout.setLowerZone (5); - testLayout.setUpperZone (6); - } - - void runTest() override - { - beginTest ("initial zone layout"); - { - MPEInstrument test; - expect (! test.getZoneLayout().getLowerZone().isActive()); - expect (! test.getZoneLayout().getUpperZone().isActive()); - } - - beginTest ("get/setZoneLayout"); - { - MPEInstrument test; - test.setZoneLayout (testLayout); - - auto newLayout = test.getZoneLayout(); - - expect (test.getZoneLayout().getLowerZone().isActive()); - expect (test.getZoneLayout().getUpperZone().isActive()); - expectEquals (newLayout.getLowerZone().getMasterChannel(), 1); - expectEquals (newLayout.getLowerZone().numMemberChannels, 5); - expectEquals (newLayout.getUpperZone().getMasterChannel(), 16); - expectEquals (newLayout.getUpperZone().numMemberChannels, 6); - } - - beginTest ("noteOn / noteOff"); - { - { - MPEInstrument test; - test.setZoneLayout (testLayout); - expectEquals (test.getNumPlayingNotes(), 0); - } - { - UnitTestInstrument test; - test.setZoneLayout (testLayout); - - // note-on on unused channel - ignore - test.noteOn (7, 60, MPEValue::from7BitInt (100)); - expectEquals (test.getNumPlayingNotes(), 0); - expectEquals (test.noteAddedCallCounter, 0); - - // note-on on member channel - create new note - test.noteOn (3, 60, MPEValue::from7BitInt (100)); - expectEquals (test.getNumPlayingNotes(), 1); - expectEquals (test.noteAddedCallCounter, 1); - expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown); - - // note-off - test.noteOff (3, 60, MPEValue::from7BitInt (33)); - expectEquals (test.getNumPlayingNotes(), 0); - expectEquals (test.noteReleasedCallCounter, 1); - expectHasFinishedNote (test, 3, 60, 33); - - - // note-on on master channel - create new note - test.noteOn (1, 62, MPEValue::from7BitInt (100)); - expectEquals (test.getNumPlayingNotes(), 1); - expectEquals (test.noteAddedCallCounter, 2); - expectNote (test.getNote (1, 62), 100, 0, 8192, 64, MPENote::keyDown); - - // note-off - test.noteOff (1, 62, MPEValue::from7BitInt (33)); - expectEquals (test.getNumPlayingNotes(), 0); - expectEquals (test.noteReleasedCallCounter, 2); - expectHasFinishedNote (test, 1, 62, 33); - } - - { - UnitTestInstrument test; - test.setZoneLayout (testLayout); - test.noteOn (3, 60, MPEValue::from7BitInt (100)); - - // note off with non-matching note number shouldn't do anything - test.noteOff (3, 61, MPEValue::from7BitInt (33)); - expectEquals (test.getNumPlayingNotes(), 1); - expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown); - expectEquals (test.noteReleasedCallCounter, 0); - - // note off with non-matching midi channel shouldn't do anything - test.noteOff (2, 60, MPEValue::from7BitInt (33)); - expectEquals (test.getNumPlayingNotes(), 1); - expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown); - expectEquals (test.noteReleasedCallCounter, 0); - } - - { - // can have multiple notes on the same channel - UnitTestInstrument test; - test.setZoneLayout (testLayout); - test.noteOn (3, 0, MPEValue::from7BitInt (100)); - test.noteOn (3, 1, MPEValue::from7BitInt (100)); - test.noteOn (3, 2, MPEValue::from7BitInt (100)); - expectEquals (test.getNumPlayingNotes(), 3); - expectNote (test.getNote (3, 0), 100, 0, 8192, 64, MPENote::keyDown); - expectNote (test.getNote (3, 1), 100, 0, 8192, 64, MPENote::keyDown); - expectNote (test.getNote (3, 2), 100, 0, 8192, 64, MPENote::keyDown); - } - { - // pathological case: second note-on for same note should retrigger it. - UnitTestInstrument test; - test.setZoneLayout (testLayout); - test.noteOn (3, 0, MPEValue::from7BitInt (100)); - test.noteOn (3, 0, MPEValue::from7BitInt (60)); - expectEquals (test.getNumPlayingNotes(), 1); - expectNote (test.getNote (3, 0), 60, 0, 8192, 64, MPENote::keyDown); - } - } - - beginTest ("noteReleased after setZoneLayout"); - { - UnitTestInstrument test; - test.setZoneLayout (testLayout); - - test.noteOn (3, 60, MPEValue::from7BitInt (100)); - test.noteOn (3, 61, MPEValue::from7BitInt (100)); - test.noteOn (4, 61, MPEValue::from7BitInt (100)); - expectEquals (test.getNumPlayingNotes(), 3); - expectEquals (test.noteReleasedCallCounter, 0); - - test.setZoneLayout (testLayout); - expectEquals (test.getNumPlayingNotes(), 0); - expectEquals (test.noteReleasedCallCounter, 3); - } - - beginTest ("releaseAllNotes"); - { - UnitTestInstrument test; - test.setZoneLayout (testLayout); - test.noteOn (3, 60, MPEValue::from7BitInt (100)); - test.noteOn (4, 61, MPEValue::from7BitInt (100)); - test.noteOn (15, 62, MPEValue::from7BitInt (100)); - expectEquals (test.getNumPlayingNotes(), 3); - - test.releaseAllNotes(); - expectEquals (test.getNumPlayingNotes(), 0); - } - - beginTest ("sustainPedal"); - { - UnitTestInstrument test; - test.setZoneLayout (testLayout); - test.noteOn (3, 60, MPEValue::from7BitInt (100)); // note in lower zone - test.noteOn (10, 60, MPEValue::from7BitInt (100)); // note in upper zone - - // sustain pedal on per-note channel shouldn't do anything. - test.sustainPedal (3, true); - expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown); - - expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown); - expectNote (test.getNote (10, 60), 100, 0, 8192, 64, MPENote::keyDown); - expectEquals (test.noteKeyStateChangedCallCounter, 0); - - // sustain pedal on non-zone channel shouldn't do anything either. - test.sustainPedal (7, true); - expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown); - expectNote (test.getNote (10, 60), 100, 0, 8192, 64, MPENote::keyDown); - expectEquals (test.noteKeyStateChangedCallCounter, 0); - - // sustain pedal on master channel should sustain notes on _that_ zone. - test.sustainPedal (1, true); - expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDownAndSustained); - expectNote (test.getNote (10, 60), 100, 0, 8192, 64, MPENote::keyDown); - expectEquals (test.noteKeyStateChangedCallCounter, 1); - - // release - test.sustainPedal (1, false); - expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown); - expectNote (test.getNote (10, 60), 100, 0, 8192, 64, MPENote::keyDown); - expectEquals (test.noteKeyStateChangedCallCounter, 2); - - // should also sustain new notes added after the press - test.sustainPedal (1, true); - expectEquals (test.noteKeyStateChangedCallCounter, 3); - test.noteOn (4, 51, MPEValue::from7BitInt (100)); - expectNote (test.getNote (4, 51), 100, 0, 8192, 64, MPENote::keyDownAndSustained); - expectEquals (test.noteKeyStateChangedCallCounter, 3); - - // ...but only if that sustain came on the master channel of that zone! - test.sustainPedal (11, true); - test.noteOn (11, 52, MPEValue::from7BitInt (100)); - expectNote (test.getNote (11, 52), 100, 0, 8192, 64, MPENote::keyDown); - test.noteOff (11, 52, MPEValue::from7BitInt (100)); - expectEquals (test.noteReleasedCallCounter, 1); - - // note-off should not turn off sustained notes inside the same zone - test.noteOff (3, 60, MPEValue::from7BitInt (100)); - test.noteOff (4, 51, MPEValue::from7BitInt (100)); - test.noteOff (10, 60, MPEValue::from7BitInt (100)); // not affected! - expectEquals (test.getNumPlayingNotes(), 2); - expectEquals (test.noteReleasedCallCounter, 2); - expectEquals (test.noteKeyStateChangedCallCounter, 5); - expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::sustained); - expectNote (test.getNote (4, 51), 100, 0, 8192, 64, MPENote::sustained); - - // notes should be turned off when pedal is released - test.sustainPedal (1, false); - expectEquals (test.getNumPlayingNotes(), 0); - expectEquals (test.noteReleasedCallCounter, 4); - } - - beginTest ("sostenutoPedal"); - { - UnitTestInstrument test; - test.setZoneLayout (testLayout); - test.noteOn (3, 60, MPEValue::from7BitInt (100)); // note in lower zone - test.noteOn (10, 60, MPEValue::from7BitInt (100)); // note in upper zone - - // sostenuto pedal on per-note channel shouldn't do anything. - test.sostenutoPedal (3, true); - expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown); - expectNote (test.getNote (10, 60), 100, 0, 8192, 64, MPENote::keyDown); - expectEquals (test.noteKeyStateChangedCallCounter, 0); - - // sostenuto pedal on non-zone channel shouldn't do anything either. - test.sostenutoPedal (9, true); - expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown); - expectNote (test.getNote (10, 60), 100, 0, 8192, 64, MPENote::keyDown); - expectEquals (test.noteKeyStateChangedCallCounter, 0); - - // sostenuto pedal on master channel should sustain notes on *that* zone. - test.sostenutoPedal (1, true); - expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDownAndSustained); - expectNote (test.getNote (10, 60), 100, 0, 8192, 64, MPENote::keyDown); - expectEquals (test.noteKeyStateChangedCallCounter, 1); - - // release - test.sostenutoPedal (1, false); - expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown); - expectNote (test.getNote (10, 60), 100, 0, 8192, 64, MPENote::keyDown); - expectEquals (test.noteKeyStateChangedCallCounter, 2); - - // should only sustain notes turned on *before* the press (difference to sustain pedal) - test.sostenutoPedal (1, true); - expectEquals (test.noteKeyStateChangedCallCounter, 3); - test.noteOn (4, 51, MPEValue::from7BitInt (100)); - expectEquals (test.getNumPlayingNotes(), 3); - expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDownAndSustained); - expectNote (test.getNote (4, 51), 100, 0, 8192, 64, MPENote::keyDown); - expectNote (test.getNote (10, 60), 100, 0, 8192, 64, MPENote::keyDown); - expectEquals (test.noteKeyStateChangedCallCounter, 3); - - // note-off should not turn off sustained notes inside the same zone, - // but only if they were turned on *before* the sostenuto pedal (difference to sustain pedal) - test.noteOff (3, 60, MPEValue::from7BitInt (100)); - test.noteOff (4, 51, MPEValue::from7BitInt (100)); - test.noteOff (10, 60, MPEValue::from7BitInt (100)); // not affected! - expectEquals (test.getNumPlayingNotes(), 1); - expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::sustained); - expectEquals (test.noteReleasedCallCounter, 2); - expectEquals (test.noteKeyStateChangedCallCounter, 4); - - // notes should be turned off when pedal is released - test.sustainPedal (1, false); - expectEquals (test.getNumPlayingNotes(), 0); - expectEquals (test.noteReleasedCallCounter, 3); - } - - beginTest ("getMostRecentNote"); - { - MPEInstrument test; - test.setZoneLayout (testLayout); - - test.noteOn (3, 60, MPEValue::from7BitInt (100)); - test.noteOn (3, 61, MPEValue::from7BitInt (100)); - - { - auto note = test.getMostRecentNote (2); - expect (! note.isValid()); - } - { - auto note = test.getMostRecentNote (3); - expect (note.isValid()); - expectEquals (int (note.midiChannel), 3); - expectEquals (int (note.initialNote), 61); - } - - test.sustainPedal (1, true); - test.noteOff (3, 61, MPEValue::from7BitInt (100)); - - { - auto note = test.getMostRecentNote (3); - expect (note.isValid()); - expectEquals (int (note.midiChannel), 3); - expectEquals (int (note.initialNote), 60); - } - - test.sustainPedal (1, false); - test.noteOff (3, 60, MPEValue::from7BitInt (100)); - - { - auto note = test.getMostRecentNote (3); - expect (! note.isValid()); - } - } - - beginTest ("getMostRecentNoteOtherThan"); - { - MPENote testNote (3, 60, - MPEValue::centreValue(), MPEValue::centreValue(), - MPEValue::centreValue(), MPEValue::centreValue()); - - { - // case 1: the note to exclude is not the most recent one. - - MPEInstrument test; - test.setZoneLayout (testLayout); - expect (! test.getMostRecentNoteOtherThan (testNote).isValid()); - - test.noteOn (3, 60, MPEValue::from7BitInt (100)); - expect (! test.getMostRecentNoteOtherThan (testNote).isValid()); - - test.noteOn (4, 61, MPEValue::from7BitInt (100)); - expect (test.getMostRecentNoteOtherThan (testNote).isValid()); - expect (test.getMostRecentNoteOtherThan (testNote).midiChannel == 4); - expect (test.getMostRecentNoteOtherThan (testNote).initialNote == 61); - } - { - // case 2: the note to exclude is the most recent one. - - MPEInstrument test; - test.setZoneLayout (testLayout); - expect (! test.getMostRecentNoteOtherThan (testNote).isValid()); - - test.noteOn (4, 61, MPEValue::from7BitInt (100)); - expect (test.getMostRecentNoteOtherThan (testNote).isValid()); - expect (test.getMostRecentNoteOtherThan (testNote).midiChannel == 4); - expect (test.getMostRecentNoteOtherThan (testNote).initialNote == 61); - - test.noteOn (3, 60, MPEValue::from7BitInt (100)); - expect (test.getMostRecentNoteOtherThan (testNote).isValid()); - expect (test.getMostRecentNoteOtherThan (testNote).midiChannel == 4); - expect (test.getMostRecentNoteOtherThan (testNote).initialNote == 61); - } - } - - beginTest ("pressure"); - { - { - UnitTestInstrument test; - test.setZoneLayout (testLayout); - - test.noteOn (3, 60, MPEValue::from7BitInt (100)); - test.noteOn (4, 60, MPEValue::from7BitInt (100)); - test.noteOn (10, 60, MPEValue::from7BitInt (100)); - - // applying pressure on a per-note channel should modulate one note - test.pressure (3, MPEValue::from7BitInt (33)); - expectNote (test.getNote (3, 60), 100, 33, 8192, 64, MPENote::keyDown); - expectNote (test.getNote (4, 60), 100, 0, 8192, 64, MPENote::keyDown); - expectNote (test.getNote (10, 60), 100, 0, 8192, 64, MPENote::keyDown); - expectEquals (test.notePressureChangedCallCounter, 1); - - // applying pressure on a master channel should modulate all notes in this zone - test.pressure (1, MPEValue::from7BitInt (44)); - expectNote (test.getNote (3, 60), 100, 44, 8192, 64, MPENote::keyDown); - expectNote (test.getNote (4, 60), 100, 44, 8192, 64, MPENote::keyDown); - expectNote (test.getNote (10, 60), 100, 0, 8192, 64, MPENote::keyDown); - expectEquals (test.notePressureChangedCallCounter, 3); - - // applying pressure on an unrelated channel should be ignored - test.pressure (8, MPEValue::from7BitInt (55)); - expectNote (test.getNote (3, 60), 100, 44, 8192, 64, MPENote::keyDown); - expectNote (test.getNote (4, 60), 100, 44, 8192, 64, MPENote::keyDown); - expectNote (test.getNote (10, 60), 100, 0, 8192, 64, MPENote::keyDown); - expectEquals (test.notePressureChangedCallCounter, 3); - } - { - UnitTestInstrument test; - test.setZoneLayout (testLayout); - - // two notes on same channel - only last added should be modulated - test.noteOn (3, 60, MPEValue::from7BitInt (100)); - test.noteOn (3, 61, MPEValue::from7BitInt (100)); - test.pressure (3, MPEValue::from7BitInt (66)); - expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown); - expectNote (test.getNote (3, 61), 100, 66, 8192, 64, MPENote::keyDown); - expectEquals (test.notePressureChangedCallCounter, 1); - } - { - UnitTestInstrument test; - test.setZoneLayout (testLayout); - - // edge case: two notes on same channel, one gets released, - // then the other should be modulated - test.noteOn (3, 60, MPEValue::from7BitInt (100)); - test.noteOn (3, 61, MPEValue::from7BitInt (100)); - test.noteOff (3, 61, MPEValue::from7BitInt (100)); - test.pressure (3, MPEValue::from7BitInt (77)); - expectEquals (test.getNumPlayingNotes(), 1); - expectNote (test.getNote (3, 60), 100, 77, 8192, 64, MPENote::keyDown); - expectEquals (test.notePressureChangedCallCounter, 1); - } - { - UnitTestInstrument test; - test.setZoneLayout (testLayout); - - // if no pressure is sent before note-on, default = 0 should be used - test.noteOn (3, 60, MPEValue::from7BitInt (100)); - expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown); - } - { - UnitTestInstrument test; - test.setZoneLayout (testLayout); - - // if pressure is sent before note-on, use that - test.pressure (3, MPEValue::from7BitInt (77)); - test.noteOn (3, 60, MPEValue::from7BitInt (100)); - expectNote (test.getNote (3, 60), 100, 77, 8192, 64, MPENote::keyDown); - } - { - UnitTestInstrument test; - test.setZoneLayout (testLayout); - - // if pressure is sent before note-on, but it belonged to another note - // on the same channel that has since been turned off, use default = 0 - test.noteOn (3, 61, MPEValue::from7BitInt (100)); - test.pressure (3, MPEValue::from7BitInt (77)); - test.noteOff (3, 61, MPEValue::from7BitInt (100)); - test.noteOn (3, 60, MPEValue::from7BitInt (100)); - expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown); - } - { - UnitTestInstrument test; - test.setZoneLayout (testLayout); - - // edge case: two notes on the same channel simultaneously. the second one should use - // pressure = 0 initially but then react to additional pressure messages - test.noteOn (3, 61, MPEValue::from7BitInt (100)); - test.pressure (3, MPEValue::from7BitInt (77)); - test.noteOn (3, 60, MPEValue::from7BitInt (100)); - expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown); - test.pressure (3, MPEValue::from7BitInt (78)); - expectNote (test.getNote (3, 60), 100, 78, 8192, 64, MPENote::keyDown); - expectNote (test.getNote (3, 61), 100, 77, 8192, 64, MPENote::keyDown); - } - - { - UnitTestInstrument test; - test.setZoneLayout (testLayout); - - // master channel will use poly-aftertouch for pressure - test.noteOn (16, 60, MPEValue::from7BitInt (100)); - expectNote (test.getNote (16, 60), 100, 0, 8192, 64, MPENote::keyDown); - test.aftertouch (16, 60, MPEValue::from7BitInt (27)); - expectNote (test.getNote (16, 60), 100, 27, 8192, 64, MPENote::keyDown); - - // member channels will not respond to poly-aftertouch - test.noteOn (3, 60, MPEValue::from7BitInt (100)); - test.aftertouch (3, 60, MPEValue::from7BitInt (50)); - expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown); - } - } - - beginTest ("pitchbend"); - { - { - UnitTestInstrument test; - test.setZoneLayout (testLayout); - - test.noteOn (3, 60, MPEValue::from7BitInt (100)); - test.noteOn (4, 60, MPEValue::from7BitInt (100)); - test.noteOn (10, 60, MPEValue::from7BitInt (100)); - - // applying pitchbend on a per-note channel should modulate one note - test.pitchbend (3, MPEValue::from14BitInt (1111)); - expectNote (test.getNote (3, 60), 100, 0, 1111, 64, MPENote::keyDown); - expectNote (test.getNote (4, 60), 100, 0, 8192, 64, MPENote::keyDown); - expectNote (test.getNote (10, 60), 100, 0, 8192, 64, MPENote::keyDown); - expectEquals (test.notePitchbendChangedCallCounter, 1); - - // applying pitchbend on a master channel should be ignored for the - // value of per-note pitchbend. Tests covering master pitchbend below. - // Note: noteChanged will be called anyway for notes in that zone - // because the total pitchbend for those notes has changed - test.pitchbend (1, MPEValue::from14BitInt (2222)); - expectNote (test.getNote (3, 60), 100, 0, 1111, 64, MPENote::keyDown); - expectNote (test.getNote (4, 60), 100, 0, 8192, 64, MPENote::keyDown); - expectNote (test.getNote (10, 60), 100, 0, 8192, 64, MPENote::keyDown); - expectEquals (test.notePitchbendChangedCallCounter, 3); - - // applying pitchbend on an unrelated channel should do nothing. - test.pitchbend (8, MPEValue::from14BitInt (3333)); - expectNote (test.getNote (3, 60), 100, 0, 1111, 64, MPENote::keyDown); - expectNote (test.getNote (4, 60), 100, 0, 8192, 64, MPENote::keyDown); - expectNote (test.getNote (10, 60), 100, 0, 8192, 64, MPENote::keyDown); - expectEquals (test.notePitchbendChangedCallCounter, 3); - } - { - UnitTestInstrument test; - test.setZoneLayout (testLayout); - - // two notes on same channel - only last added should be bent - test.noteOn (3, 60, MPEValue::from7BitInt (100)); - test.noteOn (3, 61, MPEValue::from7BitInt (100)); - test.pitchbend (3, MPEValue::from14BitInt (4444)); - expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown); - expectNote (test.getNote (3, 61), 100, 0, 4444, 64, MPENote::keyDown); - expectEquals (test.notePitchbendChangedCallCounter, 1); - } - { - UnitTestInstrument test; - test.setZoneLayout (testLayout); - - // edge case: two notes on same channel, one gets released, - // then the other should be bent - test.noteOn (3, 60, MPEValue::from7BitInt (100)); - test.noteOn (3, 61, MPEValue::from7BitInt (100)); - test.noteOff (3, 61, MPEValue::from7BitInt (100)); - test.pitchbend (3, MPEValue::from14BitInt (5555)); - expectEquals (test.getNumPlayingNotes(), 1); - expectNote (test.getNote (3, 60), 100, 0, 5555, 64, MPENote::keyDown); - expectEquals (test.notePitchbendChangedCallCounter, 1); - } - { - UnitTestInstrument test; - test.setZoneLayout (testLayout); - - // Richard's edge case: - // - press one note - // - press sustain (careful: must be sent on master channel) - // - release first note (is still sustained!) - // - press another note (happens to be on the same MIDI channel!) - // - pitchbend that other note - // - the first note should not be bent, only the second one. - - test.noteOn (3, 60, MPEValue::from7BitInt (100)); - test.sustainPedal (1, true); - test.noteOff (3, 60, MPEValue::from7BitInt (64)); - expectEquals (test.getNumPlayingNotes(), 1); - expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::sustained); - expectEquals (test.noteKeyStateChangedCallCounter, 2); - - test.noteOn (3, 61, MPEValue::from7BitInt (100)); - test.pitchbend (3, MPEValue::from14BitInt (6666)); - expectEquals (test.getNumPlayingNotes(), 2); - expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::sustained); - expectNote (test.getNote (3, 61), 100, 0, 6666, 64, MPENote::keyDownAndSustained); - expectEquals (test.notePitchbendChangedCallCounter, 1); - } - { - UnitTestInstrument test; - test.setZoneLayout (testLayout); - - // Zsolt's edge case: - // - press one note - // - modulate pitchbend or timbre - // - release the note - // - press same note again without sending a pitchbend or timbre message before the note-on - // - the note should be turned on with a default value for pitchbend/timbre, - // and *not* the last value received on channel. - - test.noteOn (3, 60, MPEValue::from7BitInt (100)); - test.pitchbend (3, MPEValue::from14BitInt (5555)); - expectNote (test.getNote (3, 60), 100, 0, 5555, 64, MPENote::keyDown); - - test.noteOff (3, 60, MPEValue::from7BitInt (100)); - test.noteOn (3, 60, MPEValue::from7BitInt (100)); - expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown); - } - { - // applying per-note pitchbend should set the note's totalPitchbendInSemitones - // correctly depending on the per-note pitchbend range of the zone. - UnitTestInstrument test; - - MPEZoneLayout layout = testLayout; - test.setZoneLayout (layout); // default should be +/- 48 semitones - test.noteOn (3, 60, MPEValue::from7BitInt (100)); - test.pitchbend (3, MPEValue::from14BitInt (4096)); - expectDoubleWithinRelativeError (test.getMostRecentNote (3).totalPitchbendInSemitones, -24.0, 0.01); - - layout.setLowerZone (5, 96); - test.setZoneLayout (layout); - test.noteOn (3, 60, MPEValue::from7BitInt (100)); - test.pitchbend (3, MPEValue::from14BitInt (0)); // -max - expectDoubleWithinRelativeError (test.getMostRecentNote (3).totalPitchbendInSemitones, -96.0, 0.01); - - layout.setLowerZone (5, 1); - test.setZoneLayout (layout); - test.noteOn (3, 60, MPEValue::from7BitInt (100)); - test.pitchbend (3, MPEValue::from14BitInt (16383)); // +max - expectDoubleWithinRelativeError (test.getMostRecentNote (3).totalPitchbendInSemitones, 1.0, 0.01); - - layout.setLowerZone (5, 0); // pitchbendrange = 0 --> no pitchbend at all - test.setZoneLayout (layout); - test.noteOn (3, 60, MPEValue::from7BitInt (100)); - test.pitchbend (3, MPEValue::from14BitInt (12345)); - expectDoubleWithinRelativeError (test.getMostRecentNote (3).totalPitchbendInSemitones, 0.0, 0.01); - } - { - // applying master pitchbend should set the note's totalPitchbendInSemitones - // correctly depending on the master pitchbend range of the zone. - UnitTestInstrument test; - - MPEZoneLayout layout = testLayout; - test.setZoneLayout (layout); // default should be +/- 2 semitones - test.noteOn (3, 60, MPEValue::from7BitInt (100)); - test.pitchbend (1, MPEValue::from14BitInt (4096)); //halfway between -max and centre - expectDoubleWithinRelativeError (test.getMostRecentNote (3).totalPitchbendInSemitones, -1.0, 0.01); - - layout.setLowerZone (5, 48, 96); - test.setZoneLayout (layout); - test.noteOn (3, 60, MPEValue::from7BitInt (100)); - test.pitchbend (1, MPEValue::from14BitInt (0)); // -max - expectDoubleWithinRelativeError (test.getMostRecentNote (3).totalPitchbendInSemitones, -96.0, 0.01); - - layout.setLowerZone (5, 48, 1); - test.setZoneLayout (layout); - test.noteOn (3, 60, MPEValue::from7BitInt (100)); - test.pitchbend (1, MPEValue::from14BitInt (16383)); // +max - expectDoubleWithinRelativeError (test.getMostRecentNote (3).totalPitchbendInSemitones, 1.0, 0.01); - - layout.setLowerZone (5, 48, 0); // pitchbendrange = 0 --> no pitchbend at all - test.setZoneLayout (layout); - test.noteOn (3, 60, MPEValue::from7BitInt (100)); - test.pitchbend (1, MPEValue::from14BitInt (12345)); - expectDoubleWithinRelativeError (test.getMostRecentNote (3).totalPitchbendInSemitones, 0.0, 0.01); - } - { - // applying both per-note and master pitchbend simultaneously should set - // the note's totalPitchbendInSemitones to the sum of both, correctly - // weighted with the per-note and master pitchbend range, respectively. - UnitTestInstrument test; - - MPEZoneLayout layout = testLayout; - layout.setLowerZone (5, 12, 1); - test.setZoneLayout (layout); - - test.pitchbend (1, MPEValue::from14BitInt (4096)); // master pitchbend 0.5 semitones down - test.pitchbend (3, MPEValue::from14BitInt (0)); // per-note pitchbend 12 semitones down - // additionally, note should react to both pitchbend messages - // correctly even if they arrived before the note-on. - test.noteOn (3, 60, MPEValue::from7BitInt (100)); - expectDoubleWithinRelativeError (test.getMostRecentNote (3).totalPitchbendInSemitones, -12.5, 0.01); - } - } - - beginTest ("timbre"); - { - { - UnitTestInstrument test; - test.setZoneLayout (testLayout); - - test.noteOn (3, 60, MPEValue::from7BitInt (100)); - test.noteOn (4, 60, MPEValue::from7BitInt (100)); - test.noteOn (10, 60, MPEValue::from7BitInt (100)); - - // modulating timbre on a per-note channel should modulate one note - test.timbre (3, MPEValue::from7BitInt (33)); - expectNote (test.getNote (3, 60), 100, 0, 8192, 33, MPENote::keyDown); - expectNote (test.getNote (4, 60), 100, 0, 8192, 64, MPENote::keyDown); - expectNote (test.getNote (10, 60), 100, 0, 8192, 64, MPENote::keyDown); - expectEquals (test.noteTimbreChangedCallCounter, 1); - - // modulating timbre on a master channel should modulate all notes in this zone - test.timbre (1, MPEValue::from7BitInt (44)); - expectNote (test.getNote (3, 60), 100, 0, 8192, 44, MPENote::keyDown); - expectNote (test.getNote (4, 60), 100, 0, 8192, 44, MPENote::keyDown); - expectNote (test.getNote (10, 60), 100, 0, 8192, 64, MPENote::keyDown); - expectEquals (test.noteTimbreChangedCallCounter, 3); - - // modulating timbre on an unrelated channel should be ignored - test.timbre (9, MPEValue::from7BitInt (55)); - expectNote (test.getNote (3, 60), 100, 0, 8192, 44, MPENote::keyDown); - expectNote (test.getNote (4, 60), 100, 0, 8192, 44, MPENote::keyDown); - expectNote (test.getNote (10, 60), 100, 0, 8192, 64, MPENote::keyDown); - expectEquals (test.noteTimbreChangedCallCounter, 3); - } - { - UnitTestInstrument test; - test.setZoneLayout (testLayout); - - // two notes on same channel - only last added should be modulated - test.noteOn (3, 60, MPEValue::from7BitInt (100)); - test.noteOn (3, 61, MPEValue::from7BitInt (100)); - test.timbre (3, MPEValue::from7BitInt (66)); - expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown); - expectNote (test.getNote (3, 61), 100, 0, 8192, 66, MPENote::keyDown); - expectEquals (test.noteTimbreChangedCallCounter, 1); - } - { - UnitTestInstrument test; - test.setZoneLayout (testLayout); - - // edge case: two notes on same channel, one gets released, - // then the other should be modulated - test.noteOn (3, 60, MPEValue::from7BitInt (100)); - test.noteOn (3, 61, MPEValue::from7BitInt (100)); - test.noteOff (3, 61, MPEValue::from7BitInt (100)); - test.timbre (3, MPEValue::from7BitInt (77)); - expectEquals (test.getNumPlayingNotes(), 1); - expectNote (test.getNote (3, 60), 100, 0, 8192, 77, MPENote::keyDown); - expectEquals (test.noteTimbreChangedCallCounter, 1); - } - { - UnitTestInstrument test; - test.setZoneLayout (testLayout); - - // Zsolt's edge case for timbre - test.noteOn (3, 60, MPEValue::from7BitInt (100)); - test.timbre (3, MPEValue::from7BitInt (42)); - expectNote (test.getNote (3, 60), 100, 0, 8192, 42, MPENote::keyDown); - - test.noteOff (3, 60, MPEValue::from7BitInt (100)); - test.noteOn (3, 60, MPEValue::from7BitInt (100)); - expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown); - } - } - - beginTest ("setPressureTrackingMode"); - { - { - // last note played (= default) - UnitTestInstrument test; - test.setZoneLayout (testLayout); - - test.setPressureTrackingMode (MPEInstrument::lastNotePlayedOnChannel); - test.noteOn (3, 60, MPEValue::from7BitInt (100)); - test.noteOn (3, 62, MPEValue::from7BitInt (100)); - test.noteOn (3, 61, MPEValue::from7BitInt (100)); - test.pressure (3, MPEValue::from7BitInt (99)); - expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown); - expectNote (test.getNote (3, 62), 100, 0, 8192, 64, MPENote::keyDown); - expectNote (test.getNote (3, 61), 100, 99, 8192, 64, MPENote::keyDown); - expectEquals (test.notePressureChangedCallCounter, 1); - } - { - // lowest note - UnitTestInstrument test; - test.setZoneLayout (testLayout); - - test.setPressureTrackingMode (MPEInstrument::lowestNoteOnChannel); - test.noteOn (3, 60, MPEValue::from7BitInt (100)); - test.noteOn (3, 62, MPEValue::from7BitInt (100)); - test.noteOn (3, 61, MPEValue::from7BitInt (100)); - test.pressure (3, MPEValue::from7BitInt (99)); - expectNote (test.getNote (3, 60), 100, 99, 8192, 64, MPENote::keyDown); - expectNote (test.getNote (3, 62), 100, 0, 8192, 64, MPENote::keyDown); - expectNote (test.getNote (3, 61), 100, 0, 8192, 64, MPENote::keyDown); - expectEquals (test.notePressureChangedCallCounter, 1); - } - { - // highest note - UnitTestInstrument test; - test.setZoneLayout (testLayout); - - test.setPressureTrackingMode (MPEInstrument::highestNoteOnChannel); - test.noteOn (3, 60, MPEValue::from7BitInt (100)); - test.noteOn (3, 62, MPEValue::from7BitInt (100)); - test.noteOn (3, 61, MPEValue::from7BitInt (100)); - test.pressure (3, MPEValue::from7BitInt (99)); - expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown); - expectNote (test.getNote (3, 62), 100, 99, 8192, 64, MPENote::keyDown); - expectNote (test.getNote (3, 61), 100, 0, 8192, 64, MPENote::keyDown); - expectEquals (test.notePressureChangedCallCounter, 1); - } - { - // all notes - UnitTestInstrument test; - test.setZoneLayout (testLayout); - - test.setPressureTrackingMode (MPEInstrument::allNotesOnChannel); - test.noteOn (3, 60, MPEValue::from7BitInt (100)); - test.noteOn (3, 62, MPEValue::from7BitInt (100)); - test.noteOn (3, 61, MPEValue::from7BitInt (100)); - test.pressure (3, MPEValue::from7BitInt (99)); - expectNote (test.getNote (3, 60), 100, 99, 8192, 64, MPENote::keyDown); - expectNote (test.getNote (3, 62), 100, 99, 8192, 64, MPENote::keyDown); - expectNote (test.getNote (3, 61), 100, 99, 8192, 64, MPENote::keyDown); - expectEquals (test.notePressureChangedCallCounter, 3); - } - } - - beginTest ("setPitchbendTrackingMode"); - { - { - // last note played (= default) - UnitTestInstrument test; - test.setZoneLayout (testLayout); - - test.setPitchbendTrackingMode (MPEInstrument::lastNotePlayedOnChannel); - test.noteOn (3, 60, MPEValue::from7BitInt (100)); - test.noteOn (3, 62, MPEValue::from7BitInt (100)); - test.noteOn (3, 61, MPEValue::from7BitInt (100)); - test.pitchbend (3, MPEValue::from14BitInt (9999)); - expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown); - expectNote (test.getNote (3, 62), 100, 0, 8192, 64, MPENote::keyDown); - expectNote (test.getNote (3, 61), 100, 0, 9999, 64, MPENote::keyDown); - expectEquals (test.notePitchbendChangedCallCounter, 1); - } - { - // lowest note - UnitTestInstrument test; - test.setZoneLayout (testLayout); - - test.setPitchbendTrackingMode (MPEInstrument::lowestNoteOnChannel); - test.noteOn (3, 60, MPEValue::from7BitInt (100)); - test.noteOn (3, 62, MPEValue::from7BitInt (100)); - test.noteOn (3, 61, MPEValue::from7BitInt (100)); - test.pitchbend (3, MPEValue::from14BitInt (9999)); - expectNote (test.getNote (3, 60), 100, 0, 9999, 64, MPENote::keyDown); - expectNote (test.getNote (3, 62), 100, 0, 8192, 64, MPENote::keyDown); - expectNote (test.getNote (3, 61), 100, 0, 8192, 64, MPENote::keyDown); - expectEquals (test.notePitchbendChangedCallCounter, 1); - } - { - // highest note - UnitTestInstrument test; - test.setZoneLayout (testLayout); - - test.setPitchbendTrackingMode (MPEInstrument::highestNoteOnChannel); - test.noteOn (3, 60, MPEValue::from7BitInt (100)); - test.noteOn (3, 62, MPEValue::from7BitInt (100)); - test.noteOn (3, 61, MPEValue::from7BitInt (100)); - test.pitchbend (3, MPEValue::from14BitInt (9999)); - expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown); - expectNote (test.getNote (3, 62), 100, 0, 9999, 64, MPENote::keyDown); - expectNote (test.getNote (3, 61), 100, 0, 8192, 64, MPENote::keyDown); - expectEquals (test.notePitchbendChangedCallCounter, 1); - } - { - // all notes - UnitTestInstrument test; - test.setZoneLayout (testLayout); - - test.setPitchbendTrackingMode (MPEInstrument::allNotesOnChannel); - test.noteOn (3, 60, MPEValue::from7BitInt (100)); - test.noteOn (3, 62, MPEValue::from7BitInt (100)); - test.noteOn (3, 61, MPEValue::from7BitInt (100)); - test.pitchbend (3, MPEValue::from14BitInt (9999)); - expectNote (test.getNote (3, 60), 100, 0, 9999, 64, MPENote::keyDown); - expectNote (test.getNote (3, 62), 100, 0, 9999, 64, MPENote::keyDown); - expectNote (test.getNote (3, 61), 100, 0, 9999, 64, MPENote::keyDown); - expectEquals (test.notePitchbendChangedCallCounter, 3); - } - } - - beginTest ("setTimbreTrackingMode"); - { - { - // last note played (= default) - UnitTestInstrument test; - test.setZoneLayout (testLayout); - - test.setTimbreTrackingMode (MPEInstrument::lastNotePlayedOnChannel); - test.noteOn (3, 60, MPEValue::from7BitInt (100)); - test.noteOn (3, 62, MPEValue::from7BitInt (100)); - test.noteOn (3, 61, MPEValue::from7BitInt (100)); - test.timbre (3, MPEValue::from7BitInt (99)); - expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown); - expectNote (test.getNote (3, 62), 100, 0, 8192, 64, MPENote::keyDown); - expectNote (test.getNote (3, 61), 100, 0, 8192, 99, MPENote::keyDown); - expectEquals (test.noteTimbreChangedCallCounter, 1); - } - { - // lowest note - UnitTestInstrument test; - test.setZoneLayout (testLayout); - - test.setTimbreTrackingMode (MPEInstrument::lowestNoteOnChannel); - test.noteOn (3, 60, MPEValue::from7BitInt (100)); - test.noteOn (3, 62, MPEValue::from7BitInt (100)); - test.noteOn (3, 61, MPEValue::from7BitInt (100)); - test.timbre (3, MPEValue::from7BitInt (99)); - expectNote (test.getNote (3, 60), 100, 0, 8192, 99, MPENote::keyDown); - expectNote (test.getNote (3, 62), 100, 0, 8192, 64, MPENote::keyDown); - expectNote (test.getNote (3, 61), 100, 0, 8192, 64, MPENote::keyDown); - expectEquals (test.noteTimbreChangedCallCounter, 1); - } - { - // highest note - UnitTestInstrument test; - test.setZoneLayout (testLayout); - - test.setTimbreTrackingMode (MPEInstrument::highestNoteOnChannel); - test.noteOn (3, 60, MPEValue::from7BitInt (100)); - test.noteOn (3, 62, MPEValue::from7BitInt (100)); - test.noteOn (3, 61, MPEValue::from7BitInt (100)); - test.timbre (3, MPEValue::from7BitInt (99)); - expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown); - expectNote (test.getNote (3, 62), 100, 0, 8192, 99, MPENote::keyDown); - expectNote (test.getNote (3, 61), 100, 0, 8192, 64, MPENote::keyDown); - expectEquals (test.noteTimbreChangedCallCounter, 1); - } - { - // all notes - UnitTestInstrument test; - test.setZoneLayout (testLayout); - - test.setTimbreTrackingMode (MPEInstrument::allNotesOnChannel); - test.noteOn (3, 60, MPEValue::from7BitInt (100)); - test.noteOn (3, 62, MPEValue::from7BitInt (100)); - test.noteOn (3, 61, MPEValue::from7BitInt (100)); - test.timbre (3, MPEValue::from7BitInt (99)); - expectNote (test.getNote (3, 60), 100, 0, 8192, 99, MPENote::keyDown); - expectNote (test.getNote (3, 62), 100, 0, 8192, 99, MPENote::keyDown); - expectNote (test.getNote (3, 61), 100, 0, 8192, 99, MPENote::keyDown); - expectEquals (test.noteTimbreChangedCallCounter, 3); - } - } - - beginTest ("processNextMidiEvent"); - { - UnitTestInstrument test; - - // note on should trigger noteOn method call - - test.processNextMidiEvent (MidiMessage::noteOn (3, 42, uint8 (92))); - expectEquals (test.noteOnCallCounter, 1); - expectEquals (test.lastMidiChannelReceived, 3); - expectEquals (test.lastMidiNoteNumberReceived, 42); - expectEquals (test.lastMPEValueReceived.as7BitInt(), 92); - - // note off should trigger noteOff method call - - test.processNextMidiEvent (MidiMessage::noteOff (4, 12, uint8 (33))); - expectEquals (test.noteOffCallCounter, 1); - expectEquals (test.lastMidiChannelReceived, 4); - expectEquals (test.lastMidiNoteNumberReceived, 12); - expectEquals (test.lastMPEValueReceived.as7BitInt(), 33); - - // note on with velocity = 0 should trigger noteOff method call - // with a note off velocity of 64 (centre value) - - test.processNextMidiEvent (MidiMessage::noteOn (5, 11, uint8 (0))); - expectEquals (test.noteOffCallCounter, 2); - expectEquals (test.lastMidiChannelReceived, 5); - expectEquals (test.lastMidiNoteNumberReceived, 11); - expectEquals (test.lastMPEValueReceived.as7BitInt(), 64); - - // pitchwheel message should trigger pitchbend method call - - test.processNextMidiEvent (MidiMessage::pitchWheel (1, 3333)); - expectEquals (test.pitchbendCallCounter, 1); - expectEquals (test.lastMidiChannelReceived, 1); - expectEquals (test.lastMPEValueReceived.as14BitInt(), 3333); - - // pressure using channel pressure message (7-bit value) should - // trigger pressure method call - - test.processNextMidiEvent (MidiMessage::channelPressureChange (10, 35)); - expectEquals (test.pressureCallCounter, 1); - expectEquals (test.lastMidiChannelReceived, 10); - expectEquals (test.lastMPEValueReceived.as7BitInt(), 35); - - // pressure using 14-bit value over CC70 and CC102 should trigger - // pressure method call after the MSB is sent - - // a) sending only the MSB - test.processNextMidiEvent (MidiMessage::controllerEvent (3, 70, 120)); - expectEquals (test.pressureCallCounter, 2); - expectEquals (test.lastMidiChannelReceived, 3); - expectEquals (test.lastMPEValueReceived.as7BitInt(), 120); - - // b) sending LSB and MSB (only the MSB should trigger the call) - per MIDI channel! - test.processNextMidiEvent (MidiMessage::controllerEvent (4, 102, 121)); - expectEquals (test.pressureCallCounter, 2); - test.processNextMidiEvent (MidiMessage::controllerEvent (5, 102, 122)); - expectEquals (test.pressureCallCounter, 2); - test.processNextMidiEvent (MidiMessage::controllerEvent (4, 70, 123)); - expectEquals (test.pressureCallCounter, 3); - expectEquals (test.lastMidiChannelReceived, 4); - expectEquals (test.lastMPEValueReceived.as14BitInt(), 121 + (123 << 7)); - test.processNextMidiEvent (MidiMessage::controllerEvent (5, 70, 124)); - expectEquals (test.pressureCallCounter, 4); - expectEquals (test.lastMidiChannelReceived, 5); - expectEquals (test.lastMPEValueReceived.as14BitInt(), 122 + (124 << 7)); - test.processNextMidiEvent (MidiMessage::controllerEvent (5, 70, 64)); - expectEquals (test.pressureCallCounter, 5); - expectEquals (test.lastMidiChannelReceived, 5); - expectEquals (test.lastMPEValueReceived.as7BitInt(), 64); - - // same for timbre 14-bit value over CC74 and CC106 - test.processNextMidiEvent (MidiMessage::controllerEvent (3, 74, 120)); - expectEquals (test.timbreCallCounter, 1); - expectEquals (test.lastMidiChannelReceived, 3); - expectEquals (test.lastMPEValueReceived.as7BitInt(), 120); - test.processNextMidiEvent (MidiMessage::controllerEvent (4, 106, 121)); - expectEquals (test.timbreCallCounter, 1); - test.processNextMidiEvent (MidiMessage::controllerEvent (5, 106, 122)); - expectEquals (test.timbreCallCounter, 1); - test.processNextMidiEvent (MidiMessage::controllerEvent (4, 74, 123)); - expectEquals (test.timbreCallCounter, 2); - expectEquals (test.lastMidiChannelReceived, 4); - expectEquals (test.lastMPEValueReceived.as14BitInt(), 121 + (123 << 7)); - test.processNextMidiEvent (MidiMessage::controllerEvent (5, 74, 124)); - expectEquals (test.timbreCallCounter, 3); - expectEquals (test.lastMidiChannelReceived, 5); - expectEquals (test.lastMPEValueReceived.as14BitInt(), 122 + (124 << 7)); - test.processNextMidiEvent (MidiMessage::controllerEvent (5, 74, 64)); - expectEquals (test.timbreCallCounter, 4); - expectEquals (test.lastMidiChannelReceived, 5); - expectEquals (test.lastMPEValueReceived.as7BitInt(), 64); - - // sustain pedal message (CC64) should trigger sustainPedal method call - test.processNextMidiEvent (MidiMessage::controllerEvent (1, 64, 127)); - expectEquals (test.sustainPedalCallCounter, 1); - expectEquals (test.lastMidiChannelReceived, 1); - expect (test.lastSustainPedalValueReceived); - test.processNextMidiEvent (MidiMessage::controllerEvent (16, 64, 0)); - expectEquals (test.sustainPedalCallCounter, 2); - expectEquals (test.lastMidiChannelReceived, 16); - expect (! test.lastSustainPedalValueReceived); - - // sostenuto pedal message (CC66) should trigger sostenutoPedal method call - test.processNextMidiEvent (MidiMessage::controllerEvent (1, 66, 127)); - expectEquals (test.sostenutoPedalCallCounter, 1); - expectEquals (test.lastMidiChannelReceived, 1); - expect (test.lastSostenutoPedalValueReceived); - test.processNextMidiEvent (MidiMessage::controllerEvent (16, 66, 0)); - expectEquals (test.sostenutoPedalCallCounter, 2); - expectEquals (test.lastMidiChannelReceived, 16); - expect (! test.lastSostenutoPedalValueReceived); - } - { - // MIDI messages modifying the zone layout should be correctly - // forwarded to the internal zone layout and modify it. - // (testing the actual logic of the zone layout is done in the - // MPEZoneLayout unit tests) - MPEInstrument test; - - MidiBuffer buffer; - buffer.addEvents (MPEMessages::setLowerZone (5), 0, -1, 0); - buffer.addEvents (MPEMessages::setUpperZone (6), 0, -1, 0); - - MidiBuffer::Iterator iter (buffer); - MidiMessage message; - int samplePosition; // not actually used, so no need to initialise. - - while (iter.getNextEvent (message, samplePosition)) - test.processNextMidiEvent (message); - - expect (test.getZoneLayout().getLowerZone().isActive()); - expect (test.getZoneLayout().getUpperZone().isActive()); - expectEquals (test.getZoneLayout().getLowerZone().getMasterChannel(), 1); - expectEquals (test.getZoneLayout().getLowerZone().numMemberChannels, 5); - expectEquals (test.getZoneLayout().getUpperZone().getMasterChannel(), 16); - expectEquals (test.getZoneLayout().getUpperZone().numMemberChannels, 6); - } - - beginTest ("MIDI all notes off"); - { - UnitTestInstrument test; - test.setZoneLayout (testLayout); - test.noteOn (3, 60, MPEValue::from7BitInt (100)); - test.noteOn (4, 61, MPEValue::from7BitInt (100)); - test.noteOn (15, 62, MPEValue::from7BitInt (100)); - test.noteOn (15, 63, MPEValue::from7BitInt (100)); - expectEquals (test.getNumPlayingNotes(), 4); - - // on note channel: ignore. - test.processNextMidiEvent (MidiMessage::allControllersOff (3)); - expectEquals (test.getNumPlayingNotes(), 4); - - // on unused channel: ignore. - test.processNextMidiEvent (MidiMessage::allControllersOff (9)); - expectEquals (test.getNumPlayingNotes(), 4); - - // on master channel: release notes in that zone only. - test.processNextMidiEvent (MidiMessage::allControllersOff (1)); - expectEquals (test.getNumPlayingNotes(), 2); - test.processNextMidiEvent (MidiMessage::allControllersOff (16)); - expectEquals (test.getNumPlayingNotes(), 0); - } - - beginTest ("MIDI all notes off (legacy mode)"); - { - UnitTestInstrument test; - test.enableLegacyMode(); - test.noteOn (3, 60, MPEValue::from7BitInt (100)); - test.noteOn (4, 61, MPEValue::from7BitInt (100)); - test.noteOn (15, 62, MPEValue::from7BitInt (100)); - test.noteOn (15, 63, MPEValue::from7BitInt (100)); - expectEquals (test.getNumPlayingNotes(), 4); - - test.processNextMidiEvent (MidiMessage::allControllersOff (3)); - expectEquals (test.getNumPlayingNotes(), 3); - - test.processNextMidiEvent (MidiMessage::allControllersOff (15)); - expectEquals (test.getNumPlayingNotes(), 1); - - test.processNextMidiEvent (MidiMessage::allControllersOff (4)); - expectEquals (test.getNumPlayingNotes(), 0); - } - - beginTest ("default initial values for pitchbend and timbre"); - { - MPEInstrument test; - test.setZoneLayout (testLayout); - - test.pitchbend (3, MPEValue::from14BitInt (3333)); // use for next note-on on ch. 3 - test.pitchbend (2, MPEValue::from14BitInt (4444)); // ignore - test.pitchbend (2, MPEValue::from14BitInt (5555)); // ignore - - test.timbre (3, MPEValue::from7BitInt (66)); // use for next note-on on ch. 3 - test.timbre (2, MPEValue::from7BitInt (77)); // ignore - test.timbre (2, MPEValue::from7BitInt (88)); // ignore - - test.noteOn (3, 60, MPEValue::from7BitInt (100)); - - expectNote (test.getMostRecentNote (3), 100, 0, 3333, 66, MPENote::keyDown); - } - - beginTest ("Legacy mode"); - { - { - // basic check - MPEInstrument test; - expect (! test.isLegacyModeEnabled()); - - test.setZoneLayout (testLayout); - expect (! test.isLegacyModeEnabled()); - - test.enableLegacyMode(); - expect (test.isLegacyModeEnabled()); - - test.setZoneLayout (testLayout); - expect (! test.isLegacyModeEnabled()); - } - { - // constructor w/o default arguments - MPEInstrument test; - test.enableLegacyMode (0, Range (1, 11)); - expectEquals (test.getLegacyModePitchbendRange(), 0); - expect (test.getLegacyModeChannelRange() == Range (1, 11)); - } - { - // getters and setters - MPEInstrument test; - test.enableLegacyMode(); - - expectEquals (test.getLegacyModePitchbendRange(), 2); - expect (test.getLegacyModeChannelRange() == Range (1, 17)); - - test.setLegacyModePitchbendRange (96); - expectEquals (test.getLegacyModePitchbendRange(), 96); - - test.setLegacyModeChannelRange (Range (10, 12)); - expect (test.getLegacyModeChannelRange() == Range (10, 12)); - } - { - // note on should trigger notes on all 16 channels - - UnitTestInstrument test; - test.enableLegacyMode(); - - test.noteOn (1, 60, MPEValue::from7BitInt (100)); - test.noteOn (2, 60, MPEValue::from7BitInt (100)); - test.noteOn (15, 60, MPEValue::from7BitInt (100)); - test.noteOn (16, 60, MPEValue::from7BitInt (100)); - expectEquals (test.getNumPlayingNotes(), 4); - - // polyphonic modulation should work across all 16 channels - - test.pitchbend (1, MPEValue::from14BitInt (9999)); - test.pressure (2, MPEValue::from7BitInt (88)); - test.timbre (15, MPEValue::from7BitInt (77)); - - expectNote (test.getNote (1, 60), 100, 0, 9999, 64, MPENote::keyDown); - expectNote (test.getNote (2, 60), 100, 88, 8192, 64, MPENote::keyDown); - expectNote (test.getNote (15, 60), 100, 0, 8192, 77, MPENote::keyDown); - expectNote (test.getNote (16, 60), 100, 0, 8192, 64, MPENote::keyDown); - - // note off should work in legacy mode - - test.noteOff (15, 60, MPEValue::from7BitInt (0)); - test.noteOff (1, 60, MPEValue::from7BitInt (0)); - test.noteOff (2, 60, MPEValue::from7BitInt (0)); - test.noteOff (16, 60, MPEValue::from7BitInt (0)); - expectEquals (test.getNumPlayingNotes(), 0); - } - { - // legacy mode w/ custom channel range: note on should trigger notes only within range - - UnitTestInstrument test; - test.enableLegacyMode (2, Range (3, 8)); // channels 3-7 - - test.noteOn (1, 60, MPEValue::from7BitInt (100)); - test.noteOn (2, 60, MPEValue::from7BitInt (100)); - test.noteOn (3, 60, MPEValue::from7BitInt (100)); // should trigger - test.noteOn (4, 60, MPEValue::from7BitInt (100)); // should trigger - test.noteOn (6, 60, MPEValue::from7BitInt (100)); // should trigger - test.noteOn (7, 60, MPEValue::from7BitInt (100)); // should trigger - test.noteOn (8, 60, MPEValue::from7BitInt (100)); - test.noteOn (16, 60, MPEValue::from7BitInt (100)); - - expectEquals (test.getNumPlayingNotes(), 4); - expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown); - expectNote (test.getNote (4, 60), 100, 0, 8192, 64, MPENote::keyDown); - expectNote (test.getNote (6, 60), 100, 0, 8192, 64, MPENote::keyDown); - expectNote (test.getNote (7, 60), 100, 0, 8192, 64, MPENote::keyDown); - } - { - // tracking mode in legacy mode - { - UnitTestInstrument test; - test.enableLegacyMode(); - - test.setPitchbendTrackingMode (MPEInstrument::lastNotePlayedOnChannel); - test.noteOn (1, 60, MPEValue::from7BitInt (100)); - test.noteOn (1, 62, MPEValue::from7BitInt (100)); - test.noteOn (1, 61, MPEValue::from7BitInt (100)); - test.pitchbend (1, MPEValue::from14BitInt (9999)); - expectNote (test.getNote (1, 60), 100, 0, 8192, 64, MPENote::keyDown); - expectNote (test.getNote (1, 61), 100, 0, 9999, 64, MPENote::keyDown); - expectNote (test.getNote (1, 62), 100, 0, 8192, 64, MPENote::keyDown); - } - { - UnitTestInstrument test; - test.enableLegacyMode(); - - test.setPitchbendTrackingMode (MPEInstrument::lowestNoteOnChannel); - test.noteOn (1, 60, MPEValue::from7BitInt (100)); - test.noteOn (1, 62, MPEValue::from7BitInt (100)); - test.noteOn (1, 61, MPEValue::from7BitInt (100)); - test.pitchbend (1, MPEValue::from14BitInt (9999)); - expectNote (test.getNote (1, 60), 100, 0, 9999, 64, MPENote::keyDown); - expectNote (test.getNote (1, 61), 100, 0, 8192, 64, MPENote::keyDown); - expectNote (test.getNote (1, 62), 100, 0, 8192, 64, MPENote::keyDown); - } - { - UnitTestInstrument test; - test.enableLegacyMode(); - - test.setPitchbendTrackingMode (MPEInstrument::highestNoteOnChannel); - test.noteOn (1, 60, MPEValue::from7BitInt (100)); - test.noteOn (1, 62, MPEValue::from7BitInt (100)); - test.noteOn (1, 61, MPEValue::from7BitInt (100)); - test.pitchbend (1, MPEValue::from14BitInt (9999)); - expectNote (test.getNote (1, 60), 100, 0, 8192, 64, MPENote::keyDown); - expectNote (test.getNote (1, 61), 100, 0, 8192, 64, MPENote::keyDown); - expectNote (test.getNote (1, 62), 100, 0, 9999, 64, MPENote::keyDown); - } - { - UnitTestInstrument test; - test.enableLegacyMode(); - - test.setPitchbendTrackingMode (MPEInstrument::allNotesOnChannel); - test.noteOn (1, 60, MPEValue::from7BitInt (100)); - test.noteOn (1, 62, MPEValue::from7BitInt (100)); - test.noteOn (1, 61, MPEValue::from7BitInt (100)); - test.pitchbend (1, MPEValue::from14BitInt (9999)); - expectNote (test.getNote (1, 60), 100, 0, 9999, 64, MPENote::keyDown); - expectNote (test.getNote (1, 61), 100, 0, 9999, 64, MPENote::keyDown); - expectNote (test.getNote (1, 62), 100, 0, 9999, 64, MPENote::keyDown); - } - } - { - // custom pitchbend range in legacy mode. - UnitTestInstrument test; - test.enableLegacyMode (11); - - test.pitchbend (1, MPEValue::from14BitInt (4096)); - test.noteOn (1, 60, MPEValue::from7BitInt (100)); - expectDoubleWithinRelativeError (test.getMostRecentNote (1).totalPitchbendInSemitones, -5.5, 0.01); - } - { - // sustain pedal should be per channel in legacy mode. - UnitTestInstrument test; - test.enableLegacyMode(); - - test.sustainPedal (1, true); - test.noteOn (2, 61, MPEValue::from7BitInt (100)); - test.noteOff (2, 61, MPEValue::from7BitInt (100)); - test.noteOn (1, 60, MPEValue::from7BitInt (100)); - test.noteOff (1, 60, MPEValue::from7BitInt (100)); - - expectEquals (test.getNumPlayingNotes(), 1); - expectNote (test.getNote (1, 60), 100, 0, 8192, 64, MPENote::sustained); - - test.sustainPedal (1, false); - expectEquals (test.getNumPlayingNotes(), 0); - - test.noteOn (2, 61, MPEValue::from7BitInt (100)); - test.sustainPedal (1, true); - test.noteOff (2, 61, MPEValue::from7BitInt (100)); - expectEquals (test.getNumPlayingNotes(), 0); - - } - { - // sostenuto pedal should be per channel in legacy mode. - UnitTestInstrument test; - test.enableLegacyMode(); - - test.noteOn (1, 60, MPEValue::from7BitInt (100)); - test.sostenutoPedal (1, true); - test.noteOff (1, 60, MPEValue::from7BitInt (100)); - test.noteOn (2, 61, MPEValue::from7BitInt (100)); - test.noteOff (2, 61, MPEValue::from7BitInt (100)); - - expectEquals (test.getNumPlayingNotes(), 1); - expectNote (test.getNote (1, 60), 100, 0, 8192, 64, MPENote::sustained); - - test.sostenutoPedal (1, false); - expectEquals (test.getNumPlayingNotes(), 0); - - test.noteOn (2, 61, MPEValue::from7BitInt (100)); - test.sostenutoPedal (1, true); - test.noteOff (2, 61, MPEValue::from7BitInt (100)); - expectEquals (test.getNumPlayingNotes(), 0); - } - { - // all notes released when switching layout - UnitTestInstrument test; - test.setZoneLayout (testLayout); - test.noteOn (3, 60, MPEValue::from7BitInt (100)); - expectEquals (test.getNumPlayingNotes(), 1); - - test.enableLegacyMode(); - expectEquals (test.getNumPlayingNotes(), 0); - test.noteOn (3, 60, MPEValue::from7BitInt (100)); - expectEquals (test.getNumPlayingNotes(), 1); - - test.setZoneLayout (testLayout); - expectEquals (test.getNumPlayingNotes(), 0); - } - } - } - -private: - //============================================================================== - /* This mock class is used for unit testing whether the methods of - MPEInstrument are called correctly. - */ - class UnitTestInstrument : public MPEInstrument, - private MPEInstrument::Listener - { - using Base = MPEInstrument; - - public: - UnitTestInstrument() - : noteOnCallCounter (0), noteOffCallCounter (0), pitchbendCallCounter (0), - pressureCallCounter (0), timbreCallCounter (0), sustainPedalCallCounter (0), - sostenutoPedalCallCounter (0), noteAddedCallCounter (0), notePressureChangedCallCounter (0), - notePitchbendChangedCallCounter (0), noteTimbreChangedCallCounter (0), - noteKeyStateChangedCallCounter (0), noteReleasedCallCounter (0), - lastMidiChannelReceived (-1), lastMidiNoteNumberReceived (-1), - lastSustainPedalValueReceived (false), lastSostenutoPedalValueReceived (false) - { - addListener (this); - } - - void noteOn (int midiChannel, int midiNoteNumber, MPEValue midiNoteOnVelocity) override - { - Base::noteOn (midiChannel, midiNoteNumber, midiNoteOnVelocity); - - noteOnCallCounter++; - lastMidiChannelReceived = midiChannel; - lastMidiNoteNumberReceived = midiNoteNumber; - lastMPEValueReceived = midiNoteOnVelocity; - } - - void noteOff (int midiChannel, int midiNoteNumber, MPEValue midiNoteOffVelocity) override - { - Base::noteOff (midiChannel, midiNoteNumber, midiNoteOffVelocity); - - noteOffCallCounter++; - lastMidiChannelReceived = midiChannel; - lastMidiNoteNumberReceived = midiNoteNumber; - lastMPEValueReceived = midiNoteOffVelocity; - } - - void pitchbend (int midiChannel, MPEValue value) override - { - Base::pitchbend (midiChannel, value); - - pitchbendCallCounter++; - lastMidiChannelReceived = midiChannel; - lastMPEValueReceived = value; - } - - void pressure (int midiChannel, MPEValue value) override - { - Base::pressure (midiChannel, value); - - pressureCallCounter++; - lastMidiChannelReceived = midiChannel; - lastMPEValueReceived = value; - } - - void timbre (int midiChannel, MPEValue value) override - { - Base::timbre (midiChannel, value); - - timbreCallCounter++; - lastMidiChannelReceived = midiChannel; - lastMPEValueReceived = value; - } - - void sustainPedal (int midiChannel, bool value) override - { - Base::sustainPedal (midiChannel, value); - - sustainPedalCallCounter++; - lastMidiChannelReceived = midiChannel; - lastSustainPedalValueReceived = value; - } - - void sostenutoPedal (int midiChannel, bool value) override - { - Base::sostenutoPedal (midiChannel, value); - - sostenutoPedalCallCounter++; - lastMidiChannelReceived = midiChannel; - lastSostenutoPedalValueReceived = value; - } - - void aftertouch (int midiChannel, int midiNoteNumber, MPEValue value) - { - const auto message = juce::MidiMessage::aftertouchChange (midiChannel, midiNoteNumber, value.as7BitInt()); - processNextMidiEvent (message); - } - - int noteOnCallCounter, noteOffCallCounter, pitchbendCallCounter, - pressureCallCounter, timbreCallCounter, sustainPedalCallCounter, - sostenutoPedalCallCounter, noteAddedCallCounter, - notePressureChangedCallCounter, notePitchbendChangedCallCounter, - noteTimbreChangedCallCounter, noteKeyStateChangedCallCounter, - noteReleasedCallCounter, lastMidiChannelReceived, lastMidiNoteNumberReceived; - - bool lastSustainPedalValueReceived, lastSostenutoPedalValueReceived; - MPEValue lastMPEValueReceived; - std::unique_ptr lastNoteFinished; - - private: - //============================================================================== - void noteAdded (MPENote) override { noteAddedCallCounter++; } - - void notePressureChanged (MPENote) override { notePressureChangedCallCounter++; } - void notePitchbendChanged (MPENote) override { notePitchbendChangedCallCounter++; } - void noteTimbreChanged (MPENote) override { noteTimbreChangedCallCounter++; } - void noteKeyStateChanged (MPENote) override { noteKeyStateChangedCallCounter++; } - - void noteReleased (MPENote finishedNote) override - { - noteReleasedCallCounter++; - lastNoteFinished.reset (new MPENote (finishedNote)); - } - }; - - //============================================================================== - void expectNote (MPENote noteToTest, - int noteOnVelocity7Bit, - int pressure7Bit, - int pitchbend14Bit, - int timbre7Bit, - MPENote::KeyState keyState) - { - expect (noteToTest.isValid()); - expectEquals (noteToTest.noteOnVelocity.as7BitInt(), noteOnVelocity7Bit); - expectEquals (noteToTest.pressure.as7BitInt(), pressure7Bit); - expectEquals (noteToTest.pitchbend.as14BitInt(), pitchbend14Bit); - expectEquals (noteToTest.timbre.as7BitInt(),timbre7Bit); - expect (noteToTest.keyState == keyState); - } - - void expectHasFinishedNote (const UnitTestInstrument& test, - int channel, int noteNumber, int noteOffVelocity7Bit) - { - expect (test.lastNoteFinished != nullptr); - expectEquals (int (test.lastNoteFinished->midiChannel), channel); - expectEquals (int (test.lastNoteFinished->initialNote), noteNumber); - expectEquals (test.lastNoteFinished->noteOffVelocity.as7BitInt(), noteOffVelocity7Bit); - expect (test.lastNoteFinished->keyState == MPENote::off); - } - - void expectDoubleWithinRelativeError (double actual, double expected, double maxRelativeError) - { - const double maxAbsoluteError = jmax (1.0, std::abs (expected)) * maxRelativeError; - expect (std::abs (expected - actual) < maxAbsoluteError); - } - - //============================================================================== - MPEZoneLayout testLayout; -}; - -static MPEInstrumentTests MPEInstrumentUnitTests; - -#endif - -} // namespace juce +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2017 - ROLI Ltd. + + JUCE is an open source library subject to commercial or open-source + licensing. + + The code included in this file is provided under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license. Permission + To use, copy, modify, and/or distribute this software for any purpose with or + without fee is hereby granted provided that the above copyright notice and + this permission notice appear in all copies. + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +namespace juce +{ + +namespace +{ + const uint8 noLSBValueReceived = 0xff; + const Range allChannels { 1, 17 }; +} + +//============================================================================== +MPEInstrument::MPEInstrument() noexcept +{ + std::fill_n (lastPressureLowerBitReceivedOnChannel, 16, noLSBValueReceived); + std::fill_n (lastTimbreLowerBitReceivedOnChannel, 16, noLSBValueReceived); + std::fill_n (isMemberChannelSustained, 16, false); + + pitchbendDimension.value = &MPENote::pitchbend; + pressureDimension.value = &MPENote::pressure; + timbreDimension.value = &MPENote::timbre; + + // the default value for pressure is 0, for all other dimension it is centre (= default MPEValue) + std::fill_n (pressureDimension.lastValueReceivedOnChannel, 16, MPEValue::minValue()); + + legacyMode.isEnabled = false; + legacyMode.pitchbendRange = 2; + legacyMode.channelRange = allChannels; +} + +MPEInstrument::~MPEInstrument() +{ +} + +//============================================================================== +MPEZoneLayout MPEInstrument::getZoneLayout() const noexcept +{ + return zoneLayout; +} + +void MPEInstrument::setZoneLayout (MPEZoneLayout newLayout) +{ + releaseAllNotes(); + + const ScopedLock sl (lock); + legacyMode.isEnabled = false; + zoneLayout = newLayout; +} + +//============================================================================== +void MPEInstrument::enableLegacyMode (int pitchbendRange, Range channelRange) +{ + releaseAllNotes(); + + const ScopedLock sl (lock); + legacyMode.isEnabled = true; + legacyMode.pitchbendRange = pitchbendRange; + legacyMode.channelRange = channelRange; + zoneLayout.clearAllZones(); +} + +bool MPEInstrument::isLegacyModeEnabled() const noexcept +{ + return legacyMode.isEnabled; +} + +Range MPEInstrument::getLegacyModeChannelRange() const noexcept +{ + return legacyMode.channelRange; +} + +void MPEInstrument::setLegacyModeChannelRange (Range channelRange) +{ + jassert (allChannels.contains (channelRange)); + + releaseAllNotes(); + const ScopedLock sl (lock); + legacyMode.channelRange = channelRange; +} + +int MPEInstrument::getLegacyModePitchbendRange() const noexcept +{ + return legacyMode.pitchbendRange; +} + +void MPEInstrument::setLegacyModePitchbendRange (int pitchbendRange) +{ + jassert (pitchbendRange >= 0 && pitchbendRange <= 96); + + releaseAllNotes(); + const ScopedLock sl (lock); + legacyMode.pitchbendRange = pitchbendRange; +} + +//============================================================================== +void MPEInstrument::setPressureTrackingMode (TrackingMode modeToUse) +{ + pressureDimension.trackingMode = modeToUse; +} + +void MPEInstrument::setPitchbendTrackingMode (TrackingMode modeToUse) +{ + pitchbendDimension.trackingMode = modeToUse; +} + +void MPEInstrument::setTimbreTrackingMode (TrackingMode modeToUse) +{ + timbreDimension.trackingMode = modeToUse; +} + +//============================================================================== +void MPEInstrument::addListener (Listener* listenerToAdd) +{ + listeners.add (listenerToAdd); +} + +void MPEInstrument::removeListener (Listener* listenerToRemove) +{ + listeners.remove (listenerToRemove); +} + +//============================================================================== +void MPEInstrument::processNextMidiEvent (const MidiMessage& message) +{ + zoneLayout.processNextMidiEvent (message); + + if (message.isNoteOn (true)) processMidiNoteOnMessage (message); + else if (message.isNoteOff (false)) processMidiNoteOffMessage (message); + else if (message.isResetAllControllers() + || message.isAllNotesOff()) processMidiResetAllControllersMessage (message); + else if (message.isPitchWheel()) processMidiPitchWheelMessage (message); + else if (message.isChannelPressure()) processMidiChannelPressureMessage (message); + else if (message.isController()) processMidiControllerMessage (message); + else if (message.isAftertouch()) processMidiAfterTouchMessage (message); +} + +//============================================================================== +void MPEInstrument::processMidiNoteOnMessage (const MidiMessage& message) +{ + // Note: If a note-on with velocity = 0 is used to convey a note-off, + // then the actual note-off velocity is not known. In this case, + // the MPE convention is to use note-off velocity = 64. + + if (message.getVelocity() == 0) + { + noteOff (message.getChannel(), + message.getNoteNumber(), + MPEValue::from7BitInt (64)); + } + else + { + noteOn (message.getChannel(), + message.getNoteNumber(), + MPEValue::from7BitInt (message.getVelocity())); + } +} + +//============================================================================== +void MPEInstrument::processMidiNoteOffMessage (const MidiMessage& message) +{ + noteOff (message.getChannel(), + message.getNoteNumber(), + MPEValue::from7BitInt (message.getVelocity())); +} + +//============================================================================== +void MPEInstrument::processMidiPitchWheelMessage (const MidiMessage& message) +{ + pitchbend (message.getChannel(), + MPEValue::from14BitInt (message.getPitchWheelValue())); +} + +//============================================================================== +void MPEInstrument::processMidiChannelPressureMessage (const MidiMessage& message) +{ + pressure (message.getChannel(), + MPEValue::from7BitInt (message.getChannelPressureValue())); +} + +//============================================================================== +void MPEInstrument::processMidiControllerMessage (const MidiMessage& message) +{ + switch (message.getControllerNumber()) + { + case 64: sustainPedal (message.getChannel(), message.isSustainPedalOn()); break; + case 66: sostenutoPedal (message.getChannel(), message.isSostenutoPedalOn()); break; + case 70: handlePressureMSB (message.getChannel(), message.getControllerValue()); break; + case 74: handleTimbreMSB (message.getChannel(), message.getControllerValue()); break; + case 102: handlePressureLSB (message.getChannel(), message.getControllerValue()); break; + case 106: handleTimbreLSB (message.getChannel(), message.getControllerValue()); break; + default: break; + } +} + +//============================================================================== +void MPEInstrument::processMidiResetAllControllersMessage (const MidiMessage& message) +{ + // in MPE mode, "reset all controllers" is per-zone and expected on the master channel; + // in legacy mode, it is per MIDI channel (within the channel range used). + + if (legacyMode.isEnabled && legacyMode.channelRange.contains (message.getChannel())) + { + for (auto i = notes.size(); --i >= 0;) + { + auto& note = notes.getReference (i); + + if (note.midiChannel == message.getChannel()) + { + note.keyState = MPENote::off; + note.noteOffVelocity = MPEValue::from7BitInt (64); // some reasonable number + listeners.call ([&] (Listener& l) { l.noteReleased (note); }); + notes.remove (i); + } + } + } + else if (isMasterChannel (message.getChannel())) + { + auto zone = (message.getChannel() == 1 ? zoneLayout.getLowerZone() + : zoneLayout.getUpperZone()); + + for (auto i = notes.size(); --i >= 0;) + { + auto& note = notes.getReference (i); + + if (zone.isUsing (note.midiChannel)) + { + note.keyState = MPENote::off; + note.noteOffVelocity = MPEValue::from7BitInt (64); // some reasonable number + listeners.call ([&] (Listener& l) { l.noteReleased (note); }); + notes.remove (i); + } + } + } +} + +void MPEInstrument::processMidiAfterTouchMessage (const MidiMessage& message) +{ + if (! isMasterChannel (message.getChannel())) + return; + + polyAftertouch (message.getChannel(), message.getNoteNumber(), + MPEValue::from7BitInt (message.getAfterTouchValue())); +} + +//============================================================================== +void MPEInstrument::handlePressureMSB (int midiChannel, int value) noexcept +{ + auto lsb = lastPressureLowerBitReceivedOnChannel[midiChannel - 1]; + + pressure (midiChannel, lsb == noLSBValueReceived ? MPEValue::from7BitInt (value) + : MPEValue::from14BitInt (lsb + (value << 7))); +} + +void MPEInstrument::handlePressureLSB (int midiChannel, int value) noexcept +{ + lastPressureLowerBitReceivedOnChannel[midiChannel - 1] = uint8 (value); +} + +void MPEInstrument::handleTimbreMSB (int midiChannel, int value) noexcept +{ + auto lsb = lastTimbreLowerBitReceivedOnChannel[midiChannel - 1]; + + timbre (midiChannel, lsb == noLSBValueReceived ? MPEValue::from7BitInt (value) + : MPEValue::from14BitInt (lsb + (value << 7))); +} + +void MPEInstrument::handleTimbreLSB (int midiChannel, int value) noexcept +{ + lastTimbreLowerBitReceivedOnChannel[midiChannel - 1] = uint8 (value); +} + +//============================================================================== +void MPEInstrument::noteOn (int midiChannel, + int midiNoteNumber, + MPEValue midiNoteOnVelocity) +{ + if (! isUsingChannel (midiChannel)) + return; + + MPENote newNote (midiChannel, + midiNoteNumber, + midiNoteOnVelocity, + getInitialValueForNewNote (midiChannel, pitchbendDimension), + getInitialValueForNewNote (midiChannel, pressureDimension), + getInitialValueForNewNote (midiChannel, timbreDimension), + isMemberChannelSustained[midiChannel - 1] ? MPENote::keyDownAndSustained : MPENote::keyDown); + + const ScopedLock sl (lock); + updateNoteTotalPitchbend (newNote); + + if (auto* alreadyPlayingNote = getNotePtr (midiChannel, midiNoteNumber)) + { + // pathological case: second note-on received for same note -> retrigger it + alreadyPlayingNote->keyState = MPENote::off; + alreadyPlayingNote->noteOffVelocity = MPEValue::from7BitInt (64); // some reasonable number + listeners.call ([=] (Listener& l) { l.noteReleased (*alreadyPlayingNote); }); + notes.remove (alreadyPlayingNote); + } + + notes.add (newNote); + listeners.call ([&] (Listener& l) { l.noteAdded (newNote); }); +} + +//============================================================================== +void MPEInstrument::noteOff (int midiChannel, + int midiNoteNumber, + MPEValue midiNoteOffVelocity) +{ + if (notes.isEmpty() || ! isUsingChannel (midiChannel)) + return; + + const ScopedLock sl (lock); + + if (auto* note = getNotePtr (midiChannel, midiNoteNumber)) + { + note->keyState = (note->keyState == MPENote::keyDownAndSustained) ? MPENote::sustained : MPENote::off; + note->noteOffVelocity = midiNoteOffVelocity; + + // If no more notes are playing on this channel, reset the dimension values + if (getLastNotePlayedPtr (midiChannel) == nullptr) + { + pressureDimension.lastValueReceivedOnChannel[midiChannel - 1] = MPEValue::minValue(); + pitchbendDimension.lastValueReceivedOnChannel[midiChannel - 1] = MPEValue::centreValue(); + timbreDimension.lastValueReceivedOnChannel[midiChannel - 1] = MPEValue::centreValue(); + } + + if (note->keyState == MPENote::off) + { + listeners.call ([=] (Listener& l) { l.noteReleased (*note); }); + notes.remove (note); + } + else + { + listeners.call ([=] (Listener& l) { l.noteKeyStateChanged (*note); }); + } + } +} + +//============================================================================== +void MPEInstrument::pitchbend (int midiChannel, MPEValue value) +{ + const ScopedLock sl (lock); + updateDimension (midiChannel, pitchbendDimension, value); +} + +void MPEInstrument::pressure (int midiChannel, MPEValue value) +{ + const ScopedLock sl (lock); + updateDimension (midiChannel, pressureDimension, value); +} + +void MPEInstrument::timbre (int midiChannel, MPEValue value) +{ + const ScopedLock sl (lock); + updateDimension (midiChannel, timbreDimension, value); +} + +void MPEInstrument::polyAftertouch (int midiChannel, int midiNoteNumber, MPEValue value) +{ + const ScopedLock sl (lock); + + for (auto i = notes.size(); --i >= 0;) + { + auto& note = notes.getReference (i); + + if (note.midiChannel == midiChannel + && note.initialNote == midiNoteNumber + && pressureDimension.getValue (note) != value) + { + pressureDimension.getValue (note) = value; + callListenersDimensionChanged (note, pressureDimension); + } + } +} + +MPEValue MPEInstrument::getInitialValueForNewNote (int midiChannel, MPEDimension& dimension) const +{ + if (getLastNotePlayedPtr (midiChannel) != nullptr) + return &dimension == &pressureDimension ? MPEValue::minValue() : MPEValue::centreValue(); + + return dimension.lastValueReceivedOnChannel[midiChannel - 1]; +} + +//============================================================================== +void MPEInstrument::updateDimension (int midiChannel, MPEDimension& dimension, MPEValue value) +{ + dimension.lastValueReceivedOnChannel[midiChannel - 1] = value; + + if (notes.isEmpty()) + return; + + if (isMemberChannel (midiChannel)) + { + if (dimension.trackingMode == allNotesOnChannel) + { + for (auto i = notes.size(); --i >= 0;) + { + auto& note = notes.getReference (i); + + if (note.midiChannel == midiChannel) + updateDimensionForNote (note, dimension, value); + } + } + else + { + if (auto* note = getNotePtr (midiChannel, dimension.trackingMode)) + updateDimensionForNote (*note, dimension, value); + } + } + else if (isMasterChannel (midiChannel)) + { + updateDimensionMaster (midiChannel == 1, dimension, value); + } +} + +//============================================================================== +void MPEInstrument::updateDimensionMaster (bool isLowerZone, MPEDimension& dimension, MPEValue value) +{ + auto zone = (isLowerZone ? zoneLayout.getLowerZone() + : zoneLayout.getUpperZone()); + + if (! zone.isActive()) + return; + + for (auto i = notes.size(); --i >= 0;) + { + auto& note = notes.getReference (i); + + if (! zone.isUsing (note.midiChannel)) + continue; + + if (&dimension == &pitchbendDimension) + { + // master pitchbend is a special case: we don't change the note's own pitchbend, + // instead we have to update its total (master + note) pitchbend. + updateNoteTotalPitchbend (note); + listeners.call ([&] (Listener& l) { l.notePitchbendChanged (note); }); + } + else if (dimension.getValue (note) != value) + { + dimension.getValue (note) = value; + callListenersDimensionChanged (note, dimension); + } + } +} + +//============================================================================== +void MPEInstrument::updateDimensionForNote (MPENote& note, MPEDimension& dimension, MPEValue value) +{ + if (dimension.getValue (note) != value) + { + dimension.getValue (note) = value; + + if (&dimension == &pitchbendDimension) + updateNoteTotalPitchbend (note); + + callListenersDimensionChanged (note, dimension); + } +} + +//============================================================================== +void MPEInstrument::callListenersDimensionChanged (const MPENote& note, const MPEDimension& dimension) +{ + if (&dimension == &pressureDimension) { listeners.call ([&] (Listener& l) { l.notePressureChanged (note); }); return; } + if (&dimension == &timbreDimension) { listeners.call ([&] (Listener& l) { l.noteTimbreChanged (note); }); return; } + if (&dimension == &pitchbendDimension) { listeners.call ([&] (Listener& l) { l.notePitchbendChanged (note); }); return; } +} + +//============================================================================== +void MPEInstrument::updateNoteTotalPitchbend (MPENote& note) +{ + if (legacyMode.isEnabled) + { + note.totalPitchbendInSemitones = note.pitchbend.asSignedFloat() * legacyMode.pitchbendRange; + } + else + { + auto zone = zoneLayout.getLowerZone(); + + if (! zone.isUsing (note.midiChannel)) + { + if (zoneLayout.getUpperZone().isUsing (note.midiChannel)) + { + zone = zoneLayout.getUpperZone(); + } + else + { + // this note doesn't belong to any zone! + jassertfalse; + return; + } + } + + auto notePitchbendInSemitones = 0.0f; + + if (zone.isUsingChannelAsMemberChannel (note.midiChannel)) + notePitchbendInSemitones = note.pitchbend.asSignedFloat() * zone.perNotePitchbendRange; + + auto masterPitchbendInSemitones = pitchbendDimension.lastValueReceivedOnChannel[zone.getMasterChannel() - 1] + .asSignedFloat() + * zone.masterPitchbendRange; + + note.totalPitchbendInSemitones = notePitchbendInSemitones + masterPitchbendInSemitones; + } +} + +//============================================================================== +void MPEInstrument::sustainPedal (int midiChannel, bool isDown) +{ + const ScopedLock sl (lock); + handleSustainOrSostenuto (midiChannel, isDown, false); +} + +void MPEInstrument::sostenutoPedal (int midiChannel, bool isDown) +{ + const ScopedLock sl (lock); + handleSustainOrSostenuto (midiChannel, isDown, true); +} + +//============================================================================== +void MPEInstrument::handleSustainOrSostenuto (int midiChannel, bool isDown, bool isSostenuto) +{ + // in MPE mode, sustain/sostenuto is per-zone and expected on the master channel; + // in legacy mode, sustain/sostenuto is per MIDI channel (within the channel range used). + + if (legacyMode.isEnabled ? (! legacyMode.channelRange.contains (midiChannel)) : (! isMasterChannel (midiChannel))) + return; + + auto zone = (midiChannel == 1 ? zoneLayout.getLowerZone() + : zoneLayout.getUpperZone()); + + for (auto i = notes.size(); --i >= 0;) + { + auto& note = notes.getReference (i); + + if (legacyMode.isEnabled ? (note.midiChannel == midiChannel) : zone.isUsing (note.midiChannel)) + { + if (note.keyState == MPENote::keyDown && isDown) + note.keyState = MPENote::keyDownAndSustained; + else if (note.keyState == MPENote::sustained && ! isDown) + note.keyState = MPENote::off; + else if (note.keyState == MPENote::keyDownAndSustained && ! isDown) + note.keyState = MPENote::keyDown; + + if (note.keyState == MPENote::off) + { + listeners.call ([&] (Listener& l) { l.noteReleased (note); }); + notes.remove (i); + } + else + { + listeners.call ([&] (Listener& l) { l.noteKeyStateChanged (note); }); + } + } + } + + if (! isSostenuto) + { + if (legacyMode.isEnabled) + { + isMemberChannelSustained[midiChannel - 1] = isDown; + } + else + { + if (zone.isLowerZone()) + for (auto i = zone.getFirstMemberChannel(); i <= zone.getLastMemberChannel(); ++i) + isMemberChannelSustained[i - 1] = isDown; + else + for (auto i = zone.getFirstMemberChannel(); i >= zone.getLastMemberChannel(); --i) + isMemberChannelSustained[i - 1] = isDown; + } + } +} + +//============================================================================== +bool MPEInstrument::isMemberChannel (int midiChannel) const noexcept +{ + if (legacyMode.isEnabled) + return legacyMode.channelRange.contains (midiChannel); + + return zoneLayout.getLowerZone().isUsingChannelAsMemberChannel (midiChannel) + || zoneLayout.getUpperZone().isUsingChannelAsMemberChannel (midiChannel); +} + +bool MPEInstrument::isMasterChannel (int midiChannel) const noexcept +{ + if (legacyMode.isEnabled) + return false; + + const auto lowerZone = zoneLayout.getLowerZone(); + const auto upperZone = zoneLayout.getUpperZone(); + + return (lowerZone.isActive() && midiChannel == lowerZone.getMasterChannel()) + || (upperZone.isActive() && midiChannel == upperZone.getMasterChannel()); +} + +bool MPEInstrument::isUsingChannel (int midiChannel) const noexcept +{ + if (legacyMode.isEnabled) + return legacyMode.channelRange.contains (midiChannel); + + return zoneLayout.getLowerZone().isUsing (midiChannel) + || zoneLayout.getUpperZone().isUsing (midiChannel); +} + +//============================================================================== +int MPEInstrument::getNumPlayingNotes() const noexcept +{ + return notes.size(); +} + +MPENote MPEInstrument::getNote (int midiChannel, int midiNoteNumber) const noexcept +{ + if (auto* note = getNotePtr (midiChannel, midiNoteNumber)) + return *note; + + return {}; +} + +MPENote MPEInstrument::getNote (int index) const noexcept +{ + return notes[index]; +} + +//============================================================================== +MPENote MPEInstrument::getMostRecentNote (int midiChannel) const noexcept +{ + if (auto* note = getLastNotePlayedPtr (midiChannel)) + return *note; + + return {}; +} + +MPENote MPEInstrument::getMostRecentNoteOtherThan (MPENote otherThanThisNote) const noexcept +{ + for (auto i = notes.size(); --i >= 0;) + { + auto& note = notes.getReference (i); + + if (note != otherThanThisNote) + return note; + } + + return {}; +} + +//============================================================================== +const MPENote* MPEInstrument::getNotePtr (int midiChannel, int midiNoteNumber) const noexcept +{ + for (int i = 0; i < notes.size(); ++i) + { + auto& note = notes.getReference (i); + + if (note.midiChannel == midiChannel && note.initialNote == midiNoteNumber) + return ¬e; + } + + return nullptr; +} + +MPENote* MPEInstrument::getNotePtr (int midiChannel, int midiNoteNumber) noexcept +{ + return const_cast (static_cast (*this).getNotePtr (midiChannel, midiNoteNumber)); +} + +//============================================================================== +const MPENote* MPEInstrument::getNotePtr (int midiChannel, TrackingMode mode) const noexcept +{ + // for the "all notes" tracking mode, this method can never possibly + // work because it returns 0 or 1 note but there might be more than one! + jassert (mode != allNotesOnChannel); + + if (mode == lastNotePlayedOnChannel) return getLastNotePlayedPtr (midiChannel); + if (mode == lowestNoteOnChannel) return getLowestNotePtr (midiChannel); + if (mode == highestNoteOnChannel) return getHighestNotePtr (midiChannel); + + return nullptr; +} + +MPENote* MPEInstrument::getNotePtr (int midiChannel, TrackingMode mode) noexcept +{ + return const_cast (static_cast (*this).getNotePtr (midiChannel, mode)); +} + +//============================================================================== +const MPENote* MPEInstrument::getLastNotePlayedPtr (int midiChannel) const noexcept +{ + for (auto i = notes.size(); --i >= 0;) + { + auto& note = notes.getReference (i); + + if (note.midiChannel == midiChannel + && (note.keyState == MPENote::keyDown || note.keyState == MPENote::keyDownAndSustained)) + return ¬e; + } + + return nullptr; +} + +MPENote* MPEInstrument::getLastNotePlayedPtr (int midiChannel) noexcept +{ + return const_cast (static_cast (*this).getLastNotePlayedPtr (midiChannel)); +} + +//============================================================================== +const MPENote* MPEInstrument::getHighestNotePtr (int midiChannel) const noexcept +{ + int initialNoteMax = -1; + const MPENote* result = nullptr; + + for (auto i = notes.size(); --i >= 0;) + { + auto& note = notes.getReference (i); + + if (note.midiChannel == midiChannel + && (note.keyState == MPENote::keyDown || note.keyState == MPENote::keyDownAndSustained) + && note.initialNote > initialNoteMax) + { + result = ¬e; + initialNoteMax = note.initialNote; + } + } + + return result; +} + +MPENote* MPEInstrument::getHighestNotePtr (int midiChannel) noexcept +{ + return const_cast (static_cast (*this).getHighestNotePtr (midiChannel)); +} + +const MPENote* MPEInstrument::getLowestNotePtr (int midiChannel) const noexcept +{ + int initialNoteMin = 128; + const MPENote* result = nullptr; + + for (auto i = notes.size(); --i >= 0;) + { + auto& note = notes.getReference (i); + + if (note.midiChannel == midiChannel + && (note.keyState == MPENote::keyDown || note.keyState == MPENote::keyDownAndSustained) + && note.initialNote < initialNoteMin) + { + result = ¬e; + initialNoteMin = note.initialNote; + } + } + + return result; +} + +MPENote* MPEInstrument::getLowestNotePtr (int midiChannel) noexcept +{ + return const_cast (static_cast (*this).getLowestNotePtr (midiChannel)); +} + +//============================================================================== +void MPEInstrument::releaseAllNotes() +{ + const ScopedLock sl (lock); + + for (auto i = notes.size(); --i >= 0;) + { + auto& note = notes.getReference (i); + note.keyState = MPENote::off; + note.noteOffVelocity = MPEValue::from7BitInt (64); // some reasonable number + listeners.call ([&] (Listener& l) { l.noteReleased (note); }); + } + + notes.clear(); +} + + +//============================================================================== +//============================================================================== +#if JUCE_UNIT_TESTS + +class MPEInstrumentTests : public UnitTest +{ +public: + MPEInstrumentTests() + : UnitTest ("MPEInstrument class", UnitTestCategories::midi) + { + // using lower and upper MPE zones with the following layout for testing + // + // 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 + // * ...................| |........................ * + + testLayout.setLowerZone (5); + testLayout.setUpperZone (6); + } + + void runTest() override + { + beginTest ("initial zone layout"); + { + MPEInstrument test; + expect (! test.getZoneLayout().getLowerZone().isActive()); + expect (! test.getZoneLayout().getUpperZone().isActive()); + } + + beginTest ("get/setZoneLayout"); + { + MPEInstrument test; + test.setZoneLayout (testLayout); + + auto newLayout = test.getZoneLayout(); + + expect (test.getZoneLayout().getLowerZone().isActive()); + expect (test.getZoneLayout().getUpperZone().isActive()); + expectEquals (newLayout.getLowerZone().getMasterChannel(), 1); + expectEquals (newLayout.getLowerZone().numMemberChannels, 5); + expectEquals (newLayout.getUpperZone().getMasterChannel(), 16); + expectEquals (newLayout.getUpperZone().numMemberChannels, 6); + } + + beginTest ("noteOn / noteOff"); + { + { + MPEInstrument test; + test.setZoneLayout (testLayout); + expectEquals (test.getNumPlayingNotes(), 0); + } + { + UnitTestInstrument test; + test.setZoneLayout (testLayout); + + // note-on on unused channel - ignore + test.noteOn (7, 60, MPEValue::from7BitInt (100)); + expectEquals (test.getNumPlayingNotes(), 0); + expectEquals (test.noteAddedCallCounter, 0); + + // note-on on member channel - create new note + test.noteOn (3, 60, MPEValue::from7BitInt (100)); + expectEquals (test.getNumPlayingNotes(), 1); + expectEquals (test.noteAddedCallCounter, 1); + expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown); + + // note-off + test.noteOff (3, 60, MPEValue::from7BitInt (33)); + expectEquals (test.getNumPlayingNotes(), 0); + expectEquals (test.noteReleasedCallCounter, 1); + expectHasFinishedNote (test, 3, 60, 33); + + + // note-on on master channel - create new note + test.noteOn (1, 62, MPEValue::from7BitInt (100)); + expectEquals (test.getNumPlayingNotes(), 1); + expectEquals (test.noteAddedCallCounter, 2); + expectNote (test.getNote (1, 62), 100, 0, 8192, 64, MPENote::keyDown); + + // note-off + test.noteOff (1, 62, MPEValue::from7BitInt (33)); + expectEquals (test.getNumPlayingNotes(), 0); + expectEquals (test.noteReleasedCallCounter, 2); + expectHasFinishedNote (test, 1, 62, 33); + } + + { + UnitTestInstrument test; + test.setZoneLayout (testLayout); + test.noteOn (3, 60, MPEValue::from7BitInt (100)); + + // note off with non-matching note number shouldn't do anything + test.noteOff (3, 61, MPEValue::from7BitInt (33)); + expectEquals (test.getNumPlayingNotes(), 1); + expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown); + expectEquals (test.noteReleasedCallCounter, 0); + + // note off with non-matching midi channel shouldn't do anything + test.noteOff (2, 60, MPEValue::from7BitInt (33)); + expectEquals (test.getNumPlayingNotes(), 1); + expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown); + expectEquals (test.noteReleasedCallCounter, 0); + } + + { + // can have multiple notes on the same channel + UnitTestInstrument test; + test.setZoneLayout (testLayout); + test.noteOn (3, 0, MPEValue::from7BitInt (100)); + test.noteOn (3, 1, MPEValue::from7BitInt (100)); + test.noteOn (3, 2, MPEValue::from7BitInt (100)); + expectEquals (test.getNumPlayingNotes(), 3); + expectNote (test.getNote (3, 0), 100, 0, 8192, 64, MPENote::keyDown); + expectNote (test.getNote (3, 1), 100, 0, 8192, 64, MPENote::keyDown); + expectNote (test.getNote (3, 2), 100, 0, 8192, 64, MPENote::keyDown); + } + { + // pathological case: second note-on for same note should retrigger it. + UnitTestInstrument test; + test.setZoneLayout (testLayout); + test.noteOn (3, 0, MPEValue::from7BitInt (100)); + test.noteOn (3, 0, MPEValue::from7BitInt (60)); + expectEquals (test.getNumPlayingNotes(), 1); + expectNote (test.getNote (3, 0), 60, 0, 8192, 64, MPENote::keyDown); + } + } + + beginTest ("noteReleased after setZoneLayout"); + { + UnitTestInstrument test; + test.setZoneLayout (testLayout); + + test.noteOn (3, 60, MPEValue::from7BitInt (100)); + test.noteOn (3, 61, MPEValue::from7BitInt (100)); + test.noteOn (4, 61, MPEValue::from7BitInt (100)); + expectEquals (test.getNumPlayingNotes(), 3); + expectEquals (test.noteReleasedCallCounter, 0); + + test.setZoneLayout (testLayout); + expectEquals (test.getNumPlayingNotes(), 0); + expectEquals (test.noteReleasedCallCounter, 3); + } + + beginTest ("releaseAllNotes"); + { + UnitTestInstrument test; + test.setZoneLayout (testLayout); + test.noteOn (3, 60, MPEValue::from7BitInt (100)); + test.noteOn (4, 61, MPEValue::from7BitInt (100)); + test.noteOn (15, 62, MPEValue::from7BitInt (100)); + expectEquals (test.getNumPlayingNotes(), 3); + + test.releaseAllNotes(); + expectEquals (test.getNumPlayingNotes(), 0); + } + + beginTest ("sustainPedal"); + { + UnitTestInstrument test; + test.setZoneLayout (testLayout); + test.noteOn (3, 60, MPEValue::from7BitInt (100)); // note in lower zone + test.noteOn (10, 60, MPEValue::from7BitInt (100)); // note in upper zone + + // sustain pedal on per-note channel shouldn't do anything. + test.sustainPedal (3, true); + expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown); + + expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown); + expectNote (test.getNote (10, 60), 100, 0, 8192, 64, MPENote::keyDown); + expectEquals (test.noteKeyStateChangedCallCounter, 0); + + // sustain pedal on non-zone channel shouldn't do anything either. + test.sustainPedal (7, true); + expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown); + expectNote (test.getNote (10, 60), 100, 0, 8192, 64, MPENote::keyDown); + expectEquals (test.noteKeyStateChangedCallCounter, 0); + + // sustain pedal on master channel should sustain notes on _that_ zone. + test.sustainPedal (1, true); + expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDownAndSustained); + expectNote (test.getNote (10, 60), 100, 0, 8192, 64, MPENote::keyDown); + expectEquals (test.noteKeyStateChangedCallCounter, 1); + + // release + test.sustainPedal (1, false); + expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown); + expectNote (test.getNote (10, 60), 100, 0, 8192, 64, MPENote::keyDown); + expectEquals (test.noteKeyStateChangedCallCounter, 2); + + // should also sustain new notes added after the press + test.sustainPedal (1, true); + expectEquals (test.noteKeyStateChangedCallCounter, 3); + test.noteOn (4, 51, MPEValue::from7BitInt (100)); + expectNote (test.getNote (4, 51), 100, 0, 8192, 64, MPENote::keyDownAndSustained); + expectEquals (test.noteKeyStateChangedCallCounter, 3); + + // ...but only if that sustain came on the master channel of that zone! + test.sustainPedal (11, true); + test.noteOn (11, 52, MPEValue::from7BitInt (100)); + expectNote (test.getNote (11, 52), 100, 0, 8192, 64, MPENote::keyDown); + test.noteOff (11, 52, MPEValue::from7BitInt (100)); + expectEquals (test.noteReleasedCallCounter, 1); + + // note-off should not turn off sustained notes inside the same zone + test.noteOff (3, 60, MPEValue::from7BitInt (100)); + test.noteOff (4, 51, MPEValue::from7BitInt (100)); + test.noteOff (10, 60, MPEValue::from7BitInt (100)); // not affected! + expectEquals (test.getNumPlayingNotes(), 2); + expectEquals (test.noteReleasedCallCounter, 2); + expectEquals (test.noteKeyStateChangedCallCounter, 5); + expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::sustained); + expectNote (test.getNote (4, 51), 100, 0, 8192, 64, MPENote::sustained); + + // notes should be turned off when pedal is released + test.sustainPedal (1, false); + expectEquals (test.getNumPlayingNotes(), 0); + expectEquals (test.noteReleasedCallCounter, 4); + } + + beginTest ("sostenutoPedal"); + { + UnitTestInstrument test; + test.setZoneLayout (testLayout); + test.noteOn (3, 60, MPEValue::from7BitInt (100)); // note in lower zone + test.noteOn (10, 60, MPEValue::from7BitInt (100)); // note in upper zone + + // sostenuto pedal on per-note channel shouldn't do anything. + test.sostenutoPedal (3, true); + expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown); + expectNote (test.getNote (10, 60), 100, 0, 8192, 64, MPENote::keyDown); + expectEquals (test.noteKeyStateChangedCallCounter, 0); + + // sostenuto pedal on non-zone channel shouldn't do anything either. + test.sostenutoPedal (9, true); + expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown); + expectNote (test.getNote (10, 60), 100, 0, 8192, 64, MPENote::keyDown); + expectEquals (test.noteKeyStateChangedCallCounter, 0); + + // sostenuto pedal on master channel should sustain notes on *that* zone. + test.sostenutoPedal (1, true); + expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDownAndSustained); + expectNote (test.getNote (10, 60), 100, 0, 8192, 64, MPENote::keyDown); + expectEquals (test.noteKeyStateChangedCallCounter, 1); + + // release + test.sostenutoPedal (1, false); + expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown); + expectNote (test.getNote (10, 60), 100, 0, 8192, 64, MPENote::keyDown); + expectEquals (test.noteKeyStateChangedCallCounter, 2); + + // should only sustain notes turned on *before* the press (difference to sustain pedal) + test.sostenutoPedal (1, true); + expectEquals (test.noteKeyStateChangedCallCounter, 3); + test.noteOn (4, 51, MPEValue::from7BitInt (100)); + expectEquals (test.getNumPlayingNotes(), 3); + expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDownAndSustained); + expectNote (test.getNote (4, 51), 100, 0, 8192, 64, MPENote::keyDown); + expectNote (test.getNote (10, 60), 100, 0, 8192, 64, MPENote::keyDown); + expectEquals (test.noteKeyStateChangedCallCounter, 3); + + // note-off should not turn off sustained notes inside the same zone, + // but only if they were turned on *before* the sostenuto pedal (difference to sustain pedal) + test.noteOff (3, 60, MPEValue::from7BitInt (100)); + test.noteOff (4, 51, MPEValue::from7BitInt (100)); + test.noteOff (10, 60, MPEValue::from7BitInt (100)); // not affected! + expectEquals (test.getNumPlayingNotes(), 1); + expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::sustained); + expectEquals (test.noteReleasedCallCounter, 2); + expectEquals (test.noteKeyStateChangedCallCounter, 4); + + // notes should be turned off when pedal is released + test.sustainPedal (1, false); + expectEquals (test.getNumPlayingNotes(), 0); + expectEquals (test.noteReleasedCallCounter, 3); + } + + beginTest ("getMostRecentNote"); + { + MPEInstrument test; + test.setZoneLayout (testLayout); + + test.noteOn (3, 60, MPEValue::from7BitInt (100)); + test.noteOn (3, 61, MPEValue::from7BitInt (100)); + + { + auto note = test.getMostRecentNote (2); + expect (! note.isValid()); + } + { + auto note = test.getMostRecentNote (3); + expect (note.isValid()); + expectEquals (int (note.midiChannel), 3); + expectEquals (int (note.initialNote), 61); + } + + test.sustainPedal (1, true); + test.noteOff (3, 61, MPEValue::from7BitInt (100)); + + { + auto note = test.getMostRecentNote (3); + expect (note.isValid()); + expectEquals (int (note.midiChannel), 3); + expectEquals (int (note.initialNote), 60); + } + + test.sustainPedal (1, false); + test.noteOff (3, 60, MPEValue::from7BitInt (100)); + + { + auto note = test.getMostRecentNote (3); + expect (! note.isValid()); + } + } + + beginTest ("getMostRecentNoteOtherThan"); + { + MPENote testNote (3, 60, + MPEValue::centreValue(), MPEValue::centreValue(), + MPEValue::centreValue(), MPEValue::centreValue()); + + { + // case 1: the note to exclude is not the most recent one. + + MPEInstrument test; + test.setZoneLayout (testLayout); + expect (! test.getMostRecentNoteOtherThan (testNote).isValid()); + + test.noteOn (3, 60, MPEValue::from7BitInt (100)); + expect (! test.getMostRecentNoteOtherThan (testNote).isValid()); + + test.noteOn (4, 61, MPEValue::from7BitInt (100)); + expect (test.getMostRecentNoteOtherThan (testNote).isValid()); + expect (test.getMostRecentNoteOtherThan (testNote).midiChannel == 4); + expect (test.getMostRecentNoteOtherThan (testNote).initialNote == 61); + } + { + // case 2: the note to exclude is the most recent one. + + MPEInstrument test; + test.setZoneLayout (testLayout); + expect (! test.getMostRecentNoteOtherThan (testNote).isValid()); + + test.noteOn (4, 61, MPEValue::from7BitInt (100)); + expect (test.getMostRecentNoteOtherThan (testNote).isValid()); + expect (test.getMostRecentNoteOtherThan (testNote).midiChannel == 4); + expect (test.getMostRecentNoteOtherThan (testNote).initialNote == 61); + + test.noteOn (3, 60, MPEValue::from7BitInt (100)); + expect (test.getMostRecentNoteOtherThan (testNote).isValid()); + expect (test.getMostRecentNoteOtherThan (testNote).midiChannel == 4); + expect (test.getMostRecentNoteOtherThan (testNote).initialNote == 61); + } + } + + beginTest ("pressure"); + { + { + UnitTestInstrument test; + test.setZoneLayout (testLayout); + + test.noteOn (3, 60, MPEValue::from7BitInt (100)); + test.noteOn (4, 60, MPEValue::from7BitInt (100)); + test.noteOn (10, 60, MPEValue::from7BitInt (100)); + + // applying pressure on a per-note channel should modulate one note + test.pressure (3, MPEValue::from7BitInt (33)); + expectNote (test.getNote (3, 60), 100, 33, 8192, 64, MPENote::keyDown); + expectNote (test.getNote (4, 60), 100, 0, 8192, 64, MPENote::keyDown); + expectNote (test.getNote (10, 60), 100, 0, 8192, 64, MPENote::keyDown); + expectEquals (test.notePressureChangedCallCounter, 1); + + // applying pressure on a master channel should modulate all notes in this zone + test.pressure (1, MPEValue::from7BitInt (44)); + expectNote (test.getNote (3, 60), 100, 44, 8192, 64, MPENote::keyDown); + expectNote (test.getNote (4, 60), 100, 44, 8192, 64, MPENote::keyDown); + expectNote (test.getNote (10, 60), 100, 0, 8192, 64, MPENote::keyDown); + expectEquals (test.notePressureChangedCallCounter, 3); + + // applying pressure on an unrelated channel should be ignored + test.pressure (8, MPEValue::from7BitInt (55)); + expectNote (test.getNote (3, 60), 100, 44, 8192, 64, MPENote::keyDown); + expectNote (test.getNote (4, 60), 100, 44, 8192, 64, MPENote::keyDown); + expectNote (test.getNote (10, 60), 100, 0, 8192, 64, MPENote::keyDown); + expectEquals (test.notePressureChangedCallCounter, 3); + } + { + UnitTestInstrument test; + test.setZoneLayout (testLayout); + + // two notes on same channel - only last added should be modulated + test.noteOn (3, 60, MPEValue::from7BitInt (100)); + test.noteOn (3, 61, MPEValue::from7BitInt (100)); + test.pressure (3, MPEValue::from7BitInt (66)); + expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown); + expectNote (test.getNote (3, 61), 100, 66, 8192, 64, MPENote::keyDown); + expectEquals (test.notePressureChangedCallCounter, 1); + } + { + UnitTestInstrument test; + test.setZoneLayout (testLayout); + + // edge case: two notes on same channel, one gets released, + // then the other should be modulated + test.noteOn (3, 60, MPEValue::from7BitInt (100)); + test.noteOn (3, 61, MPEValue::from7BitInt (100)); + test.noteOff (3, 61, MPEValue::from7BitInt (100)); + test.pressure (3, MPEValue::from7BitInt (77)); + expectEquals (test.getNumPlayingNotes(), 1); + expectNote (test.getNote (3, 60), 100, 77, 8192, 64, MPENote::keyDown); + expectEquals (test.notePressureChangedCallCounter, 1); + } + { + UnitTestInstrument test; + test.setZoneLayout (testLayout); + + // if no pressure is sent before note-on, default = 0 should be used + test.noteOn (3, 60, MPEValue::from7BitInt (100)); + expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown); + } + { + UnitTestInstrument test; + test.setZoneLayout (testLayout); + + // if pressure is sent before note-on, use that + test.pressure (3, MPEValue::from7BitInt (77)); + test.noteOn (3, 60, MPEValue::from7BitInt (100)); + expectNote (test.getNote (3, 60), 100, 77, 8192, 64, MPENote::keyDown); + } + { + UnitTestInstrument test; + test.setZoneLayout (testLayout); + + // if pressure is sent before note-on, but it belonged to another note + // on the same channel that has since been turned off, use default = 0 + test.noteOn (3, 61, MPEValue::from7BitInt (100)); + test.pressure (3, MPEValue::from7BitInt (77)); + test.noteOff (3, 61, MPEValue::from7BitInt (100)); + test.noteOn (3, 60, MPEValue::from7BitInt (100)); + expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown); + } + { + UnitTestInstrument test; + test.setZoneLayout (testLayout); + + // edge case: two notes on the same channel simultaneously. the second one should use + // pressure = 0 initially but then react to additional pressure messages + test.noteOn (3, 61, MPEValue::from7BitInt (100)); + test.pressure (3, MPEValue::from7BitInt (77)); + test.noteOn (3, 60, MPEValue::from7BitInt (100)); + expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown); + test.pressure (3, MPEValue::from7BitInt (78)); + expectNote (test.getNote (3, 60), 100, 78, 8192, 64, MPENote::keyDown); + expectNote (test.getNote (3, 61), 100, 77, 8192, 64, MPENote::keyDown); + } + + { + UnitTestInstrument test; + test.setZoneLayout (testLayout); + + // master channel will use poly-aftertouch for pressure + test.noteOn (16, 60, MPEValue::from7BitInt (100)); + expectNote (test.getNote (16, 60), 100, 0, 8192, 64, MPENote::keyDown); + test.aftertouch (16, 60, MPEValue::from7BitInt (27)); + expectNote (test.getNote (16, 60), 100, 27, 8192, 64, MPENote::keyDown); + + // member channels will not respond to poly-aftertouch + test.noteOn (3, 60, MPEValue::from7BitInt (100)); + test.aftertouch (3, 60, MPEValue::from7BitInt (50)); + expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown); + } + } + + beginTest ("pitchbend"); + { + { + UnitTestInstrument test; + test.setZoneLayout (testLayout); + + test.noteOn (3, 60, MPEValue::from7BitInt (100)); + test.noteOn (4, 60, MPEValue::from7BitInt (100)); + test.noteOn (10, 60, MPEValue::from7BitInt (100)); + + // applying pitchbend on a per-note channel should modulate one note + test.pitchbend (3, MPEValue::from14BitInt (1111)); + expectNote (test.getNote (3, 60), 100, 0, 1111, 64, MPENote::keyDown); + expectNote (test.getNote (4, 60), 100, 0, 8192, 64, MPENote::keyDown); + expectNote (test.getNote (10, 60), 100, 0, 8192, 64, MPENote::keyDown); + expectEquals (test.notePitchbendChangedCallCounter, 1); + + // applying pitchbend on a master channel should be ignored for the + // value of per-note pitchbend. Tests covering master pitchbend below. + // Note: noteChanged will be called anyway for notes in that zone + // because the total pitchbend for those notes has changed + test.pitchbend (1, MPEValue::from14BitInt (2222)); + expectNote (test.getNote (3, 60), 100, 0, 1111, 64, MPENote::keyDown); + expectNote (test.getNote (4, 60), 100, 0, 8192, 64, MPENote::keyDown); + expectNote (test.getNote (10, 60), 100, 0, 8192, 64, MPENote::keyDown); + expectEquals (test.notePitchbendChangedCallCounter, 3); + + // applying pitchbend on an unrelated channel should do nothing. + test.pitchbend (8, MPEValue::from14BitInt (3333)); + expectNote (test.getNote (3, 60), 100, 0, 1111, 64, MPENote::keyDown); + expectNote (test.getNote (4, 60), 100, 0, 8192, 64, MPENote::keyDown); + expectNote (test.getNote (10, 60), 100, 0, 8192, 64, MPENote::keyDown); + expectEquals (test.notePitchbendChangedCallCounter, 3); + } + { + UnitTestInstrument test; + test.setZoneLayout (testLayout); + + // two notes on same channel - only last added should be bent + test.noteOn (3, 60, MPEValue::from7BitInt (100)); + test.noteOn (3, 61, MPEValue::from7BitInt (100)); + test.pitchbend (3, MPEValue::from14BitInt (4444)); + expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown); + expectNote (test.getNote (3, 61), 100, 0, 4444, 64, MPENote::keyDown); + expectEquals (test.notePitchbendChangedCallCounter, 1); + } + { + UnitTestInstrument test; + test.setZoneLayout (testLayout); + + // edge case: two notes on same channel, one gets released, + // then the other should be bent + test.noteOn (3, 60, MPEValue::from7BitInt (100)); + test.noteOn (3, 61, MPEValue::from7BitInt (100)); + test.noteOff (3, 61, MPEValue::from7BitInt (100)); + test.pitchbend (3, MPEValue::from14BitInt (5555)); + expectEquals (test.getNumPlayingNotes(), 1); + expectNote (test.getNote (3, 60), 100, 0, 5555, 64, MPENote::keyDown); + expectEquals (test.notePitchbendChangedCallCounter, 1); + } + { + UnitTestInstrument test; + test.setZoneLayout (testLayout); + + // Richard's edge case: + // - press one note + // - press sustain (careful: must be sent on master channel) + // - release first note (is still sustained!) + // - press another note (happens to be on the same MIDI channel!) + // - pitchbend that other note + // - the first note should not be bent, only the second one. + + test.noteOn (3, 60, MPEValue::from7BitInt (100)); + test.sustainPedal (1, true); + test.noteOff (3, 60, MPEValue::from7BitInt (64)); + expectEquals (test.getNumPlayingNotes(), 1); + expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::sustained); + expectEquals (test.noteKeyStateChangedCallCounter, 2); + + test.noteOn (3, 61, MPEValue::from7BitInt (100)); + test.pitchbend (3, MPEValue::from14BitInt (6666)); + expectEquals (test.getNumPlayingNotes(), 2); + expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::sustained); + expectNote (test.getNote (3, 61), 100, 0, 6666, 64, MPENote::keyDownAndSustained); + expectEquals (test.notePitchbendChangedCallCounter, 1); + } + { + UnitTestInstrument test; + test.setZoneLayout (testLayout); + + // Zsolt's edge case: + // - press one note + // - modulate pitchbend or timbre + // - release the note + // - press same note again without sending a pitchbend or timbre message before the note-on + // - the note should be turned on with a default value for pitchbend/timbre, + // and *not* the last value received on channel. + + test.noteOn (3, 60, MPEValue::from7BitInt (100)); + test.pitchbend (3, MPEValue::from14BitInt (5555)); + expectNote (test.getNote (3, 60), 100, 0, 5555, 64, MPENote::keyDown); + + test.noteOff (3, 60, MPEValue::from7BitInt (100)); + test.noteOn (3, 60, MPEValue::from7BitInt (100)); + expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown); + } + { + // applying per-note pitchbend should set the note's totalPitchbendInSemitones + // correctly depending on the per-note pitchbend range of the zone. + UnitTestInstrument test; + + MPEZoneLayout layout = testLayout; + test.setZoneLayout (layout); // default should be +/- 48 semitones + test.noteOn (3, 60, MPEValue::from7BitInt (100)); + test.pitchbend (3, MPEValue::from14BitInt (4096)); + expectDoubleWithinRelativeError (test.getMostRecentNote (3).totalPitchbendInSemitones, -24.0, 0.01); + + layout.setLowerZone (5, 96); + test.setZoneLayout (layout); + test.noteOn (3, 60, MPEValue::from7BitInt (100)); + test.pitchbend (3, MPEValue::from14BitInt (0)); // -max + expectDoubleWithinRelativeError (test.getMostRecentNote (3).totalPitchbendInSemitones, -96.0, 0.01); + + layout.setLowerZone (5, 1); + test.setZoneLayout (layout); + test.noteOn (3, 60, MPEValue::from7BitInt (100)); + test.pitchbend (3, MPEValue::from14BitInt (16383)); // +max + expectDoubleWithinRelativeError (test.getMostRecentNote (3).totalPitchbendInSemitones, 1.0, 0.01); + + layout.setLowerZone (5, 0); // pitchbendrange = 0 --> no pitchbend at all + test.setZoneLayout (layout); + test.noteOn (3, 60, MPEValue::from7BitInt (100)); + test.pitchbend (3, MPEValue::from14BitInt (12345)); + expectDoubleWithinRelativeError (test.getMostRecentNote (3).totalPitchbendInSemitones, 0.0, 0.01); + } + { + // applying master pitchbend should set the note's totalPitchbendInSemitones + // correctly depending on the master pitchbend range of the zone. + UnitTestInstrument test; + + MPEZoneLayout layout = testLayout; + test.setZoneLayout (layout); // default should be +/- 2 semitones + test.noteOn (3, 60, MPEValue::from7BitInt (100)); + test.pitchbend (1, MPEValue::from14BitInt (4096)); //halfway between -max and centre + expectDoubleWithinRelativeError (test.getMostRecentNote (3).totalPitchbendInSemitones, -1.0, 0.01); + + layout.setLowerZone (5, 48, 96); + test.setZoneLayout (layout); + test.noteOn (3, 60, MPEValue::from7BitInt (100)); + test.pitchbend (1, MPEValue::from14BitInt (0)); // -max + expectDoubleWithinRelativeError (test.getMostRecentNote (3).totalPitchbendInSemitones, -96.0, 0.01); + + layout.setLowerZone (5, 48, 1); + test.setZoneLayout (layout); + test.noteOn (3, 60, MPEValue::from7BitInt (100)); + test.pitchbend (1, MPEValue::from14BitInt (16383)); // +max + expectDoubleWithinRelativeError (test.getMostRecentNote (3).totalPitchbendInSemitones, 1.0, 0.01); + + layout.setLowerZone (5, 48, 0); // pitchbendrange = 0 --> no pitchbend at all + test.setZoneLayout (layout); + test.noteOn (3, 60, MPEValue::from7BitInt (100)); + test.pitchbend (1, MPEValue::from14BitInt (12345)); + expectDoubleWithinRelativeError (test.getMostRecentNote (3).totalPitchbendInSemitones, 0.0, 0.01); + } + { + // applying both per-note and master pitchbend simultaneously should set + // the note's totalPitchbendInSemitones to the sum of both, correctly + // weighted with the per-note and master pitchbend range, respectively. + UnitTestInstrument test; + + MPEZoneLayout layout = testLayout; + layout.setLowerZone (5, 12, 1); + test.setZoneLayout (layout); + + test.pitchbend (1, MPEValue::from14BitInt (4096)); // master pitchbend 0.5 semitones down + test.pitchbend (3, MPEValue::from14BitInt (0)); // per-note pitchbend 12 semitones down + // additionally, note should react to both pitchbend messages + // correctly even if they arrived before the note-on. + test.noteOn (3, 60, MPEValue::from7BitInt (100)); + expectDoubleWithinRelativeError (test.getMostRecentNote (3).totalPitchbendInSemitones, -12.5, 0.01); + } + } + + beginTest ("timbre"); + { + { + UnitTestInstrument test; + test.setZoneLayout (testLayout); + + test.noteOn (3, 60, MPEValue::from7BitInt (100)); + test.noteOn (4, 60, MPEValue::from7BitInt (100)); + test.noteOn (10, 60, MPEValue::from7BitInt (100)); + + // modulating timbre on a per-note channel should modulate one note + test.timbre (3, MPEValue::from7BitInt (33)); + expectNote (test.getNote (3, 60), 100, 0, 8192, 33, MPENote::keyDown); + expectNote (test.getNote (4, 60), 100, 0, 8192, 64, MPENote::keyDown); + expectNote (test.getNote (10, 60), 100, 0, 8192, 64, MPENote::keyDown); + expectEquals (test.noteTimbreChangedCallCounter, 1); + + // modulating timbre on a master channel should modulate all notes in this zone + test.timbre (1, MPEValue::from7BitInt (44)); + expectNote (test.getNote (3, 60), 100, 0, 8192, 44, MPENote::keyDown); + expectNote (test.getNote (4, 60), 100, 0, 8192, 44, MPENote::keyDown); + expectNote (test.getNote (10, 60), 100, 0, 8192, 64, MPENote::keyDown); + expectEquals (test.noteTimbreChangedCallCounter, 3); + + // modulating timbre on an unrelated channel should be ignored + test.timbre (9, MPEValue::from7BitInt (55)); + expectNote (test.getNote (3, 60), 100, 0, 8192, 44, MPENote::keyDown); + expectNote (test.getNote (4, 60), 100, 0, 8192, 44, MPENote::keyDown); + expectNote (test.getNote (10, 60), 100, 0, 8192, 64, MPENote::keyDown); + expectEquals (test.noteTimbreChangedCallCounter, 3); + } + { + UnitTestInstrument test; + test.setZoneLayout (testLayout); + + // two notes on same channel - only last added should be modulated + test.noteOn (3, 60, MPEValue::from7BitInt (100)); + test.noteOn (3, 61, MPEValue::from7BitInt (100)); + test.timbre (3, MPEValue::from7BitInt (66)); + expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown); + expectNote (test.getNote (3, 61), 100, 0, 8192, 66, MPENote::keyDown); + expectEquals (test.noteTimbreChangedCallCounter, 1); + } + { + UnitTestInstrument test; + test.setZoneLayout (testLayout); + + // edge case: two notes on same channel, one gets released, + // then the other should be modulated + test.noteOn (3, 60, MPEValue::from7BitInt (100)); + test.noteOn (3, 61, MPEValue::from7BitInt (100)); + test.noteOff (3, 61, MPEValue::from7BitInt (100)); + test.timbre (3, MPEValue::from7BitInt (77)); + expectEquals (test.getNumPlayingNotes(), 1); + expectNote (test.getNote (3, 60), 100, 0, 8192, 77, MPENote::keyDown); + expectEquals (test.noteTimbreChangedCallCounter, 1); + } + { + UnitTestInstrument test; + test.setZoneLayout (testLayout); + + // Zsolt's edge case for timbre + test.noteOn (3, 60, MPEValue::from7BitInt (100)); + test.timbre (3, MPEValue::from7BitInt (42)); + expectNote (test.getNote (3, 60), 100, 0, 8192, 42, MPENote::keyDown); + + test.noteOff (3, 60, MPEValue::from7BitInt (100)); + test.noteOn (3, 60, MPEValue::from7BitInt (100)); + expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown); + } + } + + beginTest ("setPressureTrackingMode"); + { + { + // last note played (= default) + UnitTestInstrument test; + test.setZoneLayout (testLayout); + + test.setPressureTrackingMode (MPEInstrument::lastNotePlayedOnChannel); + test.noteOn (3, 60, MPEValue::from7BitInt (100)); + test.noteOn (3, 62, MPEValue::from7BitInt (100)); + test.noteOn (3, 61, MPEValue::from7BitInt (100)); + test.pressure (3, MPEValue::from7BitInt (99)); + expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown); + expectNote (test.getNote (3, 62), 100, 0, 8192, 64, MPENote::keyDown); + expectNote (test.getNote (3, 61), 100, 99, 8192, 64, MPENote::keyDown); + expectEquals (test.notePressureChangedCallCounter, 1); + } + { + // lowest note + UnitTestInstrument test; + test.setZoneLayout (testLayout); + + test.setPressureTrackingMode (MPEInstrument::lowestNoteOnChannel); + test.noteOn (3, 60, MPEValue::from7BitInt (100)); + test.noteOn (3, 62, MPEValue::from7BitInt (100)); + test.noteOn (3, 61, MPEValue::from7BitInt (100)); + test.pressure (3, MPEValue::from7BitInt (99)); + expectNote (test.getNote (3, 60), 100, 99, 8192, 64, MPENote::keyDown); + expectNote (test.getNote (3, 62), 100, 0, 8192, 64, MPENote::keyDown); + expectNote (test.getNote (3, 61), 100, 0, 8192, 64, MPENote::keyDown); + expectEquals (test.notePressureChangedCallCounter, 1); + } + { + // highest note + UnitTestInstrument test; + test.setZoneLayout (testLayout); + + test.setPressureTrackingMode (MPEInstrument::highestNoteOnChannel); + test.noteOn (3, 60, MPEValue::from7BitInt (100)); + test.noteOn (3, 62, MPEValue::from7BitInt (100)); + test.noteOn (3, 61, MPEValue::from7BitInt (100)); + test.pressure (3, MPEValue::from7BitInt (99)); + expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown); + expectNote (test.getNote (3, 62), 100, 99, 8192, 64, MPENote::keyDown); + expectNote (test.getNote (3, 61), 100, 0, 8192, 64, MPENote::keyDown); + expectEquals (test.notePressureChangedCallCounter, 1); + } + { + // all notes + UnitTestInstrument test; + test.setZoneLayout (testLayout); + + test.setPressureTrackingMode (MPEInstrument::allNotesOnChannel); + test.noteOn (3, 60, MPEValue::from7BitInt (100)); + test.noteOn (3, 62, MPEValue::from7BitInt (100)); + test.noteOn (3, 61, MPEValue::from7BitInt (100)); + test.pressure (3, MPEValue::from7BitInt (99)); + expectNote (test.getNote (3, 60), 100, 99, 8192, 64, MPENote::keyDown); + expectNote (test.getNote (3, 62), 100, 99, 8192, 64, MPENote::keyDown); + expectNote (test.getNote (3, 61), 100, 99, 8192, 64, MPENote::keyDown); + expectEquals (test.notePressureChangedCallCounter, 3); + } + } + + beginTest ("setPitchbendTrackingMode"); + { + { + // last note played (= default) + UnitTestInstrument test; + test.setZoneLayout (testLayout); + + test.setPitchbendTrackingMode (MPEInstrument::lastNotePlayedOnChannel); + test.noteOn (3, 60, MPEValue::from7BitInt (100)); + test.noteOn (3, 62, MPEValue::from7BitInt (100)); + test.noteOn (3, 61, MPEValue::from7BitInt (100)); + test.pitchbend (3, MPEValue::from14BitInt (9999)); + expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown); + expectNote (test.getNote (3, 62), 100, 0, 8192, 64, MPENote::keyDown); + expectNote (test.getNote (3, 61), 100, 0, 9999, 64, MPENote::keyDown); + expectEquals (test.notePitchbendChangedCallCounter, 1); + } + { + // lowest note + UnitTestInstrument test; + test.setZoneLayout (testLayout); + + test.setPitchbendTrackingMode (MPEInstrument::lowestNoteOnChannel); + test.noteOn (3, 60, MPEValue::from7BitInt (100)); + test.noteOn (3, 62, MPEValue::from7BitInt (100)); + test.noteOn (3, 61, MPEValue::from7BitInt (100)); + test.pitchbend (3, MPEValue::from14BitInt (9999)); + expectNote (test.getNote (3, 60), 100, 0, 9999, 64, MPENote::keyDown); + expectNote (test.getNote (3, 62), 100, 0, 8192, 64, MPENote::keyDown); + expectNote (test.getNote (3, 61), 100, 0, 8192, 64, MPENote::keyDown); + expectEquals (test.notePitchbendChangedCallCounter, 1); + } + { + // highest note + UnitTestInstrument test; + test.setZoneLayout (testLayout); + + test.setPitchbendTrackingMode (MPEInstrument::highestNoteOnChannel); + test.noteOn (3, 60, MPEValue::from7BitInt (100)); + test.noteOn (3, 62, MPEValue::from7BitInt (100)); + test.noteOn (3, 61, MPEValue::from7BitInt (100)); + test.pitchbend (3, MPEValue::from14BitInt (9999)); + expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown); + expectNote (test.getNote (3, 62), 100, 0, 9999, 64, MPENote::keyDown); + expectNote (test.getNote (3, 61), 100, 0, 8192, 64, MPENote::keyDown); + expectEquals (test.notePitchbendChangedCallCounter, 1); + } + { + // all notes + UnitTestInstrument test; + test.setZoneLayout (testLayout); + + test.setPitchbendTrackingMode (MPEInstrument::allNotesOnChannel); + test.noteOn (3, 60, MPEValue::from7BitInt (100)); + test.noteOn (3, 62, MPEValue::from7BitInt (100)); + test.noteOn (3, 61, MPEValue::from7BitInt (100)); + test.pitchbend (3, MPEValue::from14BitInt (9999)); + expectNote (test.getNote (3, 60), 100, 0, 9999, 64, MPENote::keyDown); + expectNote (test.getNote (3, 62), 100, 0, 9999, 64, MPENote::keyDown); + expectNote (test.getNote (3, 61), 100, 0, 9999, 64, MPENote::keyDown); + expectEquals (test.notePitchbendChangedCallCounter, 3); + } + } + + beginTest ("setTimbreTrackingMode"); + { + { + // last note played (= default) + UnitTestInstrument test; + test.setZoneLayout (testLayout); + + test.setTimbreTrackingMode (MPEInstrument::lastNotePlayedOnChannel); + test.noteOn (3, 60, MPEValue::from7BitInt (100)); + test.noteOn (3, 62, MPEValue::from7BitInt (100)); + test.noteOn (3, 61, MPEValue::from7BitInt (100)); + test.timbre (3, MPEValue::from7BitInt (99)); + expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown); + expectNote (test.getNote (3, 62), 100, 0, 8192, 64, MPENote::keyDown); + expectNote (test.getNote (3, 61), 100, 0, 8192, 99, MPENote::keyDown); + expectEquals (test.noteTimbreChangedCallCounter, 1); + } + { + // lowest note + UnitTestInstrument test; + test.setZoneLayout (testLayout); + + test.setTimbreTrackingMode (MPEInstrument::lowestNoteOnChannel); + test.noteOn (3, 60, MPEValue::from7BitInt (100)); + test.noteOn (3, 62, MPEValue::from7BitInt (100)); + test.noteOn (3, 61, MPEValue::from7BitInt (100)); + test.timbre (3, MPEValue::from7BitInt (99)); + expectNote (test.getNote (3, 60), 100, 0, 8192, 99, MPENote::keyDown); + expectNote (test.getNote (3, 62), 100, 0, 8192, 64, MPENote::keyDown); + expectNote (test.getNote (3, 61), 100, 0, 8192, 64, MPENote::keyDown); + expectEquals (test.noteTimbreChangedCallCounter, 1); + } + { + // highest note + UnitTestInstrument test; + test.setZoneLayout (testLayout); + + test.setTimbreTrackingMode (MPEInstrument::highestNoteOnChannel); + test.noteOn (3, 60, MPEValue::from7BitInt (100)); + test.noteOn (3, 62, MPEValue::from7BitInt (100)); + test.noteOn (3, 61, MPEValue::from7BitInt (100)); + test.timbre (3, MPEValue::from7BitInt (99)); + expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown); + expectNote (test.getNote (3, 62), 100, 0, 8192, 99, MPENote::keyDown); + expectNote (test.getNote (3, 61), 100, 0, 8192, 64, MPENote::keyDown); + expectEquals (test.noteTimbreChangedCallCounter, 1); + } + { + // all notes + UnitTestInstrument test; + test.setZoneLayout (testLayout); + + test.setTimbreTrackingMode (MPEInstrument::allNotesOnChannel); + test.noteOn (3, 60, MPEValue::from7BitInt (100)); + test.noteOn (3, 62, MPEValue::from7BitInt (100)); + test.noteOn (3, 61, MPEValue::from7BitInt (100)); + test.timbre (3, MPEValue::from7BitInt (99)); + expectNote (test.getNote (3, 60), 100, 0, 8192, 99, MPENote::keyDown); + expectNote (test.getNote (3, 62), 100, 0, 8192, 99, MPENote::keyDown); + expectNote (test.getNote (3, 61), 100, 0, 8192, 99, MPENote::keyDown); + expectEquals (test.noteTimbreChangedCallCounter, 3); + } + } + + beginTest ("processNextMidiEvent"); + { + UnitTestInstrument test; + + // note on should trigger noteOn method call + + test.processNextMidiEvent (MidiMessage::noteOn (3, 42, uint8 (92))); + expectEquals (test.noteOnCallCounter, 1); + expectEquals (test.lastMidiChannelReceived, 3); + expectEquals (test.lastMidiNoteNumberReceived, 42); + expectEquals (test.lastMPEValueReceived.as7BitInt(), 92); + + // note off should trigger noteOff method call + + test.processNextMidiEvent (MidiMessage::noteOff (4, 12, uint8 (33))); + expectEquals (test.noteOffCallCounter, 1); + expectEquals (test.lastMidiChannelReceived, 4); + expectEquals (test.lastMidiNoteNumberReceived, 12); + expectEquals (test.lastMPEValueReceived.as7BitInt(), 33); + + // note on with velocity = 0 should trigger noteOff method call + // with a note off velocity of 64 (centre value) + + test.processNextMidiEvent (MidiMessage::noteOn (5, 11, uint8 (0))); + expectEquals (test.noteOffCallCounter, 2); + expectEquals (test.lastMidiChannelReceived, 5); + expectEquals (test.lastMidiNoteNumberReceived, 11); + expectEquals (test.lastMPEValueReceived.as7BitInt(), 64); + + // pitchwheel message should trigger pitchbend method call + + test.processNextMidiEvent (MidiMessage::pitchWheel (1, 3333)); + expectEquals (test.pitchbendCallCounter, 1); + expectEquals (test.lastMidiChannelReceived, 1); + expectEquals (test.lastMPEValueReceived.as14BitInt(), 3333); + + // pressure using channel pressure message (7-bit value) should + // trigger pressure method call + + test.processNextMidiEvent (MidiMessage::channelPressureChange (10, 35)); + expectEquals (test.pressureCallCounter, 1); + expectEquals (test.lastMidiChannelReceived, 10); + expectEquals (test.lastMPEValueReceived.as7BitInt(), 35); + + // pressure using 14-bit value over CC70 and CC102 should trigger + // pressure method call after the MSB is sent + + // a) sending only the MSB + test.processNextMidiEvent (MidiMessage::controllerEvent (3, 70, 120)); + expectEquals (test.pressureCallCounter, 2); + expectEquals (test.lastMidiChannelReceived, 3); + expectEquals (test.lastMPEValueReceived.as7BitInt(), 120); + + // b) sending LSB and MSB (only the MSB should trigger the call) - per MIDI channel! + test.processNextMidiEvent (MidiMessage::controllerEvent (4, 102, 121)); + expectEquals (test.pressureCallCounter, 2); + test.processNextMidiEvent (MidiMessage::controllerEvent (5, 102, 122)); + expectEquals (test.pressureCallCounter, 2); + test.processNextMidiEvent (MidiMessage::controllerEvent (4, 70, 123)); + expectEquals (test.pressureCallCounter, 3); + expectEquals (test.lastMidiChannelReceived, 4); + expectEquals (test.lastMPEValueReceived.as14BitInt(), 121 + (123 << 7)); + test.processNextMidiEvent (MidiMessage::controllerEvent (5, 70, 124)); + expectEquals (test.pressureCallCounter, 4); + expectEquals (test.lastMidiChannelReceived, 5); + expectEquals (test.lastMPEValueReceived.as14BitInt(), 122 + (124 << 7)); + test.processNextMidiEvent (MidiMessage::controllerEvent (5, 70, 64)); + expectEquals (test.pressureCallCounter, 5); + expectEquals (test.lastMidiChannelReceived, 5); + expectEquals (test.lastMPEValueReceived.as7BitInt(), 64); + + // same for timbre 14-bit value over CC74 and CC106 + test.processNextMidiEvent (MidiMessage::controllerEvent (3, 74, 120)); + expectEquals (test.timbreCallCounter, 1); + expectEquals (test.lastMidiChannelReceived, 3); + expectEquals (test.lastMPEValueReceived.as7BitInt(), 120); + test.processNextMidiEvent (MidiMessage::controllerEvent (4, 106, 121)); + expectEquals (test.timbreCallCounter, 1); + test.processNextMidiEvent (MidiMessage::controllerEvent (5, 106, 122)); + expectEquals (test.timbreCallCounter, 1); + test.processNextMidiEvent (MidiMessage::controllerEvent (4, 74, 123)); + expectEquals (test.timbreCallCounter, 2); + expectEquals (test.lastMidiChannelReceived, 4); + expectEquals (test.lastMPEValueReceived.as14BitInt(), 121 + (123 << 7)); + test.processNextMidiEvent (MidiMessage::controllerEvent (5, 74, 124)); + expectEquals (test.timbreCallCounter, 3); + expectEquals (test.lastMidiChannelReceived, 5); + expectEquals (test.lastMPEValueReceived.as14BitInt(), 122 + (124 << 7)); + test.processNextMidiEvent (MidiMessage::controllerEvent (5, 74, 64)); + expectEquals (test.timbreCallCounter, 4); + expectEquals (test.lastMidiChannelReceived, 5); + expectEquals (test.lastMPEValueReceived.as7BitInt(), 64); + + // sustain pedal message (CC64) should trigger sustainPedal method call + test.processNextMidiEvent (MidiMessage::controllerEvent (1, 64, 127)); + expectEquals (test.sustainPedalCallCounter, 1); + expectEquals (test.lastMidiChannelReceived, 1); + expect (test.lastSustainPedalValueReceived); + test.processNextMidiEvent (MidiMessage::controllerEvent (16, 64, 0)); + expectEquals (test.sustainPedalCallCounter, 2); + expectEquals (test.lastMidiChannelReceived, 16); + expect (! test.lastSustainPedalValueReceived); + + // sostenuto pedal message (CC66) should trigger sostenutoPedal method call + test.processNextMidiEvent (MidiMessage::controllerEvent (1, 66, 127)); + expectEquals (test.sostenutoPedalCallCounter, 1); + expectEquals (test.lastMidiChannelReceived, 1); + expect (test.lastSostenutoPedalValueReceived); + test.processNextMidiEvent (MidiMessage::controllerEvent (16, 66, 0)); + expectEquals (test.sostenutoPedalCallCounter, 2); + expectEquals (test.lastMidiChannelReceived, 16); + expect (! test.lastSostenutoPedalValueReceived); + } + { + // MIDI messages modifying the zone layout should be correctly + // forwarded to the internal zone layout and modify it. + // (testing the actual logic of the zone layout is done in the + // MPEZoneLayout unit tests) + MPEInstrument test; + + MidiBuffer buffer; + buffer.addEvents (MPEMessages::setLowerZone (5), 0, -1, 0); + buffer.addEvents (MPEMessages::setUpperZone (6), 0, -1, 0); + + MidiBuffer::Iterator iter (buffer); + MidiMessage message; + int samplePosition; // not actually used, so no need to initialise. + + while (iter.getNextEvent (message, samplePosition)) + test.processNextMidiEvent (message); + + expect (test.getZoneLayout().getLowerZone().isActive()); + expect (test.getZoneLayout().getUpperZone().isActive()); + expectEquals (test.getZoneLayout().getLowerZone().getMasterChannel(), 1); + expectEquals (test.getZoneLayout().getLowerZone().numMemberChannels, 5); + expectEquals (test.getZoneLayout().getUpperZone().getMasterChannel(), 16); + expectEquals (test.getZoneLayout().getUpperZone().numMemberChannels, 6); + } + + beginTest ("MIDI all notes off"); + { + UnitTestInstrument test; + test.setZoneLayout (testLayout); + test.noteOn (3, 60, MPEValue::from7BitInt (100)); + test.noteOn (4, 61, MPEValue::from7BitInt (100)); + test.noteOn (15, 62, MPEValue::from7BitInt (100)); + test.noteOn (15, 63, MPEValue::from7BitInt (100)); + expectEquals (test.getNumPlayingNotes(), 4); + + // on note channel: ignore. + test.processNextMidiEvent (MidiMessage::allControllersOff (3)); + expectEquals (test.getNumPlayingNotes(), 4); + + // on unused channel: ignore. + test.processNextMidiEvent (MidiMessage::allControllersOff (9)); + expectEquals (test.getNumPlayingNotes(), 4); + + // on master channel: release notes in that zone only. + test.processNextMidiEvent (MidiMessage::allControllersOff (1)); + expectEquals (test.getNumPlayingNotes(), 2); + test.processNextMidiEvent (MidiMessage::allControllersOff (16)); + expectEquals (test.getNumPlayingNotes(), 0); + } + + beginTest ("MIDI all notes off (legacy mode)"); + { + UnitTestInstrument test; + test.enableLegacyMode(); + test.noteOn (3, 60, MPEValue::from7BitInt (100)); + test.noteOn (4, 61, MPEValue::from7BitInt (100)); + test.noteOn (15, 62, MPEValue::from7BitInt (100)); + test.noteOn (15, 63, MPEValue::from7BitInt (100)); + expectEquals (test.getNumPlayingNotes(), 4); + + test.processNextMidiEvent (MidiMessage::allControllersOff (3)); + expectEquals (test.getNumPlayingNotes(), 3); + + test.processNextMidiEvent (MidiMessage::allControllersOff (15)); + expectEquals (test.getNumPlayingNotes(), 1); + + test.processNextMidiEvent (MidiMessage::allControllersOff (4)); + expectEquals (test.getNumPlayingNotes(), 0); + } + + beginTest ("default initial values for pitchbend and timbre"); + { + MPEInstrument test; + test.setZoneLayout (testLayout); + + test.pitchbend (3, MPEValue::from14BitInt (3333)); // use for next note-on on ch. 3 + test.pitchbend (2, MPEValue::from14BitInt (4444)); // ignore + test.pitchbend (2, MPEValue::from14BitInt (5555)); // ignore + + test.timbre (3, MPEValue::from7BitInt (66)); // use for next note-on on ch. 3 + test.timbre (2, MPEValue::from7BitInt (77)); // ignore + test.timbre (2, MPEValue::from7BitInt (88)); // ignore + + test.noteOn (3, 60, MPEValue::from7BitInt (100)); + + expectNote (test.getMostRecentNote (3), 100, 0, 3333, 66, MPENote::keyDown); + } + + beginTest ("Legacy mode"); + { + { + // basic check + MPEInstrument test; + expect (! test.isLegacyModeEnabled()); + + test.setZoneLayout (testLayout); + expect (! test.isLegacyModeEnabled()); + + test.enableLegacyMode(); + expect (test.isLegacyModeEnabled()); + + test.setZoneLayout (testLayout); + expect (! test.isLegacyModeEnabled()); + } + { + // constructor w/o default arguments + MPEInstrument test; + test.enableLegacyMode (0, Range (1, 11)); + expectEquals (test.getLegacyModePitchbendRange(), 0); + expect (test.getLegacyModeChannelRange() == Range (1, 11)); + } + { + // getters and setters + MPEInstrument test; + test.enableLegacyMode(); + + expectEquals (test.getLegacyModePitchbendRange(), 2); + expect (test.getLegacyModeChannelRange() == Range (1, 17)); + + test.setLegacyModePitchbendRange (96); + expectEquals (test.getLegacyModePitchbendRange(), 96); + + test.setLegacyModeChannelRange (Range (10, 12)); + expect (test.getLegacyModeChannelRange() == Range (10, 12)); + } + { + // note on should trigger notes on all 16 channels + + UnitTestInstrument test; + test.enableLegacyMode(); + + test.noteOn (1, 60, MPEValue::from7BitInt (100)); + test.noteOn (2, 60, MPEValue::from7BitInt (100)); + test.noteOn (15, 60, MPEValue::from7BitInt (100)); + test.noteOn (16, 60, MPEValue::from7BitInt (100)); + expectEquals (test.getNumPlayingNotes(), 4); + + // polyphonic modulation should work across all 16 channels + + test.pitchbend (1, MPEValue::from14BitInt (9999)); + test.pressure (2, MPEValue::from7BitInt (88)); + test.timbre (15, MPEValue::from7BitInt (77)); + + expectNote (test.getNote (1, 60), 100, 0, 9999, 64, MPENote::keyDown); + expectNote (test.getNote (2, 60), 100, 88, 8192, 64, MPENote::keyDown); + expectNote (test.getNote (15, 60), 100, 0, 8192, 77, MPENote::keyDown); + expectNote (test.getNote (16, 60), 100, 0, 8192, 64, MPENote::keyDown); + + // note off should work in legacy mode + + test.noteOff (15, 60, MPEValue::from7BitInt (0)); + test.noteOff (1, 60, MPEValue::from7BitInt (0)); + test.noteOff (2, 60, MPEValue::from7BitInt (0)); + test.noteOff (16, 60, MPEValue::from7BitInt (0)); + expectEquals (test.getNumPlayingNotes(), 0); + } + { + // legacy mode w/ custom channel range: note on should trigger notes only within range + + UnitTestInstrument test; + test.enableLegacyMode (2, Range (3, 8)); // channels 3-7 + + test.noteOn (1, 60, MPEValue::from7BitInt (100)); + test.noteOn (2, 60, MPEValue::from7BitInt (100)); + test.noteOn (3, 60, MPEValue::from7BitInt (100)); // should trigger + test.noteOn (4, 60, MPEValue::from7BitInt (100)); // should trigger + test.noteOn (6, 60, MPEValue::from7BitInt (100)); // should trigger + test.noteOn (7, 60, MPEValue::from7BitInt (100)); // should trigger + test.noteOn (8, 60, MPEValue::from7BitInt (100)); + test.noteOn (16, 60, MPEValue::from7BitInt (100)); + + expectEquals (test.getNumPlayingNotes(), 4); + expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown); + expectNote (test.getNote (4, 60), 100, 0, 8192, 64, MPENote::keyDown); + expectNote (test.getNote (6, 60), 100, 0, 8192, 64, MPENote::keyDown); + expectNote (test.getNote (7, 60), 100, 0, 8192, 64, MPENote::keyDown); + } + { + // tracking mode in legacy mode + { + UnitTestInstrument test; + test.enableLegacyMode(); + + test.setPitchbendTrackingMode (MPEInstrument::lastNotePlayedOnChannel); + test.noteOn (1, 60, MPEValue::from7BitInt (100)); + test.noteOn (1, 62, MPEValue::from7BitInt (100)); + test.noteOn (1, 61, MPEValue::from7BitInt (100)); + test.pitchbend (1, MPEValue::from14BitInt (9999)); + expectNote (test.getNote (1, 60), 100, 0, 8192, 64, MPENote::keyDown); + expectNote (test.getNote (1, 61), 100, 0, 9999, 64, MPENote::keyDown); + expectNote (test.getNote (1, 62), 100, 0, 8192, 64, MPENote::keyDown); + } + { + UnitTestInstrument test; + test.enableLegacyMode(); + + test.setPitchbendTrackingMode (MPEInstrument::lowestNoteOnChannel); + test.noteOn (1, 60, MPEValue::from7BitInt (100)); + test.noteOn (1, 62, MPEValue::from7BitInt (100)); + test.noteOn (1, 61, MPEValue::from7BitInt (100)); + test.pitchbend (1, MPEValue::from14BitInt (9999)); + expectNote (test.getNote (1, 60), 100, 0, 9999, 64, MPENote::keyDown); + expectNote (test.getNote (1, 61), 100, 0, 8192, 64, MPENote::keyDown); + expectNote (test.getNote (1, 62), 100, 0, 8192, 64, MPENote::keyDown); + } + { + UnitTestInstrument test; + test.enableLegacyMode(); + + test.setPitchbendTrackingMode (MPEInstrument::highestNoteOnChannel); + test.noteOn (1, 60, MPEValue::from7BitInt (100)); + test.noteOn (1, 62, MPEValue::from7BitInt (100)); + test.noteOn (1, 61, MPEValue::from7BitInt (100)); + test.pitchbend (1, MPEValue::from14BitInt (9999)); + expectNote (test.getNote (1, 60), 100, 0, 8192, 64, MPENote::keyDown); + expectNote (test.getNote (1, 61), 100, 0, 8192, 64, MPENote::keyDown); + expectNote (test.getNote (1, 62), 100, 0, 9999, 64, MPENote::keyDown); + } + { + UnitTestInstrument test; + test.enableLegacyMode(); + + test.setPitchbendTrackingMode (MPEInstrument::allNotesOnChannel); + test.noteOn (1, 60, MPEValue::from7BitInt (100)); + test.noteOn (1, 62, MPEValue::from7BitInt (100)); + test.noteOn (1, 61, MPEValue::from7BitInt (100)); + test.pitchbend (1, MPEValue::from14BitInt (9999)); + expectNote (test.getNote (1, 60), 100, 0, 9999, 64, MPENote::keyDown); + expectNote (test.getNote (1, 61), 100, 0, 9999, 64, MPENote::keyDown); + expectNote (test.getNote (1, 62), 100, 0, 9999, 64, MPENote::keyDown); + } + } + { + // custom pitchbend range in legacy mode. + UnitTestInstrument test; + test.enableLegacyMode (11); + + test.pitchbend (1, MPEValue::from14BitInt (4096)); + test.noteOn (1, 60, MPEValue::from7BitInt (100)); + expectDoubleWithinRelativeError (test.getMostRecentNote (1).totalPitchbendInSemitones, -5.5, 0.01); + } + { + // sustain pedal should be per channel in legacy mode. + UnitTestInstrument test; + test.enableLegacyMode(); + + test.sustainPedal (1, true); + test.noteOn (2, 61, MPEValue::from7BitInt (100)); + test.noteOff (2, 61, MPEValue::from7BitInt (100)); + test.noteOn (1, 60, MPEValue::from7BitInt (100)); + test.noteOff (1, 60, MPEValue::from7BitInt (100)); + + expectEquals (test.getNumPlayingNotes(), 1); + expectNote (test.getNote (1, 60), 100, 0, 8192, 64, MPENote::sustained); + + test.sustainPedal (1, false); + expectEquals (test.getNumPlayingNotes(), 0); + + test.noteOn (2, 61, MPEValue::from7BitInt (100)); + test.sustainPedal (1, true); + test.noteOff (2, 61, MPEValue::from7BitInt (100)); + expectEquals (test.getNumPlayingNotes(), 0); + + } + { + // sostenuto pedal should be per channel in legacy mode. + UnitTestInstrument test; + test.enableLegacyMode(); + + test.noteOn (1, 60, MPEValue::from7BitInt (100)); + test.sostenutoPedal (1, true); + test.noteOff (1, 60, MPEValue::from7BitInt (100)); + test.noteOn (2, 61, MPEValue::from7BitInt (100)); + test.noteOff (2, 61, MPEValue::from7BitInt (100)); + + expectEquals (test.getNumPlayingNotes(), 1); + expectNote (test.getNote (1, 60), 100, 0, 8192, 64, MPENote::sustained); + + test.sostenutoPedal (1, false); + expectEquals (test.getNumPlayingNotes(), 0); + + test.noteOn (2, 61, MPEValue::from7BitInt (100)); + test.sostenutoPedal (1, true); + test.noteOff (2, 61, MPEValue::from7BitInt (100)); + expectEquals (test.getNumPlayingNotes(), 0); + } + { + // all notes released when switching layout + UnitTestInstrument test; + test.setZoneLayout (testLayout); + test.noteOn (3, 60, MPEValue::from7BitInt (100)); + expectEquals (test.getNumPlayingNotes(), 1); + + test.enableLegacyMode(); + expectEquals (test.getNumPlayingNotes(), 0); + test.noteOn (3, 60, MPEValue::from7BitInt (100)); + expectEquals (test.getNumPlayingNotes(), 1); + + test.setZoneLayout (testLayout); + expectEquals (test.getNumPlayingNotes(), 0); + } + } + } + +private: + //============================================================================== + /* This mock class is used for unit testing whether the methods of + MPEInstrument are called correctly. + */ + class UnitTestInstrument : public MPEInstrument, + private MPEInstrument::Listener + { + using Base = MPEInstrument; + + public: + UnitTestInstrument() + : noteOnCallCounter (0), noteOffCallCounter (0), pitchbendCallCounter (0), + pressureCallCounter (0), timbreCallCounter (0), sustainPedalCallCounter (0), + sostenutoPedalCallCounter (0), noteAddedCallCounter (0), notePressureChangedCallCounter (0), + notePitchbendChangedCallCounter (0), noteTimbreChangedCallCounter (0), + noteKeyStateChangedCallCounter (0), noteReleasedCallCounter (0), + lastMidiChannelReceived (-1), lastMidiNoteNumberReceived (-1), + lastSustainPedalValueReceived (false), lastSostenutoPedalValueReceived (false) + { + addListener (this); + } + + void noteOn (int midiChannel, int midiNoteNumber, MPEValue midiNoteOnVelocity) override + { + Base::noteOn (midiChannel, midiNoteNumber, midiNoteOnVelocity); + + noteOnCallCounter++; + lastMidiChannelReceived = midiChannel; + lastMidiNoteNumberReceived = midiNoteNumber; + lastMPEValueReceived = midiNoteOnVelocity; + } + + void noteOff (int midiChannel, int midiNoteNumber, MPEValue midiNoteOffVelocity) override + { + Base::noteOff (midiChannel, midiNoteNumber, midiNoteOffVelocity); + + noteOffCallCounter++; + lastMidiChannelReceived = midiChannel; + lastMidiNoteNumberReceived = midiNoteNumber; + lastMPEValueReceived = midiNoteOffVelocity; + } + + void pitchbend (int midiChannel, MPEValue value) override + { + Base::pitchbend (midiChannel, value); + + pitchbendCallCounter++; + lastMidiChannelReceived = midiChannel; + lastMPEValueReceived = value; + } + + void pressure (int midiChannel, MPEValue value) override + { + Base::pressure (midiChannel, value); + + pressureCallCounter++; + lastMidiChannelReceived = midiChannel; + lastMPEValueReceived = value; + } + + void timbre (int midiChannel, MPEValue value) override + { + Base::timbre (midiChannel, value); + + timbreCallCounter++; + lastMidiChannelReceived = midiChannel; + lastMPEValueReceived = value; + } + + void sustainPedal (int midiChannel, bool value) override + { + Base::sustainPedal (midiChannel, value); + + sustainPedalCallCounter++; + lastMidiChannelReceived = midiChannel; + lastSustainPedalValueReceived = value; + } + + void sostenutoPedal (int midiChannel, bool value) override + { + Base::sostenutoPedal (midiChannel, value); + + sostenutoPedalCallCounter++; + lastMidiChannelReceived = midiChannel; + lastSostenutoPedalValueReceived = value; + } + + void aftertouch (int midiChannel, int midiNoteNumber, MPEValue value) + { + const auto message = juce::MidiMessage::aftertouchChange (midiChannel, midiNoteNumber, value.as7BitInt()); + processNextMidiEvent (message); + } + + int noteOnCallCounter, noteOffCallCounter, pitchbendCallCounter, + pressureCallCounter, timbreCallCounter, sustainPedalCallCounter, + sostenutoPedalCallCounter, noteAddedCallCounter, + notePressureChangedCallCounter, notePitchbendChangedCallCounter, + noteTimbreChangedCallCounter, noteKeyStateChangedCallCounter, + noteReleasedCallCounter, lastMidiChannelReceived, lastMidiNoteNumberReceived; + + bool lastSustainPedalValueReceived, lastSostenutoPedalValueReceived; + MPEValue lastMPEValueReceived; + std::unique_ptr lastNoteFinished; + + private: + //============================================================================== + void noteAdded (MPENote) override { noteAddedCallCounter++; } + + void notePressureChanged (MPENote) override { notePressureChangedCallCounter++; } + void notePitchbendChanged (MPENote) override { notePitchbendChangedCallCounter++; } + void noteTimbreChanged (MPENote) override { noteTimbreChangedCallCounter++; } + void noteKeyStateChanged (MPENote) override { noteKeyStateChangedCallCounter++; } + + void noteReleased (MPENote finishedNote) override + { + noteReleasedCallCounter++; + lastNoteFinished.reset (new MPENote (finishedNote)); + } + }; + + //============================================================================== + void expectNote (MPENote noteToTest, + int noteOnVelocity7Bit, + int pressure7Bit, + int pitchbend14Bit, + int timbre7Bit, + MPENote::KeyState keyState) + { + expect (noteToTest.isValid()); + expectEquals (noteToTest.noteOnVelocity.as7BitInt(), noteOnVelocity7Bit); + expectEquals (noteToTest.pressure.as7BitInt(), pressure7Bit); + expectEquals (noteToTest.pitchbend.as14BitInt(), pitchbend14Bit); + expectEquals (noteToTest.timbre.as7BitInt(),timbre7Bit); + expect (noteToTest.keyState == keyState); + } + + void expectHasFinishedNote (const UnitTestInstrument& test, + int channel, int noteNumber, int noteOffVelocity7Bit) + { + expect (test.lastNoteFinished != nullptr); + expectEquals (int (test.lastNoteFinished->midiChannel), channel); + expectEquals (int (test.lastNoteFinished->initialNote), noteNumber); + expectEquals (test.lastNoteFinished->noteOffVelocity.as7BitInt(), noteOffVelocity7Bit); + expect (test.lastNoteFinished->keyState == MPENote::off); + } + + void expectDoubleWithinRelativeError (double actual, double expected, double maxRelativeError) + { + const double maxAbsoluteError = jmax (1.0, std::abs (expected)) * maxRelativeError; + expect (std::abs (expected - actual) < maxAbsoluteError); + } + + //============================================================================== + MPEZoneLayout testLayout; +}; + +static MPEInstrumentTests MPEInstrumentUnitTests; + +#endif + +} // namespace juce diff --git a/JuceLibraryCode/modules/juce_audio_basics/mpe/juce_MPEInstrument.h b/JuceLibraryCode/modules/juce_audio_basics/mpe/juce_MPEInstrument.h index 91a0b2b..7754759 100644 --- a/JuceLibraryCode/modules/juce_audio_basics/mpe/juce_MPEInstrument.h +++ b/JuceLibraryCode/modules/juce_audio_basics/mpe/juce_MPEInstrument.h @@ -1,413 +1,413 @@ -/* - ============================================================================== - - This file is part of the JUCE library. - Copyright (c) 2017 - ROLI Ltd. - - JUCE is an open source library subject to commercial or open-source - licensing. - - The code included in this file is provided under the terms of the ISC license - http://www.isc.org/downloads/software-support-policy/isc-license. Permission - To use, copy, modify, and/or distribute this software for any purpose with or - without fee is hereby granted provided that the above copyright notice and - this permission notice appear in all copies. - - JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER - EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE - DISCLAIMED. - - ============================================================================== -*/ - -namespace juce -{ - -//============================================================================== -/** - This class represents an instrument handling MPE. - - It has an MPE zone layout and maintains a state of currently - active (playing) notes and the values of their dimensions of expression. - - You can trigger and modulate notes: - - by passing MIDI messages with the method processNextMidiEvent; - - by directly calling the methods noteOn, noteOff etc. - - The class implements the channel and note management logic specified in - MPE. If you pass it a message, it will know what notes on what - channels (if any) should be affected by that message. - - The class has a Listener class with the three callbacks MPENoteAdded, - MPENoteChanged, and MPENoteFinished. Implement such a - Listener class to react to note changes and trigger some functionality for - your application that depends on the MPE note state. - For example, you can use this class to write an MPE visualiser. - - If you want to write a real-time audio synth with MPE functionality, - you should instead use the classes MPESynthesiserBase, which adds - the ability to render audio and to manage voices. - - @see MPENote, MPEZoneLayout, MPESynthesiser - - @tags{Audio} -*/ -class JUCE_API MPEInstrument -{ -public: - /** Constructor. - - This will construct an MPE instrument with inactive lower and upper zones. - - In order to process incoming MIDI, call setZoneLayout, define the layout - via MIDI RPN messages, or set the instrument to legacy mode. - */ - MPEInstrument() noexcept; - - /** Destructor. */ - virtual ~MPEInstrument(); - - //============================================================================== - /** Returns the current zone layout of the instrument. - This happens by value, to enforce thread-safety and class invariants. - - Note: If the instrument is in legacy mode, the return value of this - method is unspecified. - */ - MPEZoneLayout getZoneLayout() const noexcept; - - /** Re-sets the zone layout of the instrument to the one passed in. - As a side effect, this will discard all currently playing notes, - and call noteReleased for all of them. - - This will also disable legacy mode in case it was enabled previously. - */ - void setZoneLayout (MPEZoneLayout newLayout); - - /** Returns true if the given MIDI channel (1-16) is a note channel in any - of the MPEInstrument's MPE zones; false otherwise. - - When in legacy mode, this will return true if the given channel is - contained in the current legacy mode channel range; false otherwise. - */ - bool isMemberChannel (int midiChannel) const noexcept; - - /** Returns true if the given MIDI channel (1-16) is a master channel (channel - 1 or 16). - - In legacy mode, this will always return false. - */ - bool isMasterChannel (int midiChannel) const noexcept; - - /** Returns true if the given MIDI channel (1-16) is used by any of the - MPEInstrument's MPE zones; false otherwise. - - When in legacy mode, this will return true if the given channel is - contained in the current legacy mode channel range; false otherwise. - */ - bool isUsingChannel (int midiChannel) const noexcept; - - //============================================================================== - /** The MPE note tracking mode. In case there is more than one note playing - simultaneously on the same MIDI channel, this determines which of these - notes will be modulated by an incoming MPE message on that channel - (pressure, pitchbend, or timbre). - - The default is lastNotePlayedOnChannel. - */ - enum TrackingMode - { - lastNotePlayedOnChannel, /**< The most recent note on the channel that is still played (key down and/or sustained). */ - lowestNoteOnChannel, /**< The lowest note (by initialNote) on the channel with the note key still down. */ - highestNoteOnChannel, /**< The highest note (by initialNote) on the channel with the note key still down. */ - allNotesOnChannel /**< All notes on the channel (key down and/or sustained). */ - }; - - /** Set the MPE tracking mode for the pressure dimension. */ - void setPressureTrackingMode (TrackingMode modeToUse); - - /** Set the MPE tracking mode for the pitchbend dimension. */ - void setPitchbendTrackingMode (TrackingMode modeToUse); - - /** Set the MPE tracking mode for the timbre dimension. */ - void setTimbreTrackingMode (TrackingMode modeToUse); - - //============================================================================== - /** Process a MIDI message and trigger the appropriate method calls - (noteOn, noteOff etc.) - - You can override this method if you need some special MIDI message - treatment on top of the standard MPE logic implemented here. - */ - virtual void processNextMidiEvent (const MidiMessage& message); - - //============================================================================== - /** Request a note-on on the given channel, with the given initial note - number and velocity. - - If the message arrives on a valid note channel, this will create a - new MPENote and call the noteAdded callback. - */ - virtual void noteOn (int midiChannel, int midiNoteNumber, MPEValue midiNoteOnVelocity); - - /** Request a note-off. - - If there is a matching playing note, this will release the note - (except if it is sustained by a sustain or sostenuto pedal) and call - the noteReleased callback. - */ - virtual void noteOff (int midiChannel, int midiNoteNumber, MPEValue midiNoteOffVelocity); - - /** Request a pitchbend on the given channel with the given value (in units - of MIDI pitchwheel position). - - Internally, this will determine whether the pitchwheel move is a - per-note pitchbend or a master pitchbend (depending on midiChannel), - take the correct per-note or master pitchbend range of the affected MPE - zone, and apply the resulting pitchbend to the affected note(s) (if any). - */ - virtual void pitchbend (int midiChannel, MPEValue pitchbend); - - /** Request a pressure change on the given channel with the given value. - - This will modify the pressure dimension of the note currently held down - on this channel (if any). If the channel is a zone master channel, - the pressure change will be broadcast to all notes in this zone. - */ - virtual void pressure (int midiChannel, MPEValue value); - - /** Request a third dimension (timbre) change on the given channel with the - given value. - - This will modify the timbre dimension of the note currently held down - on this channel (if any). If the channel is a zone master channel, - the timbre change will be broadcast to all notes in this zone. - */ - virtual void timbre (int midiChannel, MPEValue value); - - /** Request a poly-aftertouch change for a given note number. - - The change will be broadcast to all notes sharing the channel and note - number of the change message. - */ - virtual void polyAftertouch (int midiChannel, int midiNoteNumber, MPEValue value); - - /** Request a sustain pedal press or release. - - If midiChannel is a zone's master channel, this will act on all notes in - that zone; otherwise, nothing will happen. - */ - virtual void sustainPedal (int midiChannel, bool isDown); - - /** Request a sostenuto pedal press or release. - - If midiChannel is a zone's master channel, this will act on all notes in - that zone; otherwise, nothing will happen. - */ - virtual void sostenutoPedal (int midiChannel, bool isDown); - - /** Discard all currently playing notes. - - This will also call the noteReleased listener callback for all of them. - */ - void releaseAllNotes(); - - //============================================================================== - /** Returns the number of MPE notes currently played by the instrument. */ - int getNumPlayingNotes() const noexcept; - - /** Returns the note at the given index. - - If there is no such note, returns an invalid MPENote. The notes are sorted - such that the most recently added note is the last element. - */ - MPENote getNote (int index) const noexcept; - - /** Returns the note currently playing on the given midiChannel with the - specified initial MIDI note number, if there is such a note. Otherwise, - this returns an invalid MPENote (check with note.isValid() before use!) - */ - MPENote getNote (int midiChannel, int midiNoteNumber) const noexcept; - - /** Returns the most recent note that is playing on the given midiChannel - (this will be the note which has received the most recent note-on without - a corresponding note-off), if there is such a note. Otherwise, this returns an - invalid MPENote (check with note.isValid() before use!) - */ - MPENote getMostRecentNote (int midiChannel) const noexcept; - - /** Returns the most recent note that is not the note passed in. If there is no - such note, this returns an invalid MPENote (check with note.isValid() before use!). - - This helper method might be useful for some custom voice handling algorithms. - */ - MPENote getMostRecentNoteOtherThan (MPENote otherThanThisNote) const noexcept; - - //============================================================================== - /** Derive from this class to be informed about any changes in the expressive - MIDI notes played by this instrument. - - Note: This listener type receives its callbacks immediately, and not - via the message thread (so you might be for example in the MIDI thread). - Therefore you should never do heavy work such as graphics rendering etc. - inside those callbacks. - */ - class JUCE_API Listener - { - public: - /** Destructor. */ - virtual ~Listener() = default; - - /** Implement this callback to be informed whenever a new expressive MIDI - note is triggered. - */ - virtual void noteAdded (MPENote newNote) { ignoreUnused (newNote); } - - /** Implement this callback to be informed whenever a currently playing - MPE note's pressure value changes. - */ - virtual void notePressureChanged (MPENote changedNote) { ignoreUnused (changedNote); } - - /** Implement this callback to be informed whenever a currently playing - MPE note's pitchbend value changes. - - Note: This can happen if the note itself is bent, if there is a - master channel pitchbend event, or if both occur simultaneously. - Call MPENote::getFrequencyInHertz to get the effective note frequency. - */ - virtual void notePitchbendChanged (MPENote changedNote) { ignoreUnused (changedNote); } - - /** Implement this callback to be informed whenever a currently playing - MPE note's timbre value changes. - */ - virtual void noteTimbreChanged (MPENote changedNote) { ignoreUnused (changedNote); } - - /** Implement this callback to be informed whether a currently playing - MPE note's key state (whether the key is down and/or the note is - sustained) has changed. - - Note: If the key state changes to MPENote::off, noteReleased is - called instead. - */ - virtual void noteKeyStateChanged (MPENote changedNote) { ignoreUnused (changedNote); } - - /** Implement this callback to be informed whenever an MPE note - is released (either by a note-off message, or by a sustain/sostenuto - pedal release for a note that already received a note-off), - and should therefore stop playing. - */ - virtual void noteReleased (MPENote finishedNote) { ignoreUnused (finishedNote); } - }; - - //============================================================================== - /** Adds a listener. */ - void addListener (Listener* listenerToAdd); - - /** Removes a listener. */ - void removeListener (Listener* listenerToRemove); - - //============================================================================== - /** Puts the instrument into legacy mode. - As a side effect, this will discard all currently playing notes, - and call noteReleased for all of them. - - This special zone layout mode is for backwards compatibility with - non-MPE MIDI devices. In this mode, the instrument will ignore the - current MPE zone layout. It will instead take a range of MIDI channels - (default: all channels 1-16) and treat them as note channels, with no - master channel. MIDI channels outside of this range will be ignored. - - @param pitchbendRange The note pitchbend range in semitones to use when in legacy mode. - Must be between 0 and 96, otherwise behaviour is undefined. - The default pitchbend range in legacy mode is +/- 2 semitones. - - @param channelRange The range of MIDI channels to use for notes when in legacy mode. - The default is to use all MIDI channels (1-16). - - To get out of legacy mode, set a new MPE zone layout using setZoneLayout. - */ - void enableLegacyMode (int pitchbendRange = 2, - Range channelRange = Range (1, 17)); - - /** Returns true if the instrument is in legacy mode, false otherwise. */ - bool isLegacyModeEnabled() const noexcept; - - /** Returns the range of MIDI channels (1-16) to be used for notes when in legacy mode. */ - Range getLegacyModeChannelRange() const noexcept; - - /** Re-sets the range of MIDI channels (1-16) to be used for notes when in legacy mode. */ - void setLegacyModeChannelRange (Range channelRange); - - /** Returns the pitchbend range in semitones (0-96) to be used for notes when in legacy mode. */ - int getLegacyModePitchbendRange() const noexcept; - - /** Re-sets the pitchbend range in semitones (0-96) to be used for notes when in legacy mode. */ - void setLegacyModePitchbendRange (int pitchbendRange); - -protected: - //============================================================================== - CriticalSection lock; - -private: - //============================================================================== - Array notes; - MPEZoneLayout zoneLayout; - ListenerList listeners; - - uint8 lastPressureLowerBitReceivedOnChannel[16]; - uint8 lastTimbreLowerBitReceivedOnChannel[16]; - bool isMemberChannelSustained[16]; - - struct LegacyMode - { - bool isEnabled; - Range channelRange; - int pitchbendRange; - }; - - struct MPEDimension - { - TrackingMode trackingMode = lastNotePlayedOnChannel; - MPEValue lastValueReceivedOnChannel[16]; - MPEValue MPENote::* value; - MPEValue& getValue (MPENote& note) noexcept { return note.*(value); } - }; - - LegacyMode legacyMode; - MPEDimension pitchbendDimension, pressureDimension, timbreDimension; - - void updateDimension (int midiChannel, MPEDimension&, MPEValue); - void updateDimensionMaster (bool, MPEDimension&, MPEValue); - void updateDimensionForNote (MPENote&, MPEDimension&, MPEValue); - void callListenersDimensionChanged (const MPENote&, const MPEDimension&); - MPEValue getInitialValueForNewNote (int midiChannel, MPEDimension&) const; - - void processMidiNoteOnMessage (const MidiMessage&); - void processMidiNoteOffMessage (const MidiMessage&); - void processMidiPitchWheelMessage (const MidiMessage&); - void processMidiChannelPressureMessage (const MidiMessage&); - void processMidiControllerMessage (const MidiMessage&); - void processMidiResetAllControllersMessage (const MidiMessage&); - void processMidiAfterTouchMessage (const MidiMessage&); - void handlePressureMSB (int midiChannel, int value) noexcept; - void handlePressureLSB (int midiChannel, int value) noexcept; - void handleTimbreMSB (int midiChannel, int value) noexcept; - void handleTimbreLSB (int midiChannel, int value) noexcept; - void handleSustainOrSostenuto (int midiChannel, bool isDown, bool isSostenuto); - - const MPENote* getNotePtr (int midiChannel, int midiNoteNumber) const noexcept; - MPENote* getNotePtr (int midiChannel, int midiNoteNumber) noexcept; - const MPENote* getNotePtr (int midiChannel, TrackingMode) const noexcept; - MPENote* getNotePtr (int midiChannel, TrackingMode) noexcept; - const MPENote* getLastNotePlayedPtr (int midiChannel) const noexcept; - MPENote* getLastNotePlayedPtr (int midiChannel) noexcept; - const MPENote* getHighestNotePtr (int midiChannel) const noexcept; - MPENote* getHighestNotePtr (int midiChannel) noexcept; - const MPENote* getLowestNotePtr (int midiChannel) const noexcept; - MPENote* getLowestNotePtr (int midiChannel) noexcept; - void updateNoteTotalPitchbend (MPENote&); - - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MPEInstrument) -}; - -} // namespace juce +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2017 - ROLI Ltd. + + JUCE is an open source library subject to commercial or open-source + licensing. + + The code included in this file is provided under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license. Permission + To use, copy, modify, and/or distribute this software for any purpose with or + without fee is hereby granted provided that the above copyright notice and + this permission notice appear in all copies. + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +namespace juce +{ + +//============================================================================== +/** + This class represents an instrument handling MPE. + + It has an MPE zone layout and maintains a state of currently + active (playing) notes and the values of their dimensions of expression. + + You can trigger and modulate notes: + - by passing MIDI messages with the method processNextMidiEvent; + - by directly calling the methods noteOn, noteOff etc. + + The class implements the channel and note management logic specified in + MPE. If you pass it a message, it will know what notes on what + channels (if any) should be affected by that message. + + The class has a Listener class with the three callbacks MPENoteAdded, + MPENoteChanged, and MPENoteFinished. Implement such a + Listener class to react to note changes and trigger some functionality for + your application that depends on the MPE note state. + For example, you can use this class to write an MPE visualiser. + + If you want to write a real-time audio synth with MPE functionality, + you should instead use the classes MPESynthesiserBase, which adds + the ability to render audio and to manage voices. + + @see MPENote, MPEZoneLayout, MPESynthesiser + + @tags{Audio} +*/ +class JUCE_API MPEInstrument +{ +public: + /** Constructor. + + This will construct an MPE instrument with inactive lower and upper zones. + + In order to process incoming MIDI, call setZoneLayout, define the layout + via MIDI RPN messages, or set the instrument to legacy mode. + */ + MPEInstrument() noexcept; + + /** Destructor. */ + virtual ~MPEInstrument(); + + //============================================================================== + /** Returns the current zone layout of the instrument. + This happens by value, to enforce thread-safety and class invariants. + + Note: If the instrument is in legacy mode, the return value of this + method is unspecified. + */ + MPEZoneLayout getZoneLayout() const noexcept; + + /** Re-sets the zone layout of the instrument to the one passed in. + As a side effect, this will discard all currently playing notes, + and call noteReleased for all of them. + + This will also disable legacy mode in case it was enabled previously. + */ + void setZoneLayout (MPEZoneLayout newLayout); + + /** Returns true if the given MIDI channel (1-16) is a note channel in any + of the MPEInstrument's MPE zones; false otherwise. + + When in legacy mode, this will return true if the given channel is + contained in the current legacy mode channel range; false otherwise. + */ + bool isMemberChannel (int midiChannel) const noexcept; + + /** Returns true if the given MIDI channel (1-16) is a master channel (channel + 1 or 16). + + In legacy mode, this will always return false. + */ + bool isMasterChannel (int midiChannel) const noexcept; + + /** Returns true if the given MIDI channel (1-16) is used by any of the + MPEInstrument's MPE zones; false otherwise. + + When in legacy mode, this will return true if the given channel is + contained in the current legacy mode channel range; false otherwise. + */ + bool isUsingChannel (int midiChannel) const noexcept; + + //============================================================================== + /** The MPE note tracking mode. In case there is more than one note playing + simultaneously on the same MIDI channel, this determines which of these + notes will be modulated by an incoming MPE message on that channel + (pressure, pitchbend, or timbre). + + The default is lastNotePlayedOnChannel. + */ + enum TrackingMode + { + lastNotePlayedOnChannel, /**< The most recent note on the channel that is still played (key down and/or sustained). */ + lowestNoteOnChannel, /**< The lowest note (by initialNote) on the channel with the note key still down. */ + highestNoteOnChannel, /**< The highest note (by initialNote) on the channel with the note key still down. */ + allNotesOnChannel /**< All notes on the channel (key down and/or sustained). */ + }; + + /** Set the MPE tracking mode for the pressure dimension. */ + void setPressureTrackingMode (TrackingMode modeToUse); + + /** Set the MPE tracking mode for the pitchbend dimension. */ + void setPitchbendTrackingMode (TrackingMode modeToUse); + + /** Set the MPE tracking mode for the timbre dimension. */ + void setTimbreTrackingMode (TrackingMode modeToUse); + + //============================================================================== + /** Process a MIDI message and trigger the appropriate method calls + (noteOn, noteOff etc.) + + You can override this method if you need some special MIDI message + treatment on top of the standard MPE logic implemented here. + */ + virtual void processNextMidiEvent (const MidiMessage& message); + + //============================================================================== + /** Request a note-on on the given channel, with the given initial note + number and velocity. + + If the message arrives on a valid note channel, this will create a + new MPENote and call the noteAdded callback. + */ + virtual void noteOn (int midiChannel, int midiNoteNumber, MPEValue midiNoteOnVelocity); + + /** Request a note-off. + + If there is a matching playing note, this will release the note + (except if it is sustained by a sustain or sostenuto pedal) and call + the noteReleased callback. + */ + virtual void noteOff (int midiChannel, int midiNoteNumber, MPEValue midiNoteOffVelocity); + + /** Request a pitchbend on the given channel with the given value (in units + of MIDI pitchwheel position). + + Internally, this will determine whether the pitchwheel move is a + per-note pitchbend or a master pitchbend (depending on midiChannel), + take the correct per-note or master pitchbend range of the affected MPE + zone, and apply the resulting pitchbend to the affected note(s) (if any). + */ + virtual void pitchbend (int midiChannel, MPEValue pitchbend); + + /** Request a pressure change on the given channel with the given value. + + This will modify the pressure dimension of the note currently held down + on this channel (if any). If the channel is a zone master channel, + the pressure change will be broadcast to all notes in this zone. + */ + virtual void pressure (int midiChannel, MPEValue value); + + /** Request a third dimension (timbre) change on the given channel with the + given value. + + This will modify the timbre dimension of the note currently held down + on this channel (if any). If the channel is a zone master channel, + the timbre change will be broadcast to all notes in this zone. + */ + virtual void timbre (int midiChannel, MPEValue value); + + /** Request a poly-aftertouch change for a given note number. + + The change will be broadcast to all notes sharing the channel and note + number of the change message. + */ + virtual void polyAftertouch (int midiChannel, int midiNoteNumber, MPEValue value); + + /** Request a sustain pedal press or release. + + If midiChannel is a zone's master channel, this will act on all notes in + that zone; otherwise, nothing will happen. + */ + virtual void sustainPedal (int midiChannel, bool isDown); + + /** Request a sostenuto pedal press or release. + + If midiChannel is a zone's master channel, this will act on all notes in + that zone; otherwise, nothing will happen. + */ + virtual void sostenutoPedal (int midiChannel, bool isDown); + + /** Discard all currently playing notes. + + This will also call the noteReleased listener callback for all of them. + */ + void releaseAllNotes(); + + //============================================================================== + /** Returns the number of MPE notes currently played by the instrument. */ + int getNumPlayingNotes() const noexcept; + + /** Returns the note at the given index. + + If there is no such note, returns an invalid MPENote. The notes are sorted + such that the most recently added note is the last element. + */ + MPENote getNote (int index) const noexcept; + + /** Returns the note currently playing on the given midiChannel with the + specified initial MIDI note number, if there is such a note. Otherwise, + this returns an invalid MPENote (check with note.isValid() before use!) + */ + MPENote getNote (int midiChannel, int midiNoteNumber) const noexcept; + + /** Returns the most recent note that is playing on the given midiChannel + (this will be the note which has received the most recent note-on without + a corresponding note-off), if there is such a note. Otherwise, this returns an + invalid MPENote (check with note.isValid() before use!) + */ + MPENote getMostRecentNote (int midiChannel) const noexcept; + + /** Returns the most recent note that is not the note passed in. If there is no + such note, this returns an invalid MPENote (check with note.isValid() before use!). + + This helper method might be useful for some custom voice handling algorithms. + */ + MPENote getMostRecentNoteOtherThan (MPENote otherThanThisNote) const noexcept; + + //============================================================================== + /** Derive from this class to be informed about any changes in the expressive + MIDI notes played by this instrument. + + Note: This listener type receives its callbacks immediately, and not + via the message thread (so you might be for example in the MIDI thread). + Therefore you should never do heavy work such as graphics rendering etc. + inside those callbacks. + */ + class JUCE_API Listener + { + public: + /** Destructor. */ + virtual ~Listener() = default; + + /** Implement this callback to be informed whenever a new expressive MIDI + note is triggered. + */ + virtual void noteAdded (MPENote newNote) { ignoreUnused (newNote); } + + /** Implement this callback to be informed whenever a currently playing + MPE note's pressure value changes. + */ + virtual void notePressureChanged (MPENote changedNote) { ignoreUnused (changedNote); } + + /** Implement this callback to be informed whenever a currently playing + MPE note's pitchbend value changes. + + Note: This can happen if the note itself is bent, if there is a + master channel pitchbend event, or if both occur simultaneously. + Call MPENote::getFrequencyInHertz to get the effective note frequency. + */ + virtual void notePitchbendChanged (MPENote changedNote) { ignoreUnused (changedNote); } + + /** Implement this callback to be informed whenever a currently playing + MPE note's timbre value changes. + */ + virtual void noteTimbreChanged (MPENote changedNote) { ignoreUnused (changedNote); } + + /** Implement this callback to be informed whether a currently playing + MPE note's key state (whether the key is down and/or the note is + sustained) has changed. + + Note: If the key state changes to MPENote::off, noteReleased is + called instead. + */ + virtual void noteKeyStateChanged (MPENote changedNote) { ignoreUnused (changedNote); } + + /** Implement this callback to be informed whenever an MPE note + is released (either by a note-off message, or by a sustain/sostenuto + pedal release for a note that already received a note-off), + and should therefore stop playing. + */ + virtual void noteReleased (MPENote finishedNote) { ignoreUnused (finishedNote); } + }; + + //============================================================================== + /** Adds a listener. */ + void addListener (Listener* listenerToAdd); + + /** Removes a listener. */ + void removeListener (Listener* listenerToRemove); + + //============================================================================== + /** Puts the instrument into legacy mode. + As a side effect, this will discard all currently playing notes, + and call noteReleased for all of them. + + This special zone layout mode is for backwards compatibility with + non-MPE MIDI devices. In this mode, the instrument will ignore the + current MPE zone layout. It will instead take a range of MIDI channels + (default: all channels 1-16) and treat them as note channels, with no + master channel. MIDI channels outside of this range will be ignored. + + @param pitchbendRange The note pitchbend range in semitones to use when in legacy mode. + Must be between 0 and 96, otherwise behaviour is undefined. + The default pitchbend range in legacy mode is +/- 2 semitones. + + @param channelRange The range of MIDI channels to use for notes when in legacy mode. + The default is to use all MIDI channels (1-16). + + To get out of legacy mode, set a new MPE zone layout using setZoneLayout. + */ + void enableLegacyMode (int pitchbendRange = 2, + Range channelRange = Range (1, 17)); + + /** Returns true if the instrument is in legacy mode, false otherwise. */ + bool isLegacyModeEnabled() const noexcept; + + /** Returns the range of MIDI channels (1-16) to be used for notes when in legacy mode. */ + Range getLegacyModeChannelRange() const noexcept; + + /** Re-sets the range of MIDI channels (1-16) to be used for notes when in legacy mode. */ + void setLegacyModeChannelRange (Range channelRange); + + /** Returns the pitchbend range in semitones (0-96) to be used for notes when in legacy mode. */ + int getLegacyModePitchbendRange() const noexcept; + + /** Re-sets the pitchbend range in semitones (0-96) to be used for notes when in legacy mode. */ + void setLegacyModePitchbendRange (int pitchbendRange); + +protected: + //============================================================================== + CriticalSection lock; + +private: + //============================================================================== + Array notes; + MPEZoneLayout zoneLayout; + ListenerList listeners; + + uint8 lastPressureLowerBitReceivedOnChannel[16]; + uint8 lastTimbreLowerBitReceivedOnChannel[16]; + bool isMemberChannelSustained[16]; + + struct LegacyMode + { + bool isEnabled; + Range channelRange; + int pitchbendRange; + }; + + struct MPEDimension + { + TrackingMode trackingMode = lastNotePlayedOnChannel; + MPEValue lastValueReceivedOnChannel[16]; + MPEValue MPENote::* value; + MPEValue& getValue (MPENote& note) noexcept { return note.*(value); } + }; + + LegacyMode legacyMode; + MPEDimension pitchbendDimension, pressureDimension, timbreDimension; + + void updateDimension (int midiChannel, MPEDimension&, MPEValue); + void updateDimensionMaster (bool, MPEDimension&, MPEValue); + void updateDimensionForNote (MPENote&, MPEDimension&, MPEValue); + void callListenersDimensionChanged (const MPENote&, const MPEDimension&); + MPEValue getInitialValueForNewNote (int midiChannel, MPEDimension&) const; + + void processMidiNoteOnMessage (const MidiMessage&); + void processMidiNoteOffMessage (const MidiMessage&); + void processMidiPitchWheelMessage (const MidiMessage&); + void processMidiChannelPressureMessage (const MidiMessage&); + void processMidiControllerMessage (const MidiMessage&); + void processMidiResetAllControllersMessage (const MidiMessage&); + void processMidiAfterTouchMessage (const MidiMessage&); + void handlePressureMSB (int midiChannel, int value) noexcept; + void handlePressureLSB (int midiChannel, int value) noexcept; + void handleTimbreMSB (int midiChannel, int value) noexcept; + void handleTimbreLSB (int midiChannel, int value) noexcept; + void handleSustainOrSostenuto (int midiChannel, bool isDown, bool isSostenuto); + + const MPENote* getNotePtr (int midiChannel, int midiNoteNumber) const noexcept; + MPENote* getNotePtr (int midiChannel, int midiNoteNumber) noexcept; + const MPENote* getNotePtr (int midiChannel, TrackingMode) const noexcept; + MPENote* getNotePtr (int midiChannel, TrackingMode) noexcept; + const MPENote* getLastNotePlayedPtr (int midiChannel) const noexcept; + MPENote* getLastNotePlayedPtr (int midiChannel) noexcept; + const MPENote* getHighestNotePtr (int midiChannel) const noexcept; + MPENote* getHighestNotePtr (int midiChannel) noexcept; + const MPENote* getLowestNotePtr (int midiChannel) const noexcept; + MPENote* getLowestNotePtr (int midiChannel) noexcept; + void updateNoteTotalPitchbend (MPENote&); + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MPEInstrument) +}; + +} // namespace juce diff --git a/JuceLibraryCode/modules/juce_audio_basics/mpe/juce_MPEMessages.cpp b/JuceLibraryCode/modules/juce_audio_basics/mpe/juce_MPEMessages.cpp index 214df7c..f233f79 100644 --- a/JuceLibraryCode/modules/juce_audio_basics/mpe/juce_MPEMessages.cpp +++ b/JuceLibraryCode/modules/juce_audio_basics/mpe/juce_MPEMessages.cpp @@ -1,241 +1,241 @@ -/* - ============================================================================== - - This file is part of the JUCE library. - Copyright (c) 2017 - ROLI Ltd. - - JUCE is an open source library subject to commercial or open-source - licensing. - - The code included in this file is provided under the terms of the ISC license - http://www.isc.org/downloads/software-support-policy/isc-license. Permission - To use, copy, modify, and/or distribute this software for any purpose with or - without fee is hereby granted provided that the above copyright notice and - this permission notice appear in all copies. - - JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER - EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE - DISCLAIMED. - - ============================================================================== -*/ - -namespace juce -{ - -MidiBuffer MPEMessages::setLowerZone (int numMemberChannels, int perNotePitchbendRange, int masterPitchbendRange) -{ - auto buffer = MidiRPNGenerator::generate (1, zoneLayoutMessagesRpnNumber, numMemberChannels, false, false); - - buffer.addEvents (setLowerZonePerNotePitchbendRange (perNotePitchbendRange), 0, -1, 0); - buffer.addEvents (setLowerZoneMasterPitchbendRange (masterPitchbendRange), 0, -1, 0); - - return buffer; -} - -MidiBuffer MPEMessages::setUpperZone (int numMemberChannels, int perNotePitchbendRange, int masterPitchbendRange) -{ - auto buffer = MidiRPNGenerator::generate (16, zoneLayoutMessagesRpnNumber, numMemberChannels, false, false); - - buffer.addEvents (setUpperZonePerNotePitchbendRange (perNotePitchbendRange), 0, -1, 0); - buffer.addEvents (setUpperZoneMasterPitchbendRange (masterPitchbendRange), 0, -1, 0); - - return buffer; -} - -MidiBuffer MPEMessages::setLowerZonePerNotePitchbendRange (int perNotePitchbendRange) -{ - return MidiRPNGenerator::generate (2, 0, perNotePitchbendRange, false, false); -} - -MidiBuffer MPEMessages::setUpperZonePerNotePitchbendRange (int perNotePitchbendRange) -{ - return MidiRPNGenerator::generate (15, 0, perNotePitchbendRange, false, false); -} - -MidiBuffer MPEMessages::setLowerZoneMasterPitchbendRange (int masterPitchbendRange) -{ - return MidiRPNGenerator::generate (1, 0, masterPitchbendRange, false, false); -} - -MidiBuffer MPEMessages::setUpperZoneMasterPitchbendRange (int masterPitchbendRange) -{ - return MidiRPNGenerator::generate (16, 0, masterPitchbendRange, false, false); -} - -MidiBuffer MPEMessages::clearLowerZone() -{ - return MidiRPNGenerator::generate (1, zoneLayoutMessagesRpnNumber, 0, false, false); -} - -MidiBuffer MPEMessages::clearUpperZone() -{ - return MidiRPNGenerator::generate (16, zoneLayoutMessagesRpnNumber, 0, false, false); -} - -MidiBuffer MPEMessages::clearAllZones() -{ - MidiBuffer buffer; - - buffer.addEvents (clearLowerZone(), 0, -1, 0); - buffer.addEvents (clearUpperZone(), 0, -1, 0); - - return buffer; -} - -MidiBuffer MPEMessages::setZoneLayout (MPEZoneLayout layout) -{ - MidiBuffer buffer; - - buffer.addEvents (clearAllZones(), 0, -1, 0); - - auto lowerZone = layout.getLowerZone(); - if (lowerZone.isActive()) - buffer.addEvents (setLowerZone (lowerZone.numMemberChannels, - lowerZone.perNotePitchbendRange, - lowerZone.masterPitchbendRange), - 0, -1, 0); - - auto upperZone = layout.getUpperZone(); - if (upperZone.isActive()) - buffer.addEvents (setUpperZone (upperZone.numMemberChannels, - upperZone.perNotePitchbendRange, - upperZone.masterPitchbendRange), - 0, -1, 0); - - return buffer; -} - - -//============================================================================== -//============================================================================== -#if JUCE_UNIT_TESTS - -class MPEMessagesTests : public UnitTest -{ -public: - MPEMessagesTests() - : UnitTest ("MPEMessages class", UnitTestCategories::midi) - {} - - void runTest() override - { - beginTest ("add zone"); - { - { - MidiBuffer buffer = MPEMessages::setLowerZone (7); - - const uint8 expectedBytes[] = - { - 0xb0, 0x64, 0x06, 0xb0, 0x65, 0x00, 0xb0, 0x06, 0x07, // set up zone - 0xb1, 0x64, 0x00, 0xb1, 0x65, 0x00, 0xb1, 0x06, 0x30, // per-note pbrange (default = 48) - 0xb0, 0x64, 0x00, 0xb0, 0x65, 0x00, 0xb0, 0x06, 0x02 // master pbrange (default = 2) - }; - - testMidiBuffer (buffer, expectedBytes, sizeof (expectedBytes)); - } - { - MidiBuffer buffer = MPEMessages::setUpperZone (5, 96, 0); - - const uint8 expectedBytes[] = - { - 0xbf, 0x64, 0x06, 0xbf, 0x65, 0x00, 0xbf, 0x06, 0x05, // set up zone - 0xbe, 0x64, 0x00, 0xbe, 0x65, 0x00, 0xbe, 0x06, 0x60, // per-note pbrange (custom) - 0xbf, 0x64, 0x00, 0xbf, 0x65, 0x00, 0xbf, 0x06, 0x00 // master pbrange (custom) - }; - - testMidiBuffer (buffer, expectedBytes, sizeof (expectedBytes)); - } - } - - beginTest ("set per-note pitchbend range"); - { - MidiBuffer buffer = MPEMessages::setLowerZonePerNotePitchbendRange (96); - - const uint8 expectedBytes[] = { 0xb1, 0x64, 0x00, 0xb1, 0x65, 0x00, 0xb1, 0x06, 0x60 }; - - testMidiBuffer (buffer, expectedBytes, sizeof (expectedBytes)); - } - - - beginTest ("set master pitchbend range"); - { - MidiBuffer buffer = MPEMessages::setUpperZoneMasterPitchbendRange (60); - - const uint8 expectedBytes[] = { 0xbf, 0x64, 0x00, 0xbf, 0x65, 0x00, 0xbf, 0x06, 0x3c }; - - testMidiBuffer (buffer, expectedBytes, sizeof (expectedBytes)); - } - - beginTest ("clear all zones"); - { - MidiBuffer buffer = MPEMessages::clearAllZones(); - - const uint8 expectedBytes[] = { 0xb0, 0x64, 0x06, 0xb0, 0x65, 0x00, 0xb0, 0x06, 0x00, // clear lower zone - 0xbf, 0x64, 0x06, 0xbf, 0x65, 0x00, 0xbf, 0x06, 0x00 // clear upper zone - }; - - testMidiBuffer (buffer, expectedBytes, sizeof (expectedBytes)); - } - - beginTest ("set complete state"); - { - MPEZoneLayout layout; - - layout.setLowerZone (7, 96, 0); - layout.setUpperZone (7); - - MidiBuffer buffer = MPEMessages::setZoneLayout (layout); - - const uint8 expectedBytes[] = { - 0xb0, 0x64, 0x06, 0xb0, 0x65, 0x00, 0xb0, 0x06, 0x00, // clear lower zone - 0xbf, 0x64, 0x06, 0xbf, 0x65, 0x00, 0xbf, 0x06, 0x00, // clear upper zone - 0xb0, 0x64, 0x06, 0xb0, 0x65, 0x00, 0xb0, 0x06, 0x07, // set lower zone - 0xb1, 0x64, 0x00, 0xb1, 0x65, 0x00, 0xb1, 0x06, 0x60, // per-note pbrange (custom) - 0xb0, 0x64, 0x00, 0xb0, 0x65, 0x00, 0xb0, 0x06, 0x00, // master pbrange (custom) - 0xbf, 0x64, 0x06, 0xbf, 0x65, 0x00, 0xbf, 0x06, 0x07, // set upper zone - 0xbe, 0x64, 0x00, 0xbe, 0x65, 0x00, 0xbe, 0x06, 0x30, // per-note pbrange (default = 48) - 0xbf, 0x64, 0x00, 0xbf, 0x65, 0x00, 0xbf, 0x06, 0x02 // master pbrange (default = 2) - }; - - testMidiBuffer (buffer, expectedBytes, sizeof (expectedBytes)); - } - } - -private: - //============================================================================== - void testMidiBuffer (MidiBuffer& buffer, const uint8* expectedBytes, int expectedBytesSize) - { - uint8 actualBytes[128] = { 0 }; - extractRawBinaryData (buffer, actualBytes, sizeof (actualBytes)); - - expectEquals (std::memcmp (actualBytes, expectedBytes, (std::size_t) expectedBytesSize), 0); - } - - //============================================================================== - void extractRawBinaryData (const MidiBuffer& midiBuffer, const uint8* bufferToCopyTo, std::size_t maxBytes) - { - std::size_t pos = 0; - MidiBuffer::Iterator iter (midiBuffer); - MidiMessage midiMessage; - int samplePosition; // Note: Not actually used, so no need to initialise. - - while (iter.getNextEvent (midiMessage, samplePosition)) - { - const uint8* data = midiMessage.getRawData(); - std::size_t dataSize = (std::size_t) midiMessage.getRawDataSize(); - - if (pos + dataSize > maxBytes) - return; - - std::memcpy ((void*) (bufferToCopyTo + pos), data, dataSize); - pos += dataSize; - } - } -}; - -static MPEMessagesTests MPEMessagesUnitTests; - -#endif - -} // namespace juce +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2017 - ROLI Ltd. + + JUCE is an open source library subject to commercial or open-source + licensing. + + The code included in this file is provided under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license. Permission + To use, copy, modify, and/or distribute this software for any purpose with or + without fee is hereby granted provided that the above copyright notice and + this permission notice appear in all copies. + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +namespace juce +{ + +MidiBuffer MPEMessages::setLowerZone (int numMemberChannels, int perNotePitchbendRange, int masterPitchbendRange) +{ + auto buffer = MidiRPNGenerator::generate (1, zoneLayoutMessagesRpnNumber, numMemberChannels, false, false); + + buffer.addEvents (setLowerZonePerNotePitchbendRange (perNotePitchbendRange), 0, -1, 0); + buffer.addEvents (setLowerZoneMasterPitchbendRange (masterPitchbendRange), 0, -1, 0); + + return buffer; +} + +MidiBuffer MPEMessages::setUpperZone (int numMemberChannels, int perNotePitchbendRange, int masterPitchbendRange) +{ + auto buffer = MidiRPNGenerator::generate (16, zoneLayoutMessagesRpnNumber, numMemberChannels, false, false); + + buffer.addEvents (setUpperZonePerNotePitchbendRange (perNotePitchbendRange), 0, -1, 0); + buffer.addEvents (setUpperZoneMasterPitchbendRange (masterPitchbendRange), 0, -1, 0); + + return buffer; +} + +MidiBuffer MPEMessages::setLowerZonePerNotePitchbendRange (int perNotePitchbendRange) +{ + return MidiRPNGenerator::generate (2, 0, perNotePitchbendRange, false, false); +} + +MidiBuffer MPEMessages::setUpperZonePerNotePitchbendRange (int perNotePitchbendRange) +{ + return MidiRPNGenerator::generate (15, 0, perNotePitchbendRange, false, false); +} + +MidiBuffer MPEMessages::setLowerZoneMasterPitchbendRange (int masterPitchbendRange) +{ + return MidiRPNGenerator::generate (1, 0, masterPitchbendRange, false, false); +} + +MidiBuffer MPEMessages::setUpperZoneMasterPitchbendRange (int masterPitchbendRange) +{ + return MidiRPNGenerator::generate (16, 0, masterPitchbendRange, false, false); +} + +MidiBuffer MPEMessages::clearLowerZone() +{ + return MidiRPNGenerator::generate (1, zoneLayoutMessagesRpnNumber, 0, false, false); +} + +MidiBuffer MPEMessages::clearUpperZone() +{ + return MidiRPNGenerator::generate (16, zoneLayoutMessagesRpnNumber, 0, false, false); +} + +MidiBuffer MPEMessages::clearAllZones() +{ + MidiBuffer buffer; + + buffer.addEvents (clearLowerZone(), 0, -1, 0); + buffer.addEvents (clearUpperZone(), 0, -1, 0); + + return buffer; +} + +MidiBuffer MPEMessages::setZoneLayout (MPEZoneLayout layout) +{ + MidiBuffer buffer; + + buffer.addEvents (clearAllZones(), 0, -1, 0); + + auto lowerZone = layout.getLowerZone(); + if (lowerZone.isActive()) + buffer.addEvents (setLowerZone (lowerZone.numMemberChannels, + lowerZone.perNotePitchbendRange, + lowerZone.masterPitchbendRange), + 0, -1, 0); + + auto upperZone = layout.getUpperZone(); + if (upperZone.isActive()) + buffer.addEvents (setUpperZone (upperZone.numMemberChannels, + upperZone.perNotePitchbendRange, + upperZone.masterPitchbendRange), + 0, -1, 0); + + return buffer; +} + + +//============================================================================== +//============================================================================== +#if JUCE_UNIT_TESTS + +class MPEMessagesTests : public UnitTest +{ +public: + MPEMessagesTests() + : UnitTest ("MPEMessages class", UnitTestCategories::midi) + {} + + void runTest() override + { + beginTest ("add zone"); + { + { + MidiBuffer buffer = MPEMessages::setLowerZone (7); + + const uint8 expectedBytes[] = + { + 0xb0, 0x64, 0x06, 0xb0, 0x65, 0x00, 0xb0, 0x06, 0x07, // set up zone + 0xb1, 0x64, 0x00, 0xb1, 0x65, 0x00, 0xb1, 0x06, 0x30, // per-note pbrange (default = 48) + 0xb0, 0x64, 0x00, 0xb0, 0x65, 0x00, 0xb0, 0x06, 0x02 // master pbrange (default = 2) + }; + + testMidiBuffer (buffer, expectedBytes, sizeof (expectedBytes)); + } + { + MidiBuffer buffer = MPEMessages::setUpperZone (5, 96, 0); + + const uint8 expectedBytes[] = + { + 0xbf, 0x64, 0x06, 0xbf, 0x65, 0x00, 0xbf, 0x06, 0x05, // set up zone + 0xbe, 0x64, 0x00, 0xbe, 0x65, 0x00, 0xbe, 0x06, 0x60, // per-note pbrange (custom) + 0xbf, 0x64, 0x00, 0xbf, 0x65, 0x00, 0xbf, 0x06, 0x00 // master pbrange (custom) + }; + + testMidiBuffer (buffer, expectedBytes, sizeof (expectedBytes)); + } + } + + beginTest ("set per-note pitchbend range"); + { + MidiBuffer buffer = MPEMessages::setLowerZonePerNotePitchbendRange (96); + + const uint8 expectedBytes[] = { 0xb1, 0x64, 0x00, 0xb1, 0x65, 0x00, 0xb1, 0x06, 0x60 }; + + testMidiBuffer (buffer, expectedBytes, sizeof (expectedBytes)); + } + + + beginTest ("set master pitchbend range"); + { + MidiBuffer buffer = MPEMessages::setUpperZoneMasterPitchbendRange (60); + + const uint8 expectedBytes[] = { 0xbf, 0x64, 0x00, 0xbf, 0x65, 0x00, 0xbf, 0x06, 0x3c }; + + testMidiBuffer (buffer, expectedBytes, sizeof (expectedBytes)); + } + + beginTest ("clear all zones"); + { + MidiBuffer buffer = MPEMessages::clearAllZones(); + + const uint8 expectedBytes[] = { 0xb0, 0x64, 0x06, 0xb0, 0x65, 0x00, 0xb0, 0x06, 0x00, // clear lower zone + 0xbf, 0x64, 0x06, 0xbf, 0x65, 0x00, 0xbf, 0x06, 0x00 // clear upper zone + }; + + testMidiBuffer (buffer, expectedBytes, sizeof (expectedBytes)); + } + + beginTest ("set complete state"); + { + MPEZoneLayout layout; + + layout.setLowerZone (7, 96, 0); + layout.setUpperZone (7); + + MidiBuffer buffer = MPEMessages::setZoneLayout (layout); + + const uint8 expectedBytes[] = { + 0xb0, 0x64, 0x06, 0xb0, 0x65, 0x00, 0xb0, 0x06, 0x00, // clear lower zone + 0xbf, 0x64, 0x06, 0xbf, 0x65, 0x00, 0xbf, 0x06, 0x00, // clear upper zone + 0xb0, 0x64, 0x06, 0xb0, 0x65, 0x00, 0xb0, 0x06, 0x07, // set lower zone + 0xb1, 0x64, 0x00, 0xb1, 0x65, 0x00, 0xb1, 0x06, 0x60, // per-note pbrange (custom) + 0xb0, 0x64, 0x00, 0xb0, 0x65, 0x00, 0xb0, 0x06, 0x00, // master pbrange (custom) + 0xbf, 0x64, 0x06, 0xbf, 0x65, 0x00, 0xbf, 0x06, 0x07, // set upper zone + 0xbe, 0x64, 0x00, 0xbe, 0x65, 0x00, 0xbe, 0x06, 0x30, // per-note pbrange (default = 48) + 0xbf, 0x64, 0x00, 0xbf, 0x65, 0x00, 0xbf, 0x06, 0x02 // master pbrange (default = 2) + }; + + testMidiBuffer (buffer, expectedBytes, sizeof (expectedBytes)); + } + } + +private: + //============================================================================== + void testMidiBuffer (MidiBuffer& buffer, const uint8* expectedBytes, int expectedBytesSize) + { + uint8 actualBytes[128] = { 0 }; + extractRawBinaryData (buffer, actualBytes, sizeof (actualBytes)); + + expectEquals (std::memcmp (actualBytes, expectedBytes, (std::size_t) expectedBytesSize), 0); + } + + //============================================================================== + void extractRawBinaryData (const MidiBuffer& midiBuffer, const uint8* bufferToCopyTo, std::size_t maxBytes) + { + std::size_t pos = 0; + MidiBuffer::Iterator iter (midiBuffer); + MidiMessage midiMessage; + int samplePosition; // Note: Not actually used, so no need to initialise. + + while (iter.getNextEvent (midiMessage, samplePosition)) + { + const uint8* data = midiMessage.getRawData(); + std::size_t dataSize = (std::size_t) midiMessage.getRawDataSize(); + + if (pos + dataSize > maxBytes) + return; + + std::memcpy ((void*) (bufferToCopyTo + pos), data, dataSize); + pos += dataSize; + } + } +}; + +static MPEMessagesTests MPEMessagesUnitTests; + +#endif + +} // namespace juce diff --git a/JuceLibraryCode/modules/juce_audio_basics/mpe/juce_MPEMessages.h b/JuceLibraryCode/modules/juce_audio_basics/mpe/juce_MPEMessages.h index 1275dac..96357b6 100644 --- a/JuceLibraryCode/modules/juce_audio_basics/mpe/juce_MPEMessages.h +++ b/JuceLibraryCode/modules/juce_audio_basics/mpe/juce_MPEMessages.h @@ -1,116 +1,116 @@ -/* - ============================================================================== - - This file is part of the JUCE library. - Copyright (c) 2017 - ROLI Ltd. - - JUCE is an open source library subject to commercial or open-source - licensing. - - The code included in this file is provided under the terms of the ISC license - http://www.isc.org/downloads/software-support-policy/isc-license. Permission - To use, copy, modify, and/or distribute this software for any purpose with or - without fee is hereby granted provided that the above copyright notice and - this permission notice appear in all copies. - - JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER - EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE - DISCLAIMED. - - ============================================================================== -*/ - -namespace juce -{ - -//============================================================================== -/** - This helper class contains the necessary helper functions to generate - MIDI messages that are exclusive to MPE, such as defining the upper and lower - MPE zones and setting per-note and master pitchbend ranges. - You can then send them to your MPE device using MidiOutput::sendBlockOfMessagesNow. - - All other MPE messages like per-note pitchbend, pressure, and third - dimension, are ordinary MIDI messages that should be created using the MidiMessage - class instead. You just need to take care to send them to the appropriate - per-note MIDI channel. - - Note: If you are working with an MPEZoneLayout object inside your app, - you should not use the message sequences provided here. Instead, you should - change the zone layout programmatically with the member functions provided in the - MPEZoneLayout class itself. You should also make sure that the Expressive - MIDI zone layout of your C++ code and of the MPE device are kept in sync. - - @see MidiMessage, MPEZoneLayout - - @tags{Audio} -*/ -class JUCE_API MPEMessages -{ -public: - /** Returns the sequence of MIDI messages that, if sent to an Expressive - MIDI device, will set the lower MPE zone. - */ - static MidiBuffer setLowerZone (int numMemberChannels = 0, - int perNotePitchbendRange = 48, - int masterPitchbendRange = 2); - - /** Returns the sequence of MIDI messages that, if sent to an Expressive - MIDI device, will set the upper MPE zone. - */ - static MidiBuffer setUpperZone (int numMemberChannels = 0, - int perNotePitchbendRange = 48, - int masterPitchbendRange = 2); - - /** Returns the sequence of MIDI messages that, if sent to an Expressive - MIDI device, will set the per-note pitchbend range of the lower MPE zone. - */ - static MidiBuffer setLowerZonePerNotePitchbendRange (int perNotePitchbendRange = 48); - - /** Returns the sequence of MIDI messages that, if sent to an Expressive - MIDI device, will set the per-note pitchbend range of the upper MPE zone. - */ - static MidiBuffer setUpperZonePerNotePitchbendRange (int perNotePitchbendRange = 48); - - /** Returns the sequence of MIDI messages that, if sent to an Expressive - MIDI device, will set the master pitchbend range of the lower MPE zone. - */ - static MidiBuffer setLowerZoneMasterPitchbendRange (int masterPitchbendRange = 2); - - /** Returns the sequence of MIDI messages that, if sent to an Expressive - MIDI device, will set the master pitchbend range of the upper MPE zone. - */ - static MidiBuffer setUpperZoneMasterPitchbendRange (int masterPitchbendRange = 2); - - /** Returns the sequence of MIDI messages that, if sent to an Expressive - MIDI device, will clear the lower zone. - */ - static MidiBuffer clearLowerZone(); - - /** Returns the sequence of MIDI messages that, if sent to an Expressive - MIDI device, will clear the upper zone. - */ - static MidiBuffer clearUpperZone(); - - /** Returns the sequence of MIDI messages that, if sent to an Expressive - MIDI device, will clear the lower and upper zones. - */ - static MidiBuffer clearAllZones(); - - /** Returns the sequence of MIDI messages that, if sent to an Expressive - MIDI device, will reset the whole MPE zone layout of the - device to the layout passed in. This will first clear the current lower and upper - zones, then then set the zones contained in the passed-in zone layout, and set their - per-note and master pitchbend ranges to their current values. - */ - static MidiBuffer setZoneLayout (MPEZoneLayout layout); - - /** The RPN number used for MPE zone layout messages. - - Pitchbend range messages (both per-note and master) are instead sent - on RPN 0 as in standard MIDI 1.0. - */ - static const int zoneLayoutMessagesRpnNumber = 6; -}; - -} // namespace juce +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2017 - ROLI Ltd. + + JUCE is an open source library subject to commercial or open-source + licensing. + + The code included in this file is provided under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license. Permission + To use, copy, modify, and/or distribute this software for any purpose with or + without fee is hereby granted provided that the above copyright notice and + this permission notice appear in all copies. + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +namespace juce +{ + +//============================================================================== +/** + This helper class contains the necessary helper functions to generate + MIDI messages that are exclusive to MPE, such as defining the upper and lower + MPE zones and setting per-note and master pitchbend ranges. + You can then send them to your MPE device using MidiOutput::sendBlockOfMessagesNow. + + All other MPE messages like per-note pitchbend, pressure, and third + dimension, are ordinary MIDI messages that should be created using the MidiMessage + class instead. You just need to take care to send them to the appropriate + per-note MIDI channel. + + Note: If you are working with an MPEZoneLayout object inside your app, + you should not use the message sequences provided here. Instead, you should + change the zone layout programmatically with the member functions provided in the + MPEZoneLayout class itself. You should also make sure that the Expressive + MIDI zone layout of your C++ code and of the MPE device are kept in sync. + + @see MidiMessage, MPEZoneLayout + + @tags{Audio} +*/ +class JUCE_API MPEMessages +{ +public: + /** Returns the sequence of MIDI messages that, if sent to an Expressive + MIDI device, will set the lower MPE zone. + */ + static MidiBuffer setLowerZone (int numMemberChannels = 0, + int perNotePitchbendRange = 48, + int masterPitchbendRange = 2); + + /** Returns the sequence of MIDI messages that, if sent to an Expressive + MIDI device, will set the upper MPE zone. + */ + static MidiBuffer setUpperZone (int numMemberChannels = 0, + int perNotePitchbendRange = 48, + int masterPitchbendRange = 2); + + /** Returns the sequence of MIDI messages that, if sent to an Expressive + MIDI device, will set the per-note pitchbend range of the lower MPE zone. + */ + static MidiBuffer setLowerZonePerNotePitchbendRange (int perNotePitchbendRange = 48); + + /** Returns the sequence of MIDI messages that, if sent to an Expressive + MIDI device, will set the per-note pitchbend range of the upper MPE zone. + */ + static MidiBuffer setUpperZonePerNotePitchbendRange (int perNotePitchbendRange = 48); + + /** Returns the sequence of MIDI messages that, if sent to an Expressive + MIDI device, will set the master pitchbend range of the lower MPE zone. + */ + static MidiBuffer setLowerZoneMasterPitchbendRange (int masterPitchbendRange = 2); + + /** Returns the sequence of MIDI messages that, if sent to an Expressive + MIDI device, will set the master pitchbend range of the upper MPE zone. + */ + static MidiBuffer setUpperZoneMasterPitchbendRange (int masterPitchbendRange = 2); + + /** Returns the sequence of MIDI messages that, if sent to an Expressive + MIDI device, will clear the lower zone. + */ + static MidiBuffer clearLowerZone(); + + /** Returns the sequence of MIDI messages that, if sent to an Expressive + MIDI device, will clear the upper zone. + */ + static MidiBuffer clearUpperZone(); + + /** Returns the sequence of MIDI messages that, if sent to an Expressive + MIDI device, will clear the lower and upper zones. + */ + static MidiBuffer clearAllZones(); + + /** Returns the sequence of MIDI messages that, if sent to an Expressive + MIDI device, will reset the whole MPE zone layout of the + device to the layout passed in. This will first clear the current lower and upper + zones, then then set the zones contained in the passed-in zone layout, and set their + per-note and master pitchbend ranges to their current values. + */ + static MidiBuffer setZoneLayout (MPEZoneLayout layout); + + /** The RPN number used for MPE zone layout messages. + + Pitchbend range messages (both per-note and master) are instead sent + on RPN 0 as in standard MIDI 1.0. + */ + static const int zoneLayoutMessagesRpnNumber = 6; +}; + +} // namespace juce diff --git a/JuceLibraryCode/modules/juce_audio_basics/mpe/juce_MPENote.cpp b/JuceLibraryCode/modules/juce_audio_basics/mpe/juce_MPENote.cpp index 742c778..792a3db 100644 --- a/JuceLibraryCode/modules/juce_audio_basics/mpe/juce_MPENote.cpp +++ b/JuceLibraryCode/modules/juce_audio_basics/mpe/juce_MPENote.cpp @@ -1,127 +1,127 @@ -/* - ============================================================================== - - This file is part of the JUCE library. - Copyright (c) 2017 - ROLI Ltd. - - JUCE is an open source library subject to commercial or open-source - licensing. - - The code included in this file is provided under the terms of the ISC license - http://www.isc.org/downloads/software-support-policy/isc-license. Permission - To use, copy, modify, and/or distribute this software for any purpose with or - without fee is hereby granted provided that the above copyright notice and - this permission notice appear in all copies. - - JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER - EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE - DISCLAIMED. - - ============================================================================== -*/ - -namespace juce -{ - -namespace -{ - uint16 generateNoteID (int midiChannel, int midiNoteNumber) noexcept - { - jassert (midiChannel > 0 && midiChannel <= 16); - jassert (midiNoteNumber >= 0 && midiNoteNumber < 128); - - return uint16 ((midiChannel << 7) + midiNoteNumber); - } -} - -//============================================================================== -MPENote::MPENote (int midiChannel_, - int initialNote_, - MPEValue noteOnVelocity_, - MPEValue pitchbend_, - MPEValue pressure_, - MPEValue timbre_, - KeyState keyState_) noexcept - : noteID (generateNoteID (midiChannel_, initialNote_)), - midiChannel (uint8 (midiChannel_)), - initialNote (uint8 (initialNote_)), - noteOnVelocity (noteOnVelocity_), - pitchbend (pitchbend_), - pressure (pressure_), - initialTimbre (timbre_), - timbre (timbre_), - keyState (keyState_) -{ - jassert (keyState != MPENote::off); - jassert (isValid()); -} - -MPENote::MPENote() noexcept {} - -//============================================================================== -bool MPENote::isValid() const noexcept -{ - return midiChannel > 0 && midiChannel <= 16 && initialNote < 128; -} - -//============================================================================== -double MPENote::getFrequencyInHertz (double frequencyOfA) const noexcept -{ - auto pitchInSemitones = double (initialNote) + totalPitchbendInSemitones; - return frequencyOfA * std::pow (2.0, (pitchInSemitones - 69.0) / 12.0); -} - -//============================================================================== -bool MPENote::operator== (const MPENote& other) const noexcept -{ - jassert (isValid() && other.isValid()); - return noteID == other.noteID; -} - -bool MPENote::operator!= (const MPENote& other) const noexcept -{ - jassert (isValid() && other.isValid()); - return noteID != other.noteID; -} - - -//============================================================================== -//============================================================================== -#if JUCE_UNIT_TESTS - -class MPENoteTests : public UnitTest -{ -public: - MPENoteTests() - : UnitTest ("MPENote class", UnitTestCategories::midi) - {} - - //============================================================================== - void runTest() override - { - beginTest ("getFrequencyInHertz"); - { - MPENote note; - note.initialNote = 60; - note.totalPitchbendInSemitones = -0.5; - expectEqualsWithinOneCent (note.getFrequencyInHertz(), 254.178); - } - } - -private: - //============================================================================== - void expectEqualsWithinOneCent (double frequencyInHertzActual, - double frequencyInHertzExpected) - { - double ratio = frequencyInHertzActual / frequencyInHertzExpected; - double oneCent = 1.0005946; - expect (ratio < oneCent); - expect (ratio > 1.0 / oneCent); - } -}; - -static MPENoteTests MPENoteUnitTests; - -#endif - -} // namespace juce +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2017 - ROLI Ltd. + + JUCE is an open source library subject to commercial or open-source + licensing. + + The code included in this file is provided under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license. Permission + To use, copy, modify, and/or distribute this software for any purpose with or + without fee is hereby granted provided that the above copyright notice and + this permission notice appear in all copies. + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +namespace juce +{ + +namespace +{ + uint16 generateNoteID (int midiChannel, int midiNoteNumber) noexcept + { + jassert (midiChannel > 0 && midiChannel <= 16); + jassert (midiNoteNumber >= 0 && midiNoteNumber < 128); + + return uint16 ((midiChannel << 7) + midiNoteNumber); + } +} + +//============================================================================== +MPENote::MPENote (int midiChannel_, + int initialNote_, + MPEValue noteOnVelocity_, + MPEValue pitchbend_, + MPEValue pressure_, + MPEValue timbre_, + KeyState keyState_) noexcept + : noteID (generateNoteID (midiChannel_, initialNote_)), + midiChannel (uint8 (midiChannel_)), + initialNote (uint8 (initialNote_)), + noteOnVelocity (noteOnVelocity_), + pitchbend (pitchbend_), + pressure (pressure_), + initialTimbre (timbre_), + timbre (timbre_), + keyState (keyState_) +{ + jassert (keyState != MPENote::off); + jassert (isValid()); +} + +MPENote::MPENote() noexcept {} + +//============================================================================== +bool MPENote::isValid() const noexcept +{ + return midiChannel > 0 && midiChannel <= 16 && initialNote < 128; +} + +//============================================================================== +double MPENote::getFrequencyInHertz (double frequencyOfA) const noexcept +{ + auto pitchInSemitones = double (initialNote) + totalPitchbendInSemitones; + return frequencyOfA * std::pow (2.0, (pitchInSemitones - 69.0) / 12.0); +} + +//============================================================================== +bool MPENote::operator== (const MPENote& other) const noexcept +{ + jassert (isValid() && other.isValid()); + return noteID == other.noteID; +} + +bool MPENote::operator!= (const MPENote& other) const noexcept +{ + jassert (isValid() && other.isValid()); + return noteID != other.noteID; +} + + +//============================================================================== +//============================================================================== +#if JUCE_UNIT_TESTS + +class MPENoteTests : public UnitTest +{ +public: + MPENoteTests() + : UnitTest ("MPENote class", UnitTestCategories::midi) + {} + + //============================================================================== + void runTest() override + { + beginTest ("getFrequencyInHertz"); + { + MPENote note; + note.initialNote = 60; + note.totalPitchbendInSemitones = -0.5; + expectEqualsWithinOneCent (note.getFrequencyInHertz(), 254.178); + } + } + +private: + //============================================================================== + void expectEqualsWithinOneCent (double frequencyInHertzActual, + double frequencyInHertzExpected) + { + double ratio = frequencyInHertzActual / frequencyInHertzExpected; + double oneCent = 1.0005946; + expect (ratio < oneCent); + expect (ratio > 1.0 / oneCent); + } +}; + +static MPENoteTests MPENoteUnitTests; + +#endif + +} // namespace juce diff --git a/JuceLibraryCode/modules/juce_audio_basics/mpe/juce_MPENote.h b/JuceLibraryCode/modules/juce_audio_basics/mpe/juce_MPENote.h index 0a99745..0f27351 100644 --- a/JuceLibraryCode/modules/juce_audio_basics/mpe/juce_MPENote.h +++ b/JuceLibraryCode/modules/juce_audio_basics/mpe/juce_MPENote.h @@ -1,184 +1,184 @@ -/* - ============================================================================== - - This file is part of the JUCE library. - Copyright (c) 2017 - ROLI Ltd. - - JUCE is an open source library subject to commercial or open-source - licensing. - - The code included in this file is provided under the terms of the ISC license - http://www.isc.org/downloads/software-support-policy/isc-license. Permission - To use, copy, modify, and/or distribute this software for any purpose with or - without fee is hereby granted provided that the above copyright notice and - this permission notice appear in all copies. - - JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER - EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE - DISCLAIMED. - - ============================================================================== -*/ - -namespace juce -{ - -//============================================================================== -/** - This struct represents a playing MPE note. - - A note is identified by a unique ID, or alternatively, by a MIDI channel - and an initial note. It is characterised by five dimensions of continuous - expressive control. Their current values are represented as - MPEValue objects. - - @see MPEValue - - @tags{Audio} -*/ -struct JUCE_API MPENote -{ - //============================================================================== - /** Possible values for the note key state. */ - enum KeyState - { - off = 0, /**< The key is up (off). */ - keyDown = 1, /**< The note key is currently down (pressed). */ - sustained = 2, /**< The note is sustained (by a sustain or sostenuto pedal). */ - keyDownAndSustained = 3 /**< The note key is down and sustained (by a sustain or sostenuto pedal). */ - }; - - //============================================================================== - /** Constructor. - - @param midiChannel The MIDI channel of the note, between 2 and 15. - (Channel 1 and channel 16 can never be note channels in MPE). - - @param initialNote The MIDI note number, between 0 and 127. - - @param velocity The note-on velocity of the note. - - @param pitchbend The initial per-note pitchbend of the note. - - @param pressure The initial pressure of the note. - - @param timbre The timbre value of the note. - - @param keyState The key state of the note (whether the key is down - and/or the note is sustained). This value must not - be MPENote::off, since you are triggering a new note. - (If not specified, the default value will be MPENote::keyDown.) - */ - MPENote (int midiChannel, - int initialNote, - MPEValue velocity, - MPEValue pitchbend, - MPEValue pressure, - MPEValue timbre, - KeyState keyState = MPENote::keyDown) noexcept; - - /** Default constructor. - - Constructs an invalid MPE note (a note with the key state MPENote::off - and an invalid MIDI channel. The only allowed use for such a note is to - call isValid() on it; everything else is undefined behaviour. - */ - MPENote() noexcept; - - /** Checks whether the MPE note is valid. */ - bool isValid() const noexcept; - - //============================================================================== - // Invariants that define the note. - - /** A unique ID. Useful to distinguish the note from other simultaneously - sounding notes that may use the same note number or MIDI channel. - This should never change during the lifetime of a note object. - */ - uint16 noteID = 0; - - /** The MIDI channel which this note uses. - This should never change during the lifetime of an MPENote object. - */ - uint8 midiChannel = 0; - - /** The MIDI note number that was sent when the note was triggered. - This should never change during the lifetime of an MPENote object. - */ - uint8 initialNote = 0; - - //============================================================================== - // The five dimensions of continuous expressive control - - /** The velocity ("strike") of the note-on. - This dimension will stay constant after the note has been turned on. - */ - MPEValue noteOnVelocity { MPEValue::minValue() }; - - /** Current per-note pitchbend of the note (in units of MIDI pitchwheel - position). This dimension can be modulated while the note sounds. - - Note: This value is not aware of the currently used pitchbend range, - or an additional master pitchbend that may be simultaneously applied. - To compute the actual effective pitchbend of an MPENote, you should - probably use the member totalPitchbendInSemitones instead. - - @see totalPitchbendInSemitones, getFrequencyInHertz - */ - MPEValue pitchbend { MPEValue::centreValue() }; - - /** Current pressure with which the note is held down. - This dimension can be modulated while the note sounds. - */ - MPEValue pressure { MPEValue::centreValue() }; - - /** Initial value of timbre when the note was triggered. - This should never change during the lifetime of an MPENote object. - */ - MPEValue initialTimbre { MPEValue::centreValue() }; - - /** Current value of the note's third expressive dimension, typically - encoding some kind of timbre parameter. - This dimension can be modulated while the note sounds. - */ - MPEValue timbre { MPEValue::centreValue() }; - - /** The release velocity ("lift") of the note after a note-off has been - received. - This dimension will only have a meaningful value after a note-off has - been received for the note (and keyState is set to MPENote::off or - MPENote::sustained). Initially, the value is undefined. - */ - MPEValue noteOffVelocity { MPEValue::minValue() }; - - //============================================================================== - /** Current effective pitchbend of the note in units of semitones, relative - to initialNote. You should use this to compute the actual effective pitch - of the note. This value is computed and set by an MPEInstrument to the - sum of the per-note pitchbend value (stored in MPEValue::pitchbend) - and the master pitchbend of the MPE zone, weighted with the per-note - pitchbend range and master pitchbend range of the zone, respectively. - - @see getFrequencyInHertz - */ - double totalPitchbendInSemitones; - - /** Current key state. Indicates whether the note key is currently down (pressed) - and/or the note is sustained (by a sustain or sostenuto pedal). - */ - KeyState keyState { MPENote::off }; - - //============================================================================== - /** Returns the current frequency of the note in Hertz. This is the sum of - the initialNote and the totalPitchbendInSemitones, converted to Hertz. - */ - double getFrequencyInHertz (double frequencyOfA = 440.0) const noexcept; - - /** Returns true if two notes are the same, determined by their unique ID. */ - bool operator== (const MPENote& other) const noexcept; - - /** Returns true if two notes are different notes, determined by their unique ID. */ - bool operator!= (const MPENote& other) const noexcept; -}; - -} // namespace juce +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2017 - ROLI Ltd. + + JUCE is an open source library subject to commercial or open-source + licensing. + + The code included in this file is provided under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license. Permission + To use, copy, modify, and/or distribute this software for any purpose with or + without fee is hereby granted provided that the above copyright notice and + this permission notice appear in all copies. + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +namespace juce +{ + +//============================================================================== +/** + This struct represents a playing MPE note. + + A note is identified by a unique ID, or alternatively, by a MIDI channel + and an initial note. It is characterised by five dimensions of continuous + expressive control. Their current values are represented as + MPEValue objects. + + @see MPEValue + + @tags{Audio} +*/ +struct JUCE_API MPENote +{ + //============================================================================== + /** Possible values for the note key state. */ + enum KeyState + { + off = 0, /**< The key is up (off). */ + keyDown = 1, /**< The note key is currently down (pressed). */ + sustained = 2, /**< The note is sustained (by a sustain or sostenuto pedal). */ + keyDownAndSustained = 3 /**< The note key is down and sustained (by a sustain or sostenuto pedal). */ + }; + + //============================================================================== + /** Constructor. + + @param midiChannel The MIDI channel of the note, between 2 and 15. + (Channel 1 and channel 16 can never be note channels in MPE). + + @param initialNote The MIDI note number, between 0 and 127. + + @param velocity The note-on velocity of the note. + + @param pitchbend The initial per-note pitchbend of the note. + + @param pressure The initial pressure of the note. + + @param timbre The timbre value of the note. + + @param keyState The key state of the note (whether the key is down + and/or the note is sustained). This value must not + be MPENote::off, since you are triggering a new note. + (If not specified, the default value will be MPENote::keyDown.) + */ + MPENote (int midiChannel, + int initialNote, + MPEValue velocity, + MPEValue pitchbend, + MPEValue pressure, + MPEValue timbre, + KeyState keyState = MPENote::keyDown) noexcept; + + /** Default constructor. + + Constructs an invalid MPE note (a note with the key state MPENote::off + and an invalid MIDI channel. The only allowed use for such a note is to + call isValid() on it; everything else is undefined behaviour. + */ + MPENote() noexcept; + + /** Checks whether the MPE note is valid. */ + bool isValid() const noexcept; + + //============================================================================== + // Invariants that define the note. + + /** A unique ID. Useful to distinguish the note from other simultaneously + sounding notes that may use the same note number or MIDI channel. + This should never change during the lifetime of a note object. + */ + uint16 noteID = 0; + + /** The MIDI channel which this note uses. + This should never change during the lifetime of an MPENote object. + */ + uint8 midiChannel = 0; + + /** The MIDI note number that was sent when the note was triggered. + This should never change during the lifetime of an MPENote object. + */ + uint8 initialNote = 0; + + //============================================================================== + // The five dimensions of continuous expressive control + + /** The velocity ("strike") of the note-on. + This dimension will stay constant after the note has been turned on. + */ + MPEValue noteOnVelocity { MPEValue::minValue() }; + + /** Current per-note pitchbend of the note (in units of MIDI pitchwheel + position). This dimension can be modulated while the note sounds. + + Note: This value is not aware of the currently used pitchbend range, + or an additional master pitchbend that may be simultaneously applied. + To compute the actual effective pitchbend of an MPENote, you should + probably use the member totalPitchbendInSemitones instead. + + @see totalPitchbendInSemitones, getFrequencyInHertz + */ + MPEValue pitchbend { MPEValue::centreValue() }; + + /** Current pressure with which the note is held down. + This dimension can be modulated while the note sounds. + */ + MPEValue pressure { MPEValue::centreValue() }; + + /** Initial value of timbre when the note was triggered. + This should never change during the lifetime of an MPENote object. + */ + MPEValue initialTimbre { MPEValue::centreValue() }; + + /** Current value of the note's third expressive dimension, typically + encoding some kind of timbre parameter. + This dimension can be modulated while the note sounds. + */ + MPEValue timbre { MPEValue::centreValue() }; + + /** The release velocity ("lift") of the note after a note-off has been + received. + This dimension will only have a meaningful value after a note-off has + been received for the note (and keyState is set to MPENote::off or + MPENote::sustained). Initially, the value is undefined. + */ + MPEValue noteOffVelocity { MPEValue::minValue() }; + + //============================================================================== + /** Current effective pitchbend of the note in units of semitones, relative + to initialNote. You should use this to compute the actual effective pitch + of the note. This value is computed and set by an MPEInstrument to the + sum of the per-note pitchbend value (stored in MPEValue::pitchbend) + and the master pitchbend of the MPE zone, weighted with the per-note + pitchbend range and master pitchbend range of the zone, respectively. + + @see getFrequencyInHertz + */ + double totalPitchbendInSemitones; + + /** Current key state. Indicates whether the note key is currently down (pressed) + and/or the note is sustained (by a sustain or sostenuto pedal). + */ + KeyState keyState { MPENote::off }; + + //============================================================================== + /** Returns the current frequency of the note in Hertz. This is the sum of + the initialNote and the totalPitchbendInSemitones, converted to Hertz. + */ + double getFrequencyInHertz (double frequencyOfA = 440.0) const noexcept; + + /** Returns true if two notes are the same, determined by their unique ID. */ + bool operator== (const MPENote& other) const noexcept; + + /** Returns true if two notes are different notes, determined by their unique ID. */ + bool operator!= (const MPENote& other) const noexcept; +}; + +} // namespace juce diff --git a/JuceLibraryCode/modules/juce_audio_basics/mpe/juce_MPESynthesiser.cpp b/JuceLibraryCode/modules/juce_audio_basics/mpe/juce_MPESynthesiser.cpp index 8eaf95c..d891a21 100644 --- a/JuceLibraryCode/modules/juce_audio_basics/mpe/juce_MPESynthesiser.cpp +++ b/JuceLibraryCode/modules/juce_audio_basics/mpe/juce_MPESynthesiser.cpp @@ -1,338 +1,338 @@ -/* - ============================================================================== - - This file is part of the JUCE library. - Copyright (c) 2017 - ROLI Ltd. - - JUCE is an open source library subject to commercial or open-source - licensing. - - The code included in this file is provided under the terms of the ISC license - http://www.isc.org/downloads/software-support-policy/isc-license. Permission - To use, copy, modify, and/or distribute this software for any purpose with or - without fee is hereby granted provided that the above copyright notice and - this permission notice appear in all copies. - - JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER - EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE - DISCLAIMED. - - ============================================================================== -*/ - -namespace juce -{ - -MPESynthesiser::MPESynthesiser() -{ - MPEZoneLayout zoneLayout; - zoneLayout.setLowerZone (15); - setZoneLayout (zoneLayout); -} - -MPESynthesiser::MPESynthesiser (MPEInstrument* mpeInstrument) : MPESynthesiserBase (mpeInstrument) -{ -} - -MPESynthesiser::~MPESynthesiser() -{ -} - -//============================================================================== -void MPESynthesiser::startVoice (MPESynthesiserVoice* voice, MPENote noteToStart) -{ - jassert (voice != nullptr); - - voice->currentlyPlayingNote = noteToStart; - voice->noteOnTime = lastNoteOnCounter++; - voice->noteStarted(); -} - -void MPESynthesiser::stopVoice (MPESynthesiserVoice* voice, MPENote noteToStop, bool allowTailOff) -{ - jassert (voice != nullptr); - - voice->currentlyPlayingNote = noteToStop; - voice->noteStopped (allowTailOff); -} - -//============================================================================== -void MPESynthesiser::noteAdded (MPENote newNote) -{ - const ScopedLock sl (voicesLock); - - if (auto* voice = findFreeVoice (newNote, shouldStealVoices)) - startVoice (voice, newNote); -} - -void MPESynthesiser::notePressureChanged (MPENote changedNote) -{ - const ScopedLock sl (voicesLock); - - for (auto* voice : voices) - { - if (voice->isCurrentlyPlayingNote (changedNote)) - { - voice->currentlyPlayingNote = changedNote; - voice->notePressureChanged(); - } - } -} - -void MPESynthesiser::notePitchbendChanged (MPENote changedNote) -{ - const ScopedLock sl (voicesLock); - - for (auto* voice : voices) - { - if (voice->isCurrentlyPlayingNote (changedNote)) - { - voice->currentlyPlayingNote = changedNote; - voice->notePitchbendChanged(); - } - } -} - -void MPESynthesiser::noteTimbreChanged (MPENote changedNote) -{ - const ScopedLock sl (voicesLock); - - for (auto* voice : voices) - { - if (voice->isCurrentlyPlayingNote (changedNote)) - { - voice->currentlyPlayingNote = changedNote; - voice->noteTimbreChanged(); - } - } -} - -void MPESynthesiser::noteKeyStateChanged (MPENote changedNote) -{ - const ScopedLock sl (voicesLock); - - for (auto* voice : voices) - { - if (voice->isCurrentlyPlayingNote (changedNote)) - { - voice->currentlyPlayingNote = changedNote; - voice->noteKeyStateChanged(); - } - } -} - -void MPESynthesiser::noteReleased (MPENote finishedNote) -{ - const ScopedLock sl (voicesLock); - - for (auto i = voices.size(); --i >= 0;) - { - auto* voice = voices.getUnchecked (i); - - if (voice->isCurrentlyPlayingNote(finishedNote)) - stopVoice (voice, finishedNote, true); - } -} - -void MPESynthesiser::setCurrentPlaybackSampleRate (const double newRate) -{ - MPESynthesiserBase::setCurrentPlaybackSampleRate (newRate); - - const ScopedLock sl (voicesLock); - - turnOffAllVoices (false); - - for (auto i = voices.size(); --i >= 0;) - voices.getUnchecked (i)->setCurrentSampleRate (newRate); -} - -void MPESynthesiser::handleMidiEvent (const MidiMessage& m) -{ - if (m.isController()) - handleController (m.getChannel(), m.getControllerNumber(), m.getControllerValue()); - else if (m.isProgramChange()) - handleProgramChange (m.getChannel(), m.getProgramChangeNumber()); - - MPESynthesiserBase::handleMidiEvent (m); -} - -MPESynthesiserVoice* MPESynthesiser::findFreeVoice (MPENote noteToFindVoiceFor, bool stealIfNoneAvailable) const -{ - const ScopedLock sl (voicesLock); - - for (auto* voice : voices) - { - if (! voice->isActive()) - return voice; - } - - if (stealIfNoneAvailable) - return findVoiceToSteal (noteToFindVoiceFor); - - return nullptr; -} - -MPESynthesiserVoice* MPESynthesiser::findVoiceToSteal (MPENote noteToStealVoiceFor) const -{ - // This voice-stealing algorithm applies the following heuristics: - // - Re-use the oldest notes first - // - Protect the lowest & topmost notes, even if sustained, but not if they've been released. - - - // apparently you are trying to render audio without having any voices... - jassert (voices.size() > 0); - - // These are the voices we want to protect (ie: only steal if unavoidable) - MPESynthesiserVoice* low = nullptr; // Lowest sounding note, might be sustained, but NOT in release phase - MPESynthesiserVoice* top = nullptr; // Highest sounding note, might be sustained, but NOT in release phase - - // this is a list of voices we can steal, sorted by how long they've been running - Array usableVoices; - usableVoices.ensureStorageAllocated (voices.size()); - - for (auto* voice : voices) - { - jassert (voice->isActive()); // We wouldn't be here otherwise - - usableVoices.add (voice); - - // NB: Using a functor rather than a lambda here due to scare-stories about - // compilers generating code containing heap allocations.. - struct Sorter - { - bool operator() (const MPESynthesiserVoice* a, const MPESynthesiserVoice* b) const noexcept { return a->noteOnTime < b->noteOnTime; } - }; - - std::sort (usableVoices.begin(), usableVoices.end(), Sorter()); - - if (! voice->isPlayingButReleased()) // Don't protect released notes - { - auto noteNumber = voice->getCurrentlyPlayingNote().initialNote; - - if (low == nullptr || noteNumber < low->getCurrentlyPlayingNote().initialNote) - low = voice; - - if (top == nullptr || noteNumber > top->getCurrentlyPlayingNote().initialNote) - top = voice; - } - } - - // Eliminate pathological cases (ie: only 1 note playing): we always give precedence to the lowest note(s) - if (top == low) - top = nullptr; - - // If we want to re-use the voice to trigger a new note, - // then The oldest note that's playing the same note number is ideal. - if (noteToStealVoiceFor.isValid()) - for (auto* voice : usableVoices) - if (voice->getCurrentlyPlayingNote().initialNote == noteToStealVoiceFor.initialNote) - return voice; - - // Oldest voice that has been released (no finger on it and not held by sustain pedal) - for (auto* voice : usableVoices) - if (voice != low && voice != top && voice->isPlayingButReleased()) - return voice; - - // Oldest voice that doesn't have a finger on it: - for (auto* voice : usableVoices) - if (voice != low && voice != top - && voice->getCurrentlyPlayingNote().keyState != MPENote::keyDown - && voice->getCurrentlyPlayingNote().keyState != MPENote::keyDownAndSustained) - return voice; - - // Oldest voice that isn't protected - for (auto* voice : usableVoices) - if (voice != low && voice != top) - return voice; - - // We've only got "protected" voices now: lowest note takes priority - jassert (low != nullptr); - - // Duophonic synth: give priority to the bass note: - if (top != nullptr) - return top; - - return low; -} - -//============================================================================== -void MPESynthesiser::addVoice (MPESynthesiserVoice* const newVoice) -{ - const ScopedLock sl (voicesLock); - newVoice->setCurrentSampleRate (getSampleRate()); - voices.add (newVoice); -} - -void MPESynthesiser::clearVoices() -{ - const ScopedLock sl (voicesLock); - voices.clear(); -} - -MPESynthesiserVoice* MPESynthesiser::getVoice (const int index) const -{ - const ScopedLock sl (voicesLock); - return voices [index]; -} - -void MPESynthesiser::removeVoice (const int index) -{ - const ScopedLock sl (voicesLock); - voices.remove (index); -} - -void MPESynthesiser::reduceNumVoices (const int newNumVoices) -{ - // we can't possibly get to a negative number of voices... - jassert (newNumVoices >= 0); - - const ScopedLock sl (voicesLock); - - while (voices.size() > newNumVoices) - { - if (auto* voice = findFreeVoice ({}, true)) - voices.removeObject (voice); - else - voices.remove (0); // if there's no voice to steal, kill the oldest voice - } -} - -void MPESynthesiser::turnOffAllVoices (bool allowTailOff) -{ - { - const ScopedLock sl (voicesLock); - - // first turn off all voices (it's more efficient to do this immediately - // rather than to go through the MPEInstrument for this). - for (auto* voice : voices) - voice->noteStopped (allowTailOff); - } - - // finally make sure the MPE Instrument also doesn't have any notes anymore. - instrument->releaseAllNotes(); -} - -//============================================================================== -void MPESynthesiser::renderNextSubBlock (AudioBuffer& buffer, int startSample, int numSamples) -{ - const ScopedLock sl (voicesLock); - - for (auto* voice : voices) - { - if (voice->isActive()) - voice->renderNextBlock (buffer, startSample, numSamples); - } -} - -void MPESynthesiser::renderNextSubBlock (AudioBuffer& buffer, int startSample, int numSamples) -{ - const ScopedLock sl (voicesLock); - - for (auto* voice : voices) - { - if (voice->isActive()) - voice->renderNextBlock (buffer, startSample, numSamples); - } -} - -} // namespace juce +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2017 - ROLI Ltd. + + JUCE is an open source library subject to commercial or open-source + licensing. + + The code included in this file is provided under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license. Permission + To use, copy, modify, and/or distribute this software for any purpose with or + without fee is hereby granted provided that the above copyright notice and + this permission notice appear in all copies. + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +namespace juce +{ + +MPESynthesiser::MPESynthesiser() +{ + MPEZoneLayout zoneLayout; + zoneLayout.setLowerZone (15); + setZoneLayout (zoneLayout); +} + +MPESynthesiser::MPESynthesiser (MPEInstrument* mpeInstrument) : MPESynthesiserBase (mpeInstrument) +{ +} + +MPESynthesiser::~MPESynthesiser() +{ +} + +//============================================================================== +void MPESynthesiser::startVoice (MPESynthesiserVoice* voice, MPENote noteToStart) +{ + jassert (voice != nullptr); + + voice->currentlyPlayingNote = noteToStart; + voice->noteOnTime = lastNoteOnCounter++; + voice->noteStarted(); +} + +void MPESynthesiser::stopVoice (MPESynthesiserVoice* voice, MPENote noteToStop, bool allowTailOff) +{ + jassert (voice != nullptr); + + voice->currentlyPlayingNote = noteToStop; + voice->noteStopped (allowTailOff); +} + +//============================================================================== +void MPESynthesiser::noteAdded (MPENote newNote) +{ + const ScopedLock sl (voicesLock); + + if (auto* voice = findFreeVoice (newNote, shouldStealVoices)) + startVoice (voice, newNote); +} + +void MPESynthesiser::notePressureChanged (MPENote changedNote) +{ + const ScopedLock sl (voicesLock); + + for (auto* voice : voices) + { + if (voice->isCurrentlyPlayingNote (changedNote)) + { + voice->currentlyPlayingNote = changedNote; + voice->notePressureChanged(); + } + } +} + +void MPESynthesiser::notePitchbendChanged (MPENote changedNote) +{ + const ScopedLock sl (voicesLock); + + for (auto* voice : voices) + { + if (voice->isCurrentlyPlayingNote (changedNote)) + { + voice->currentlyPlayingNote = changedNote; + voice->notePitchbendChanged(); + } + } +} + +void MPESynthesiser::noteTimbreChanged (MPENote changedNote) +{ + const ScopedLock sl (voicesLock); + + for (auto* voice : voices) + { + if (voice->isCurrentlyPlayingNote (changedNote)) + { + voice->currentlyPlayingNote = changedNote; + voice->noteTimbreChanged(); + } + } +} + +void MPESynthesiser::noteKeyStateChanged (MPENote changedNote) +{ + const ScopedLock sl (voicesLock); + + for (auto* voice : voices) + { + if (voice->isCurrentlyPlayingNote (changedNote)) + { + voice->currentlyPlayingNote = changedNote; + voice->noteKeyStateChanged(); + } + } +} + +void MPESynthesiser::noteReleased (MPENote finishedNote) +{ + const ScopedLock sl (voicesLock); + + for (auto i = voices.size(); --i >= 0;) + { + auto* voice = voices.getUnchecked (i); + + if (voice->isCurrentlyPlayingNote(finishedNote)) + stopVoice (voice, finishedNote, true); + } +} + +void MPESynthesiser::setCurrentPlaybackSampleRate (const double newRate) +{ + MPESynthesiserBase::setCurrentPlaybackSampleRate (newRate); + + const ScopedLock sl (voicesLock); + + turnOffAllVoices (false); + + for (auto i = voices.size(); --i >= 0;) + voices.getUnchecked (i)->setCurrentSampleRate (newRate); +} + +void MPESynthesiser::handleMidiEvent (const MidiMessage& m) +{ + if (m.isController()) + handleController (m.getChannel(), m.getControllerNumber(), m.getControllerValue()); + else if (m.isProgramChange()) + handleProgramChange (m.getChannel(), m.getProgramChangeNumber()); + + MPESynthesiserBase::handleMidiEvent (m); +} + +MPESynthesiserVoice* MPESynthesiser::findFreeVoice (MPENote noteToFindVoiceFor, bool stealIfNoneAvailable) const +{ + const ScopedLock sl (voicesLock); + + for (auto* voice : voices) + { + if (! voice->isActive()) + return voice; + } + + if (stealIfNoneAvailable) + return findVoiceToSteal (noteToFindVoiceFor); + + return nullptr; +} + +MPESynthesiserVoice* MPESynthesiser::findVoiceToSteal (MPENote noteToStealVoiceFor) const +{ + // This voice-stealing algorithm applies the following heuristics: + // - Re-use the oldest notes first + // - Protect the lowest & topmost notes, even if sustained, but not if they've been released. + + + // apparently you are trying to render audio without having any voices... + jassert (voices.size() > 0); + + // These are the voices we want to protect (ie: only steal if unavoidable) + MPESynthesiserVoice* low = nullptr; // Lowest sounding note, might be sustained, but NOT in release phase + MPESynthesiserVoice* top = nullptr; // Highest sounding note, might be sustained, but NOT in release phase + + // this is a list of voices we can steal, sorted by how long they've been running + Array usableVoices; + usableVoices.ensureStorageAllocated (voices.size()); + + for (auto* voice : voices) + { + jassert (voice->isActive()); // We wouldn't be here otherwise + + usableVoices.add (voice); + + // NB: Using a functor rather than a lambda here due to scare-stories about + // compilers generating code containing heap allocations.. + struct Sorter + { + bool operator() (const MPESynthesiserVoice* a, const MPESynthesiserVoice* b) const noexcept { return a->noteOnTime < b->noteOnTime; } + }; + + std::sort (usableVoices.begin(), usableVoices.end(), Sorter()); + + if (! voice->isPlayingButReleased()) // Don't protect released notes + { + auto noteNumber = voice->getCurrentlyPlayingNote().initialNote; + + if (low == nullptr || noteNumber < low->getCurrentlyPlayingNote().initialNote) + low = voice; + + if (top == nullptr || noteNumber > top->getCurrentlyPlayingNote().initialNote) + top = voice; + } + } + + // Eliminate pathological cases (ie: only 1 note playing): we always give precedence to the lowest note(s) + if (top == low) + top = nullptr; + + // If we want to re-use the voice to trigger a new note, + // then The oldest note that's playing the same note number is ideal. + if (noteToStealVoiceFor.isValid()) + for (auto* voice : usableVoices) + if (voice->getCurrentlyPlayingNote().initialNote == noteToStealVoiceFor.initialNote) + return voice; + + // Oldest voice that has been released (no finger on it and not held by sustain pedal) + for (auto* voice : usableVoices) + if (voice != low && voice != top && voice->isPlayingButReleased()) + return voice; + + // Oldest voice that doesn't have a finger on it: + for (auto* voice : usableVoices) + if (voice != low && voice != top + && voice->getCurrentlyPlayingNote().keyState != MPENote::keyDown + && voice->getCurrentlyPlayingNote().keyState != MPENote::keyDownAndSustained) + return voice; + + // Oldest voice that isn't protected + for (auto* voice : usableVoices) + if (voice != low && voice != top) + return voice; + + // We've only got "protected" voices now: lowest note takes priority + jassert (low != nullptr); + + // Duophonic synth: give priority to the bass note: + if (top != nullptr) + return top; + + return low; +} + +//============================================================================== +void MPESynthesiser::addVoice (MPESynthesiserVoice* const newVoice) +{ + const ScopedLock sl (voicesLock); + newVoice->setCurrentSampleRate (getSampleRate()); + voices.add (newVoice); +} + +void MPESynthesiser::clearVoices() +{ + const ScopedLock sl (voicesLock); + voices.clear(); +} + +MPESynthesiserVoice* MPESynthesiser::getVoice (const int index) const +{ + const ScopedLock sl (voicesLock); + return voices [index]; +} + +void MPESynthesiser::removeVoice (const int index) +{ + const ScopedLock sl (voicesLock); + voices.remove (index); +} + +void MPESynthesiser::reduceNumVoices (const int newNumVoices) +{ + // we can't possibly get to a negative number of voices... + jassert (newNumVoices >= 0); + + const ScopedLock sl (voicesLock); + + while (voices.size() > newNumVoices) + { + if (auto* voice = findFreeVoice ({}, true)) + voices.removeObject (voice); + else + voices.remove (0); // if there's no voice to steal, kill the oldest voice + } +} + +void MPESynthesiser::turnOffAllVoices (bool allowTailOff) +{ + { + const ScopedLock sl (voicesLock); + + // first turn off all voices (it's more efficient to do this immediately + // rather than to go through the MPEInstrument for this). + for (auto* voice : voices) + voice->noteStopped (allowTailOff); + } + + // finally make sure the MPE Instrument also doesn't have any notes anymore. + instrument->releaseAllNotes(); +} + +//============================================================================== +void MPESynthesiser::renderNextSubBlock (AudioBuffer& buffer, int startSample, int numSamples) +{ + const ScopedLock sl (voicesLock); + + for (auto* voice : voices) + { + if (voice->isActive()) + voice->renderNextBlock (buffer, startSample, numSamples); + } +} + +void MPESynthesiser::renderNextSubBlock (AudioBuffer& buffer, int startSample, int numSamples) +{ + const ScopedLock sl (voicesLock); + + for (auto* voice : voices) + { + if (voice->isActive()) + voice->renderNextBlock (buffer, startSample, numSamples); + } +} + +} // namespace juce diff --git a/JuceLibraryCode/modules/juce_audio_basics/mpe/juce_MPESynthesiser.h b/JuceLibraryCode/modules/juce_audio_basics/mpe/juce_MPESynthesiser.h index 8a999d6..1fbd0b0 100644 --- a/JuceLibraryCode/modules/juce_audio_basics/mpe/juce_MPESynthesiser.h +++ b/JuceLibraryCode/modules/juce_audio_basics/mpe/juce_MPESynthesiser.h @@ -1,312 +1,312 @@ -/* - ============================================================================== - - This file is part of the JUCE library. - Copyright (c) 2017 - ROLI Ltd. - - JUCE is an open source library subject to commercial or open-source - licensing. - - The code included in this file is provided under the terms of the ISC license - http://www.isc.org/downloads/software-support-policy/isc-license. Permission - To use, copy, modify, and/or distribute this software for any purpose with or - without fee is hereby granted provided that the above copyright notice and - this permission notice appear in all copies. - - JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER - EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE - DISCLAIMED. - - ============================================================================== -*/ - -namespace juce -{ - -//============================================================================== -/** - Base class for an MPE-compatible musical device that can play sounds. - - This class extends MPESynthesiserBase by adding the concept of voices, - each of which can play a sound triggered by a MPENote that can be modulated - by MPE dimensions like pressure, pitchbend, and timbre, while the note is - sounding. - - To create a synthesiser, you'll need to create a subclass of MPESynthesiserVoice - which can play back one of these sounds at a time. - - Then you can use the addVoice() methods to give the synthesiser a set of voices - it can use to play notes. If you only give it one voice it will be monophonic - - the more voices it has, the more polyphony it'll have available. - - Then repeatedly call the renderNextBlock() method to produce the audio (inherited - from MPESynthesiserBase). The voices will be started, stopped, and modulated - automatically, based on the MPE/MIDI messages that the synthesiser receives. - - Before rendering, be sure to call the setCurrentPlaybackSampleRate() to tell it - what the target playback rate is. This value is passed on to the voices so that - they can pitch their output correctly. - - @see MPESynthesiserBase, MPESynthesiserVoice, MPENote, MPEInstrument - - @tags{Audio} -*/ -class JUCE_API MPESynthesiser : public MPESynthesiserBase -{ -public: - //============================================================================== - /** Constructor. - You'll need to add some voices before it'll make any sound. - - @see addVoice - */ - MPESynthesiser(); - - /** Constructor to pass to the synthesiser a custom MPEInstrument object - to handle the MPE note state, MIDI channel assignment etc. - (in case you need custom logic for this that goes beyond MIDI and MPE). - The synthesiser will take ownership of this object. - - @see MPESynthesiserBase, MPEInstrument - */ - MPESynthesiser (MPEInstrument* instrumentToUse); - - /** Destructor. */ - ~MPESynthesiser() override; - - //============================================================================== - /** Deletes all voices. */ - void clearVoices(); - - /** Returns the number of voices that have been added. */ - int getNumVoices() const noexcept { return voices.size(); } - - /** Returns one of the voices that have been added. */ - MPESynthesiserVoice* getVoice (int index) const; - - /** Adds a new voice to the synth. - - All the voices should be the same class of object and are treated equally. - - The object passed in will be managed by the synthesiser, which will delete - it later on when no longer needed. The caller should not retain a pointer to the - voice. - */ - void addVoice (MPESynthesiserVoice* newVoice); - - /** Deletes one of the voices. */ - void removeVoice (int index); - - /** Reduces the number of voices to newNumVoices. - - This will repeatedly call findVoiceToSteal() and remove that voice, until - the total number of voices equals newNumVoices. If newNumVoices is greater than - or equal to the current number of voices, this method does nothing. - */ - void reduceNumVoices (int newNumVoices); - - /** Release all MPE notes and turn off all voices. - - If allowTailOff is true, the voices will be allowed to fade out the notes gracefully - (if they can do). If this is false, the notes will all be cut off immediately. - - This method is meant to be called by the user, for example to implement - a MIDI panic button in a synth. - */ - virtual void turnOffAllVoices (bool allowTailOff); - - //============================================================================== - /** If set to true, then the synth will try to take over an existing voice if - it runs out and needs to play another note. - - The value of this boolean is passed into findFreeVoice(), so the result will - depend on the implementation of this method. - */ - void setVoiceStealingEnabled (bool shouldSteal) noexcept { shouldStealVoices = shouldSteal; } - - /** Returns true if note-stealing is enabled. */ - bool isVoiceStealingEnabled() const noexcept { return shouldStealVoices; } - - //============================================================================== - /** Tells the synthesiser what the sample rate is for the audio it's being used to render. - - This overrides the implementation in MPESynthesiserBase, to additionally - propagate the new value to the voices so that they can use it to render the correct - pitches. - */ - void setCurrentPlaybackSampleRate (double newRate) override; - - //============================================================================== - /** Handle incoming MIDI events. - - This method will be called automatically according to the MIDI data passed - into renderNextBlock(), but you can also call it yourself to manually - inject MIDI events. - - This implementation forwards program change messages and non-MPE-related - controller messages to handleProgramChange and handleController, respectively, - and then simply calls through to MPESynthesiserBase::handleMidiEvent to deal - with MPE-related MIDI messages used for MPE notes, zones etc. - - This method can be overridden further if you need to do custom MIDI - handling on top of what is provided here. - */ - void handleMidiEvent (const MidiMessage&) override; - - /** Callback for MIDI controller messages. The default implementation - provided here does nothing; override this method if you need custom - MIDI controller handling on top of MPE. - - This method will be called automatically according to the midi data passed into - renderNextBlock(). - */ - virtual void handleController (int /*midiChannel*/, - int /*controllerNumber*/, - int /*controllerValue*/) {} - - /** Callback for MIDI program change messages. The default implementation - provided here does nothing; override this method if you need to handle - those messages. - - This method will be called automatically according to the midi data passed into - renderNextBlock(). - */ - virtual void handleProgramChange (int /*midiChannel*/, - int /*programNumber*/) {} - -protected: - //============================================================================== - /** Attempts to start playing a new note. - - The default method here will find a free voice that is appropriate for - playing the given MPENote, and use that voice to start playing the sound. - If isNoteStealingEnabled returns true (set this by calling setNoteStealingEnabled), - the synthesiser will use the voice stealing algorithm to find a free voice for - the note (if no voices are free otherwise). - - This method will be called automatically according to the midi data passed into - renderNextBlock(). Do not call it yourself, otherwise the internal MPE note state - will become inconsistent. - */ - void noteAdded (MPENote newNote) override; - - /** Stops playing a note. - - This will be called whenever an MPE note is released (either by a note-off message, - or by a sustain/sostenuto pedal release for a note that already received a note-off), - and should therefore stop playing. - - This will find any voice that is currently playing finishedNote, - turn its currently playing note off, and call its noteStopped callback. - - This method will be called automatically according to the midi data passed into - renderNextBlock(). Do not call it yourself, otherwise the internal MPE note state - will become inconsistent. - */ - void noteReleased (MPENote finishedNote) override; - - /** Will find any voice that is currently playing changedNote, update its - currently playing note, and call its notePressureChanged method. - - This method will be called automatically according to the midi data passed into - renderNextBlock(). Do not call it yourself. - */ - void notePressureChanged (MPENote changedNote) override; - - /** Will find any voice that is currently playing changedNote, update its - currently playing note, and call its notePitchbendChanged method. - - This method will be called automatically according to the midi data passed into - renderNextBlock(). Do not call it yourself. - */ - void notePitchbendChanged (MPENote changedNote) override; - - /** Will find any voice that is currently playing changedNote, update its - currently playing note, and call its noteTimbreChanged method. - - This method will be called automatically according to the midi data passed into - renderNextBlock(). Do not call it yourself. - */ - void noteTimbreChanged (MPENote changedNote) override; - - /** Will find any voice that is currently playing changedNote, update its - currently playing note, and call its noteKeyStateChanged method. - - This method will be called automatically according to the midi data passed into - renderNextBlock(). Do not call it yourself. - */ - void noteKeyStateChanged (MPENote changedNote) override; - - //============================================================================== - /** This will simply call renderNextBlock for each currently active - voice and fill the buffer with the sum. - Override this method if you need to do more work to render your audio. - */ - void renderNextSubBlock (AudioBuffer& outputAudio, - int startSample, - int numSamples) override; - - /** This will simply call renderNextBlock for each currently active - voice and fill the buffer with the sum. (double-precision version) - Override this method if you need to do more work to render your audio. - */ - void renderNextSubBlock (AudioBuffer& outputAudio, - int startSample, - int numSamples) override; - - //============================================================================== - /** Searches through the voices to find one that's not currently playing, and - which can play the given MPE note. - - If all voices are active and stealIfNoneAvailable is false, this returns - a nullptr. If all voices are active and stealIfNoneAvailable is true, - this will call findVoiceToSteal() to find a voice. - - If you need to find a free voice for something else than playing a note - (e.g. for deleting it), you can pass an invalid (default-constructed) MPENote. - */ - virtual MPESynthesiserVoice* findFreeVoice (MPENote noteToFindVoiceFor, - bool stealIfNoneAvailable) const; - - /** Chooses a voice that is most suitable for being re-used to play a new - note, or for being deleted by reduceNumVoices. - - The default method will attempt to find the oldest voice that isn't the - bottom or top note being played. If that's not suitable for your synth, - you can override this method and do something more cunning instead. - - If you pass a valid MPENote for the optional argument, then the note number - of that note will be taken into account for finding the ideal voice to steal. - If you pass an invalid (default-constructed) MPENote instead, this part of - the algorithm will be ignored. - */ - virtual MPESynthesiserVoice* findVoiceToSteal (MPENote noteToStealVoiceFor = MPENote()) const; - - /** Starts a specified voice and tells it to play a particular MPENote. - You should never need to call this, it's called internally by - MPESynthesiserBase::instrument via the noteStarted callback, - but is protected in case it's useful for some custom subclasses. - */ - void startVoice (MPESynthesiserVoice* voice, MPENote noteToStart); - - /** Stops a given voice and tells it to stop playing a particular MPENote - (which should be the same note it is actually playing). - You should never need to call this, it's called internally by - MPESynthesiserBase::instrument via the noteReleased callback, - but is protected in case it's useful for some custom subclasses. - */ - void stopVoice (MPESynthesiserVoice* voice, MPENote noteToStop, bool allowTailOff); - - //============================================================================== - OwnedArray voices; - CriticalSection voicesLock; - -private: - //============================================================================== - bool shouldStealVoices = false; - uint32 lastNoteOnCounter = 0; - - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MPESynthesiser) -}; - -} // namespace juce +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2017 - ROLI Ltd. + + JUCE is an open source library subject to commercial or open-source + licensing. + + The code included in this file is provided under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license. Permission + To use, copy, modify, and/or distribute this software for any purpose with or + without fee is hereby granted provided that the above copyright notice and + this permission notice appear in all copies. + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +namespace juce +{ + +//============================================================================== +/** + Base class for an MPE-compatible musical device that can play sounds. + + This class extends MPESynthesiserBase by adding the concept of voices, + each of which can play a sound triggered by a MPENote that can be modulated + by MPE dimensions like pressure, pitchbend, and timbre, while the note is + sounding. + + To create a synthesiser, you'll need to create a subclass of MPESynthesiserVoice + which can play back one of these sounds at a time. + + Then you can use the addVoice() methods to give the synthesiser a set of voices + it can use to play notes. If you only give it one voice it will be monophonic - + the more voices it has, the more polyphony it'll have available. + + Then repeatedly call the renderNextBlock() method to produce the audio (inherited + from MPESynthesiserBase). The voices will be started, stopped, and modulated + automatically, based on the MPE/MIDI messages that the synthesiser receives. + + Before rendering, be sure to call the setCurrentPlaybackSampleRate() to tell it + what the target playback rate is. This value is passed on to the voices so that + they can pitch their output correctly. + + @see MPESynthesiserBase, MPESynthesiserVoice, MPENote, MPEInstrument + + @tags{Audio} +*/ +class JUCE_API MPESynthesiser : public MPESynthesiserBase +{ +public: + //============================================================================== + /** Constructor. + You'll need to add some voices before it'll make any sound. + + @see addVoice + */ + MPESynthesiser(); + + /** Constructor to pass to the synthesiser a custom MPEInstrument object + to handle the MPE note state, MIDI channel assignment etc. + (in case you need custom logic for this that goes beyond MIDI and MPE). + The synthesiser will take ownership of this object. + + @see MPESynthesiserBase, MPEInstrument + */ + MPESynthesiser (MPEInstrument* instrumentToUse); + + /** Destructor. */ + ~MPESynthesiser() override; + + //============================================================================== + /** Deletes all voices. */ + void clearVoices(); + + /** Returns the number of voices that have been added. */ + int getNumVoices() const noexcept { return voices.size(); } + + /** Returns one of the voices that have been added. */ + MPESynthesiserVoice* getVoice (int index) const; + + /** Adds a new voice to the synth. + + All the voices should be the same class of object and are treated equally. + + The object passed in will be managed by the synthesiser, which will delete + it later on when no longer needed. The caller should not retain a pointer to the + voice. + */ + void addVoice (MPESynthesiserVoice* newVoice); + + /** Deletes one of the voices. */ + void removeVoice (int index); + + /** Reduces the number of voices to newNumVoices. + + This will repeatedly call findVoiceToSteal() and remove that voice, until + the total number of voices equals newNumVoices. If newNumVoices is greater than + or equal to the current number of voices, this method does nothing. + */ + void reduceNumVoices (int newNumVoices); + + /** Release all MPE notes and turn off all voices. + + If allowTailOff is true, the voices will be allowed to fade out the notes gracefully + (if they can do). If this is false, the notes will all be cut off immediately. + + This method is meant to be called by the user, for example to implement + a MIDI panic button in a synth. + */ + virtual void turnOffAllVoices (bool allowTailOff); + + //============================================================================== + /** If set to true, then the synth will try to take over an existing voice if + it runs out and needs to play another note. + + The value of this boolean is passed into findFreeVoice(), so the result will + depend on the implementation of this method. + */ + void setVoiceStealingEnabled (bool shouldSteal) noexcept { shouldStealVoices = shouldSteal; } + + /** Returns true if note-stealing is enabled. */ + bool isVoiceStealingEnabled() const noexcept { return shouldStealVoices; } + + //============================================================================== + /** Tells the synthesiser what the sample rate is for the audio it's being used to render. + + This overrides the implementation in MPESynthesiserBase, to additionally + propagate the new value to the voices so that they can use it to render the correct + pitches. + */ + void setCurrentPlaybackSampleRate (double newRate) override; + + //============================================================================== + /** Handle incoming MIDI events. + + This method will be called automatically according to the MIDI data passed + into renderNextBlock(), but you can also call it yourself to manually + inject MIDI events. + + This implementation forwards program change messages and non-MPE-related + controller messages to handleProgramChange and handleController, respectively, + and then simply calls through to MPESynthesiserBase::handleMidiEvent to deal + with MPE-related MIDI messages used for MPE notes, zones etc. + + This method can be overridden further if you need to do custom MIDI + handling on top of what is provided here. + */ + void handleMidiEvent (const MidiMessage&) override; + + /** Callback for MIDI controller messages. The default implementation + provided here does nothing; override this method if you need custom + MIDI controller handling on top of MPE. + + This method will be called automatically according to the midi data passed into + renderNextBlock(). + */ + virtual void handleController (int /*midiChannel*/, + int /*controllerNumber*/, + int /*controllerValue*/) {} + + /** Callback for MIDI program change messages. The default implementation + provided here does nothing; override this method if you need to handle + those messages. + + This method will be called automatically according to the midi data passed into + renderNextBlock(). + */ + virtual void handleProgramChange (int /*midiChannel*/, + int /*programNumber*/) {} + +protected: + //============================================================================== + /** Attempts to start playing a new note. + + The default method here will find a free voice that is appropriate for + playing the given MPENote, and use that voice to start playing the sound. + If isNoteStealingEnabled returns true (set this by calling setNoteStealingEnabled), + the synthesiser will use the voice stealing algorithm to find a free voice for + the note (if no voices are free otherwise). + + This method will be called automatically according to the midi data passed into + renderNextBlock(). Do not call it yourself, otherwise the internal MPE note state + will become inconsistent. + */ + void noteAdded (MPENote newNote) override; + + /** Stops playing a note. + + This will be called whenever an MPE note is released (either by a note-off message, + or by a sustain/sostenuto pedal release for a note that already received a note-off), + and should therefore stop playing. + + This will find any voice that is currently playing finishedNote, + turn its currently playing note off, and call its noteStopped callback. + + This method will be called automatically according to the midi data passed into + renderNextBlock(). Do not call it yourself, otherwise the internal MPE note state + will become inconsistent. + */ + void noteReleased (MPENote finishedNote) override; + + /** Will find any voice that is currently playing changedNote, update its + currently playing note, and call its notePressureChanged method. + + This method will be called automatically according to the midi data passed into + renderNextBlock(). Do not call it yourself. + */ + void notePressureChanged (MPENote changedNote) override; + + /** Will find any voice that is currently playing changedNote, update its + currently playing note, and call its notePitchbendChanged method. + + This method will be called automatically according to the midi data passed into + renderNextBlock(). Do not call it yourself. + */ + void notePitchbendChanged (MPENote changedNote) override; + + /** Will find any voice that is currently playing changedNote, update its + currently playing note, and call its noteTimbreChanged method. + + This method will be called automatically according to the midi data passed into + renderNextBlock(). Do not call it yourself. + */ + void noteTimbreChanged (MPENote changedNote) override; + + /** Will find any voice that is currently playing changedNote, update its + currently playing note, and call its noteKeyStateChanged method. + + This method will be called automatically according to the midi data passed into + renderNextBlock(). Do not call it yourself. + */ + void noteKeyStateChanged (MPENote changedNote) override; + + //============================================================================== + /** This will simply call renderNextBlock for each currently active + voice and fill the buffer with the sum. + Override this method if you need to do more work to render your audio. + */ + void renderNextSubBlock (AudioBuffer& outputAudio, + int startSample, + int numSamples) override; + + /** This will simply call renderNextBlock for each currently active + voice and fill the buffer with the sum. (double-precision version) + Override this method if you need to do more work to render your audio. + */ + void renderNextSubBlock (AudioBuffer& outputAudio, + int startSample, + int numSamples) override; + + //============================================================================== + /** Searches through the voices to find one that's not currently playing, and + which can play the given MPE note. + + If all voices are active and stealIfNoneAvailable is false, this returns + a nullptr. If all voices are active and stealIfNoneAvailable is true, + this will call findVoiceToSteal() to find a voice. + + If you need to find a free voice for something else than playing a note + (e.g. for deleting it), you can pass an invalid (default-constructed) MPENote. + */ + virtual MPESynthesiserVoice* findFreeVoice (MPENote noteToFindVoiceFor, + bool stealIfNoneAvailable) const; + + /** Chooses a voice that is most suitable for being re-used to play a new + note, or for being deleted by reduceNumVoices. + + The default method will attempt to find the oldest voice that isn't the + bottom or top note being played. If that's not suitable for your synth, + you can override this method and do something more cunning instead. + + If you pass a valid MPENote for the optional argument, then the note number + of that note will be taken into account for finding the ideal voice to steal. + If you pass an invalid (default-constructed) MPENote instead, this part of + the algorithm will be ignored. + */ + virtual MPESynthesiserVoice* findVoiceToSteal (MPENote noteToStealVoiceFor = MPENote()) const; + + /** Starts a specified voice and tells it to play a particular MPENote. + You should never need to call this, it's called internally by + MPESynthesiserBase::instrument via the noteStarted callback, + but is protected in case it's useful for some custom subclasses. + */ + void startVoice (MPESynthesiserVoice* voice, MPENote noteToStart); + + /** Stops a given voice and tells it to stop playing a particular MPENote + (which should be the same note it is actually playing). + You should never need to call this, it's called internally by + MPESynthesiserBase::instrument via the noteReleased callback, + but is protected in case it's useful for some custom subclasses. + */ + void stopVoice (MPESynthesiserVoice* voice, MPENote noteToStop, bool allowTailOff); + + //============================================================================== + OwnedArray voices; + CriticalSection voicesLock; + +private: + //============================================================================== + bool shouldStealVoices = false; + uint32 lastNoteOnCounter = 0; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MPESynthesiser) +}; + +} // namespace juce diff --git a/JuceLibraryCode/modules/juce_audio_basics/mpe/juce_MPESynthesiserBase.cpp b/JuceLibraryCode/modules/juce_audio_basics/mpe/juce_MPESynthesiserBase.cpp index 51483f0..0a61dfc 100644 --- a/JuceLibraryCode/modules/juce_audio_basics/mpe/juce_MPESynthesiserBase.cpp +++ b/JuceLibraryCode/modules/juce_audio_basics/mpe/juce_MPESynthesiserBase.cpp @@ -1,180 +1,180 @@ -/* - ============================================================================== - - This file is part of the JUCE library. - Copyright (c) 2017 - ROLI Ltd. - - JUCE is an open source library subject to commercial or open-source - licensing. - - The code included in this file is provided under the terms of the ISC license - http://www.isc.org/downloads/software-support-policy/isc-license. Permission - To use, copy, modify, and/or distribute this software for any purpose with or - without fee is hereby granted provided that the above copyright notice and - this permission notice appear in all copies. - - JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER - EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE - DISCLAIMED. - - ============================================================================== -*/ - -namespace juce -{ - -MPESynthesiserBase::MPESynthesiserBase() - : instrument (new MPEInstrument) -{ - instrument->addListener (this); -} - -MPESynthesiserBase::MPESynthesiserBase (MPEInstrument* inst) - : instrument (inst) -{ - jassert (instrument != nullptr); - instrument->addListener (this); -} - -//============================================================================== -MPEZoneLayout MPESynthesiserBase::getZoneLayout() const noexcept -{ - return instrument->getZoneLayout(); -} - -void MPESynthesiserBase::setZoneLayout (MPEZoneLayout newLayout) -{ - instrument->setZoneLayout (newLayout); -} - -//============================================================================== -void MPESynthesiserBase::enableLegacyMode (int pitchbendRange, Range channelRange) -{ - instrument->enableLegacyMode (pitchbendRange, channelRange); -} - -bool MPESynthesiserBase::isLegacyModeEnabled() const noexcept -{ - return instrument->isLegacyModeEnabled(); -} - -Range MPESynthesiserBase::getLegacyModeChannelRange() const noexcept -{ - return instrument->getLegacyModeChannelRange(); -} - -void MPESynthesiserBase::setLegacyModeChannelRange (Range channelRange) -{ - instrument->setLegacyModeChannelRange (channelRange); -} - -int MPESynthesiserBase::getLegacyModePitchbendRange() const noexcept -{ - return instrument->getLegacyModePitchbendRange(); -} - -void MPESynthesiserBase::setLegacyModePitchbendRange (int pitchbendRange) -{ - instrument->setLegacyModePitchbendRange (pitchbendRange); -} - -//============================================================================== -void MPESynthesiserBase::setPressureTrackingMode (TrackingMode modeToUse) -{ - instrument->setPressureTrackingMode (modeToUse); -} - -void MPESynthesiserBase::setPitchbendTrackingMode (TrackingMode modeToUse) -{ - instrument->setPitchbendTrackingMode (modeToUse); -} - -void MPESynthesiserBase::setTimbreTrackingMode (TrackingMode modeToUse) -{ - instrument->setTimbreTrackingMode (modeToUse); -} - -//============================================================================== -void MPESynthesiserBase::handleMidiEvent (const MidiMessage& m) -{ - instrument->processNextMidiEvent (m); -} - -//============================================================================== -template -void MPESynthesiserBase::renderNextBlock (AudioBuffer& outputAudio, - const MidiBuffer& inputMidi, - int startSample, - int numSamples) -{ - // you must set the sample rate before using this! - jassert (sampleRate != 0); - - MidiBuffer::Iterator midiIterator (inputMidi); - midiIterator.setNextSamplePosition (startSample); - - bool firstEvent = true; - int midiEventPos; - MidiMessage m; - - const ScopedLock sl (noteStateLock); - - while (numSamples > 0) - { - if (! midiIterator.getNextEvent (m, midiEventPos)) - { - renderNextSubBlock (outputAudio, startSample, numSamples); - return; - } - - auto samplesToNextMidiMessage = midiEventPos - startSample; - - if (samplesToNextMidiMessage >= numSamples) - { - renderNextSubBlock (outputAudio, startSample, numSamples); - handleMidiEvent (m); - break; - } - - if (samplesToNextMidiMessage < ((firstEvent && ! subBlockSubdivisionIsStrict) ? 1 : minimumSubBlockSize)) - { - handleMidiEvent (m); - continue; - } - - firstEvent = false; - - renderNextSubBlock (outputAudio, startSample, samplesToNextMidiMessage); - handleMidiEvent (m); - startSample += samplesToNextMidiMessage; - numSamples -= samplesToNextMidiMessage; - } - - while (midiIterator.getNextEvent (m, midiEventPos)) - handleMidiEvent (m); -} - -// explicit instantiation for supported float types: -template void MPESynthesiserBase::renderNextBlock (AudioBuffer&, const MidiBuffer&, int, int); -template void MPESynthesiserBase::renderNextBlock (AudioBuffer&, const MidiBuffer&, int, int); - -//============================================================================== -void MPESynthesiserBase::setCurrentPlaybackSampleRate (const double newRate) -{ - if (sampleRate != newRate) - { - const ScopedLock sl (noteStateLock); - instrument->releaseAllNotes(); - sampleRate = newRate; - } -} - -//============================================================================== -void MPESynthesiserBase::setMinimumRenderingSubdivisionSize (int numSamples, bool shouldBeStrict) noexcept -{ - jassert (numSamples > 0); // it wouldn't make much sense for this to be less than 1 - minimumSubBlockSize = numSamples; - subBlockSubdivisionIsStrict = shouldBeStrict; -} - -} // namespace juce +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2017 - ROLI Ltd. + + JUCE is an open source library subject to commercial or open-source + licensing. + + The code included in this file is provided under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license. Permission + To use, copy, modify, and/or distribute this software for any purpose with or + without fee is hereby granted provided that the above copyright notice and + this permission notice appear in all copies. + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +namespace juce +{ + +MPESynthesiserBase::MPESynthesiserBase() + : instrument (new MPEInstrument) +{ + instrument->addListener (this); +} + +MPESynthesiserBase::MPESynthesiserBase (MPEInstrument* inst) + : instrument (inst) +{ + jassert (instrument != nullptr); + instrument->addListener (this); +} + +//============================================================================== +MPEZoneLayout MPESynthesiserBase::getZoneLayout() const noexcept +{ + return instrument->getZoneLayout(); +} + +void MPESynthesiserBase::setZoneLayout (MPEZoneLayout newLayout) +{ + instrument->setZoneLayout (newLayout); +} + +//============================================================================== +void MPESynthesiserBase::enableLegacyMode (int pitchbendRange, Range channelRange) +{ + instrument->enableLegacyMode (pitchbendRange, channelRange); +} + +bool MPESynthesiserBase::isLegacyModeEnabled() const noexcept +{ + return instrument->isLegacyModeEnabled(); +} + +Range MPESynthesiserBase::getLegacyModeChannelRange() const noexcept +{ + return instrument->getLegacyModeChannelRange(); +} + +void MPESynthesiserBase::setLegacyModeChannelRange (Range channelRange) +{ + instrument->setLegacyModeChannelRange (channelRange); +} + +int MPESynthesiserBase::getLegacyModePitchbendRange() const noexcept +{ + return instrument->getLegacyModePitchbendRange(); +} + +void MPESynthesiserBase::setLegacyModePitchbendRange (int pitchbendRange) +{ + instrument->setLegacyModePitchbendRange (pitchbendRange); +} + +//============================================================================== +void MPESynthesiserBase::setPressureTrackingMode (TrackingMode modeToUse) +{ + instrument->setPressureTrackingMode (modeToUse); +} + +void MPESynthesiserBase::setPitchbendTrackingMode (TrackingMode modeToUse) +{ + instrument->setPitchbendTrackingMode (modeToUse); +} + +void MPESynthesiserBase::setTimbreTrackingMode (TrackingMode modeToUse) +{ + instrument->setTimbreTrackingMode (modeToUse); +} + +//============================================================================== +void MPESynthesiserBase::handleMidiEvent (const MidiMessage& m) +{ + instrument->processNextMidiEvent (m); +} + +//============================================================================== +template +void MPESynthesiserBase::renderNextBlock (AudioBuffer& outputAudio, + const MidiBuffer& inputMidi, + int startSample, + int numSamples) +{ + // you must set the sample rate before using this! + jassert (sampleRate != 0); + + MidiBuffer::Iterator midiIterator (inputMidi); + midiIterator.setNextSamplePosition (startSample); + + bool firstEvent = true; + int midiEventPos; + MidiMessage m; + + const ScopedLock sl (noteStateLock); + + while (numSamples > 0) + { + if (! midiIterator.getNextEvent (m, midiEventPos)) + { + renderNextSubBlock (outputAudio, startSample, numSamples); + return; + } + + auto samplesToNextMidiMessage = midiEventPos - startSample; + + if (samplesToNextMidiMessage >= numSamples) + { + renderNextSubBlock (outputAudio, startSample, numSamples); + handleMidiEvent (m); + break; + } + + if (samplesToNextMidiMessage < ((firstEvent && ! subBlockSubdivisionIsStrict) ? 1 : minimumSubBlockSize)) + { + handleMidiEvent (m); + continue; + } + + firstEvent = false; + + renderNextSubBlock (outputAudio, startSample, samplesToNextMidiMessage); + handleMidiEvent (m); + startSample += samplesToNextMidiMessage; + numSamples -= samplesToNextMidiMessage; + } + + while (midiIterator.getNextEvent (m, midiEventPos)) + handleMidiEvent (m); +} + +// explicit instantiation for supported float types: +template void MPESynthesiserBase::renderNextBlock (AudioBuffer&, const MidiBuffer&, int, int); +template void MPESynthesiserBase::renderNextBlock (AudioBuffer&, const MidiBuffer&, int, int); + +//============================================================================== +void MPESynthesiserBase::setCurrentPlaybackSampleRate (const double newRate) +{ + if (sampleRate != newRate) + { + const ScopedLock sl (noteStateLock); + instrument->releaseAllNotes(); + sampleRate = newRate; + } +} + +//============================================================================== +void MPESynthesiserBase::setMinimumRenderingSubdivisionSize (int numSamples, bool shouldBeStrict) noexcept +{ + jassert (numSamples > 0); // it wouldn't make much sense for this to be less than 1 + minimumSubBlockSize = numSamples; + subBlockSubdivisionIsStrict = shouldBeStrict; +} + +} // namespace juce diff --git a/JuceLibraryCode/modules/juce_audio_basics/mpe/juce_MPESynthesiserBase.h b/JuceLibraryCode/modules/juce_audio_basics/mpe/juce_MPESynthesiserBase.h index ca4c026..fc6723c 100644 --- a/JuceLibraryCode/modules/juce_audio_basics/mpe/juce_MPESynthesiserBase.h +++ b/JuceLibraryCode/modules/juce_audio_basics/mpe/juce_MPESynthesiserBase.h @@ -1,210 +1,210 @@ -/* - ============================================================================== - - This file is part of the JUCE library. - Copyright (c) 2017 - ROLI Ltd. - - JUCE is an open source library subject to commercial or open-source - licensing. - - The code included in this file is provided under the terms of the ISC license - http://www.isc.org/downloads/software-support-policy/isc-license. Permission - To use, copy, modify, and/or distribute this software for any purpose with or - without fee is hereby granted provided that the above copyright notice and - this permission notice appear in all copies. - - JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER - EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE - DISCLAIMED. - - ============================================================================== -*/ - -namespace juce -{ - -//============================================================================== -/** - Derive from this class to create a basic audio generator capable of MPE. - Implement the callbacks of MPEInstrument::Listener (noteAdded, notePressureChanged - etc.) to let your audio generator know that MPE notes were triggered, modulated, - or released. What to do inside them, and how that influences your audio generator, - is up to you! - - This class uses an instance of MPEInstrument internally to handle the MPE - note state logic. - - This class is a very low-level base class for an MPE instrument. If you need - something more sophisticated, have a look at MPESynthesiser. This class extends - MPESynthesiserBase by adding the concept of voices that can play notes, - a voice stealing algorithm, and much more. - - @see MPESynthesiser, MPEInstrument - - @tags{Audio} -*/ -struct JUCE_API MPESynthesiserBase : public MPEInstrument::Listener -{ -public: - //============================================================================== - /** Constructor. */ - MPESynthesiserBase(); - - /** Constructor. - - If you use this constructor, the synthesiser will take ownership of the - provided instrument object, and will use it internally to handle the - MPE note state logic. - This is useful if you want to use an instance of your own class derived - from MPEInstrument for the MPE logic. - */ - MPESynthesiserBase (MPEInstrument* instrument); - - //============================================================================== - /** Returns the synthesiser's internal MPE zone layout. - This happens by value, to enforce thread-safety and class invariants. - */ - MPEZoneLayout getZoneLayout() const noexcept; - - /** Re-sets the synthesiser's internal MPE zone layout to the one passed in. - As a side effect, this will discard all currently playing notes, - call noteReleased for all of them, and disable legacy mode (if previously enabled). - */ - void setZoneLayout (MPEZoneLayout newLayout); - - //============================================================================== - /** Tells the synthesiser what the sample rate is for the audio it's being - used to render. - */ - virtual void setCurrentPlaybackSampleRate (double sampleRate); - - /** Returns the current target sample rate at which rendering is being done. - Subclasses may need to know this so that they can pitch things correctly. - */ - double getSampleRate() const noexcept { return sampleRate; } - - //============================================================================== - /** Creates the next block of audio output. - - Call this to make sound. This will chop up the AudioBuffer into subBlock - pieces separated by events in the MIDI buffer, and then call - processNextSubBlock on each one of them. In between you will get calls - to noteAdded/Changed/Finished, where you can update parameters that - depend on those notes to use for your audio rendering. - */ - template - void renderNextBlock (AudioBuffer& outputAudio, - const MidiBuffer& inputMidi, - int startSample, - int numSamples); - - //============================================================================== - /** Handle incoming MIDI events (called from renderNextBlock). - - The default implementation provided here simply forwards everything - to MPEInstrument::processNextMidiEvent, where it is used to update the - MPE notes, zones etc. MIDI messages not relevant for MPE are ignored. - - This method can be overridden if you need to do custom MIDI handling - on top of MPE. The MPESynthesiser class overrides this to implement - callbacks for MIDI program changes and non-MPE-related MIDI controller - messages. - */ - virtual void handleMidiEvent (const MidiMessage&); - - //============================================================================== - /** Sets a minimum limit on the size to which audio sub-blocks will be divided when rendering. - - When rendering, the audio blocks that are passed into renderNextBlock() will be split up - into smaller blocks that lie between all the incoming midi messages, and it is these smaller - sub-blocks that are rendered with multiple calls to renderVoices(). - - Obviously in a pathological case where there are midi messages on every sample, then - renderVoices() could be called once per sample and lead to poor performance, so this - setting allows you to set a lower limit on the block size. - - The default setting is 32, which means that midi messages are accurate to about < 1ms - accuracy, which is probably fine for most purposes, but you may want to increase or - decrease this value for your synth. - - If shouldBeStrict is true, the audio sub-blocks will strictly never be smaller than numSamples. - - If shouldBeStrict is false (default), the first audio sub-block in the buffer is allowed - to be smaller, to make sure that the first MIDI event in a buffer will always be sample-accurate - (this can sometimes help to avoid quantisation or phasing issues). - */ - void setMinimumRenderingSubdivisionSize (int numSamples, bool shouldBeStrict = false) noexcept; - - //============================================================================== - /** Puts the synthesiser into legacy mode. - - @param pitchbendRange The note pitchbend range in semitones to use when in legacy mode. - Must be between 0 and 96, otherwise behaviour is undefined. - The default pitchbend range in legacy mode is +/- 2 semitones. - @param channelRange The range of MIDI channels to use for notes when in legacy mode. - The default is to use all MIDI channels (1-16). - - To get out of legacy mode, set a new MPE zone layout using setZoneLayout. - */ - void enableLegacyMode (int pitchbendRange = 2, - Range channelRange = Range (1, 17)); - - /** Returns true if the instrument is in legacy mode, false otherwise. */ - bool isLegacyModeEnabled() const noexcept; - - /** Returns the range of MIDI channels (1-16) to be used for notes when in legacy mode. */ - Range getLegacyModeChannelRange() const noexcept; - - /** Re-sets the range of MIDI channels (1-16) to be used for notes when in legacy mode. */ - void setLegacyModeChannelRange (Range channelRange); - - /** Returns the pitchbend range in semitones (0-96) to be used for notes when in legacy mode. */ - int getLegacyModePitchbendRange() const noexcept; - - /** Re-sets the pitchbend range in semitones (0-96) to be used for notes when in legacy mode. */ - void setLegacyModePitchbendRange (int pitchbendRange); - - //============================================================================== - using TrackingMode = MPEInstrument::TrackingMode; - - /** Set the MPE tracking mode for the pressure dimension. */ - void setPressureTrackingMode (TrackingMode modeToUse); - - /** Set the MPE tracking mode for the pitchbend dimension. */ - void setPitchbendTrackingMode (TrackingMode modeToUse); - - /** Set the MPE tracking mode for the timbre dimension. */ - void setTimbreTrackingMode (TrackingMode modeToUse); - -protected: - //============================================================================== - /** Implement this method to render your audio inside. - @see renderNextBlock - */ - virtual void renderNextSubBlock (AudioBuffer& outputAudio, - int startSample, - int numSamples) = 0; - - /** Implement this method if you want to render 64-bit audio as well; - otherwise leave blank. - */ - virtual void renderNextSubBlock (AudioBuffer& /*outputAudio*/, - int /*startSample*/, - int /*numSamples*/) {} - -protected: - //============================================================================== - /** @internal */ - std::unique_ptr instrument; - -private: - //============================================================================== - CriticalSection noteStateLock; - double sampleRate = 0.0; - int minimumSubBlockSize = 32; - bool subBlockSubdivisionIsStrict = false; - - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MPESynthesiserBase) -}; - -} // namespace juce +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2017 - ROLI Ltd. + + JUCE is an open source library subject to commercial or open-source + licensing. + + The code included in this file is provided under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license. Permission + To use, copy, modify, and/or distribute this software for any purpose with or + without fee is hereby granted provided that the above copyright notice and + this permission notice appear in all copies. + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +namespace juce +{ + +//============================================================================== +/** + Derive from this class to create a basic audio generator capable of MPE. + Implement the callbacks of MPEInstrument::Listener (noteAdded, notePressureChanged + etc.) to let your audio generator know that MPE notes were triggered, modulated, + or released. What to do inside them, and how that influences your audio generator, + is up to you! + + This class uses an instance of MPEInstrument internally to handle the MPE + note state logic. + + This class is a very low-level base class for an MPE instrument. If you need + something more sophisticated, have a look at MPESynthesiser. This class extends + MPESynthesiserBase by adding the concept of voices that can play notes, + a voice stealing algorithm, and much more. + + @see MPESynthesiser, MPEInstrument + + @tags{Audio} +*/ +struct JUCE_API MPESynthesiserBase : public MPEInstrument::Listener +{ +public: + //============================================================================== + /** Constructor. */ + MPESynthesiserBase(); + + /** Constructor. + + If you use this constructor, the synthesiser will take ownership of the + provided instrument object, and will use it internally to handle the + MPE note state logic. + This is useful if you want to use an instance of your own class derived + from MPEInstrument for the MPE logic. + */ + MPESynthesiserBase (MPEInstrument* instrument); + + //============================================================================== + /** Returns the synthesiser's internal MPE zone layout. + This happens by value, to enforce thread-safety and class invariants. + */ + MPEZoneLayout getZoneLayout() const noexcept; + + /** Re-sets the synthesiser's internal MPE zone layout to the one passed in. + As a side effect, this will discard all currently playing notes, + call noteReleased for all of them, and disable legacy mode (if previously enabled). + */ + void setZoneLayout (MPEZoneLayout newLayout); + + //============================================================================== + /** Tells the synthesiser what the sample rate is for the audio it's being + used to render. + */ + virtual void setCurrentPlaybackSampleRate (double sampleRate); + + /** Returns the current target sample rate at which rendering is being done. + Subclasses may need to know this so that they can pitch things correctly. + */ + double getSampleRate() const noexcept { return sampleRate; } + + //============================================================================== + /** Creates the next block of audio output. + + Call this to make sound. This will chop up the AudioBuffer into subBlock + pieces separated by events in the MIDI buffer, and then call + processNextSubBlock on each one of them. In between you will get calls + to noteAdded/Changed/Finished, where you can update parameters that + depend on those notes to use for your audio rendering. + */ + template + void renderNextBlock (AudioBuffer& outputAudio, + const MidiBuffer& inputMidi, + int startSample, + int numSamples); + + //============================================================================== + /** Handle incoming MIDI events (called from renderNextBlock). + + The default implementation provided here simply forwards everything + to MPEInstrument::processNextMidiEvent, where it is used to update the + MPE notes, zones etc. MIDI messages not relevant for MPE are ignored. + + This method can be overridden if you need to do custom MIDI handling + on top of MPE. The MPESynthesiser class overrides this to implement + callbacks for MIDI program changes and non-MPE-related MIDI controller + messages. + */ + virtual void handleMidiEvent (const MidiMessage&); + + //============================================================================== + /** Sets a minimum limit on the size to which audio sub-blocks will be divided when rendering. + + When rendering, the audio blocks that are passed into renderNextBlock() will be split up + into smaller blocks that lie between all the incoming midi messages, and it is these smaller + sub-blocks that are rendered with multiple calls to renderVoices(). + + Obviously in a pathological case where there are midi messages on every sample, then + renderVoices() could be called once per sample and lead to poor performance, so this + setting allows you to set a lower limit on the block size. + + The default setting is 32, which means that midi messages are accurate to about < 1ms + accuracy, which is probably fine for most purposes, but you may want to increase or + decrease this value for your synth. + + If shouldBeStrict is true, the audio sub-blocks will strictly never be smaller than numSamples. + + If shouldBeStrict is false (default), the first audio sub-block in the buffer is allowed + to be smaller, to make sure that the first MIDI event in a buffer will always be sample-accurate + (this can sometimes help to avoid quantisation or phasing issues). + */ + void setMinimumRenderingSubdivisionSize (int numSamples, bool shouldBeStrict = false) noexcept; + + //============================================================================== + /** Puts the synthesiser into legacy mode. + + @param pitchbendRange The note pitchbend range in semitones to use when in legacy mode. + Must be between 0 and 96, otherwise behaviour is undefined. + The default pitchbend range in legacy mode is +/- 2 semitones. + @param channelRange The range of MIDI channels to use for notes when in legacy mode. + The default is to use all MIDI channels (1-16). + + To get out of legacy mode, set a new MPE zone layout using setZoneLayout. + */ + void enableLegacyMode (int pitchbendRange = 2, + Range channelRange = Range (1, 17)); + + /** Returns true if the instrument is in legacy mode, false otherwise. */ + bool isLegacyModeEnabled() const noexcept; + + /** Returns the range of MIDI channels (1-16) to be used for notes when in legacy mode. */ + Range getLegacyModeChannelRange() const noexcept; + + /** Re-sets the range of MIDI channels (1-16) to be used for notes when in legacy mode. */ + void setLegacyModeChannelRange (Range channelRange); + + /** Returns the pitchbend range in semitones (0-96) to be used for notes when in legacy mode. */ + int getLegacyModePitchbendRange() const noexcept; + + /** Re-sets the pitchbend range in semitones (0-96) to be used for notes when in legacy mode. */ + void setLegacyModePitchbendRange (int pitchbendRange); + + //============================================================================== + using TrackingMode = MPEInstrument::TrackingMode; + + /** Set the MPE tracking mode for the pressure dimension. */ + void setPressureTrackingMode (TrackingMode modeToUse); + + /** Set the MPE tracking mode for the pitchbend dimension. */ + void setPitchbendTrackingMode (TrackingMode modeToUse); + + /** Set the MPE tracking mode for the timbre dimension. */ + void setTimbreTrackingMode (TrackingMode modeToUse); + +protected: + //============================================================================== + /** Implement this method to render your audio inside. + @see renderNextBlock + */ + virtual void renderNextSubBlock (AudioBuffer& outputAudio, + int startSample, + int numSamples) = 0; + + /** Implement this method if you want to render 64-bit audio as well; + otherwise leave blank. + */ + virtual void renderNextSubBlock (AudioBuffer& /*outputAudio*/, + int /*startSample*/, + int /*numSamples*/) {} + +protected: + //============================================================================== + /** @internal */ + std::unique_ptr instrument; + +private: + //============================================================================== + CriticalSection noteStateLock; + double sampleRate = 0.0; + int minimumSubBlockSize = 32; + bool subBlockSubdivisionIsStrict = false; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MPESynthesiserBase) +}; + +} // namespace juce diff --git a/JuceLibraryCode/modules/juce_audio_basics/mpe/juce_MPESynthesiserVoice.cpp b/JuceLibraryCode/modules/juce_audio_basics/mpe/juce_MPESynthesiserVoice.cpp index b20e13d..54fc02b 100644 --- a/JuceLibraryCode/modules/juce_audio_basics/mpe/juce_MPESynthesiserVoice.cpp +++ b/JuceLibraryCode/modules/juce_audio_basics/mpe/juce_MPESynthesiserVoice.cpp @@ -1,50 +1,50 @@ -/* - ============================================================================== - - This file is part of the JUCE library. - Copyright (c) 2017 - ROLI Ltd. - - JUCE is an open source library subject to commercial or open-source - licensing. - - The code included in this file is provided under the terms of the ISC license - http://www.isc.org/downloads/software-support-policy/isc-license. Permission - To use, copy, modify, and/or distribute this software for any purpose with or - without fee is hereby granted provided that the above copyright notice and - this permission notice appear in all copies. - - JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER - EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE - DISCLAIMED. - - ============================================================================== -*/ - -namespace juce -{ - -MPESynthesiserVoice::MPESynthesiserVoice() -{ -} - -MPESynthesiserVoice::~MPESynthesiserVoice() -{ -} - -//============================================================================== -bool MPESynthesiserVoice::isCurrentlyPlayingNote (MPENote note) const noexcept -{ - return isActive() && currentlyPlayingNote.noteID == note.noteID; -} - -bool MPESynthesiserVoice::isPlayingButReleased() const noexcept -{ - return isActive() && currentlyPlayingNote.keyState == MPENote::off; -} - -void MPESynthesiserVoice::clearCurrentNote() noexcept -{ - currentlyPlayingNote = MPENote(); -} - -} // namespace juce +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2017 - ROLI Ltd. + + JUCE is an open source library subject to commercial or open-source + licensing. + + The code included in this file is provided under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license. Permission + To use, copy, modify, and/or distribute this software for any purpose with or + without fee is hereby granted provided that the above copyright notice and + this permission notice appear in all copies. + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +namespace juce +{ + +MPESynthesiserVoice::MPESynthesiserVoice() +{ +} + +MPESynthesiserVoice::~MPESynthesiserVoice() +{ +} + +//============================================================================== +bool MPESynthesiserVoice::isCurrentlyPlayingNote (MPENote note) const noexcept +{ + return isActive() && currentlyPlayingNote.noteID == note.noteID; +} + +bool MPESynthesiserVoice::isPlayingButReleased() const noexcept +{ + return isActive() && currentlyPlayingNote.keyState == MPENote::off; +} + +void MPESynthesiserVoice::clearCurrentNote() noexcept +{ + currentlyPlayingNote = MPENote(); +} + +} // namespace juce diff --git a/JuceLibraryCode/modules/juce_audio_basics/mpe/juce_MPESynthesiserVoice.h b/JuceLibraryCode/modules/juce_audio_basics/mpe/juce_MPESynthesiserVoice.h index febd84f..93b8975 100644 --- a/JuceLibraryCode/modules/juce_audio_basics/mpe/juce_MPESynthesiserVoice.h +++ b/JuceLibraryCode/modules/juce_audio_basics/mpe/juce_MPESynthesiserVoice.h @@ -1,191 +1,191 @@ -/* - ============================================================================== - - This file is part of the JUCE library. - Copyright (c) 2017 - ROLI Ltd. - - JUCE is an open source library subject to commercial or open-source - licensing. - - The code included in this file is provided under the terms of the ISC license - http://www.isc.org/downloads/software-support-policy/isc-license. Permission - To use, copy, modify, and/or distribute this software for any purpose with or - without fee is hereby granted provided that the above copyright notice and - this permission notice appear in all copies. - - JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER - EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE - DISCLAIMED. - - ============================================================================== -*/ - -namespace juce -{ - -//============================================================================== -/** - Represents an MPE voice that an MPESynthesiser can use to play a sound. - - A voice plays a single sound at a time, and a synthesiser holds an array of - voices so that it can play polyphonically. - - @see MPESynthesiser, MPENote - - @tags{Audio} -*/ -class JUCE_API MPESynthesiserVoice -{ -public: - //============================================================================== - /** Constructor. */ - MPESynthesiserVoice(); - - /** Destructor. */ - virtual ~MPESynthesiserVoice(); - - /** Returns the MPENote that this voice is currently playing. - Returns an invalid MPENote if no note is playing - (you can check this using MPENote::isValid() or MPEVoice::isActive()). - */ - MPENote getCurrentlyPlayingNote() const noexcept { return currentlyPlayingNote; } - - /** Returns true if the voice is currently playing the given MPENote - (as identified by the note's initial note number and MIDI channel). - */ - bool isCurrentlyPlayingNote (MPENote note) const noexcept; - - /** Returns true if this voice is currently busy playing a sound. - By default this just checks whether getCurrentlyPlayingNote() - returns a valid MPE note, but can be overridden for more advanced checking. - */ - virtual bool isActive() const { return currentlyPlayingNote.isValid(); } - - /** Returns true if a voice is sounding in its release phase. **/ - bool isPlayingButReleased() const noexcept; - - /** Called by the MPESynthesiser to let the voice know that a new note has started on it. - This will be called during the rendering callback, so must be fast and thread-safe. - */ - virtual void noteStarted() = 0; - - /** Called by the MPESynthesiser to let the voice know that its currently playing note has stopped. - This will be called during the rendering callback, so must be fast and thread-safe. - - If allowTailOff is false or the voice doesn't want to tail-off, then it must stop all - sound immediately, and must call clearCurrentNote() to reset the state of this voice - and allow the synth to reassign it another sound. - - If allowTailOff is true and the voice decides to do a tail-off, then it's allowed to - begin fading out its sound, and it can stop playing until it's finished. As soon as it - finishes playing (during the rendering callback), it must make sure that it calls - clearCurrentNote(). - */ - virtual void noteStopped (bool allowTailOff) = 0; - - /** Called by the MPESynthesiser to let the voice know that its currently playing note - has changed its pressure value. - This will be called during the rendering callback, so must be fast and thread-safe. - */ - virtual void notePressureChanged() = 0; - - /** Called by the MPESynthesiser to let the voice know that its currently playing note - has changed its pitchbend value. - This will be called during the rendering callback, so must be fast and thread-safe. - - Note: You can call currentlyPlayingNote.getFrequencyInHertz() to find out the effective frequency - of the note, as a sum of the initial note number, the per-note pitchbend and the master pitchbend. - */ - virtual void notePitchbendChanged() = 0; - - /** Called by the MPESynthesiser to let the voice know that its currently playing note - has changed its timbre value. - This will be called during the rendering callback, so must be fast and thread-safe. - */ - virtual void noteTimbreChanged() = 0; - - /** Called by the MPESynthesiser to let the voice know that its currently playing note - has changed its key state. - This typically happens when a sustain or sostenuto pedal is pressed or released (on - an MPE channel relevant for this note), or if the note key is lifted while the sustained - or sostenuto pedal is still held down. - This will be called during the rendering callback, so must be fast and thread-safe. - */ - virtual void noteKeyStateChanged() = 0; - - /** Renders the next block of data for this voice. - - The output audio data must be added to the current contents of the buffer provided. - Only the region of the buffer between startSample and (startSample + numSamples) - should be altered by this method. - - If the voice is currently silent, it should just return without doing anything. - - If the sound that the voice is playing finishes during the course of this rendered - block, it must call clearCurrentNote(), to tell the synthesiser that it has finished. - - The size of the blocks that are rendered can change each time it is called, and may - involve rendering as little as 1 sample at a time. In between rendering callbacks, - the voice's methods will be called to tell it about note and controller events. - */ - virtual void renderNextBlock (AudioBuffer& outputBuffer, - int startSample, - int numSamples) = 0; - - /** Renders the next block of 64-bit data for this voice. - - Support for 64-bit audio is optional. You can choose to not override this method if - you don't need it (the default implementation simply does nothing). - */ - virtual void renderNextBlock (AudioBuffer& /*outputBuffer*/, - int /*startSample*/, - int /*numSamples*/) {} - - /** Changes the voice's reference sample rate. - - The rate is set so that subclasses know the output rate and can set their pitch - accordingly. - - This method is called by the synth, and subclasses can access the current rate with - the currentSampleRate member. - */ - virtual void setCurrentSampleRate (double newRate) { currentSampleRate = newRate; } - - /** Returns the current target sample rate at which rendering is being done. - Subclasses may need to know this so that they can pitch things correctly. - */ - double getSampleRate() const noexcept { return currentSampleRate; } - - /** This will be set to an incrementing counter value in MPESynthesiser::startVoice() - and can be used to determine the order in which voices started. - */ - uint32 noteOnTime = 0; - -protected: - //============================================================================== - /** Resets the state of this voice after a sound has finished playing. - - The subclass must call this when it finishes playing a note and becomes available - to play new ones. - - It must either call it in the stopNote() method, or if the voice is tailing off, - then it should call it later during the renderNextBlock method, as soon as it - finishes its tail-off. - - It can also be called at any time during the render callback if the sound happens - to have finished, e.g. if it's playing a sample and the sample finishes. - */ - void clearCurrentNote() noexcept; - - //============================================================================== - double currentSampleRate = 0.0; - MPENote currentlyPlayingNote; - -private: - //============================================================================== - friend class MPESynthesiser; - - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MPESynthesiserVoice) -}; - -} // namespace juce +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2017 - ROLI Ltd. + + JUCE is an open source library subject to commercial or open-source + licensing. + + The code included in this file is provided under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license. Permission + To use, copy, modify, and/or distribute this software for any purpose with or + without fee is hereby granted provided that the above copyright notice and + this permission notice appear in all copies. + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +namespace juce +{ + +//============================================================================== +/** + Represents an MPE voice that an MPESynthesiser can use to play a sound. + + A voice plays a single sound at a time, and a synthesiser holds an array of + voices so that it can play polyphonically. + + @see MPESynthesiser, MPENote + + @tags{Audio} +*/ +class JUCE_API MPESynthesiserVoice +{ +public: + //============================================================================== + /** Constructor. */ + MPESynthesiserVoice(); + + /** Destructor. */ + virtual ~MPESynthesiserVoice(); + + /** Returns the MPENote that this voice is currently playing. + Returns an invalid MPENote if no note is playing + (you can check this using MPENote::isValid() or MPEVoice::isActive()). + */ + MPENote getCurrentlyPlayingNote() const noexcept { return currentlyPlayingNote; } + + /** Returns true if the voice is currently playing the given MPENote + (as identified by the note's initial note number and MIDI channel). + */ + bool isCurrentlyPlayingNote (MPENote note) const noexcept; + + /** Returns true if this voice is currently busy playing a sound. + By default this just checks whether getCurrentlyPlayingNote() + returns a valid MPE note, but can be overridden for more advanced checking. + */ + virtual bool isActive() const { return currentlyPlayingNote.isValid(); } + + /** Returns true if a voice is sounding in its release phase. **/ + bool isPlayingButReleased() const noexcept; + + /** Called by the MPESynthesiser to let the voice know that a new note has started on it. + This will be called during the rendering callback, so must be fast and thread-safe. + */ + virtual void noteStarted() = 0; + + /** Called by the MPESynthesiser to let the voice know that its currently playing note has stopped. + This will be called during the rendering callback, so must be fast and thread-safe. + + If allowTailOff is false or the voice doesn't want to tail-off, then it must stop all + sound immediately, and must call clearCurrentNote() to reset the state of this voice + and allow the synth to reassign it another sound. + + If allowTailOff is true and the voice decides to do a tail-off, then it's allowed to + begin fading out its sound, and it can stop playing until it's finished. As soon as it + finishes playing (during the rendering callback), it must make sure that it calls + clearCurrentNote(). + */ + virtual void noteStopped (bool allowTailOff) = 0; + + /** Called by the MPESynthesiser to let the voice know that its currently playing note + has changed its pressure value. + This will be called during the rendering callback, so must be fast and thread-safe. + */ + virtual void notePressureChanged() = 0; + + /** Called by the MPESynthesiser to let the voice know that its currently playing note + has changed its pitchbend value. + This will be called during the rendering callback, so must be fast and thread-safe. + + Note: You can call currentlyPlayingNote.getFrequencyInHertz() to find out the effective frequency + of the note, as a sum of the initial note number, the per-note pitchbend and the master pitchbend. + */ + virtual void notePitchbendChanged() = 0; + + /** Called by the MPESynthesiser to let the voice know that its currently playing note + has changed its timbre value. + This will be called during the rendering callback, so must be fast and thread-safe. + */ + virtual void noteTimbreChanged() = 0; + + /** Called by the MPESynthesiser to let the voice know that its currently playing note + has changed its key state. + This typically happens when a sustain or sostenuto pedal is pressed or released (on + an MPE channel relevant for this note), or if the note key is lifted while the sustained + or sostenuto pedal is still held down. + This will be called during the rendering callback, so must be fast and thread-safe. + */ + virtual void noteKeyStateChanged() = 0; + + /** Renders the next block of data for this voice. + + The output audio data must be added to the current contents of the buffer provided. + Only the region of the buffer between startSample and (startSample + numSamples) + should be altered by this method. + + If the voice is currently silent, it should just return without doing anything. + + If the sound that the voice is playing finishes during the course of this rendered + block, it must call clearCurrentNote(), to tell the synthesiser that it has finished. + + The size of the blocks that are rendered can change each time it is called, and may + involve rendering as little as 1 sample at a time. In between rendering callbacks, + the voice's methods will be called to tell it about note and controller events. + */ + virtual void renderNextBlock (AudioBuffer& outputBuffer, + int startSample, + int numSamples) = 0; + + /** Renders the next block of 64-bit data for this voice. + + Support for 64-bit audio is optional. You can choose to not override this method if + you don't need it (the default implementation simply does nothing). + */ + virtual void renderNextBlock (AudioBuffer& /*outputBuffer*/, + int /*startSample*/, + int /*numSamples*/) {} + + /** Changes the voice's reference sample rate. + + The rate is set so that subclasses know the output rate and can set their pitch + accordingly. + + This method is called by the synth, and subclasses can access the current rate with + the currentSampleRate member. + */ + virtual void setCurrentSampleRate (double newRate) { currentSampleRate = newRate; } + + /** Returns the current target sample rate at which rendering is being done. + Subclasses may need to know this so that they can pitch things correctly. + */ + double getSampleRate() const noexcept { return currentSampleRate; } + + /** This will be set to an incrementing counter value in MPESynthesiser::startVoice() + and can be used to determine the order in which voices started. + */ + uint32 noteOnTime = 0; + +protected: + //============================================================================== + /** Resets the state of this voice after a sound has finished playing. + + The subclass must call this when it finishes playing a note and becomes available + to play new ones. + + It must either call it in the stopNote() method, or if the voice is tailing off, + then it should call it later during the renderNextBlock method, as soon as it + finishes its tail-off. + + It can also be called at any time during the render callback if the sound happens + to have finished, e.g. if it's playing a sample and the sample finishes. + */ + void clearCurrentNote() noexcept; + + //============================================================================== + double currentSampleRate = 0.0; + MPENote currentlyPlayingNote; + +private: + //============================================================================== + friend class MPESynthesiser; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MPESynthesiserVoice) +}; + +} // namespace juce diff --git a/JuceLibraryCode/modules/juce_audio_basics/mpe/juce_MPEUtils.cpp b/JuceLibraryCode/modules/juce_audio_basics/mpe/juce_MPEUtils.cpp index 8ebc185..8ff2bdd 100644 --- a/JuceLibraryCode/modules/juce_audio_basics/mpe/juce_MPEUtils.cpp +++ b/JuceLibraryCode/modules/juce_audio_basics/mpe/juce_MPEUtils.cpp @@ -1,494 +1,494 @@ -/* - ============================================================================== - - This file is part of the JUCE library. - Copyright (c) 2017 - ROLI Ltd. - - JUCE is an open source library subject to commercial or open-source - licensing. - - The code included in this file is provided under the terms of the ISC license - http://www.isc.org/downloads/software-support-policy/isc-license. Permission - To use, copy, modify, and/or distribute this software for any purpose with or - without fee is hereby granted provided that the above copyright notice and - this permission notice appear in all copies. - - JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER - EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE - DISCLAIMED. - - ============================================================================== -*/ - -namespace juce -{ - -MPEChannelAssigner::MPEChannelAssigner (MPEZoneLayout::Zone zoneToUse) - : zone (new MPEZoneLayout::Zone (zoneToUse)), - channelIncrement (zone->isLowerZone() ? 1 : -1), - numChannels (zone->numMemberChannels), - firstChannel (zone->getFirstMemberChannel()), - lastChannel (zone->getLastMemberChannel()), - midiChannelLastAssigned (firstChannel - channelIncrement) -{ - // must be an active MPE zone! - jassert (numChannels > 0); -} - -MPEChannelAssigner::MPEChannelAssigner (Range channelRange) - : isLegacy (true), - channelIncrement (1), - numChannels (channelRange.getLength()), - firstChannel (channelRange.getStart()), - lastChannel (channelRange.getEnd() - 1), - midiChannelLastAssigned (firstChannel - channelIncrement) -{ - // must have at least one channel! - jassert (! channelRange.isEmpty()); -} - -int MPEChannelAssigner::findMidiChannelForNewNote (int noteNumber) noexcept -{ - if (numChannels <= 1) - return firstChannel; - - for (auto ch = firstChannel; (isLegacy || zone->isLowerZone() ? ch <= lastChannel : ch >= lastChannel); ch += channelIncrement) - { - if (midiChannels[ch].isFree() && midiChannels[ch].lastNotePlayed == noteNumber) - { - midiChannelLastAssigned = ch; - midiChannels[ch].notes.add (noteNumber); - return ch; - } - } - - for (auto ch = midiChannelLastAssigned + channelIncrement; ; ch += channelIncrement) - { - if (ch == lastChannel + channelIncrement) // loop wrap-around - ch = firstChannel; - - if (midiChannels[ch].isFree()) - { - midiChannelLastAssigned = ch; - midiChannels[ch].notes.add (noteNumber); - return ch; - } - - if (ch == midiChannelLastAssigned) - break; // no free channels! - } - - midiChannelLastAssigned = findMidiChannelPlayingClosestNonequalNote (noteNumber); - midiChannels[midiChannelLastAssigned].notes.add (noteNumber); - - return midiChannelLastAssigned; -} - -void MPEChannelAssigner::noteOff (int noteNumber, int midiChannel) -{ - const auto removeNote = [] (MidiChannel& ch, int noteNum) - { - if (ch.notes.removeAllInstancesOf (noteNum) > 0) - { - ch.lastNotePlayed = noteNum; - return true; - } - - return false; - }; - - if (midiChannel >= 0 && midiChannel < 17) - { - removeNote (midiChannels[midiChannel], noteNumber); - return; - } - - for (auto& ch : midiChannels) - { - if (removeNote (ch, noteNumber)) - return; - } -} - -void MPEChannelAssigner::allNotesOff() -{ - for (auto& ch : midiChannels) - { - if (ch.notes.size() > 0) - ch.lastNotePlayed = ch.notes.getLast(); - - ch.notes.clear(); - } -} - -int MPEChannelAssigner::findMidiChannelPlayingClosestNonequalNote (int noteNumber) noexcept -{ - auto channelWithClosestNote = firstChannel; - int closestNoteDistance = 127; - - for (auto ch = firstChannel; (isLegacy || zone->isLowerZone() ? ch <= lastChannel : ch >= lastChannel); ch += channelIncrement) - { - for (auto note : midiChannels[ch].notes) - { - auto noteDistance = std::abs (note - noteNumber); - - if (noteDistance > 0 && noteDistance < closestNoteDistance) - { - closestNoteDistance = noteDistance; - channelWithClosestNote = ch; - } - } - } - - return channelWithClosestNote; -} - -//============================================================================== -MPEChannelRemapper::MPEChannelRemapper (MPEZoneLayout::Zone zoneToRemap) - : zone (zoneToRemap), - channelIncrement (zone.isLowerZone() ? 1 : -1), - firstChannel (zone.getFirstMemberChannel()), - lastChannel (zone.getLastMemberChannel()) -{ - // must be an active MPE zone! - jassert (zone.numMemberChannels > 0); - zeroArrays(); -} - -void MPEChannelRemapper::remapMidiChannelIfNeeded (MidiMessage& message, uint32 mpeSourceID) noexcept -{ - auto channel = message.getChannel(); - - if (! zone.isUsingChannelAsMemberChannel (channel)) - return; - - if (channel == zone.getMasterChannel() && (message.isResetAllControllers() || message.isAllNotesOff())) - { - clearSource (mpeSourceID); - return; - } - - auto sourceAndChannelID = (((uint32) mpeSourceID << 5) | (uint32) (channel)); - - if (messageIsNoteData (message)) - { - ++counter; - - // fast path - no remap - if (applyRemapIfExisting (channel, sourceAndChannelID, message)) - return; - - // find existing remap - for (int chan = firstChannel; (zone.isLowerZone() ? chan <= lastChannel : chan >= lastChannel); chan += channelIncrement) - if (applyRemapIfExisting (chan, sourceAndChannelID, message)) - return; - - // no remap necessary - if (sourceAndChannel[channel] == notMPE) - { - lastUsed[channel] = counter; - sourceAndChannel[channel] = sourceAndChannelID; - return; - } - - // remap source & channel to new channel - auto chan = getBestChanToReuse(); - - sourceAndChannel[chan] = sourceAndChannelID; - lastUsed[chan] = counter; - message.setChannel (chan); - } -} - -void MPEChannelRemapper::reset() noexcept -{ - for (auto& s : sourceAndChannel) - s = notMPE; -} - -void MPEChannelRemapper::clearChannel (int channel) noexcept -{ - sourceAndChannel[channel] = notMPE; -} - -void MPEChannelRemapper::clearSource (uint32 mpeSourceID) -{ - for (auto& s : sourceAndChannel) - { - if (uint32 (s >> 5) == mpeSourceID) - { - s = notMPE; - return; - } - } -} - -bool MPEChannelRemapper::applyRemapIfExisting (int channel, uint32 sourceAndChannelID, MidiMessage& m) noexcept -{ - if (sourceAndChannel[channel] == sourceAndChannelID) - { - if (m.isNoteOff()) - sourceAndChannel[channel] = notMPE; - else - lastUsed[channel] = counter; - - m.setChannel (channel); - return true; - } - - return false; -} - -int MPEChannelRemapper::getBestChanToReuse() const noexcept -{ - for (int chan = firstChannel; (zone.isLowerZone() ? chan <= lastChannel : chan >= lastChannel); chan += channelIncrement) - if (sourceAndChannel[chan] == notMPE) - return chan; - - auto bestChan = firstChannel; - auto bestLastUse = counter; - - for (int chan = firstChannel; (zone.isLowerZone() ? chan <= lastChannel : chan >= lastChannel); chan += channelIncrement) - { - if (lastUsed[chan] < bestLastUse) - { - bestLastUse = lastUsed[chan]; - bestChan = chan; - } - } - - return bestChan; -} - -void MPEChannelRemapper::zeroArrays() -{ - for (int i = 0; i < 17; ++i) - { - sourceAndChannel[i] = 0; - lastUsed[i] = 0; - } -} - - -//============================================================================== -//============================================================================== -#if JUCE_UNIT_TESTS - -struct MPEUtilsUnitTests : public UnitTest -{ - MPEUtilsUnitTests() - : UnitTest ("MPE Utilities", UnitTestCategories::midi) - {} - - void runTest() override - { - beginTest ("MPEChannelAssigner"); - { - MPEZoneLayout layout; - - // lower - { - layout.setLowerZone (15); - - // lower zone - MPEChannelAssigner channelAssigner (layout.getLowerZone()); - - // check that channels are assigned in correct order - int noteNum = 60; - for (int ch = 2; ch <= 16; ++ch) - expectEquals (channelAssigner.findMidiChannelForNewNote (noteNum++), ch); - - // check that note-offs are processed - channelAssigner.noteOff (60); - expectEquals (channelAssigner.findMidiChannelForNewNote (60), 2); - - channelAssigner.noteOff (61); - expectEquals (channelAssigner.findMidiChannelForNewNote (61), 3); - - // check that assigned channel was last to play note - channelAssigner.noteOff (65); - channelAssigner.noteOff (66); - expectEquals (channelAssigner.findMidiChannelForNewNote (66), 8); - expectEquals (channelAssigner.findMidiChannelForNewNote (65), 7); - - // find closest channel playing nonequal note - expectEquals (channelAssigner.findMidiChannelForNewNote (80), 16); - expectEquals (channelAssigner.findMidiChannelForNewNote (55), 2); - - // all notes off - channelAssigner.allNotesOff(); - - // last note played - expectEquals (channelAssigner.findMidiChannelForNewNote (66), 8); - expectEquals (channelAssigner.findMidiChannelForNewNote (65), 7); - expectEquals (channelAssigner.findMidiChannelForNewNote (80), 16); - expectEquals (channelAssigner.findMidiChannelForNewNote (55), 2); - - // normal assignment - expectEquals (channelAssigner.findMidiChannelForNewNote (101), 3); - expectEquals (channelAssigner.findMidiChannelForNewNote (20), 4); - } - - // upper - { - layout.setUpperZone (15); - - // upper zone - MPEChannelAssigner channelAssigner (layout.getUpperZone()); - - // check that channels are assigned in correct order - int noteNum = 60; - for (int ch = 15; ch >= 1; --ch) - expectEquals (channelAssigner.findMidiChannelForNewNote (noteNum++), ch); - - // check that note-offs are processed - channelAssigner.noteOff (60); - expectEquals (channelAssigner.findMidiChannelForNewNote (60), 15); - - channelAssigner.noteOff (61); - expectEquals (channelAssigner.findMidiChannelForNewNote (61), 14); - - // check that assigned channel was last to play note - channelAssigner.noteOff (65); - channelAssigner.noteOff (66); - expectEquals (channelAssigner.findMidiChannelForNewNote (66), 9); - expectEquals (channelAssigner.findMidiChannelForNewNote (65), 10); - - // find closest channel playing nonequal note - expectEquals (channelAssigner.findMidiChannelForNewNote (80), 1); - expectEquals (channelAssigner.findMidiChannelForNewNote (55), 15); - - // all notes off - channelAssigner.allNotesOff(); - - // last note played - expectEquals (channelAssigner.findMidiChannelForNewNote (66), 9); - expectEquals (channelAssigner.findMidiChannelForNewNote (65), 10); - expectEquals (channelAssigner.findMidiChannelForNewNote (80), 1); - expectEquals (channelAssigner.findMidiChannelForNewNote (55), 15); - - // normal assignment - expectEquals (channelAssigner.findMidiChannelForNewNote (101), 14); - expectEquals (channelAssigner.findMidiChannelForNewNote (20), 13); - } - - // legacy - { - MPEChannelAssigner channelAssigner; - - // check that channels are assigned in correct order - int noteNum = 60; - for (int ch = 1; ch <= 16; ++ch) - expectEquals (channelAssigner.findMidiChannelForNewNote (noteNum++), ch); - - // check that note-offs are processed - channelAssigner.noteOff (60); - expectEquals (channelAssigner.findMidiChannelForNewNote (60), 1); - - channelAssigner.noteOff (61); - expectEquals (channelAssigner.findMidiChannelForNewNote (61), 2); - - // check that assigned channel was last to play note - channelAssigner.noteOff (65); - channelAssigner.noteOff (66); - expectEquals (channelAssigner.findMidiChannelForNewNote (66), 7); - expectEquals (channelAssigner.findMidiChannelForNewNote (65), 6); - - // find closest channel playing nonequal note - expectEquals (channelAssigner.findMidiChannelForNewNote (80), 16); - expectEquals (channelAssigner.findMidiChannelForNewNote (55), 1); - - // all notes off - channelAssigner.allNotesOff(); - - // last note played - expectEquals (channelAssigner.findMidiChannelForNewNote (66), 7); - expectEquals (channelAssigner.findMidiChannelForNewNote (65), 6); - expectEquals (channelAssigner.findMidiChannelForNewNote (80), 16); - expectEquals (channelAssigner.findMidiChannelForNewNote (55), 1); - - // normal assignment - expectEquals (channelAssigner.findMidiChannelForNewNote (101), 2); - expectEquals (channelAssigner.findMidiChannelForNewNote (20), 3); - } - } - - beginTest ("MPEChannelRemapper"); - { - // 3 different MPE 'sources', constant IDs - const int sourceID1 = 0; - const int sourceID2 = 1; - const int sourceID3 = 2; - - MPEZoneLayout layout; - - { - layout.setLowerZone (15); - - // lower zone - MPEChannelRemapper channelRemapper (layout.getLowerZone()); - - // first source, shouldn't remap - for (int ch = 2; ch <= 16; ++ch) - { - auto noteOn = MidiMessage::noteOn (ch, 60, 1.0f); - - channelRemapper.remapMidiChannelIfNeeded (noteOn, sourceID1); - expectEquals (noteOn.getChannel(), ch); - } - - auto noteOn = MidiMessage::noteOn (2, 60, 1.0f); - - // remap onto oldest last-used channel - channelRemapper.remapMidiChannelIfNeeded (noteOn, sourceID2); - expectEquals (noteOn.getChannel(), 2); - - // remap onto oldest last-used channel - channelRemapper.remapMidiChannelIfNeeded (noteOn, sourceID3); - expectEquals (noteOn.getChannel(), 3); - - // remap to correct channel for source ID - auto noteOff = MidiMessage::noteOff (2, 60, 1.0f); - channelRemapper.remapMidiChannelIfNeeded (noteOff, sourceID3); - expectEquals (noteOff.getChannel(), 3); - } - - { - layout.setUpperZone (15); - - // upper zone - MPEChannelRemapper channelRemapper (layout.getUpperZone()); - - // first source, shouldn't remap - for (int ch = 15; ch >= 1; --ch) - { - auto noteOn = MidiMessage::noteOn (ch, 60, 1.0f); - - channelRemapper.remapMidiChannelIfNeeded (noteOn, sourceID1); - expectEquals (noteOn.getChannel(), ch); - } - - auto noteOn = MidiMessage::noteOn (15, 60, 1.0f); - - // remap onto oldest last-used channel - channelRemapper.remapMidiChannelIfNeeded (noteOn, sourceID2); - expectEquals (noteOn.getChannel(), 15); - - // remap onto oldest last-used channel - channelRemapper.remapMidiChannelIfNeeded (noteOn, sourceID3); - expectEquals (noteOn.getChannel(), 14); - - // remap to correct channel for source ID - auto noteOff = MidiMessage::noteOff (15, 60, 1.0f); - channelRemapper.remapMidiChannelIfNeeded (noteOff, sourceID3); - expectEquals (noteOff.getChannel(), 14); - } - } - } -}; - -static MPEUtilsUnitTests MPEUtilsUnitTests; - -#endif - -} // namespace juce +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2017 - ROLI Ltd. + + JUCE is an open source library subject to commercial or open-source + licensing. + + The code included in this file is provided under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license. Permission + To use, copy, modify, and/or distribute this software for any purpose with or + without fee is hereby granted provided that the above copyright notice and + this permission notice appear in all copies. + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +namespace juce +{ + +MPEChannelAssigner::MPEChannelAssigner (MPEZoneLayout::Zone zoneToUse) + : zone (new MPEZoneLayout::Zone (zoneToUse)), + channelIncrement (zone->isLowerZone() ? 1 : -1), + numChannels (zone->numMemberChannels), + firstChannel (zone->getFirstMemberChannel()), + lastChannel (zone->getLastMemberChannel()), + midiChannelLastAssigned (firstChannel - channelIncrement) +{ + // must be an active MPE zone! + jassert (numChannels > 0); +} + +MPEChannelAssigner::MPEChannelAssigner (Range channelRange) + : isLegacy (true), + channelIncrement (1), + numChannels (channelRange.getLength()), + firstChannel (channelRange.getStart()), + lastChannel (channelRange.getEnd() - 1), + midiChannelLastAssigned (firstChannel - channelIncrement) +{ + // must have at least one channel! + jassert (! channelRange.isEmpty()); +} + +int MPEChannelAssigner::findMidiChannelForNewNote (int noteNumber) noexcept +{ + if (numChannels <= 1) + return firstChannel; + + for (auto ch = firstChannel; (isLegacy || zone->isLowerZone() ? ch <= lastChannel : ch >= lastChannel); ch += channelIncrement) + { + if (midiChannels[ch].isFree() && midiChannels[ch].lastNotePlayed == noteNumber) + { + midiChannelLastAssigned = ch; + midiChannels[ch].notes.add (noteNumber); + return ch; + } + } + + for (auto ch = midiChannelLastAssigned + channelIncrement; ; ch += channelIncrement) + { + if (ch == lastChannel + channelIncrement) // loop wrap-around + ch = firstChannel; + + if (midiChannels[ch].isFree()) + { + midiChannelLastAssigned = ch; + midiChannels[ch].notes.add (noteNumber); + return ch; + } + + if (ch == midiChannelLastAssigned) + break; // no free channels! + } + + midiChannelLastAssigned = findMidiChannelPlayingClosestNonequalNote (noteNumber); + midiChannels[midiChannelLastAssigned].notes.add (noteNumber); + + return midiChannelLastAssigned; +} + +void MPEChannelAssigner::noteOff (int noteNumber, int midiChannel) +{ + const auto removeNote = [] (MidiChannel& ch, int noteNum) + { + if (ch.notes.removeAllInstancesOf (noteNum) > 0) + { + ch.lastNotePlayed = noteNum; + return true; + } + + return false; + }; + + if (midiChannel >= 0 && midiChannel < 17) + { + removeNote (midiChannels[midiChannel], noteNumber); + return; + } + + for (auto& ch : midiChannels) + { + if (removeNote (ch, noteNumber)) + return; + } +} + +void MPEChannelAssigner::allNotesOff() +{ + for (auto& ch : midiChannels) + { + if (ch.notes.size() > 0) + ch.lastNotePlayed = ch.notes.getLast(); + + ch.notes.clear(); + } +} + +int MPEChannelAssigner::findMidiChannelPlayingClosestNonequalNote (int noteNumber) noexcept +{ + auto channelWithClosestNote = firstChannel; + int closestNoteDistance = 127; + + for (auto ch = firstChannel; (isLegacy || zone->isLowerZone() ? ch <= lastChannel : ch >= lastChannel); ch += channelIncrement) + { + for (auto note : midiChannels[ch].notes) + { + auto noteDistance = std::abs (note - noteNumber); + + if (noteDistance > 0 && noteDistance < closestNoteDistance) + { + closestNoteDistance = noteDistance; + channelWithClosestNote = ch; + } + } + } + + return channelWithClosestNote; +} + +//============================================================================== +MPEChannelRemapper::MPEChannelRemapper (MPEZoneLayout::Zone zoneToRemap) + : zone (zoneToRemap), + channelIncrement (zone.isLowerZone() ? 1 : -1), + firstChannel (zone.getFirstMemberChannel()), + lastChannel (zone.getLastMemberChannel()) +{ + // must be an active MPE zone! + jassert (zone.numMemberChannels > 0); + zeroArrays(); +} + +void MPEChannelRemapper::remapMidiChannelIfNeeded (MidiMessage& message, uint32 mpeSourceID) noexcept +{ + auto channel = message.getChannel(); + + if (! zone.isUsingChannelAsMemberChannel (channel)) + return; + + if (channel == zone.getMasterChannel() && (message.isResetAllControllers() || message.isAllNotesOff())) + { + clearSource (mpeSourceID); + return; + } + + auto sourceAndChannelID = (((uint32) mpeSourceID << 5) | (uint32) (channel)); + + if (messageIsNoteData (message)) + { + ++counter; + + // fast path - no remap + if (applyRemapIfExisting (channel, sourceAndChannelID, message)) + return; + + // find existing remap + for (int chan = firstChannel; (zone.isLowerZone() ? chan <= lastChannel : chan >= lastChannel); chan += channelIncrement) + if (applyRemapIfExisting (chan, sourceAndChannelID, message)) + return; + + // no remap necessary + if (sourceAndChannel[channel] == notMPE) + { + lastUsed[channel] = counter; + sourceAndChannel[channel] = sourceAndChannelID; + return; + } + + // remap source & channel to new channel + auto chan = getBestChanToReuse(); + + sourceAndChannel[chan] = sourceAndChannelID; + lastUsed[chan] = counter; + message.setChannel (chan); + } +} + +void MPEChannelRemapper::reset() noexcept +{ + for (auto& s : sourceAndChannel) + s = notMPE; +} + +void MPEChannelRemapper::clearChannel (int channel) noexcept +{ + sourceAndChannel[channel] = notMPE; +} + +void MPEChannelRemapper::clearSource (uint32 mpeSourceID) +{ + for (auto& s : sourceAndChannel) + { + if (uint32 (s >> 5) == mpeSourceID) + { + s = notMPE; + return; + } + } +} + +bool MPEChannelRemapper::applyRemapIfExisting (int channel, uint32 sourceAndChannelID, MidiMessage& m) noexcept +{ + if (sourceAndChannel[channel] == sourceAndChannelID) + { + if (m.isNoteOff()) + sourceAndChannel[channel] = notMPE; + else + lastUsed[channel] = counter; + + m.setChannel (channel); + return true; + } + + return false; +} + +int MPEChannelRemapper::getBestChanToReuse() const noexcept +{ + for (int chan = firstChannel; (zone.isLowerZone() ? chan <= lastChannel : chan >= lastChannel); chan += channelIncrement) + if (sourceAndChannel[chan] == notMPE) + return chan; + + auto bestChan = firstChannel; + auto bestLastUse = counter; + + for (int chan = firstChannel; (zone.isLowerZone() ? chan <= lastChannel : chan >= lastChannel); chan += channelIncrement) + { + if (lastUsed[chan] < bestLastUse) + { + bestLastUse = lastUsed[chan]; + bestChan = chan; + } + } + + return bestChan; +} + +void MPEChannelRemapper::zeroArrays() +{ + for (int i = 0; i < 17; ++i) + { + sourceAndChannel[i] = 0; + lastUsed[i] = 0; + } +} + + +//============================================================================== +//============================================================================== +#if JUCE_UNIT_TESTS + +struct MPEUtilsUnitTests : public UnitTest +{ + MPEUtilsUnitTests() + : UnitTest ("MPE Utilities", UnitTestCategories::midi) + {} + + void runTest() override + { + beginTest ("MPEChannelAssigner"); + { + MPEZoneLayout layout; + + // lower + { + layout.setLowerZone (15); + + // lower zone + MPEChannelAssigner channelAssigner (layout.getLowerZone()); + + // check that channels are assigned in correct order + int noteNum = 60; + for (int ch = 2; ch <= 16; ++ch) + expectEquals (channelAssigner.findMidiChannelForNewNote (noteNum++), ch); + + // check that note-offs are processed + channelAssigner.noteOff (60); + expectEquals (channelAssigner.findMidiChannelForNewNote (60), 2); + + channelAssigner.noteOff (61); + expectEquals (channelAssigner.findMidiChannelForNewNote (61), 3); + + // check that assigned channel was last to play note + channelAssigner.noteOff (65); + channelAssigner.noteOff (66); + expectEquals (channelAssigner.findMidiChannelForNewNote (66), 8); + expectEquals (channelAssigner.findMidiChannelForNewNote (65), 7); + + // find closest channel playing nonequal note + expectEquals (channelAssigner.findMidiChannelForNewNote (80), 16); + expectEquals (channelAssigner.findMidiChannelForNewNote (55), 2); + + // all notes off + channelAssigner.allNotesOff(); + + // last note played + expectEquals (channelAssigner.findMidiChannelForNewNote (66), 8); + expectEquals (channelAssigner.findMidiChannelForNewNote (65), 7); + expectEquals (channelAssigner.findMidiChannelForNewNote (80), 16); + expectEquals (channelAssigner.findMidiChannelForNewNote (55), 2); + + // normal assignment + expectEquals (channelAssigner.findMidiChannelForNewNote (101), 3); + expectEquals (channelAssigner.findMidiChannelForNewNote (20), 4); + } + + // upper + { + layout.setUpperZone (15); + + // upper zone + MPEChannelAssigner channelAssigner (layout.getUpperZone()); + + // check that channels are assigned in correct order + int noteNum = 60; + for (int ch = 15; ch >= 1; --ch) + expectEquals (channelAssigner.findMidiChannelForNewNote (noteNum++), ch); + + // check that note-offs are processed + channelAssigner.noteOff (60); + expectEquals (channelAssigner.findMidiChannelForNewNote (60), 15); + + channelAssigner.noteOff (61); + expectEquals (channelAssigner.findMidiChannelForNewNote (61), 14); + + // check that assigned channel was last to play note + channelAssigner.noteOff (65); + channelAssigner.noteOff (66); + expectEquals (channelAssigner.findMidiChannelForNewNote (66), 9); + expectEquals (channelAssigner.findMidiChannelForNewNote (65), 10); + + // find closest channel playing nonequal note + expectEquals (channelAssigner.findMidiChannelForNewNote (80), 1); + expectEquals (channelAssigner.findMidiChannelForNewNote (55), 15); + + // all notes off + channelAssigner.allNotesOff(); + + // last note played + expectEquals (channelAssigner.findMidiChannelForNewNote (66), 9); + expectEquals (channelAssigner.findMidiChannelForNewNote (65), 10); + expectEquals (channelAssigner.findMidiChannelForNewNote (80), 1); + expectEquals (channelAssigner.findMidiChannelForNewNote (55), 15); + + // normal assignment + expectEquals (channelAssigner.findMidiChannelForNewNote (101), 14); + expectEquals (channelAssigner.findMidiChannelForNewNote (20), 13); + } + + // legacy + { + MPEChannelAssigner channelAssigner; + + // check that channels are assigned in correct order + int noteNum = 60; + for (int ch = 1; ch <= 16; ++ch) + expectEquals (channelAssigner.findMidiChannelForNewNote (noteNum++), ch); + + // check that note-offs are processed + channelAssigner.noteOff (60); + expectEquals (channelAssigner.findMidiChannelForNewNote (60), 1); + + channelAssigner.noteOff (61); + expectEquals (channelAssigner.findMidiChannelForNewNote (61), 2); + + // check that assigned channel was last to play note + channelAssigner.noteOff (65); + channelAssigner.noteOff (66); + expectEquals (channelAssigner.findMidiChannelForNewNote (66), 7); + expectEquals (channelAssigner.findMidiChannelForNewNote (65), 6); + + // find closest channel playing nonequal note + expectEquals (channelAssigner.findMidiChannelForNewNote (80), 16); + expectEquals (channelAssigner.findMidiChannelForNewNote (55), 1); + + // all notes off + channelAssigner.allNotesOff(); + + // last note played + expectEquals (channelAssigner.findMidiChannelForNewNote (66), 7); + expectEquals (channelAssigner.findMidiChannelForNewNote (65), 6); + expectEquals (channelAssigner.findMidiChannelForNewNote (80), 16); + expectEquals (channelAssigner.findMidiChannelForNewNote (55), 1); + + // normal assignment + expectEquals (channelAssigner.findMidiChannelForNewNote (101), 2); + expectEquals (channelAssigner.findMidiChannelForNewNote (20), 3); + } + } + + beginTest ("MPEChannelRemapper"); + { + // 3 different MPE 'sources', constant IDs + const int sourceID1 = 0; + const int sourceID2 = 1; + const int sourceID3 = 2; + + MPEZoneLayout layout; + + { + layout.setLowerZone (15); + + // lower zone + MPEChannelRemapper channelRemapper (layout.getLowerZone()); + + // first source, shouldn't remap + for (int ch = 2; ch <= 16; ++ch) + { + auto noteOn = MidiMessage::noteOn (ch, 60, 1.0f); + + channelRemapper.remapMidiChannelIfNeeded (noteOn, sourceID1); + expectEquals (noteOn.getChannel(), ch); + } + + auto noteOn = MidiMessage::noteOn (2, 60, 1.0f); + + // remap onto oldest last-used channel + channelRemapper.remapMidiChannelIfNeeded (noteOn, sourceID2); + expectEquals (noteOn.getChannel(), 2); + + // remap onto oldest last-used channel + channelRemapper.remapMidiChannelIfNeeded (noteOn, sourceID3); + expectEquals (noteOn.getChannel(), 3); + + // remap to correct channel for source ID + auto noteOff = MidiMessage::noteOff (2, 60, 1.0f); + channelRemapper.remapMidiChannelIfNeeded (noteOff, sourceID3); + expectEquals (noteOff.getChannel(), 3); + } + + { + layout.setUpperZone (15); + + // upper zone + MPEChannelRemapper channelRemapper (layout.getUpperZone()); + + // first source, shouldn't remap + for (int ch = 15; ch >= 1; --ch) + { + auto noteOn = MidiMessage::noteOn (ch, 60, 1.0f); + + channelRemapper.remapMidiChannelIfNeeded (noteOn, sourceID1); + expectEquals (noteOn.getChannel(), ch); + } + + auto noteOn = MidiMessage::noteOn (15, 60, 1.0f); + + // remap onto oldest last-used channel + channelRemapper.remapMidiChannelIfNeeded (noteOn, sourceID2); + expectEquals (noteOn.getChannel(), 15); + + // remap onto oldest last-used channel + channelRemapper.remapMidiChannelIfNeeded (noteOn, sourceID3); + expectEquals (noteOn.getChannel(), 14); + + // remap to correct channel for source ID + auto noteOff = MidiMessage::noteOff (15, 60, 1.0f); + channelRemapper.remapMidiChannelIfNeeded (noteOff, sourceID3); + expectEquals (noteOff.getChannel(), 14); + } + } + } +}; + +static MPEUtilsUnitTests MPEUtilsUnitTests; + +#endif + +} // namespace juce diff --git a/JuceLibraryCode/modules/juce_audio_basics/mpe/juce_MPEUtils.h b/JuceLibraryCode/modules/juce_audio_basics/mpe/juce_MPEUtils.h index 3f5f7dd..ad4c741 100644 --- a/JuceLibraryCode/modules/juce_audio_basics/mpe/juce_MPEUtils.h +++ b/JuceLibraryCode/modules/juce_audio_basics/mpe/juce_MPEUtils.h @@ -1,153 +1,153 @@ -/* - ============================================================================== - - This file is part of the JUCE library. - Copyright (c) 2017 - ROLI Ltd. - - JUCE is an open source library subject to commercial or open-source - licensing. - - The code included in this file is provided under the terms of the ISC license - http://www.isc.org/downloads/software-support-policy/isc-license. Permission - To use, copy, modify, and/or distribute this software for any purpose with or - without fee is hereby granted provided that the above copyright notice and - this permission notice appear in all copies. - - JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER - EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE - DISCLAIMED. - - ============================================================================== -*/ - -namespace juce -{ - -//============================================================================== -/** - This class handles the assignment of new MIDI notes to member channels of an active - MPE zone. - - To use it, create an instance passing in the MPE zone that it should operate on - and then call use the findMidiChannelForNewNote() method for all note-on messages - and the noteOff() method for all note-off messages. - - @tags{Audio} -*/ -class MPEChannelAssigner -{ -public: - /** Constructor. - - This will assign channels within the range of the specified MPE zone. - */ - MPEChannelAssigner (MPEZoneLayout::Zone zoneToUse); - - /** Legacy mode constructor. - - This will assign channels within the specified range. - */ - MPEChannelAssigner (Range channelRange = Range (1, 17)); - - /** This method will use a set of rules recommended in the MPE specification to - determine which member channel the specified MIDI note should be assigned to - and will return this channel number. - - The rules have the following precedence: - - find a free channel on which the last note played was the same as the one specified - - find the next free channel in round-robin assignment - - find the channel number that is currently playing the closest note (but not the same) - - @param noteNumber the MIDI note number to be assigned to a channel - @returns the zone's member channel that this note should be assigned to - */ - int findMidiChannelForNewNote (int noteNumber) noexcept; - - /** You must call this method for all note-offs that you receive so that this class - can keep track of the currently playing notes internally. - - You can specify the channel number the note off happened on. If you don't, it will - look through all channels to find the registered midi note matching the given note number. - */ - void noteOff (int noteNumber, int midiChannel = -1); - - /** Call this to clear all currently playing notes. */ - void allNotesOff(); - -private: - bool isLegacy = false; - std::unique_ptr zone; - int channelIncrement, numChannels, firstChannel, lastChannel, midiChannelLastAssigned; - - //============================================================================== - struct MidiChannel - { - Array notes; - int lastNotePlayed = -1; - bool isFree() const noexcept { return notes.isEmpty(); } - }; - MidiChannel midiChannels[17]; - - //============================================================================== - int findMidiChannelPlayingClosestNonequalNote (int noteNumber) noexcept; -}; - -//============================================================================== -/** - This class handles the logic for remapping MIDI note messages from multiple MPE - sources onto a specified MPE zone. - - @tags{Audio} -*/ -class MPEChannelRemapper -{ -public: - /** Used to indicate that a particular source & channel combination is not currently using MPE. */ - static const uint32 notMPE = 0; - - /** Constructor */ - MPEChannelRemapper (MPEZoneLayout::Zone zoneToRemap); - - //============================================================================== - /** Remaps the MIDI channel of the specified MIDI message (if necessary). - - Note that the MidiMessage object passed in will have it's channel changed if it - needs to be remapped. - - @param message the message to be remapped - @param mpeSourceID the ID of the MPE source of the message. This is up to the - user to define and keep constant - */ - void remapMidiChannelIfNeeded (MidiMessage& message, uint32 mpeSourceID) noexcept; - - //============================================================================== - /** Resets all the source & channel combinations. */ - void reset() noexcept; - - /** Clears a specified channel of this MPE zone. */ - void clearChannel (int channel) noexcept; - - /** Clears all channels in use by a specified source. */ - void clearSource (uint32 mpeSourceID); - -private: - MPEZoneLayout::Zone zone; - - int channelIncrement; - int firstChannel, lastChannel; - - uint32 sourceAndChannel[17]; - uint32 lastUsed[17]; - uint32 counter = 0; - - //============================================================================== - bool applyRemapIfExisting (int channel, uint32 sourceAndChannelID, MidiMessage& m) noexcept; - int getBestChanToReuse() const noexcept; - - void zeroArrays(); - - //============================================================================== - bool messageIsNoteData (const MidiMessage& m) { return (*m.getRawData() & 0xf0) != 0xf0; } -}; - -} // namespace juce +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2017 - ROLI Ltd. + + JUCE is an open source library subject to commercial or open-source + licensing. + + The code included in this file is provided under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license. Permission + To use, copy, modify, and/or distribute this software for any purpose with or + without fee is hereby granted provided that the above copyright notice and + this permission notice appear in all copies. + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +namespace juce +{ + +//============================================================================== +/** + This class handles the assignment of new MIDI notes to member channels of an active + MPE zone. + + To use it, create an instance passing in the MPE zone that it should operate on + and then call use the findMidiChannelForNewNote() method for all note-on messages + and the noteOff() method for all note-off messages. + + @tags{Audio} +*/ +class MPEChannelAssigner +{ +public: + /** Constructor. + + This will assign channels within the range of the specified MPE zone. + */ + MPEChannelAssigner (MPEZoneLayout::Zone zoneToUse); + + /** Legacy mode constructor. + + This will assign channels within the specified range. + */ + MPEChannelAssigner (Range channelRange = Range (1, 17)); + + /** This method will use a set of rules recommended in the MPE specification to + determine which member channel the specified MIDI note should be assigned to + and will return this channel number. + + The rules have the following precedence: + - find a free channel on which the last note played was the same as the one specified + - find the next free channel in round-robin assignment + - find the channel number that is currently playing the closest note (but not the same) + + @param noteNumber the MIDI note number to be assigned to a channel + @returns the zone's member channel that this note should be assigned to + */ + int findMidiChannelForNewNote (int noteNumber) noexcept; + + /** You must call this method for all note-offs that you receive so that this class + can keep track of the currently playing notes internally. + + You can specify the channel number the note off happened on. If you don't, it will + look through all channels to find the registered midi note matching the given note number. + */ + void noteOff (int noteNumber, int midiChannel = -1); + + /** Call this to clear all currently playing notes. */ + void allNotesOff(); + +private: + bool isLegacy = false; + std::unique_ptr zone; + int channelIncrement, numChannels, firstChannel, lastChannel, midiChannelLastAssigned; + + //============================================================================== + struct MidiChannel + { + Array notes; + int lastNotePlayed = -1; + bool isFree() const noexcept { return notes.isEmpty(); } + }; + MidiChannel midiChannels[17]; + + //============================================================================== + int findMidiChannelPlayingClosestNonequalNote (int noteNumber) noexcept; +}; + +//============================================================================== +/** + This class handles the logic for remapping MIDI note messages from multiple MPE + sources onto a specified MPE zone. + + @tags{Audio} +*/ +class MPEChannelRemapper +{ +public: + /** Used to indicate that a particular source & channel combination is not currently using MPE. */ + static const uint32 notMPE = 0; + + /** Constructor */ + MPEChannelRemapper (MPEZoneLayout::Zone zoneToRemap); + + //============================================================================== + /** Remaps the MIDI channel of the specified MIDI message (if necessary). + + Note that the MidiMessage object passed in will have it's channel changed if it + needs to be remapped. + + @param message the message to be remapped + @param mpeSourceID the ID of the MPE source of the message. This is up to the + user to define and keep constant + */ + void remapMidiChannelIfNeeded (MidiMessage& message, uint32 mpeSourceID) noexcept; + + //============================================================================== + /** Resets all the source & channel combinations. */ + void reset() noexcept; + + /** Clears a specified channel of this MPE zone. */ + void clearChannel (int channel) noexcept; + + /** Clears all channels in use by a specified source. */ + void clearSource (uint32 mpeSourceID); + +private: + MPEZoneLayout::Zone zone; + + int channelIncrement; + int firstChannel, lastChannel; + + uint32 sourceAndChannel[17]; + uint32 lastUsed[17]; + uint32 counter = 0; + + //============================================================================== + bool applyRemapIfExisting (int channel, uint32 sourceAndChannelID, MidiMessage& m) noexcept; + int getBestChanToReuse() const noexcept; + + void zeroArrays(); + + //============================================================================== + bool messageIsNoteData (const MidiMessage& m) { return (*m.getRawData() & 0xf0) != 0xf0; } +}; + +} // namespace juce diff --git a/JuceLibraryCode/modules/juce_audio_basics/mpe/juce_MPEValue.cpp b/JuceLibraryCode/modules/juce_audio_basics/mpe/juce_MPEValue.cpp index ccc20ab..6d23451 100644 --- a/JuceLibraryCode/modules/juce_audio_basics/mpe/juce_MPEValue.cpp +++ b/JuceLibraryCode/modules/juce_audio_basics/mpe/juce_MPEValue.cpp @@ -1,173 +1,173 @@ -/* - ============================================================================== - - This file is part of the JUCE library. - Copyright (c) 2017 - ROLI Ltd. - - JUCE is an open source library subject to commercial or open-source - licensing. - - The code included in this file is provided under the terms of the ISC license - http://www.isc.org/downloads/software-support-policy/isc-license. Permission - To use, copy, modify, and/or distribute this software for any purpose with or - without fee is hereby granted provided that the above copyright notice and - this permission notice appear in all copies. - - JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER - EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE - DISCLAIMED. - - ============================================================================== -*/ - -namespace juce -{ - -MPEValue::MPEValue() noexcept {} -MPEValue::MPEValue (int value) : normalisedValue (value) {} - -//============================================================================== -MPEValue MPEValue::from7BitInt (int value) noexcept -{ - jassert (value >= 0 && value <= 127); - - auto valueAs14Bit = value <= 64 ? value << 7 - : int (jmap (float (value - 64), 0.0f, 63.0f, 0.0f, 8191.0f)) + 8192; - - return { valueAs14Bit }; -} - -MPEValue MPEValue::from14BitInt (int value) noexcept -{ - jassert (value >= 0 && value <= 16383); - return { value }; -} - -//============================================================================== -MPEValue MPEValue::minValue() noexcept { return MPEValue::from7BitInt (0); } -MPEValue MPEValue::centreValue() noexcept { return MPEValue::from7BitInt (64); } -MPEValue MPEValue::maxValue() noexcept { return MPEValue::from7BitInt (127); } - -int MPEValue::as7BitInt() const noexcept -{ - return normalisedValue >> 7; -} - -int MPEValue::as14BitInt() const noexcept -{ - return normalisedValue; -} - -//============================================================================== -float MPEValue::asSignedFloat() const noexcept -{ - return (normalisedValue < 8192) - ? jmap (float (normalisedValue), 0.0f, 8192.0f, -1.0f, 0.0f) - : jmap (float (normalisedValue), 8192.0f, 16383.0f, 0.0f, 1.0f); -} - -float MPEValue::asUnsignedFloat() const noexcept -{ - return jmap (float (normalisedValue), 0.0f, 16383.0f, 0.0f, 1.0f); -} - -//============================================================================== -bool MPEValue::operator== (const MPEValue& other) const noexcept -{ - return normalisedValue == other.normalisedValue; -} - -bool MPEValue::operator!= (const MPEValue& other) const noexcept -{ - return ! operator== (other); -} - - -//============================================================================== -//============================================================================== -#if JUCE_UNIT_TESTS - -class MPEValueTests : public UnitTest -{ -public: - MPEValueTests() - : UnitTest ("MPEValue class", UnitTestCategories::midi) - {} - - void runTest() override - { - beginTest ("comparison operator"); - { - MPEValue value1 = MPEValue::from7BitInt (7); - MPEValue value2 = MPEValue::from7BitInt (7); - MPEValue value3 = MPEValue::from7BitInt (8); - - expect (value1 == value1); - expect (value1 == value2); - expect (value1 != value3); - } - - beginTest ("special values"); - { - expectEquals (MPEValue::minValue().as7BitInt(), 0); - expectEquals (MPEValue::minValue().as14BitInt(), 0); - - expectEquals (MPEValue::centreValue().as7BitInt(), 64); - expectEquals (MPEValue::centreValue().as14BitInt(), 8192); - - expectEquals (MPEValue::maxValue().as7BitInt(), 127); - expectEquals (MPEValue::maxValue().as14BitInt(), 16383); - } - - beginTest ("zero/minimum value"); - { - expectValuesConsistent (MPEValue::from7BitInt (0), 0, 0, -1.0f, 0.0f); - expectValuesConsistent (MPEValue::from14BitInt (0), 0, 0, -1.0f, 0.0f); - } - - beginTest ("maximum value"); - { - expectValuesConsistent (MPEValue::from7BitInt (127), 127, 16383, 1.0f, 1.0f); - expectValuesConsistent (MPEValue::from14BitInt (16383), 127, 16383, 1.0f, 1.0f); - } - - beginTest ("centre value"); - { - expectValuesConsistent (MPEValue::from7BitInt (64), 64, 8192, 0.0f, 0.5f); - expectValuesConsistent (MPEValue::from14BitInt (8192), 64, 8192, 0.0f, 0.5f); - } - - beginTest ("value halfway between min and centre"); - { - expectValuesConsistent (MPEValue::from7BitInt (32), 32, 4096, -0.5f, 0.25f); - expectValuesConsistent (MPEValue::from14BitInt (4096), 32, 4096, -0.5f, 0.25f); - } - } - -private: - //============================================================================== - void expectValuesConsistent (MPEValue value, - int expectedValueAs7BitInt, - int expectedValueAs14BitInt, - float expectedValueAsSignedFloat, - float expectedValueAsUnsignedFloat) - { - expectEquals (value.as7BitInt(), expectedValueAs7BitInt); - expectEquals (value.as14BitInt(), expectedValueAs14BitInt); - expectFloatWithinRelativeError (value.asSignedFloat(), expectedValueAsSignedFloat, 0.0001f); - expectFloatWithinRelativeError (value.asUnsignedFloat(), expectedValueAsUnsignedFloat, 0.0001f); - } - - //============================================================================== - void expectFloatWithinRelativeError (float actualValue, float expectedValue, float maxRelativeError) - { - const float maxAbsoluteError = jmax (1.0f, std::abs (expectedValue)) * maxRelativeError; - expect (std::abs (expectedValue - actualValue) < maxAbsoluteError); - } -}; - -static MPEValueTests MPEValueUnitTests; - -#endif - -} // namespace juce +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2017 - ROLI Ltd. + + JUCE is an open source library subject to commercial or open-source + licensing. + + The code included in this file is provided under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license. Permission + To use, copy, modify, and/or distribute this software for any purpose with or + without fee is hereby granted provided that the above copyright notice and + this permission notice appear in all copies. + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +namespace juce +{ + +MPEValue::MPEValue() noexcept {} +MPEValue::MPEValue (int value) : normalisedValue (value) {} + +//============================================================================== +MPEValue MPEValue::from7BitInt (int value) noexcept +{ + jassert (value >= 0 && value <= 127); + + auto valueAs14Bit = value <= 64 ? value << 7 + : int (jmap (float (value - 64), 0.0f, 63.0f, 0.0f, 8191.0f)) + 8192; + + return { valueAs14Bit }; +} + +MPEValue MPEValue::from14BitInt (int value) noexcept +{ + jassert (value >= 0 && value <= 16383); + return { value }; +} + +//============================================================================== +MPEValue MPEValue::minValue() noexcept { return MPEValue::from7BitInt (0); } +MPEValue MPEValue::centreValue() noexcept { return MPEValue::from7BitInt (64); } +MPEValue MPEValue::maxValue() noexcept { return MPEValue::from7BitInt (127); } + +int MPEValue::as7BitInt() const noexcept +{ + return normalisedValue >> 7; +} + +int MPEValue::as14BitInt() const noexcept +{ + return normalisedValue; +} + +//============================================================================== +float MPEValue::asSignedFloat() const noexcept +{ + return (normalisedValue < 8192) + ? jmap (float (normalisedValue), 0.0f, 8192.0f, -1.0f, 0.0f) + : jmap (float (normalisedValue), 8192.0f, 16383.0f, 0.0f, 1.0f); +} + +float MPEValue::asUnsignedFloat() const noexcept +{ + return jmap (float (normalisedValue), 0.0f, 16383.0f, 0.0f, 1.0f); +} + +//============================================================================== +bool MPEValue::operator== (const MPEValue& other) const noexcept +{ + return normalisedValue == other.normalisedValue; +} + +bool MPEValue::operator!= (const MPEValue& other) const noexcept +{ + return ! operator== (other); +} + + +//============================================================================== +//============================================================================== +#if JUCE_UNIT_TESTS + +class MPEValueTests : public UnitTest +{ +public: + MPEValueTests() + : UnitTest ("MPEValue class", UnitTestCategories::midi) + {} + + void runTest() override + { + beginTest ("comparison operator"); + { + MPEValue value1 = MPEValue::from7BitInt (7); + MPEValue value2 = MPEValue::from7BitInt (7); + MPEValue value3 = MPEValue::from7BitInt (8); + + expect (value1 == value1); + expect (value1 == value2); + expect (value1 != value3); + } + + beginTest ("special values"); + { + expectEquals (MPEValue::minValue().as7BitInt(), 0); + expectEquals (MPEValue::minValue().as14BitInt(), 0); + + expectEquals (MPEValue::centreValue().as7BitInt(), 64); + expectEquals (MPEValue::centreValue().as14BitInt(), 8192); + + expectEquals (MPEValue::maxValue().as7BitInt(), 127); + expectEquals (MPEValue::maxValue().as14BitInt(), 16383); + } + + beginTest ("zero/minimum value"); + { + expectValuesConsistent (MPEValue::from7BitInt (0), 0, 0, -1.0f, 0.0f); + expectValuesConsistent (MPEValue::from14BitInt (0), 0, 0, -1.0f, 0.0f); + } + + beginTest ("maximum value"); + { + expectValuesConsistent (MPEValue::from7BitInt (127), 127, 16383, 1.0f, 1.0f); + expectValuesConsistent (MPEValue::from14BitInt (16383), 127, 16383, 1.0f, 1.0f); + } + + beginTest ("centre value"); + { + expectValuesConsistent (MPEValue::from7BitInt (64), 64, 8192, 0.0f, 0.5f); + expectValuesConsistent (MPEValue::from14BitInt (8192), 64, 8192, 0.0f, 0.5f); + } + + beginTest ("value halfway between min and centre"); + { + expectValuesConsistent (MPEValue::from7BitInt (32), 32, 4096, -0.5f, 0.25f); + expectValuesConsistent (MPEValue::from14BitInt (4096), 32, 4096, -0.5f, 0.25f); + } + } + +private: + //============================================================================== + void expectValuesConsistent (MPEValue value, + int expectedValueAs7BitInt, + int expectedValueAs14BitInt, + float expectedValueAsSignedFloat, + float expectedValueAsUnsignedFloat) + { + expectEquals (value.as7BitInt(), expectedValueAs7BitInt); + expectEquals (value.as14BitInt(), expectedValueAs14BitInt); + expectFloatWithinRelativeError (value.asSignedFloat(), expectedValueAsSignedFloat, 0.0001f); + expectFloatWithinRelativeError (value.asUnsignedFloat(), expectedValueAsUnsignedFloat, 0.0001f); + } + + //============================================================================== + void expectFloatWithinRelativeError (float actualValue, float expectedValue, float maxRelativeError) + { + const float maxAbsoluteError = jmax (1.0f, std::abs (expectedValue)) * maxRelativeError; + expect (std::abs (expectedValue - actualValue) < maxAbsoluteError); + } +}; + +static MPEValueTests MPEValueUnitTests; + +#endif + +} // namespace juce diff --git a/JuceLibraryCode/modules/juce_audio_basics/mpe/juce_MPEValue.h b/JuceLibraryCode/modules/juce_audio_basics/mpe/juce_MPEValue.h index 242d9c5..ad9d272 100644 --- a/JuceLibraryCode/modules/juce_audio_basics/mpe/juce_MPEValue.h +++ b/JuceLibraryCode/modules/juce_audio_basics/mpe/juce_MPEValue.h @@ -1,97 +1,97 @@ -/* - ============================================================================== - - This file is part of the JUCE library. - Copyright (c) 2017 - ROLI Ltd. - - JUCE is an open source library subject to commercial or open-source - licensing. - - The code included in this file is provided under the terms of the ISC license - http://www.isc.org/downloads/software-support-policy/isc-license. Permission - To use, copy, modify, and/or distribute this software for any purpose with or - without fee is hereby granted provided that the above copyright notice and - this permission notice appear in all copies. - - JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER - EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE - DISCLAIMED. - - ============================================================================== -*/ - -namespace juce -{ - -//============================================================================== -/** - This class represents a single value for any of the MPE - dimensions of control. It supports values with 7-bit or 14-bit resolutions - (corresponding to 1 or 2 MIDI bytes, respectively). It also offers helper - functions to query the value in a variety of representations that can be - useful in an audio or MIDI context. - - @tags{Audio} -*/ -class JUCE_API MPEValue -{ -public: - //============================================================================== - /** Default constructor. - - Constructs an MPEValue corresponding to the centre value. - */ - MPEValue() noexcept; - - /** Constructs an MPEValue from an integer between 0 and 127 - (using 7-bit precision). - */ - static MPEValue from7BitInt (int value) noexcept; - - /** Constructs an MPEValue from an integer between 0 and 16383 - (using 14-bit precision). - */ - static MPEValue from14BitInt (int value) noexcept; - - /** Constructs an MPEValue corresponding to the centre value. */ - static MPEValue centreValue() noexcept; - - /** Constructs an MPEValue corresponding to the minimum value. */ - static MPEValue minValue() noexcept; - - /** Constructs an MPEValue corresponding to the maximum value. */ - static MPEValue maxValue() noexcept; - - /** Retrieves the current value as an integer between 0 and 127. - - Information will be lost if the value was initialised with a precision - higher than 7-bit. - */ - int as7BitInt() const noexcept; - - /** Retrieves the current value as an integer between 0 and 16383. - - Resolution will be lost if the value was initialised with a precision - higher than 14-bit. - */ - int as14BitInt() const noexcept; - - /** Retrieves the current value mapped to a float between -1.0f and 1.0f. */ - float asSignedFloat() const noexcept; - - /** Retrieves the current value mapped to a float between 0.0f and 1.0f. */ - float asUnsignedFloat() const noexcept; - - /** Returns true if two values are equal. */ - bool operator== (const MPEValue& other) const noexcept; - - /** Returns true if two values are not equal. */ - bool operator!= (const MPEValue& other) const noexcept; - -private: - //============================================================================== - MPEValue (int normalisedValue); - int normalisedValue = 8192; -}; - -} // namespace juce +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2017 - ROLI Ltd. + + JUCE is an open source library subject to commercial or open-source + licensing. + + The code included in this file is provided under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license. Permission + To use, copy, modify, and/or distribute this software for any purpose with or + without fee is hereby granted provided that the above copyright notice and + this permission notice appear in all copies. + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +namespace juce +{ + +//============================================================================== +/** + This class represents a single value for any of the MPE + dimensions of control. It supports values with 7-bit or 14-bit resolutions + (corresponding to 1 or 2 MIDI bytes, respectively). It also offers helper + functions to query the value in a variety of representations that can be + useful in an audio or MIDI context. + + @tags{Audio} +*/ +class JUCE_API MPEValue +{ +public: + //============================================================================== + /** Default constructor. + + Constructs an MPEValue corresponding to the centre value. + */ + MPEValue() noexcept; + + /** Constructs an MPEValue from an integer between 0 and 127 + (using 7-bit precision). + */ + static MPEValue from7BitInt (int value) noexcept; + + /** Constructs an MPEValue from an integer between 0 and 16383 + (using 14-bit precision). + */ + static MPEValue from14BitInt (int value) noexcept; + + /** Constructs an MPEValue corresponding to the centre value. */ + static MPEValue centreValue() noexcept; + + /** Constructs an MPEValue corresponding to the minimum value. */ + static MPEValue minValue() noexcept; + + /** Constructs an MPEValue corresponding to the maximum value. */ + static MPEValue maxValue() noexcept; + + /** Retrieves the current value as an integer between 0 and 127. + + Information will be lost if the value was initialised with a precision + higher than 7-bit. + */ + int as7BitInt() const noexcept; + + /** Retrieves the current value as an integer between 0 and 16383. + + Resolution will be lost if the value was initialised with a precision + higher than 14-bit. + */ + int as14BitInt() const noexcept; + + /** Retrieves the current value mapped to a float between -1.0f and 1.0f. */ + float asSignedFloat() const noexcept; + + /** Retrieves the current value mapped to a float between 0.0f and 1.0f. */ + float asUnsignedFloat() const noexcept; + + /** Returns true if two values are equal. */ + bool operator== (const MPEValue& other) const noexcept; + + /** Returns true if two values are not equal. */ + bool operator!= (const MPEValue& other) const noexcept; + +private: + //============================================================================== + MPEValue (int normalisedValue); + int normalisedValue = 8192; +}; + +} // namespace juce diff --git a/JuceLibraryCode/modules/juce_audio_basics/mpe/juce_MPEZoneLayout.cpp b/JuceLibraryCode/modules/juce_audio_basics/mpe/juce_MPEZoneLayout.cpp index dfcba78..aa10b3c 100644 --- a/JuceLibraryCode/modules/juce_audio_basics/mpe/juce_MPEZoneLayout.cpp +++ b/JuceLibraryCode/modules/juce_audio_basics/mpe/juce_MPEZoneLayout.cpp @@ -1,390 +1,390 @@ -/* - ============================================================================== - - This file is part of the JUCE library. - Copyright (c) 2017 - ROLI Ltd. - - JUCE is an open source library subject to commercial or open-source - licensing. - - The code included in this file is provided under the terms of the ISC license - http://www.isc.org/downloads/software-support-policy/isc-license. Permission - To use, copy, modify, and/or distribute this software for any purpose with or - without fee is hereby granted provided that the above copyright notice and - this permission notice appear in all copies. - - JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER - EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE - DISCLAIMED. - - ============================================================================== -*/ - -namespace juce -{ - -MPEZoneLayout::MPEZoneLayout() noexcept {} - -MPEZoneLayout::MPEZoneLayout (const MPEZoneLayout& other) - : lowerZone (other.lowerZone), - upperZone (other.upperZone) -{ -} - -MPEZoneLayout& MPEZoneLayout::operator= (const MPEZoneLayout& other) -{ - lowerZone = other.lowerZone; - upperZone = other.upperZone; - - sendLayoutChangeMessage(); - - return *this; -} - -void MPEZoneLayout::sendLayoutChangeMessage() -{ - listeners.call ([this] (Listener& l) { l.zoneLayoutChanged (*this); }); -} - -//============================================================================== -void MPEZoneLayout::setZone (bool isLower, int numMemberChannels, int perNotePitchbendRange, int masterPitchbendRange) noexcept -{ - checkAndLimitZoneParameters (0, 15, numMemberChannels); - checkAndLimitZoneParameters (0, 96, perNotePitchbendRange); - checkAndLimitZoneParameters (0, 96, masterPitchbendRange); - - if (isLower) - lowerZone = { true, numMemberChannels, perNotePitchbendRange, masterPitchbendRange }; - else - upperZone = { false, numMemberChannels, perNotePitchbendRange, masterPitchbendRange }; - - if (numMemberChannels > 0) - { - auto totalChannels = lowerZone.numMemberChannels + upperZone.numMemberChannels; - - if (totalChannels >= 15) - { - if (isLower) - upperZone.numMemberChannels = 14 - numMemberChannels; - else - lowerZone.numMemberChannels = 14 - numMemberChannels; - } - } - - sendLayoutChangeMessage(); -} - -void MPEZoneLayout::setLowerZone (int numMemberChannels, int perNotePitchbendRange, int masterPitchbendRange) noexcept -{ - setZone (true, numMemberChannels, perNotePitchbendRange, masterPitchbendRange); -} - -void MPEZoneLayout::setUpperZone (int numMemberChannels, int perNotePitchbendRange, int masterPitchbendRange) noexcept -{ - setZone (false, numMemberChannels, perNotePitchbendRange, masterPitchbendRange); -} - -void MPEZoneLayout::clearAllZones() -{ - lowerZone = { true, 0 }; - upperZone = { false, 0 }; - - sendLayoutChangeMessage(); -} - -//============================================================================== -void MPEZoneLayout::processNextMidiEvent (const MidiMessage& message) -{ - if (! message.isController()) - return; - - MidiRPNMessage rpn; - - if (rpnDetector.parseControllerMessage (message.getChannel(), - message.getControllerNumber(), - message.getControllerValue(), - rpn)) - { - processRpnMessage (rpn); - } -} - -void MPEZoneLayout::processRpnMessage (MidiRPNMessage rpn) -{ - if (rpn.parameterNumber == MPEMessages::zoneLayoutMessagesRpnNumber) - processZoneLayoutRpnMessage (rpn); - else if (rpn.parameterNumber == 0) - processPitchbendRangeRpnMessage (rpn); -} - -void MPEZoneLayout::processZoneLayoutRpnMessage (MidiRPNMessage rpn) -{ - if (rpn.value < 16) - { - if (rpn.channel == 1) - setLowerZone (rpn.value); - else if (rpn.channel == 16) - setUpperZone (rpn.value); - } -} - -void MPEZoneLayout::updateMasterPitchbend (Zone& zone, int value) -{ - if (zone.masterPitchbendRange != value) - { - checkAndLimitZoneParameters (0, 96, zone.masterPitchbendRange); - zone.masterPitchbendRange = value; - sendLayoutChangeMessage(); - } -} - -void MPEZoneLayout::updatePerNotePitchbendRange (Zone& zone, int value) -{ - if (zone.perNotePitchbendRange != value) - { - checkAndLimitZoneParameters (0, 96, zone.perNotePitchbendRange); - zone.perNotePitchbendRange = value; - sendLayoutChangeMessage(); - } -} - -void MPEZoneLayout::processPitchbendRangeRpnMessage (MidiRPNMessage rpn) -{ - if (rpn.channel == 1) - { - updateMasterPitchbend (lowerZone, rpn.value); - } - else if (rpn.channel == 16) - { - updateMasterPitchbend (upperZone, rpn.value); - } - else - { - if (lowerZone.isUsingChannelAsMemberChannel (rpn.channel)) - updatePerNotePitchbendRange (lowerZone, rpn.value); - else if (upperZone.isUsingChannelAsMemberChannel (rpn.channel)) - updatePerNotePitchbendRange (upperZone, rpn.value); - } -} - -void MPEZoneLayout::processNextMidiBuffer (const MidiBuffer& buffer) -{ - MidiBuffer::Iterator iter (buffer); - MidiMessage message; - int samplePosition; // not actually used, so no need to initialise. - - while (iter.getNextEvent (message, samplePosition)) - processNextMidiEvent (message); -} - -//============================================================================== -void MPEZoneLayout::addListener (Listener* const listenerToAdd) noexcept -{ - listeners.add (listenerToAdd); -} - -void MPEZoneLayout::removeListener (Listener* const listenerToRemove) noexcept -{ - listeners.remove (listenerToRemove); -} - -//============================================================================== -void MPEZoneLayout::checkAndLimitZoneParameters (int minValue, int maxValue, - int& valueToCheckAndLimit) noexcept -{ - if (valueToCheckAndLimit < minValue || valueToCheckAndLimit > maxValue) - { - // if you hit this, one of the parameters you supplied for this zone - // was not within the allowed range! - // we fit this back into the allowed range here to maintain a valid - // state for the zone, but probably the resulting zone is not what you - // wanted it to be! - jassertfalse; - - valueToCheckAndLimit = jlimit (minValue, maxValue, valueToCheckAndLimit); - } -} - - -//============================================================================== -//============================================================================== -#if JUCE_UNIT_TESTS - -class MPEZoneLayoutTests : public UnitTest -{ -public: - MPEZoneLayoutTests() - : UnitTest ("MPEZoneLayout class", UnitTestCategories::midi) - {} - - void runTest() override - { - beginTest ("initialisation"); - { - MPEZoneLayout layout; - expect (! layout.getLowerZone().isActive()); - expect (! layout.getUpperZone().isActive()); - } - - beginTest ("adding zones"); - { - MPEZoneLayout layout; - - layout.setLowerZone (7); - - expect (layout.getLowerZone().isActive()); - expect (! layout.getUpperZone().isActive()); - expectEquals (layout.getLowerZone().getMasterChannel(), 1); - expectEquals (layout.getLowerZone().numMemberChannels, 7); - - layout.setUpperZone (7); - - expect (layout.getLowerZone().isActive()); - expect (layout.getUpperZone().isActive()); - expectEquals (layout.getLowerZone().getMasterChannel(), 1); - expectEquals (layout.getLowerZone().numMemberChannels, 7); - expectEquals (layout.getUpperZone().getMasterChannel(), 16); - expectEquals (layout.getUpperZone().numMemberChannels, 7); - - layout.setLowerZone (3); - - expect (layout.getLowerZone().isActive()); - expect (layout.getUpperZone().isActive()); - expectEquals (layout.getLowerZone().getMasterChannel(), 1); - expectEquals (layout.getLowerZone().numMemberChannels, 3); - expectEquals (layout.getUpperZone().getMasterChannel(), 16); - expectEquals (layout.getUpperZone().numMemberChannels, 7); - - layout.setUpperZone (3); - - expect (layout.getLowerZone().isActive()); - expect (layout.getUpperZone().isActive()); - expectEquals (layout.getLowerZone().getMasterChannel(), 1); - expectEquals (layout.getLowerZone().numMemberChannels, 3); - expectEquals (layout.getUpperZone().getMasterChannel(), 16); - expectEquals (layout.getUpperZone().numMemberChannels, 3); - - layout.setLowerZone (15); - - expect (layout.getLowerZone().isActive()); - expect (! layout.getUpperZone().isActive()); - expectEquals (layout.getLowerZone().getMasterChannel(), 1); - expectEquals (layout.getLowerZone().numMemberChannels, 15); - } - - beginTest ("clear all zones"); - { - MPEZoneLayout layout; - - expect (! layout.getLowerZone().isActive()); - expect (! layout.getUpperZone().isActive()); - - layout.setLowerZone (7); - layout.setUpperZone (2); - - expect (layout.getLowerZone().isActive()); - expect (layout.getUpperZone().isActive()); - - layout.clearAllZones(); - - expect (! layout.getLowerZone().isActive()); - expect (! layout.getUpperZone().isActive()); - } - - beginTest ("process MIDI buffers"); - { - MPEZoneLayout layout; - MidiBuffer buffer; - - buffer = MPEMessages::setLowerZone (7); - layout.processNextMidiBuffer (buffer); - - expect (layout.getLowerZone().isActive()); - expect (! layout.getUpperZone().isActive()); - expectEquals (layout.getLowerZone().getMasterChannel(), 1); - expectEquals (layout.getLowerZone().numMemberChannels, 7); - - buffer = MPEMessages::setUpperZone (7); - layout.processNextMidiBuffer (buffer); - - expect (layout.getLowerZone().isActive()); - expect (layout.getUpperZone().isActive()); - expectEquals (layout.getLowerZone().getMasterChannel(), 1); - expectEquals (layout.getLowerZone().numMemberChannels, 7); - expectEquals (layout.getUpperZone().getMasterChannel(), 16); - expectEquals (layout.getUpperZone().numMemberChannels, 7); - - { - buffer = MPEMessages::setLowerZone (10); - layout.processNextMidiBuffer (buffer); - - expect (layout.getLowerZone().isActive()); - expect (layout.getUpperZone().isActive()); - expectEquals (layout.getLowerZone().getMasterChannel(), 1); - expectEquals (layout.getLowerZone().numMemberChannels, 10); - expectEquals (layout.getUpperZone().getMasterChannel(), 16); - expectEquals (layout.getUpperZone().numMemberChannels, 4); - - - buffer = MPEMessages::setLowerZone (10, 33, 44); - layout.processNextMidiBuffer (buffer); - - expectEquals (layout.getLowerZone().numMemberChannels, 10); - expectEquals (layout.getLowerZone().perNotePitchbendRange, 33); - expectEquals (layout.getLowerZone().masterPitchbendRange, 44); - } - - { - buffer = MPEMessages::setUpperZone (10); - layout.processNextMidiBuffer (buffer); - - expect (layout.getLowerZone().isActive()); - expect (layout.getUpperZone().isActive()); - expectEquals (layout.getLowerZone().getMasterChannel(), 1); - expectEquals (layout.getLowerZone().numMemberChannels, 4); - expectEquals (layout.getUpperZone().getMasterChannel(), 16); - expectEquals (layout.getUpperZone().numMemberChannels, 10); - - buffer = MPEMessages::setUpperZone (10, 33, 44); - - layout.processNextMidiBuffer (buffer); - - expectEquals (layout.getUpperZone().numMemberChannels, 10); - expectEquals (layout.getUpperZone().perNotePitchbendRange, 33); - expectEquals (layout.getUpperZone().masterPitchbendRange, 44); - } - - buffer = MPEMessages::clearAllZones(); - layout.processNextMidiBuffer (buffer); - - expect (! layout.getLowerZone().isActive()); - expect (! layout.getUpperZone().isActive()); - } - - beginTest ("process individual MIDI messages"); - { - MPEZoneLayout layout; - - layout.processNextMidiEvent ({ 0x80, 0x59, 0xd0 }); // unrelated note-off msg - layout.processNextMidiEvent ({ 0xb0, 0x64, 0x06 }); // RPN part 1 - layout.processNextMidiEvent ({ 0xb0, 0x65, 0x00 }); // RPN part 2 - layout.processNextMidiEvent ({ 0xb8, 0x0b, 0x66 }); // unrelated CC msg - layout.processNextMidiEvent ({ 0xb0, 0x06, 0x03 }); // RPN part 3 - layout.processNextMidiEvent ({ 0x90, 0x60, 0x00 }); // unrelated note-on msg - - expect (layout.getLowerZone().isActive()); - expect (! layout.getUpperZone().isActive()); - expectEquals (layout.getLowerZone().getMasterChannel(), 1); - expectEquals (layout.getLowerZone().numMemberChannels, 3); - expectEquals (layout.getLowerZone().perNotePitchbendRange, 48); - expectEquals (layout.getLowerZone().masterPitchbendRange, 2); - } - } -}; - -static MPEZoneLayoutTests MPEZoneLayoutUnitTests; - - -#endif - -} // namespace juce +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2017 - ROLI Ltd. + + JUCE is an open source library subject to commercial or open-source + licensing. + + The code included in this file is provided under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license. Permission + To use, copy, modify, and/or distribute this software for any purpose with or + without fee is hereby granted provided that the above copyright notice and + this permission notice appear in all copies. + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +namespace juce +{ + +MPEZoneLayout::MPEZoneLayout() noexcept {} + +MPEZoneLayout::MPEZoneLayout (const MPEZoneLayout& other) + : lowerZone (other.lowerZone), + upperZone (other.upperZone) +{ +} + +MPEZoneLayout& MPEZoneLayout::operator= (const MPEZoneLayout& other) +{ + lowerZone = other.lowerZone; + upperZone = other.upperZone; + + sendLayoutChangeMessage(); + + return *this; +} + +void MPEZoneLayout::sendLayoutChangeMessage() +{ + listeners.call ([this] (Listener& l) { l.zoneLayoutChanged (*this); }); +} + +//============================================================================== +void MPEZoneLayout::setZone (bool isLower, int numMemberChannels, int perNotePitchbendRange, int masterPitchbendRange) noexcept +{ + checkAndLimitZoneParameters (0, 15, numMemberChannels); + checkAndLimitZoneParameters (0, 96, perNotePitchbendRange); + checkAndLimitZoneParameters (0, 96, masterPitchbendRange); + + if (isLower) + lowerZone = { true, numMemberChannels, perNotePitchbendRange, masterPitchbendRange }; + else + upperZone = { false, numMemberChannels, perNotePitchbendRange, masterPitchbendRange }; + + if (numMemberChannels > 0) + { + auto totalChannels = lowerZone.numMemberChannels + upperZone.numMemberChannels; + + if (totalChannels >= 15) + { + if (isLower) + upperZone.numMemberChannels = 14 - numMemberChannels; + else + lowerZone.numMemberChannels = 14 - numMemberChannels; + } + } + + sendLayoutChangeMessage(); +} + +void MPEZoneLayout::setLowerZone (int numMemberChannels, int perNotePitchbendRange, int masterPitchbendRange) noexcept +{ + setZone (true, numMemberChannels, perNotePitchbendRange, masterPitchbendRange); +} + +void MPEZoneLayout::setUpperZone (int numMemberChannels, int perNotePitchbendRange, int masterPitchbendRange) noexcept +{ + setZone (false, numMemberChannels, perNotePitchbendRange, masterPitchbendRange); +} + +void MPEZoneLayout::clearAllZones() +{ + lowerZone = { true, 0 }; + upperZone = { false, 0 }; + + sendLayoutChangeMessage(); +} + +//============================================================================== +void MPEZoneLayout::processNextMidiEvent (const MidiMessage& message) +{ + if (! message.isController()) + return; + + MidiRPNMessage rpn; + + if (rpnDetector.parseControllerMessage (message.getChannel(), + message.getControllerNumber(), + message.getControllerValue(), + rpn)) + { + processRpnMessage (rpn); + } +} + +void MPEZoneLayout::processRpnMessage (MidiRPNMessage rpn) +{ + if (rpn.parameterNumber == MPEMessages::zoneLayoutMessagesRpnNumber) + processZoneLayoutRpnMessage (rpn); + else if (rpn.parameterNumber == 0) + processPitchbendRangeRpnMessage (rpn); +} + +void MPEZoneLayout::processZoneLayoutRpnMessage (MidiRPNMessage rpn) +{ + if (rpn.value < 16) + { + if (rpn.channel == 1) + setLowerZone (rpn.value); + else if (rpn.channel == 16) + setUpperZone (rpn.value); + } +} + +void MPEZoneLayout::updateMasterPitchbend (Zone& zone, int value) +{ + if (zone.masterPitchbendRange != value) + { + checkAndLimitZoneParameters (0, 96, zone.masterPitchbendRange); + zone.masterPitchbendRange = value; + sendLayoutChangeMessage(); + } +} + +void MPEZoneLayout::updatePerNotePitchbendRange (Zone& zone, int value) +{ + if (zone.perNotePitchbendRange != value) + { + checkAndLimitZoneParameters (0, 96, zone.perNotePitchbendRange); + zone.perNotePitchbendRange = value; + sendLayoutChangeMessage(); + } +} + +void MPEZoneLayout::processPitchbendRangeRpnMessage (MidiRPNMessage rpn) +{ + if (rpn.channel == 1) + { + updateMasterPitchbend (lowerZone, rpn.value); + } + else if (rpn.channel == 16) + { + updateMasterPitchbend (upperZone, rpn.value); + } + else + { + if (lowerZone.isUsingChannelAsMemberChannel (rpn.channel)) + updatePerNotePitchbendRange (lowerZone, rpn.value); + else if (upperZone.isUsingChannelAsMemberChannel (rpn.channel)) + updatePerNotePitchbendRange (upperZone, rpn.value); + } +} + +void MPEZoneLayout::processNextMidiBuffer (const MidiBuffer& buffer) +{ + MidiBuffer::Iterator iter (buffer); + MidiMessage message; + int samplePosition; // not actually used, so no need to initialise. + + while (iter.getNextEvent (message, samplePosition)) + processNextMidiEvent (message); +} + +//============================================================================== +void MPEZoneLayout::addListener (Listener* const listenerToAdd) noexcept +{ + listeners.add (listenerToAdd); +} + +void MPEZoneLayout::removeListener (Listener* const listenerToRemove) noexcept +{ + listeners.remove (listenerToRemove); +} + +//============================================================================== +void MPEZoneLayout::checkAndLimitZoneParameters (int minValue, int maxValue, + int& valueToCheckAndLimit) noexcept +{ + if (valueToCheckAndLimit < minValue || valueToCheckAndLimit > maxValue) + { + // if you hit this, one of the parameters you supplied for this zone + // was not within the allowed range! + // we fit this back into the allowed range here to maintain a valid + // state for the zone, but probably the resulting zone is not what you + // wanted it to be! + jassertfalse; + + valueToCheckAndLimit = jlimit (minValue, maxValue, valueToCheckAndLimit); + } +} + + +//============================================================================== +//============================================================================== +#if JUCE_UNIT_TESTS + +class MPEZoneLayoutTests : public UnitTest +{ +public: + MPEZoneLayoutTests() + : UnitTest ("MPEZoneLayout class", UnitTestCategories::midi) + {} + + void runTest() override + { + beginTest ("initialisation"); + { + MPEZoneLayout layout; + expect (! layout.getLowerZone().isActive()); + expect (! layout.getUpperZone().isActive()); + } + + beginTest ("adding zones"); + { + MPEZoneLayout layout; + + layout.setLowerZone (7); + + expect (layout.getLowerZone().isActive()); + expect (! layout.getUpperZone().isActive()); + expectEquals (layout.getLowerZone().getMasterChannel(), 1); + expectEquals (layout.getLowerZone().numMemberChannels, 7); + + layout.setUpperZone (7); + + expect (layout.getLowerZone().isActive()); + expect (layout.getUpperZone().isActive()); + expectEquals (layout.getLowerZone().getMasterChannel(), 1); + expectEquals (layout.getLowerZone().numMemberChannels, 7); + expectEquals (layout.getUpperZone().getMasterChannel(), 16); + expectEquals (layout.getUpperZone().numMemberChannels, 7); + + layout.setLowerZone (3); + + expect (layout.getLowerZone().isActive()); + expect (layout.getUpperZone().isActive()); + expectEquals (layout.getLowerZone().getMasterChannel(), 1); + expectEquals (layout.getLowerZone().numMemberChannels, 3); + expectEquals (layout.getUpperZone().getMasterChannel(), 16); + expectEquals (layout.getUpperZone().numMemberChannels, 7); + + layout.setUpperZone (3); + + expect (layout.getLowerZone().isActive()); + expect (layout.getUpperZone().isActive()); + expectEquals (layout.getLowerZone().getMasterChannel(), 1); + expectEquals (layout.getLowerZone().numMemberChannels, 3); + expectEquals (layout.getUpperZone().getMasterChannel(), 16); + expectEquals (layout.getUpperZone().numMemberChannels, 3); + + layout.setLowerZone (15); + + expect (layout.getLowerZone().isActive()); + expect (! layout.getUpperZone().isActive()); + expectEquals (layout.getLowerZone().getMasterChannel(), 1); + expectEquals (layout.getLowerZone().numMemberChannels, 15); + } + + beginTest ("clear all zones"); + { + MPEZoneLayout layout; + + expect (! layout.getLowerZone().isActive()); + expect (! layout.getUpperZone().isActive()); + + layout.setLowerZone (7); + layout.setUpperZone (2); + + expect (layout.getLowerZone().isActive()); + expect (layout.getUpperZone().isActive()); + + layout.clearAllZones(); + + expect (! layout.getLowerZone().isActive()); + expect (! layout.getUpperZone().isActive()); + } + + beginTest ("process MIDI buffers"); + { + MPEZoneLayout layout; + MidiBuffer buffer; + + buffer = MPEMessages::setLowerZone (7); + layout.processNextMidiBuffer (buffer); + + expect (layout.getLowerZone().isActive()); + expect (! layout.getUpperZone().isActive()); + expectEquals (layout.getLowerZone().getMasterChannel(), 1); + expectEquals (layout.getLowerZone().numMemberChannels, 7); + + buffer = MPEMessages::setUpperZone (7); + layout.processNextMidiBuffer (buffer); + + expect (layout.getLowerZone().isActive()); + expect (layout.getUpperZone().isActive()); + expectEquals (layout.getLowerZone().getMasterChannel(), 1); + expectEquals (layout.getLowerZone().numMemberChannels, 7); + expectEquals (layout.getUpperZone().getMasterChannel(), 16); + expectEquals (layout.getUpperZone().numMemberChannels, 7); + + { + buffer = MPEMessages::setLowerZone (10); + layout.processNextMidiBuffer (buffer); + + expect (layout.getLowerZone().isActive()); + expect (layout.getUpperZone().isActive()); + expectEquals (layout.getLowerZone().getMasterChannel(), 1); + expectEquals (layout.getLowerZone().numMemberChannels, 10); + expectEquals (layout.getUpperZone().getMasterChannel(), 16); + expectEquals (layout.getUpperZone().numMemberChannels, 4); + + + buffer = MPEMessages::setLowerZone (10, 33, 44); + layout.processNextMidiBuffer (buffer); + + expectEquals (layout.getLowerZone().numMemberChannels, 10); + expectEquals (layout.getLowerZone().perNotePitchbendRange, 33); + expectEquals (layout.getLowerZone().masterPitchbendRange, 44); + } + + { + buffer = MPEMessages::setUpperZone (10); + layout.processNextMidiBuffer (buffer); + + expect (layout.getLowerZone().isActive()); + expect (layout.getUpperZone().isActive()); + expectEquals (layout.getLowerZone().getMasterChannel(), 1); + expectEquals (layout.getLowerZone().numMemberChannels, 4); + expectEquals (layout.getUpperZone().getMasterChannel(), 16); + expectEquals (layout.getUpperZone().numMemberChannels, 10); + + buffer = MPEMessages::setUpperZone (10, 33, 44); + + layout.processNextMidiBuffer (buffer); + + expectEquals (layout.getUpperZone().numMemberChannels, 10); + expectEquals (layout.getUpperZone().perNotePitchbendRange, 33); + expectEquals (layout.getUpperZone().masterPitchbendRange, 44); + } + + buffer = MPEMessages::clearAllZones(); + layout.processNextMidiBuffer (buffer); + + expect (! layout.getLowerZone().isActive()); + expect (! layout.getUpperZone().isActive()); + } + + beginTest ("process individual MIDI messages"); + { + MPEZoneLayout layout; + + layout.processNextMidiEvent ({ 0x80, 0x59, 0xd0 }); // unrelated note-off msg + layout.processNextMidiEvent ({ 0xb0, 0x64, 0x06 }); // RPN part 1 + layout.processNextMidiEvent ({ 0xb0, 0x65, 0x00 }); // RPN part 2 + layout.processNextMidiEvent ({ 0xb8, 0x0b, 0x66 }); // unrelated CC msg + layout.processNextMidiEvent ({ 0xb0, 0x06, 0x03 }); // RPN part 3 + layout.processNextMidiEvent ({ 0x90, 0x60, 0x00 }); // unrelated note-on msg + + expect (layout.getLowerZone().isActive()); + expect (! layout.getUpperZone().isActive()); + expectEquals (layout.getLowerZone().getMasterChannel(), 1); + expectEquals (layout.getLowerZone().numMemberChannels, 3); + expectEquals (layout.getLowerZone().perNotePitchbendRange, 48); + expectEquals (layout.getLowerZone().masterPitchbendRange, 2); + } + } +}; + +static MPEZoneLayoutTests MPEZoneLayoutUnitTests; + + +#endif + +} // namespace juce diff --git a/JuceLibraryCode/modules/juce_audio_basics/mpe/juce_MPEZoneLayout.h b/JuceLibraryCode/modules/juce_audio_basics/mpe/juce_MPEZoneLayout.h index 58523ee..32d1a2f 100644 --- a/JuceLibraryCode/modules/juce_audio_basics/mpe/juce_MPEZoneLayout.h +++ b/JuceLibraryCode/modules/juce_audio_basics/mpe/juce_MPEZoneLayout.h @@ -1,225 +1,225 @@ -/* - ============================================================================== - - This file is part of the JUCE library. - Copyright (c) 2017 - ROLI Ltd. - - JUCE is an open source library subject to commercial or open-source - licensing. - - The code included in this file is provided under the terms of the ISC license - http://www.isc.org/downloads/software-support-policy/isc-license. Permission - To use, copy, modify, and/or distribute this software for any purpose with or - without fee is hereby granted provided that the above copyright notice and - this permission notice appear in all copies. - - JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER - EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE - DISCLAIMED. - - ============================================================================== -*/ - -namespace juce -{ - -//============================================================================== -/** - This class represents the current MPE zone layout of a device capable of handling MPE. - - An MPE device can have up to two zones: a lower zone with master channel 1 and - allocated MIDI channels increasing from channel 2, and an upper zone with master - channel 16 and allocated MIDI channels decreasing from channel 15. MPE mode is - enabled on a device when one of these zones is active and disabled when both - are inactive. - - Use the MPEMessages helper class to convert the zone layout represented - by this object to MIDI message sequences that you can send to an Expressive - MIDI device to set its zone layout, add zones etc. - - @see MPEInstrument - - @tags{Audio} -*/ -class JUCE_API MPEZoneLayout -{ -public: - /** Default constructor. - - This will create a layout with inactive lower and upper zones, representing - a device with MPE mode disabled. - - You can set the lower or upper MPE zones using the setZone() method. - - @see setZone - */ - MPEZoneLayout() noexcept; - - /** Copy constuctor. - This will not copy the listeners registered to the MPEZoneLayout. - */ - MPEZoneLayout (const MPEZoneLayout& other); - - /** Copy assignment operator. - This will not copy the listeners registered to the MPEZoneLayout. - */ - MPEZoneLayout& operator= (const MPEZoneLayout& other); - - //============================================================================== - /** - This struct represents an MPE zone. - - It can either be a lower or an upper zone, where: - - A lower zone encompasses master channel 1 and an arbitrary number of ascending - MIDI channels, increasing from channel 2. - - An upper zone encompasses master channel 16 and an arbitrary number of descending - MIDI channels, decreasing from channel 15. - - It also defines a pitchbend range (in semitones) to be applied for per-note pitchbends and - master pitchbends, respectively. - */ - struct Zone - { - Zone (const Zone& other) = default; - - bool isLowerZone() const noexcept { return lowerZone; } - bool isUpperZone() const noexcept { return ! lowerZone; } - - bool isActive() const noexcept { return numMemberChannels > 0; } - - int getMasterChannel() const noexcept { return lowerZone ? 1 : 16; } - int getFirstMemberChannel() const noexcept { return lowerZone ? 2 : 15; } - int getLastMemberChannel() const noexcept { return lowerZone ? (1 + numMemberChannels) - : (16 - numMemberChannels); } - - bool isUsingChannelAsMemberChannel (int channel) const noexcept - { - return lowerZone ? (channel > 1 && channel <= 1 + numMemberChannels) - : (channel < 16 && channel >= 16 - numMemberChannels); - } - - bool isUsing (int channel) const noexcept - { - return isUsingChannelAsMemberChannel (channel) || channel == getMasterChannel(); - } - - bool operator== (const Zone& other) const noexcept { return lowerZone == other.lowerZone - && numMemberChannels == other.numMemberChannels - && perNotePitchbendRange == other.perNotePitchbendRange - && masterPitchbendRange == other.masterPitchbendRange; } - - bool operator!= (const Zone& other) const noexcept { return ! operator== (other); } - - int numMemberChannels; - int perNotePitchbendRange; - int masterPitchbendRange; - - private: - friend class MPEZoneLayout; - - Zone (bool lower, int memberChans = 0, int perNotePb = 48, int masterPb = 2) noexcept - : numMemberChannels (memberChans), - perNotePitchbendRange (perNotePb), - masterPitchbendRange (masterPb), - lowerZone (lower) - { - } - - bool lowerZone; - }; - - /** Sets the lower zone of this layout. */ - void setLowerZone (int numMemberChannels = 0, - int perNotePitchbendRange = 48, - int masterPitchbendRange = 2) noexcept; - - /** Sets the upper zone of this layout. */ - void setUpperZone (int numMemberChannels = 0, - int perNotePitchbendRange = 48, - int masterPitchbendRange = 2) noexcept; - - /** Returns a struct representing the lower MPE zone. */ - const Zone getLowerZone() const noexcept { return lowerZone; } - - /** Returns a struct representing the upper MPE zone. */ - const Zone getUpperZone() const noexcept { return upperZone; } - - /** Clears the lower and upper zones of this layout, making them both inactive - and disabling MPE mode. - */ - void clearAllZones(); - - //============================================================================== - /** Pass incoming MIDI messages to an object of this class if you want the - zone layout to properly react to MPE RPN messages like an - MPE device. - - MPEMessages::rpnNumber will add or remove zones; RPN 0 will - set the per-note or master pitchbend ranges. - - Any other MIDI messages will be ignored by this class. - - @see MPEMessages - */ - void processNextMidiEvent (const MidiMessage& message); - - /** Pass incoming MIDI buffers to an object of this class if you want the - zone layout to properly react to MPE RPN messages like an - MPE device. - - MPEMessages::rpnNumber will add or remove zones; RPN 0 will - set the per-note or master pitchbend ranges. - - Any other MIDI messages will be ignored by this class. - - @see MPEMessages - */ - void processNextMidiBuffer (const MidiBuffer& buffer); - - //============================================================================== - /** Listener class. Derive from this class to allow your class to be - notified about changes to the zone layout. - */ - class Listener - { - public: - /** Destructor. */ - virtual ~Listener() = default; - - /** Implement this callback to be notified about any changes to this - MPEZoneLayout. Will be called whenever a zone is added, zones are - removed, or any zone's master or note pitchbend ranges change. - */ - virtual void zoneLayoutChanged (const MPEZoneLayout& layout) = 0; - }; - - //============================================================================== - /** Adds a listener. */ - void addListener (Listener* const listenerToAdd) noexcept; - - /** Removes a listener. */ - void removeListener (Listener* const listenerToRemove) noexcept; - -private: - //============================================================================== - Zone lowerZone { true, 0 }; - Zone upperZone { false, 0 }; - - MidiRPNDetector rpnDetector; - ListenerList listeners; - - //============================================================================== - void setZone (bool, int, int, int) noexcept; - - void processRpnMessage (MidiRPNMessage); - void processZoneLayoutRpnMessage (MidiRPNMessage); - void processPitchbendRangeRpnMessage (MidiRPNMessage); - - void updateMasterPitchbend (Zone&, int); - void updatePerNotePitchbendRange (Zone&, int); - - void sendLayoutChangeMessage(); - void checkAndLimitZoneParameters (int, int, int&) noexcept; -}; - -} // namespace juce +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2017 - ROLI Ltd. + + JUCE is an open source library subject to commercial or open-source + licensing. + + The code included in this file is provided under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license. Permission + To use, copy, modify, and/or distribute this software for any purpose with or + without fee is hereby granted provided that the above copyright notice and + this permission notice appear in all copies. + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +namespace juce +{ + +//============================================================================== +/** + This class represents the current MPE zone layout of a device capable of handling MPE. + + An MPE device can have up to two zones: a lower zone with master channel 1 and + allocated MIDI channels increasing from channel 2, and an upper zone with master + channel 16 and allocated MIDI channels decreasing from channel 15. MPE mode is + enabled on a device when one of these zones is active and disabled when both + are inactive. + + Use the MPEMessages helper class to convert the zone layout represented + by this object to MIDI message sequences that you can send to an Expressive + MIDI device to set its zone layout, add zones etc. + + @see MPEInstrument + + @tags{Audio} +*/ +class JUCE_API MPEZoneLayout +{ +public: + /** Default constructor. + + This will create a layout with inactive lower and upper zones, representing + a device with MPE mode disabled. + + You can set the lower or upper MPE zones using the setZone() method. + + @see setZone + */ + MPEZoneLayout() noexcept; + + /** Copy constuctor. + This will not copy the listeners registered to the MPEZoneLayout. + */ + MPEZoneLayout (const MPEZoneLayout& other); + + /** Copy assignment operator. + This will not copy the listeners registered to the MPEZoneLayout. + */ + MPEZoneLayout& operator= (const MPEZoneLayout& other); + + //============================================================================== + /** + This struct represents an MPE zone. + + It can either be a lower or an upper zone, where: + - A lower zone encompasses master channel 1 and an arbitrary number of ascending + MIDI channels, increasing from channel 2. + - An upper zone encompasses master channel 16 and an arbitrary number of descending + MIDI channels, decreasing from channel 15. + + It also defines a pitchbend range (in semitones) to be applied for per-note pitchbends and + master pitchbends, respectively. + */ + struct Zone + { + Zone (const Zone& other) = default; + + bool isLowerZone() const noexcept { return lowerZone; } + bool isUpperZone() const noexcept { return ! lowerZone; } + + bool isActive() const noexcept { return numMemberChannels > 0; } + + int getMasterChannel() const noexcept { return lowerZone ? 1 : 16; } + int getFirstMemberChannel() const noexcept { return lowerZone ? 2 : 15; } + int getLastMemberChannel() const noexcept { return lowerZone ? (1 + numMemberChannels) + : (16 - numMemberChannels); } + + bool isUsingChannelAsMemberChannel (int channel) const noexcept + { + return lowerZone ? (channel > 1 && channel <= 1 + numMemberChannels) + : (channel < 16 && channel >= 16 - numMemberChannels); + } + + bool isUsing (int channel) const noexcept + { + return isUsingChannelAsMemberChannel (channel) || channel == getMasterChannel(); + } + + bool operator== (const Zone& other) const noexcept { return lowerZone == other.lowerZone + && numMemberChannels == other.numMemberChannels + && perNotePitchbendRange == other.perNotePitchbendRange + && masterPitchbendRange == other.masterPitchbendRange; } + + bool operator!= (const Zone& other) const noexcept { return ! operator== (other); } + + int numMemberChannels; + int perNotePitchbendRange; + int masterPitchbendRange; + + private: + friend class MPEZoneLayout; + + Zone (bool lower, int memberChans = 0, int perNotePb = 48, int masterPb = 2) noexcept + : numMemberChannels (memberChans), + perNotePitchbendRange (perNotePb), + masterPitchbendRange (masterPb), + lowerZone (lower) + { + } + + bool lowerZone; + }; + + /** Sets the lower zone of this layout. */ + void setLowerZone (int numMemberChannels = 0, + int perNotePitchbendRange = 48, + int masterPitchbendRange = 2) noexcept; + + /** Sets the upper zone of this layout. */ + void setUpperZone (int numMemberChannels = 0, + int perNotePitchbendRange = 48, + int masterPitchbendRange = 2) noexcept; + + /** Returns a struct representing the lower MPE zone. */ + const Zone getLowerZone() const noexcept { return lowerZone; } + + /** Returns a struct representing the upper MPE zone. */ + const Zone getUpperZone() const noexcept { return upperZone; } + + /** Clears the lower and upper zones of this layout, making them both inactive + and disabling MPE mode. + */ + void clearAllZones(); + + //============================================================================== + /** Pass incoming MIDI messages to an object of this class if you want the + zone layout to properly react to MPE RPN messages like an + MPE device. + + MPEMessages::rpnNumber will add or remove zones; RPN 0 will + set the per-note or master pitchbend ranges. + + Any other MIDI messages will be ignored by this class. + + @see MPEMessages + */ + void processNextMidiEvent (const MidiMessage& message); + + /** Pass incoming MIDI buffers to an object of this class if you want the + zone layout to properly react to MPE RPN messages like an + MPE device. + + MPEMessages::rpnNumber will add or remove zones; RPN 0 will + set the per-note or master pitchbend ranges. + + Any other MIDI messages will be ignored by this class. + + @see MPEMessages + */ + void processNextMidiBuffer (const MidiBuffer& buffer); + + //============================================================================== + /** Listener class. Derive from this class to allow your class to be + notified about changes to the zone layout. + */ + class Listener + { + public: + /** Destructor. */ + virtual ~Listener() = default; + + /** Implement this callback to be notified about any changes to this + MPEZoneLayout. Will be called whenever a zone is added, zones are + removed, or any zone's master or note pitchbend ranges change. + */ + virtual void zoneLayoutChanged (const MPEZoneLayout& layout) = 0; + }; + + //============================================================================== + /** Adds a listener. */ + void addListener (Listener* const listenerToAdd) noexcept; + + /** Removes a listener. */ + void removeListener (Listener* const listenerToRemove) noexcept; + +private: + //============================================================================== + Zone lowerZone { true, 0 }; + Zone upperZone { false, 0 }; + + MidiRPNDetector rpnDetector; + ListenerList listeners; + + //============================================================================== + void setZone (bool, int, int, int) noexcept; + + void processRpnMessage (MidiRPNMessage); + void processZoneLayoutRpnMessage (MidiRPNMessage); + void processPitchbendRangeRpnMessage (MidiRPNMessage); + + void updateMasterPitchbend (Zone&, int); + void updatePerNotePitchbendRange (Zone&, int); + + void sendLayoutChangeMessage(); + void checkAndLimitZoneParameters (int, int, int&) noexcept; +}; + +} // namespace juce diff --git a/JuceLibraryCode/modules/juce_audio_basics/native/juce_mac_CoreAudioLayouts.h b/JuceLibraryCode/modules/juce_audio_basics/native/juce_mac_CoreAudioLayouts.h index fe79a37..798197d 100644 --- a/JuceLibraryCode/modules/juce_audio_basics/native/juce_mac_CoreAudioLayouts.h +++ b/JuceLibraryCode/modules/juce_audio_basics/native/juce_mac_CoreAudioLayouts.h @@ -1,330 +1,330 @@ -/* - ============================================================================== - - This file is part of the JUCE library. - Copyright (c) 2017 - ROLI Ltd. - - JUCE is an open source library subject to commercial or open-source - licensing. - - The code included in this file is provided under the terms of the ISC license - http://www.isc.org/downloads/software-support-policy/isc-license. Permission - To use, copy, modify, and/or distribute this software for any purpose with or - without fee is hereby granted provided that the above copyright notice and - this permission notice appear in all copies. - - JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER - EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE - DISCLAIMED. - - ============================================================================== -*/ - -namespace juce -{ - -#if ! DOXYGEN && (JUCE_MAC || JUCE_IOS) - -struct CoreAudioLayouts -{ - //============================================================================== - enum - { - coreAudioHOASN3DLayoutTag = (190U<<16) | 0 // kAudioChannelLayoutTag_HOA_ACN_SN3D - }; - - //============================================================================== - /** Convert CoreAudio's native AudioChannelLayout to JUCE's AudioChannelSet. - - Note that this method cannot preserve the order of channels. - */ - static AudioChannelSet fromCoreAudio (const AudioChannelLayout& layout) - { - return AudioChannelSet::channelSetWithChannels (getCoreAudioLayoutChannels (layout)); - } - - /** Convert CoreAudio's native AudioChannelLayoutTag to JUCE's AudioChannelSet. - - Note that this method cannot preserve the order of channels. - */ - static AudioChannelSet fromCoreAudio (AudioChannelLayoutTag layoutTag) - { - return AudioChannelSet::channelSetWithChannels (getSpeakerLayoutForCoreAudioTag (layoutTag)); - } - - /** Convert JUCE's AudioChannelSet to CoreAudio's AudioChannelLayoutTag. - - Note that this method cannot preserve the order of channels. - */ - static AudioChannelLayoutTag toCoreAudio (const AudioChannelSet& set) - { - if (set.getAmbisonicOrder() >= 0) - return coreAudioHOASN3DLayoutTag | static_cast (set.size()); - - for (auto* tbl = SpeakerLayoutTable::get(); tbl->tag != 0; ++tbl) - { - AudioChannelSet caSet; - - for (int i = 0; i < numElementsInArray (tbl->channelTypes) - && tbl->channelTypes[i] != AudioChannelSet::unknown; ++i) - caSet.addChannel (tbl->channelTypes[i]); - - if (caSet == set) - return tbl->tag; - } - - return kAudioChannelLayoutTag_DiscreteInOrder | static_cast (set.size()); - } - - static const Array& getKnownCoreAudioTags() - { - static Array tags (createKnownCoreAudioTags()); - return tags; - } - - //============================================================================== - /** Convert CoreAudio's native AudioChannelLayout to an array of JUCE ChannelTypes. */ - static Array getCoreAudioLayoutChannels (const AudioChannelLayout& layout) - { - switch (layout.mChannelLayoutTag & 0xffff0000) - { - case kAudioChannelLayoutTag_UseChannelBitmap: - return AudioChannelSet::fromWaveChannelMask (static_cast (layout.mChannelBitmap)).getChannelTypes(); - case kAudioChannelLayoutTag_UseChannelDescriptions: - { - Array channels; - - for (UInt32 i = 0; i < layout.mNumberChannelDescriptions; ++i) - channels.addIfNotAlreadyThere (getChannelTypeFromAudioChannelLabel (layout.mChannelDescriptions[i].mChannelLabel)); - - // different speaker mappings may point to the same JUCE speaker so fill up - // this array with discrete channels - for (int j = 0; channels.size() < static_cast (layout.mNumberChannelDescriptions); ++j) - channels.addIfNotAlreadyThere (static_cast (AudioChannelSet::discreteChannel0 + j)); - - return channels; - } - case kAudioChannelLayoutTag_DiscreteInOrder: - return AudioChannelSet::discreteChannels (static_cast (layout.mChannelLayoutTag) & 0xffff).getChannelTypes(); - default: - break; - } - - return getSpeakerLayoutForCoreAudioTag (layout.mChannelLayoutTag); - } - - static Array getSpeakerLayoutForCoreAudioTag (AudioChannelLayoutTag tag) - { - // You need to specify the full AudioChannelLayout when using - // the UseChannelBitmap and UseChannelDescriptions layout tag - jassert (tag != kAudioChannelLayoutTag_UseChannelBitmap && tag != kAudioChannelLayoutTag_UseChannelDescriptions); - - Array speakers; - - for (auto* tbl = SpeakerLayoutTable::get(); tbl->tag != 0; ++tbl) - { - if (tag == tbl->tag) - { - for (int i = 0; i < numElementsInArray (tbl->channelTypes) - && tbl->channelTypes[i] != AudioChannelSet::unknown; ++i) - speakers.add (tbl->channelTypes[i]); - - return speakers; - } - } - - auto numChannels = tag & 0xffff; - if (tag >= coreAudioHOASN3DLayoutTag && tag <= (coreAudioHOASN3DLayoutTag | 0xffff)) - { - auto sqrtMinusOne = std::sqrt (static_cast (numChannels)) - 1.0f; - auto ambisonicOrder = jmax (0, static_cast (std::floor (sqrtMinusOne))); - - if (static_cast (ambisonicOrder) == sqrtMinusOne) - return AudioChannelSet::ambisonic (ambisonicOrder).getChannelTypes(); - } - - for (UInt32 i = 0; i < numChannels; ++i) - speakers.add (static_cast (AudioChannelSet::discreteChannel0 + i)); - - return speakers; - } - -private: - //============================================================================== - struct LayoutTagSpeakerList - { - AudioChannelLayoutTag tag; - AudioChannelSet::ChannelType channelTypes[16]; - }; - - static Array createKnownCoreAudioTags() - { - Array tags; - - for (auto* tbl = SpeakerLayoutTable::get(); tbl->tag != 0; ++tbl) - tags.addIfNotAlreadyThere (tbl->tag); - - for (unsigned order = 0; order <= 5; ++order) - tags.addIfNotAlreadyThere (coreAudioHOASN3DLayoutTag | ((order + 1) * (order + 1))); - - return tags; - } - - //============================================================================== - // This list has been derived from https://pastebin.com/24dQ4BPJ - // Apple channel labels have been replaced by JUCE channel names - // This means that some layouts will be identical in JUCE but not in CoreAudio - - // In Apple's official definition the following tags exist with the same speaker layout and order - // even when *not* represented in JUCE channels - // kAudioChannelLayoutTag_Binaural = kAudioChannelLayoutTag_Stereo - // kAudioChannelLayoutTag_MPEG_5_0_B = kAudioChannelLayoutTag_Pentagonal - // kAudioChannelLayoutTag_ITU_2_2 = kAudioChannelLayoutTag_Quadraphonic - // kAudioChannelLayoutTag_AudioUnit_6_0 = kAudioChannelLayoutTag_Hexagonal - struct SpeakerLayoutTable : AudioChannelSet // save us some typing - { - static LayoutTagSpeakerList* get() noexcept - { - static LayoutTagSpeakerList tbl[] = { - // list layouts for which there is a corresponding named AudioChannelSet first - { kAudioChannelLayoutTag_Mono, { centre } }, - { kAudioChannelLayoutTag_Stereo, { left, right } }, - { kAudioChannelLayoutTag_MPEG_3_0_A, { left, right, centre } }, - { kAudioChannelLayoutTag_ITU_2_1, { left, right, centreSurround } }, - { kAudioChannelLayoutTag_MPEG_4_0_A, { left, right, centre, centreSurround } }, - { kAudioChannelLayoutTag_MPEG_5_0_A, { left, right, centre, leftSurround, rightSurround } }, - { kAudioChannelLayoutTag_MPEG_5_1_A, { left, right, centre, LFE, leftSurround, rightSurround } }, - { kAudioChannelLayoutTag_AudioUnit_6_0, { left, right, leftSurround, rightSurround, centre, centreSurround } }, - { kAudioChannelLayoutTag_MPEG_6_1_A, { left, right, centre, LFE, leftSurround, rightSurround, centreSurround } }, - { kAudioChannelLayoutTag_DTS_6_0_A, { leftSurroundSide, rightSurroundSide, left, right, leftSurround, rightSurround } }, - { kAudioChannelLayoutTag_DTS_6_1_A, { leftSurroundSide, rightSurroundSide, left, right, leftSurround, rightSurround, LFE } }, - { kAudioChannelLayoutTag_AudioUnit_7_0, { left, right, leftSurroundSide, rightSurroundSide, centre, leftSurroundRear, rightSurroundRear } }, - { kAudioChannelLayoutTag_AudioUnit_7_0_Front, { left, right, leftSurround, rightSurround, centre, leftCentre, rightCentre } }, - { kAudioChannelLayoutTag_MPEG_7_1_C, { left, right, centre, LFE, leftSurroundSide, rightSurroundSide, leftSurroundRear, rightSurroundRear } }, - { kAudioChannelLayoutTag_MPEG_7_1_A, { left, right, centre, LFE, leftSurround, rightSurround, leftCentre, rightCentre } }, - { kAudioChannelLayoutTag_Ambisonic_B_Format, { ambisonicW, ambisonicX, ambisonicY, ambisonicZ } }, - { kAudioChannelLayoutTag_Quadraphonic, { left, right, leftSurround, rightSurround } }, - { kAudioChannelLayoutTag_Pentagonal, { left, right, leftSurroundRear, rightSurroundRear, centre } }, - { kAudioChannelLayoutTag_Hexagonal, { left, right, leftSurroundRear, rightSurroundRear, centre, centreSurround } }, - { kAudioChannelLayoutTag_Octagonal, { left, right, leftSurround, rightSurround, centre, centreSurround, wideLeft, wideRight } }, - - // more uncommon layouts - { kAudioChannelLayoutTag_StereoHeadphones, { left, right } }, - { kAudioChannelLayoutTag_MatrixStereo, { left, right } }, - { kAudioChannelLayoutTag_MidSide, { centre, discreteChannel0 } }, - { kAudioChannelLayoutTag_XY, { ambisonicX, ambisonicY } }, - { kAudioChannelLayoutTag_Binaural, { left, right } }, - { kAudioChannelLayoutTag_Cube, { left, right, leftSurround, rightSurround, topFrontLeft, topFrontRight, topRearLeft, topRearRight } }, - { kAudioChannelLayoutTag_MPEG_3_0_B, { centre, left, right } }, - { kAudioChannelLayoutTag_MPEG_4_0_B, { centre, left, right, centreSurround } }, - { kAudioChannelLayoutTag_MPEG_5_0_B, { left, right, leftSurround, rightSurround, centre } }, - { kAudioChannelLayoutTag_MPEG_5_0_C, { left, centre, right, leftSurround, rightSurround } }, - { kAudioChannelLayoutTag_MPEG_5_0_D, { centre, left, right, leftSurround, rightSurround } }, - { kAudioChannelLayoutTag_MPEG_5_1_B, { left, right, leftSurround, rightSurround, centre, LFE } }, - { kAudioChannelLayoutTag_MPEG_5_1_C, { left, centre, right, leftSurround, rightSurround, LFE } }, - { kAudioChannelLayoutTag_MPEG_5_1_D, { centre, left, right, leftSurround, rightSurround, LFE } }, - { kAudioChannelLayoutTag_MPEG_7_1_B, { centre, leftCentre, rightCentre, left, right, leftSurround, rightSurround, LFE } }, - { kAudioChannelLayoutTag_Emagic_Default_7_1, { left, right, leftSurround, rightSurround, centre, LFE, leftCentre, rightCentre } }, - { kAudioChannelLayoutTag_SMPTE_DTV, { left, right, centre, LFE, leftSurround, rightSurround, discreteChannel0 /* leftMatrixTotal */, (ChannelType) (discreteChannel0 + 1) /* rightMatrixTotal */} }, - { kAudioChannelLayoutTag_ITU_2_2, { left, right, leftSurround, rightSurround } }, - { kAudioChannelLayoutTag_DVD_4, { left, right, LFE } }, - { kAudioChannelLayoutTag_DVD_5, { left, right, LFE, centreSurround } }, - { kAudioChannelLayoutTag_DVD_6, { left, right, LFE, leftSurround, rightSurround } }, - { kAudioChannelLayoutTag_DVD_10, { left, right, centre, LFE } }, - { kAudioChannelLayoutTag_DVD_11, { left, right, centre, LFE, centreSurround } }, - { kAudioChannelLayoutTag_DVD_18, { left, right, leftSurround, rightSurround, LFE } }, - { kAudioChannelLayoutTag_AAC_6_0, { centre, left, right, leftSurround, rightSurround, centreSurround } }, - { kAudioChannelLayoutTag_AAC_6_1, { centre, left, right, leftSurround, rightSurround, centreSurround, LFE } }, - { kAudioChannelLayoutTag_AAC_7_0, { centre, left, right, leftSurround, rightSurround, leftSurroundRear, rightSurroundRear } }, - { kAudioChannelLayoutTag_AAC_7_1_B, { centre, left, right, leftSurround, rightSurround, leftSurroundRear, rightSurroundRear, LFE } }, - { kAudioChannelLayoutTag_AAC_7_1_C, { centre, left, right, leftSurround, rightSurround, LFE, topFrontLeft, topFrontRight } }, - { kAudioChannelLayoutTag_AAC_Octagonal, { centre, left, right, leftSurround, rightSurround, leftSurroundRear, rightSurroundRear, centreSurround } }, - { kAudioChannelLayoutTag_TMH_10_2_std, { left, right, centre, topFrontCentre, leftSurroundSide, rightSurroundSide, leftSurround, rightSurround, topFrontLeft, topFrontRight, wideLeft, wideRight, topRearCentre, centreSurround, LFE, LFE2 } }, - { kAudioChannelLayoutTag_AC3_1_0_1, { centre, LFE } }, - { kAudioChannelLayoutTag_AC3_3_0, { left, centre, right } }, - { kAudioChannelLayoutTag_AC3_3_1, { left, centre, right, centreSurround } }, - { kAudioChannelLayoutTag_AC3_3_0_1, { left, centre, right, LFE } }, - { kAudioChannelLayoutTag_AC3_2_1_1, { left, right, centreSurround, LFE } }, - { kAudioChannelLayoutTag_AC3_3_1_1, { left, centre, right, centreSurround, LFE } }, - { kAudioChannelLayoutTag_EAC_6_0_A, { left, centre, right, leftSurround, rightSurround, centreSurround } }, - { kAudioChannelLayoutTag_EAC_7_0_A, { left, centre, right, leftSurround, rightSurround, leftSurroundRear, rightSurroundRear } }, - { kAudioChannelLayoutTag_EAC3_6_1_A, { left, centre, right, leftSurround, rightSurround, LFE, centreSurround } }, - { kAudioChannelLayoutTag_EAC3_6_1_B, { left, centre, right, leftSurround, rightSurround, LFE, centreSurround } }, - { kAudioChannelLayoutTag_EAC3_6_1_C, { left, centre, right, leftSurround, rightSurround, LFE, topFrontCentre } }, - { kAudioChannelLayoutTag_EAC3_7_1_A, { left, centre, right, leftSurround, rightSurround, LFE, leftSurroundRear, rightSurroundRear } }, - { kAudioChannelLayoutTag_EAC3_7_1_B, { left, centre, right, leftSurround, rightSurround, LFE, leftCentre, rightCentre } }, - { kAudioChannelLayoutTag_EAC3_7_1_C, { left, centre, right, leftSurround, rightSurround, LFE, leftSurroundSide, rightSurroundSide } }, - { kAudioChannelLayoutTag_EAC3_7_1_D, { left, centre, right, leftSurround, rightSurround, LFE, wideLeft, wideRight } }, - { kAudioChannelLayoutTag_EAC3_7_1_E, { left, centre, right, leftSurround, rightSurround, LFE, topFrontLeft, topFrontRight } }, - { kAudioChannelLayoutTag_EAC3_7_1_F, { left, centre, right, leftSurround, rightSurround, LFE, centreSurround, topMiddle } }, - { kAudioChannelLayoutTag_EAC3_7_1_G, { left, centre, right, leftSurround, rightSurround, LFE, centreSurround, topFrontCentre } }, - { kAudioChannelLayoutTag_EAC3_7_1_H, { left, centre, right, leftSurround, rightSurround, LFE, centreSurround, topFrontCentre } }, - { kAudioChannelLayoutTag_DTS_3_1, { centre, left, right, LFE } }, - { kAudioChannelLayoutTag_DTS_4_1, { centre, left, right, centreSurround, LFE } }, - { kAudioChannelLayoutTag_DTS_6_0_B, { centre, left, right, leftSurroundRear, rightSurroundRear, centreSurround } }, - { kAudioChannelLayoutTag_DTS_6_0_C, { centre, centreSurround, left, right, leftSurroundRear, rightSurroundRear } }, - { kAudioChannelLayoutTag_DTS_6_1_B, { centre, left, right, leftSurroundRear, rightSurroundRear, centreSurround, LFE } }, - { kAudioChannelLayoutTag_DTS_6_1_C, { centre, centreSurround, left, right, leftSurroundRear, rightSurroundRear, LFE } }, - { kAudioChannelLayoutTag_DTS_6_1_D, { centre, left, right, leftSurround, rightSurround, LFE, centreSurround } }, - { kAudioChannelLayoutTag_DTS_7_0, { leftCentre, centre, rightCentre, left, right, leftSurround, rightSurround } }, - { kAudioChannelLayoutTag_DTS_7_1, { leftCentre, centre, rightCentre, left, right, leftSurround, rightSurround, LFE } }, - { kAudioChannelLayoutTag_DTS_8_0_A, { leftCentre, rightCentre, left, right, leftSurround, rightSurround, leftSurroundRear, rightSurroundRear } }, - { kAudioChannelLayoutTag_DTS_8_0_B, { leftCentre, centre, rightCentre, left, right, leftSurround, centreSurround, rightSurround } }, - { kAudioChannelLayoutTag_DTS_8_1_A, { leftCentre, rightCentre, left, right, leftSurround, rightSurround, leftSurroundRear, rightSurroundRear, LFE } }, - { kAudioChannelLayoutTag_DTS_8_1_B, { leftCentre, centre, rightCentre, left, right, leftSurround, centreSurround, rightSurround, LFE } }, - { 0, {} } - }; - - return tbl; - } - }; - - //============================================================================== - static AudioChannelSet::ChannelType getChannelTypeFromAudioChannelLabel (AudioChannelLabel label) noexcept - { - if (label >= kAudioChannelLabel_Discrete_0 && label <= kAudioChannelLabel_Discrete_65535) - { - const unsigned int discreteChannelNum = label - kAudioChannelLabel_Discrete_0; - return static_cast (AudioChannelSet::discreteChannel0 + discreteChannelNum); - } - - switch (label) - { - case kAudioChannelLabel_Center: - case kAudioChannelLabel_Mono: return AudioChannelSet::centre; - case kAudioChannelLabel_Left: - case kAudioChannelLabel_HeadphonesLeft: return AudioChannelSet::left; - case kAudioChannelLabel_Right: - case kAudioChannelLabel_HeadphonesRight: return AudioChannelSet::right; - case kAudioChannelLabel_LFEScreen: return AudioChannelSet::LFE; - case kAudioChannelLabel_LeftSurround: return AudioChannelSet::leftSurround; - case kAudioChannelLabel_RightSurround: return AudioChannelSet::rightSurround; - case kAudioChannelLabel_LeftCenter: return AudioChannelSet::leftCentre; - case kAudioChannelLabel_RightCenter: return AudioChannelSet::rightCentre; - case kAudioChannelLabel_CenterSurround: return AudioChannelSet::surround; - case kAudioChannelLabel_LeftSurroundDirect: return AudioChannelSet::leftSurroundSide; - case kAudioChannelLabel_RightSurroundDirect: return AudioChannelSet::rightSurroundSide; - case kAudioChannelLabel_TopCenterSurround: return AudioChannelSet::topMiddle; - case kAudioChannelLabel_VerticalHeightLeft: return AudioChannelSet::topFrontLeft; - case kAudioChannelLabel_VerticalHeightRight: return AudioChannelSet::topFrontRight; - case kAudioChannelLabel_VerticalHeightCenter: return AudioChannelSet::topFrontCentre; - case kAudioChannelLabel_TopBackLeft: return AudioChannelSet::topRearLeft; - case kAudioChannelLabel_RearSurroundLeft: return AudioChannelSet::leftSurroundRear; - case kAudioChannelLabel_TopBackRight: return AudioChannelSet::topRearRight; - case kAudioChannelLabel_RearSurroundRight: return AudioChannelSet::rightSurroundRear; - case kAudioChannelLabel_TopBackCenter: return AudioChannelSet::topRearCentre; - case kAudioChannelLabel_LFE2: return AudioChannelSet::LFE2; - case kAudioChannelLabel_LeftWide: return AudioChannelSet::wideLeft; - case kAudioChannelLabel_RightWide: return AudioChannelSet::wideRight; - case kAudioChannelLabel_Ambisonic_W: return AudioChannelSet::ambisonicW; - case kAudioChannelLabel_Ambisonic_X: return AudioChannelSet::ambisonicX; - case kAudioChannelLabel_Ambisonic_Y: return AudioChannelSet::ambisonicY; - case kAudioChannelLabel_Ambisonic_Z: return AudioChannelSet::ambisonicZ; - default: return AudioChannelSet::unknown; - } - } -}; - -#endif - -} // namespace juce +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2017 - ROLI Ltd. + + JUCE is an open source library subject to commercial or open-source + licensing. + + The code included in this file is provided under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license. Permission + To use, copy, modify, and/or distribute this software for any purpose with or + without fee is hereby granted provided that the above copyright notice and + this permission notice appear in all copies. + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +namespace juce +{ + +#if ! DOXYGEN && (JUCE_MAC || JUCE_IOS) + +struct CoreAudioLayouts +{ + //============================================================================== + enum + { + coreAudioHOASN3DLayoutTag = (190U<<16) | 0 // kAudioChannelLayoutTag_HOA_ACN_SN3D + }; + + //============================================================================== + /** Convert CoreAudio's native AudioChannelLayout to JUCE's AudioChannelSet. + + Note that this method cannot preserve the order of channels. + */ + static AudioChannelSet fromCoreAudio (const AudioChannelLayout& layout) + { + return AudioChannelSet::channelSetWithChannels (getCoreAudioLayoutChannels (layout)); + } + + /** Convert CoreAudio's native AudioChannelLayoutTag to JUCE's AudioChannelSet. + + Note that this method cannot preserve the order of channels. + */ + static AudioChannelSet fromCoreAudio (AudioChannelLayoutTag layoutTag) + { + return AudioChannelSet::channelSetWithChannels (getSpeakerLayoutForCoreAudioTag (layoutTag)); + } + + /** Convert JUCE's AudioChannelSet to CoreAudio's AudioChannelLayoutTag. + + Note that this method cannot preserve the order of channels. + */ + static AudioChannelLayoutTag toCoreAudio (const AudioChannelSet& set) + { + if (set.getAmbisonicOrder() >= 0) + return coreAudioHOASN3DLayoutTag | static_cast (set.size()); + + for (auto* tbl = SpeakerLayoutTable::get(); tbl->tag != 0; ++tbl) + { + AudioChannelSet caSet; + + for (int i = 0; i < numElementsInArray (tbl->channelTypes) + && tbl->channelTypes[i] != AudioChannelSet::unknown; ++i) + caSet.addChannel (tbl->channelTypes[i]); + + if (caSet == set) + return tbl->tag; + } + + return kAudioChannelLayoutTag_DiscreteInOrder | static_cast (set.size()); + } + + static const Array& getKnownCoreAudioTags() + { + static Array tags (createKnownCoreAudioTags()); + return tags; + } + + //============================================================================== + /** Convert CoreAudio's native AudioChannelLayout to an array of JUCE ChannelTypes. */ + static Array getCoreAudioLayoutChannels (const AudioChannelLayout& layout) + { + switch (layout.mChannelLayoutTag & 0xffff0000) + { + case kAudioChannelLayoutTag_UseChannelBitmap: + return AudioChannelSet::fromWaveChannelMask (static_cast (layout.mChannelBitmap)).getChannelTypes(); + case kAudioChannelLayoutTag_UseChannelDescriptions: + { + Array channels; + + for (UInt32 i = 0; i < layout.mNumberChannelDescriptions; ++i) + channels.addIfNotAlreadyThere (getChannelTypeFromAudioChannelLabel (layout.mChannelDescriptions[i].mChannelLabel)); + + // different speaker mappings may point to the same JUCE speaker so fill up + // this array with discrete channels + for (int j = 0; channels.size() < static_cast (layout.mNumberChannelDescriptions); ++j) + channels.addIfNotAlreadyThere (static_cast (AudioChannelSet::discreteChannel0 + j)); + + return channels; + } + case kAudioChannelLayoutTag_DiscreteInOrder: + return AudioChannelSet::discreteChannels (static_cast (layout.mChannelLayoutTag) & 0xffff).getChannelTypes(); + default: + break; + } + + return getSpeakerLayoutForCoreAudioTag (layout.mChannelLayoutTag); + } + + static Array getSpeakerLayoutForCoreAudioTag (AudioChannelLayoutTag tag) + { + // You need to specify the full AudioChannelLayout when using + // the UseChannelBitmap and UseChannelDescriptions layout tag + jassert (tag != kAudioChannelLayoutTag_UseChannelBitmap && tag != kAudioChannelLayoutTag_UseChannelDescriptions); + + Array speakers; + + for (auto* tbl = SpeakerLayoutTable::get(); tbl->tag != 0; ++tbl) + { + if (tag == tbl->tag) + { + for (int i = 0; i < numElementsInArray (tbl->channelTypes) + && tbl->channelTypes[i] != AudioChannelSet::unknown; ++i) + speakers.add (tbl->channelTypes[i]); + + return speakers; + } + } + + auto numChannels = tag & 0xffff; + if (tag >= coreAudioHOASN3DLayoutTag && tag <= (coreAudioHOASN3DLayoutTag | 0xffff)) + { + auto sqrtMinusOne = std::sqrt (static_cast (numChannels)) - 1.0f; + auto ambisonicOrder = jmax (0, static_cast (std::floor (sqrtMinusOne))); + + if (static_cast (ambisonicOrder) == sqrtMinusOne) + return AudioChannelSet::ambisonic (ambisonicOrder).getChannelTypes(); + } + + for (UInt32 i = 0; i < numChannels; ++i) + speakers.add (static_cast (AudioChannelSet::discreteChannel0 + i)); + + return speakers; + } + +private: + //============================================================================== + struct LayoutTagSpeakerList + { + AudioChannelLayoutTag tag; + AudioChannelSet::ChannelType channelTypes[16]; + }; + + static Array createKnownCoreAudioTags() + { + Array tags; + + for (auto* tbl = SpeakerLayoutTable::get(); tbl->tag != 0; ++tbl) + tags.addIfNotAlreadyThere (tbl->tag); + + for (unsigned order = 0; order <= 5; ++order) + tags.addIfNotAlreadyThere (coreAudioHOASN3DLayoutTag | ((order + 1) * (order + 1))); + + return tags; + } + + //============================================================================== + // This list has been derived from https://pastebin.com/24dQ4BPJ + // Apple channel labels have been replaced by JUCE channel names + // This means that some layouts will be identical in JUCE but not in CoreAudio + + // In Apple's official definition the following tags exist with the same speaker layout and order + // even when *not* represented in JUCE channels + // kAudioChannelLayoutTag_Binaural = kAudioChannelLayoutTag_Stereo + // kAudioChannelLayoutTag_MPEG_5_0_B = kAudioChannelLayoutTag_Pentagonal + // kAudioChannelLayoutTag_ITU_2_2 = kAudioChannelLayoutTag_Quadraphonic + // kAudioChannelLayoutTag_AudioUnit_6_0 = kAudioChannelLayoutTag_Hexagonal + struct SpeakerLayoutTable : AudioChannelSet // save us some typing + { + static LayoutTagSpeakerList* get() noexcept + { + static LayoutTagSpeakerList tbl[] = { + // list layouts for which there is a corresponding named AudioChannelSet first + { kAudioChannelLayoutTag_Mono, { centre } }, + { kAudioChannelLayoutTag_Stereo, { left, right } }, + { kAudioChannelLayoutTag_MPEG_3_0_A, { left, right, centre } }, + { kAudioChannelLayoutTag_ITU_2_1, { left, right, centreSurround } }, + { kAudioChannelLayoutTag_MPEG_4_0_A, { left, right, centre, centreSurround } }, + { kAudioChannelLayoutTag_MPEG_5_0_A, { left, right, centre, leftSurround, rightSurround } }, + { kAudioChannelLayoutTag_MPEG_5_1_A, { left, right, centre, LFE, leftSurround, rightSurround } }, + { kAudioChannelLayoutTag_AudioUnit_6_0, { left, right, leftSurround, rightSurround, centre, centreSurround } }, + { kAudioChannelLayoutTag_MPEG_6_1_A, { left, right, centre, LFE, leftSurround, rightSurround, centreSurround } }, + { kAudioChannelLayoutTag_DTS_6_0_A, { leftSurroundSide, rightSurroundSide, left, right, leftSurround, rightSurround } }, + { kAudioChannelLayoutTag_DTS_6_1_A, { leftSurroundSide, rightSurroundSide, left, right, leftSurround, rightSurround, LFE } }, + { kAudioChannelLayoutTag_AudioUnit_7_0, { left, right, leftSurroundSide, rightSurroundSide, centre, leftSurroundRear, rightSurroundRear } }, + { kAudioChannelLayoutTag_AudioUnit_7_0_Front, { left, right, leftSurround, rightSurround, centre, leftCentre, rightCentre } }, + { kAudioChannelLayoutTag_MPEG_7_1_C, { left, right, centre, LFE, leftSurroundSide, rightSurroundSide, leftSurroundRear, rightSurroundRear } }, + { kAudioChannelLayoutTag_MPEG_7_1_A, { left, right, centre, LFE, leftSurround, rightSurround, leftCentre, rightCentre } }, + { kAudioChannelLayoutTag_Ambisonic_B_Format, { ambisonicW, ambisonicX, ambisonicY, ambisonicZ } }, + { kAudioChannelLayoutTag_Quadraphonic, { left, right, leftSurround, rightSurround } }, + { kAudioChannelLayoutTag_Pentagonal, { left, right, leftSurroundRear, rightSurroundRear, centre } }, + { kAudioChannelLayoutTag_Hexagonal, { left, right, leftSurroundRear, rightSurroundRear, centre, centreSurround } }, + { kAudioChannelLayoutTag_Octagonal, { left, right, leftSurround, rightSurround, centre, centreSurround, wideLeft, wideRight } }, + + // more uncommon layouts + { kAudioChannelLayoutTag_StereoHeadphones, { left, right } }, + { kAudioChannelLayoutTag_MatrixStereo, { left, right } }, + { kAudioChannelLayoutTag_MidSide, { centre, discreteChannel0 } }, + { kAudioChannelLayoutTag_XY, { ambisonicX, ambisonicY } }, + { kAudioChannelLayoutTag_Binaural, { left, right } }, + { kAudioChannelLayoutTag_Cube, { left, right, leftSurround, rightSurround, topFrontLeft, topFrontRight, topRearLeft, topRearRight } }, + { kAudioChannelLayoutTag_MPEG_3_0_B, { centre, left, right } }, + { kAudioChannelLayoutTag_MPEG_4_0_B, { centre, left, right, centreSurround } }, + { kAudioChannelLayoutTag_MPEG_5_0_B, { left, right, leftSurround, rightSurround, centre } }, + { kAudioChannelLayoutTag_MPEG_5_0_C, { left, centre, right, leftSurround, rightSurround } }, + { kAudioChannelLayoutTag_MPEG_5_0_D, { centre, left, right, leftSurround, rightSurround } }, + { kAudioChannelLayoutTag_MPEG_5_1_B, { left, right, leftSurround, rightSurround, centre, LFE } }, + { kAudioChannelLayoutTag_MPEG_5_1_C, { left, centre, right, leftSurround, rightSurround, LFE } }, + { kAudioChannelLayoutTag_MPEG_5_1_D, { centre, left, right, leftSurround, rightSurround, LFE } }, + { kAudioChannelLayoutTag_MPEG_7_1_B, { centre, leftCentre, rightCentre, left, right, leftSurround, rightSurround, LFE } }, + { kAudioChannelLayoutTag_Emagic_Default_7_1, { left, right, leftSurround, rightSurround, centre, LFE, leftCentre, rightCentre } }, + { kAudioChannelLayoutTag_SMPTE_DTV, { left, right, centre, LFE, leftSurround, rightSurround, discreteChannel0 /* leftMatrixTotal */, (ChannelType) (discreteChannel0 + 1) /* rightMatrixTotal */} }, + { kAudioChannelLayoutTag_ITU_2_2, { left, right, leftSurround, rightSurround } }, + { kAudioChannelLayoutTag_DVD_4, { left, right, LFE } }, + { kAudioChannelLayoutTag_DVD_5, { left, right, LFE, centreSurround } }, + { kAudioChannelLayoutTag_DVD_6, { left, right, LFE, leftSurround, rightSurround } }, + { kAudioChannelLayoutTag_DVD_10, { left, right, centre, LFE } }, + { kAudioChannelLayoutTag_DVD_11, { left, right, centre, LFE, centreSurround } }, + { kAudioChannelLayoutTag_DVD_18, { left, right, leftSurround, rightSurround, LFE } }, + { kAudioChannelLayoutTag_AAC_6_0, { centre, left, right, leftSurround, rightSurround, centreSurround } }, + { kAudioChannelLayoutTag_AAC_6_1, { centre, left, right, leftSurround, rightSurround, centreSurround, LFE } }, + { kAudioChannelLayoutTag_AAC_7_0, { centre, left, right, leftSurround, rightSurround, leftSurroundRear, rightSurroundRear } }, + { kAudioChannelLayoutTag_AAC_7_1_B, { centre, left, right, leftSurround, rightSurround, leftSurroundRear, rightSurroundRear, LFE } }, + { kAudioChannelLayoutTag_AAC_7_1_C, { centre, left, right, leftSurround, rightSurround, LFE, topFrontLeft, topFrontRight } }, + { kAudioChannelLayoutTag_AAC_Octagonal, { centre, left, right, leftSurround, rightSurround, leftSurroundRear, rightSurroundRear, centreSurround } }, + { kAudioChannelLayoutTag_TMH_10_2_std, { left, right, centre, topFrontCentre, leftSurroundSide, rightSurroundSide, leftSurround, rightSurround, topFrontLeft, topFrontRight, wideLeft, wideRight, topRearCentre, centreSurround, LFE, LFE2 } }, + { kAudioChannelLayoutTag_AC3_1_0_1, { centre, LFE } }, + { kAudioChannelLayoutTag_AC3_3_0, { left, centre, right } }, + { kAudioChannelLayoutTag_AC3_3_1, { left, centre, right, centreSurround } }, + { kAudioChannelLayoutTag_AC3_3_0_1, { left, centre, right, LFE } }, + { kAudioChannelLayoutTag_AC3_2_1_1, { left, right, centreSurround, LFE } }, + { kAudioChannelLayoutTag_AC3_3_1_1, { left, centre, right, centreSurround, LFE } }, + { kAudioChannelLayoutTag_EAC_6_0_A, { left, centre, right, leftSurround, rightSurround, centreSurround } }, + { kAudioChannelLayoutTag_EAC_7_0_A, { left, centre, right, leftSurround, rightSurround, leftSurroundRear, rightSurroundRear } }, + { kAudioChannelLayoutTag_EAC3_6_1_A, { left, centre, right, leftSurround, rightSurround, LFE, centreSurround } }, + { kAudioChannelLayoutTag_EAC3_6_1_B, { left, centre, right, leftSurround, rightSurround, LFE, centreSurround } }, + { kAudioChannelLayoutTag_EAC3_6_1_C, { left, centre, right, leftSurround, rightSurround, LFE, topFrontCentre } }, + { kAudioChannelLayoutTag_EAC3_7_1_A, { left, centre, right, leftSurround, rightSurround, LFE, leftSurroundRear, rightSurroundRear } }, + { kAudioChannelLayoutTag_EAC3_7_1_B, { left, centre, right, leftSurround, rightSurround, LFE, leftCentre, rightCentre } }, + { kAudioChannelLayoutTag_EAC3_7_1_C, { left, centre, right, leftSurround, rightSurround, LFE, leftSurroundSide, rightSurroundSide } }, + { kAudioChannelLayoutTag_EAC3_7_1_D, { left, centre, right, leftSurround, rightSurround, LFE, wideLeft, wideRight } }, + { kAudioChannelLayoutTag_EAC3_7_1_E, { left, centre, right, leftSurround, rightSurround, LFE, topFrontLeft, topFrontRight } }, + { kAudioChannelLayoutTag_EAC3_7_1_F, { left, centre, right, leftSurround, rightSurround, LFE, centreSurround, topMiddle } }, + { kAudioChannelLayoutTag_EAC3_7_1_G, { left, centre, right, leftSurround, rightSurround, LFE, centreSurround, topFrontCentre } }, + { kAudioChannelLayoutTag_EAC3_7_1_H, { left, centre, right, leftSurround, rightSurround, LFE, centreSurround, topFrontCentre } }, + { kAudioChannelLayoutTag_DTS_3_1, { centre, left, right, LFE } }, + { kAudioChannelLayoutTag_DTS_4_1, { centre, left, right, centreSurround, LFE } }, + { kAudioChannelLayoutTag_DTS_6_0_B, { centre, left, right, leftSurroundRear, rightSurroundRear, centreSurround } }, + { kAudioChannelLayoutTag_DTS_6_0_C, { centre, centreSurround, left, right, leftSurroundRear, rightSurroundRear } }, + { kAudioChannelLayoutTag_DTS_6_1_B, { centre, left, right, leftSurroundRear, rightSurroundRear, centreSurround, LFE } }, + { kAudioChannelLayoutTag_DTS_6_1_C, { centre, centreSurround, left, right, leftSurroundRear, rightSurroundRear, LFE } }, + { kAudioChannelLayoutTag_DTS_6_1_D, { centre, left, right, leftSurround, rightSurround, LFE, centreSurround } }, + { kAudioChannelLayoutTag_DTS_7_0, { leftCentre, centre, rightCentre, left, right, leftSurround, rightSurround } }, + { kAudioChannelLayoutTag_DTS_7_1, { leftCentre, centre, rightCentre, left, right, leftSurround, rightSurround, LFE } }, + { kAudioChannelLayoutTag_DTS_8_0_A, { leftCentre, rightCentre, left, right, leftSurround, rightSurround, leftSurroundRear, rightSurroundRear } }, + { kAudioChannelLayoutTag_DTS_8_0_B, { leftCentre, centre, rightCentre, left, right, leftSurround, centreSurround, rightSurround } }, + { kAudioChannelLayoutTag_DTS_8_1_A, { leftCentre, rightCentre, left, right, leftSurround, rightSurround, leftSurroundRear, rightSurroundRear, LFE } }, + { kAudioChannelLayoutTag_DTS_8_1_B, { leftCentre, centre, rightCentre, left, right, leftSurround, centreSurround, rightSurround, LFE } }, + { 0, {} } + }; + + return tbl; + } + }; + + //============================================================================== + static AudioChannelSet::ChannelType getChannelTypeFromAudioChannelLabel (AudioChannelLabel label) noexcept + { + if (label >= kAudioChannelLabel_Discrete_0 && label <= kAudioChannelLabel_Discrete_65535) + { + const unsigned int discreteChannelNum = label - kAudioChannelLabel_Discrete_0; + return static_cast (AudioChannelSet::discreteChannel0 + discreteChannelNum); + } + + switch (label) + { + case kAudioChannelLabel_Center: + case kAudioChannelLabel_Mono: return AudioChannelSet::centre; + case kAudioChannelLabel_Left: + case kAudioChannelLabel_HeadphonesLeft: return AudioChannelSet::left; + case kAudioChannelLabel_Right: + case kAudioChannelLabel_HeadphonesRight: return AudioChannelSet::right; + case kAudioChannelLabel_LFEScreen: return AudioChannelSet::LFE; + case kAudioChannelLabel_LeftSurround: return AudioChannelSet::leftSurround; + case kAudioChannelLabel_RightSurround: return AudioChannelSet::rightSurround; + case kAudioChannelLabel_LeftCenter: return AudioChannelSet::leftCentre; + case kAudioChannelLabel_RightCenter: return AudioChannelSet::rightCentre; + case kAudioChannelLabel_CenterSurround: return AudioChannelSet::surround; + case kAudioChannelLabel_LeftSurroundDirect: return AudioChannelSet::leftSurroundSide; + case kAudioChannelLabel_RightSurroundDirect: return AudioChannelSet::rightSurroundSide; + case kAudioChannelLabel_TopCenterSurround: return AudioChannelSet::topMiddle; + case kAudioChannelLabel_VerticalHeightLeft: return AudioChannelSet::topFrontLeft; + case kAudioChannelLabel_VerticalHeightRight: return AudioChannelSet::topFrontRight; + case kAudioChannelLabel_VerticalHeightCenter: return AudioChannelSet::topFrontCentre; + case kAudioChannelLabel_TopBackLeft: return AudioChannelSet::topRearLeft; + case kAudioChannelLabel_RearSurroundLeft: return AudioChannelSet::leftSurroundRear; + case kAudioChannelLabel_TopBackRight: return AudioChannelSet::topRearRight; + case kAudioChannelLabel_RearSurroundRight: return AudioChannelSet::rightSurroundRear; + case kAudioChannelLabel_TopBackCenter: return AudioChannelSet::topRearCentre; + case kAudioChannelLabel_LFE2: return AudioChannelSet::LFE2; + case kAudioChannelLabel_LeftWide: return AudioChannelSet::wideLeft; + case kAudioChannelLabel_RightWide: return AudioChannelSet::wideRight; + case kAudioChannelLabel_Ambisonic_W: return AudioChannelSet::ambisonicW; + case kAudioChannelLabel_Ambisonic_X: return AudioChannelSet::ambisonicX; + case kAudioChannelLabel_Ambisonic_Y: return AudioChannelSet::ambisonicY; + case kAudioChannelLabel_Ambisonic_Z: return AudioChannelSet::ambisonicZ; + default: return AudioChannelSet::unknown; + } + } +}; + +#endif + +} // namespace juce diff --git a/JuceLibraryCode/modules/juce_audio_basics/sources/juce_AudioSource.h b/JuceLibraryCode/modules/juce_audio_basics/sources/juce_AudioSource.h index 401c4f7..bb68143 100644 --- a/JuceLibraryCode/modules/juce_audio_basics/sources/juce_AudioSource.h +++ b/JuceLibraryCode/modules/juce_audio_basics/sources/juce_AudioSource.h @@ -1,179 +1,179 @@ -/* - ============================================================================== - - This file is part of the JUCE library. - Copyright (c) 2017 - ROLI Ltd. - - JUCE is an open source library subject to commercial or open-source - licensing. - - The code included in this file is provided under the terms of the ISC license - http://www.isc.org/downloads/software-support-policy/isc-license. Permission - To use, copy, modify, and/or distribute this software for any purpose with or - without fee is hereby granted provided that the above copyright notice and - this permission notice appear in all copies. - - JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER - EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE - DISCLAIMED. - - ============================================================================== -*/ - -namespace juce -{ - -//============================================================================== -/** - Used by AudioSource::getNextAudioBlock(). - - @tags{Audio} -*/ -struct JUCE_API AudioSourceChannelInfo -{ - /** Creates an uninitialised AudioSourceChannelInfo. */ - AudioSourceChannelInfo() = default; - - /** Creates an AudioSourceChannelInfo. */ - AudioSourceChannelInfo (AudioBuffer* bufferToUse, - int startSampleOffset, int numSamplesToUse) noexcept - : buffer (bufferToUse), - startSample (startSampleOffset), - numSamples (numSamplesToUse) - { - } - - /** Creates an AudioSourceChannelInfo that uses the whole of a buffer. - Note that the buffer provided must not be deleted while the - AudioSourceChannelInfo is still using it. - */ - explicit AudioSourceChannelInfo (AudioBuffer& bufferToUse) noexcept - : buffer (&bufferToUse), - startSample (0), - numSamples (bufferToUse.getNumSamples()) - { - } - - /** The destination buffer to fill with audio data. - - When the AudioSource::getNextAudioBlock() method is called, the active section - of this buffer should be filled with whatever output the source produces. - - Only the samples specified by the startSample and numSamples members of this structure - should be affected by the call. - - The contents of the buffer when it is passed to the AudioSource::getNextAudioBlock() - method can be treated as the input if the source is performing some kind of filter operation, - but should be cleared if this is not the case - the clearActiveBufferRegion() is - a handy way of doing this. - - The number of channels in the buffer could be anything, so the AudioSource - must cope with this in whatever way is appropriate for its function. - */ - AudioBuffer* buffer; - - /** The first sample in the buffer from which the callback is expected - to write data. */ - int startSample; - - /** The number of samples in the buffer which the callback is expected to - fill with data. */ - int numSamples; - - /** Convenient method to clear the buffer if the source is not producing any data. */ - void clearActiveBufferRegion() const - { - if (buffer != nullptr) - buffer->clear (startSample, numSamples); - } -}; - - -//============================================================================== -/** - Base class for objects that can produce a continuous stream of audio. - - An AudioSource has two states: 'prepared' and 'unprepared'. - - When a source needs to be played, it is first put into a 'prepared' state by a call to - prepareToPlay(), and then repeated calls will be made to its getNextAudioBlock() method to - process the audio data. - - Once playback has finished, the releaseResources() method is called to put the stream - back into an 'unprepared' state. - - @see AudioFormatReaderSource, ResamplingAudioSource - - @tags{Audio} -*/ -class JUCE_API AudioSource -{ -protected: - //============================================================================== - /** Creates an AudioSource. */ - AudioSource() = default; - -public: - /** Destructor. */ - virtual ~AudioSource() = default; - - //============================================================================== - /** Tells the source to prepare for playing. - - An AudioSource has two states: prepared and unprepared. - - The prepareToPlay() method is guaranteed to be called at least once on an 'unprepared' - source to put it into a 'prepared' state before any calls will be made to getNextAudioBlock(). - This callback allows the source to initialise any resources it might need when playing. - - Once playback has finished, the releaseResources() method is called to put the stream - back into an 'unprepared' state. - - Note that this method could be called more than once in succession without - a matching call to releaseResources(), so make sure your code is robust and - can handle that kind of situation. - - @param samplesPerBlockExpected the number of samples that the source - will be expected to supply each time its - getNextAudioBlock() method is called. This - number may vary slightly, because it will be dependent - on audio hardware callbacks, and these aren't - guaranteed to always use a constant block size, so - the source should be able to cope with small variations. - @param sampleRate the sample rate that the output will be used at - this - is needed by sources such as tone generators. - @see releaseResources, getNextAudioBlock - */ - virtual void prepareToPlay (int samplesPerBlockExpected, - double sampleRate) = 0; - - /** Allows the source to release anything it no longer needs after playback has stopped. - - This will be called when the source is no longer going to have its getNextAudioBlock() - method called, so it should release any spare memory, etc. that it might have - allocated during the prepareToPlay() call. - - Note that there's no guarantee that prepareToPlay() will actually have been called before - releaseResources(), and it may be called more than once in succession, so make sure your - code is robust and doesn't make any assumptions about when it will be called. - - @see prepareToPlay, getNextAudioBlock - */ - virtual void releaseResources() = 0; - - /** Called repeatedly to fetch subsequent blocks of audio data. - - After calling the prepareToPlay() method, this callback will be made each - time the audio playback hardware (or whatever other destination the audio - data is going to) needs another block of data. - - It will generally be called on a high-priority system thread, or possibly even - an interrupt, so be careful not to do too much work here, as that will cause - audio glitches! - - @see AudioSourceChannelInfo, prepareToPlay, releaseResources - */ - virtual void getNextAudioBlock (const AudioSourceChannelInfo& bufferToFill) = 0; -}; - -} // namespace juce +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2017 - ROLI Ltd. + + JUCE is an open source library subject to commercial or open-source + licensing. + + The code included in this file is provided under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license. Permission + To use, copy, modify, and/or distribute this software for any purpose with or + without fee is hereby granted provided that the above copyright notice and + this permission notice appear in all copies. + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +namespace juce +{ + +//============================================================================== +/** + Used by AudioSource::getNextAudioBlock(). + + @tags{Audio} +*/ +struct JUCE_API AudioSourceChannelInfo +{ + /** Creates an uninitialised AudioSourceChannelInfo. */ + AudioSourceChannelInfo() = default; + + /** Creates an AudioSourceChannelInfo. */ + AudioSourceChannelInfo (AudioBuffer* bufferToUse, + int startSampleOffset, int numSamplesToUse) noexcept + : buffer (bufferToUse), + startSample (startSampleOffset), + numSamples (numSamplesToUse) + { + } + + /** Creates an AudioSourceChannelInfo that uses the whole of a buffer. + Note that the buffer provided must not be deleted while the + AudioSourceChannelInfo is still using it. + */ + explicit AudioSourceChannelInfo (AudioBuffer& bufferToUse) noexcept + : buffer (&bufferToUse), + startSample (0), + numSamples (bufferToUse.getNumSamples()) + { + } + + /** The destination buffer to fill with audio data. + + When the AudioSource::getNextAudioBlock() method is called, the active section + of this buffer should be filled with whatever output the source produces. + + Only the samples specified by the startSample and numSamples members of this structure + should be affected by the call. + + The contents of the buffer when it is passed to the AudioSource::getNextAudioBlock() + method can be treated as the input if the source is performing some kind of filter operation, + but should be cleared if this is not the case - the clearActiveBufferRegion() is + a handy way of doing this. + + The number of channels in the buffer could be anything, so the AudioSource + must cope with this in whatever way is appropriate for its function. + */ + AudioBuffer* buffer; + + /** The first sample in the buffer from which the callback is expected + to write data. */ + int startSample; + + /** The number of samples in the buffer which the callback is expected to + fill with data. */ + int numSamples; + + /** Convenient method to clear the buffer if the source is not producing any data. */ + void clearActiveBufferRegion() const + { + if (buffer != nullptr) + buffer->clear (startSample, numSamples); + } +}; + + +//============================================================================== +/** + Base class for objects that can produce a continuous stream of audio. + + An AudioSource has two states: 'prepared' and 'unprepared'. + + When a source needs to be played, it is first put into a 'prepared' state by a call to + prepareToPlay(), and then repeated calls will be made to its getNextAudioBlock() method to + process the audio data. + + Once playback has finished, the releaseResources() method is called to put the stream + back into an 'unprepared' state. + + @see AudioFormatReaderSource, ResamplingAudioSource + + @tags{Audio} +*/ +class JUCE_API AudioSource +{ +protected: + //============================================================================== + /** Creates an AudioSource. */ + AudioSource() = default; + +public: + /** Destructor. */ + virtual ~AudioSource() = default; + + //============================================================================== + /** Tells the source to prepare for playing. + + An AudioSource has two states: prepared and unprepared. + + The prepareToPlay() method is guaranteed to be called at least once on an 'unprepared' + source to put it into a 'prepared' state before any calls will be made to getNextAudioBlock(). + This callback allows the source to initialise any resources it might need when playing. + + Once playback has finished, the releaseResources() method is called to put the stream + back into an 'unprepared' state. + + Note that this method could be called more than once in succession without + a matching call to releaseResources(), so make sure your code is robust and + can handle that kind of situation. + + @param samplesPerBlockExpected the number of samples that the source + will be expected to supply each time its + getNextAudioBlock() method is called. This + number may vary slightly, because it will be dependent + on audio hardware callbacks, and these aren't + guaranteed to always use a constant block size, so + the source should be able to cope with small variations. + @param sampleRate the sample rate that the output will be used at - this + is needed by sources such as tone generators. + @see releaseResources, getNextAudioBlock + */ + virtual void prepareToPlay (int samplesPerBlockExpected, + double sampleRate) = 0; + + /** Allows the source to release anything it no longer needs after playback has stopped. + + This will be called when the source is no longer going to have its getNextAudioBlock() + method called, so it should release any spare memory, etc. that it might have + allocated during the prepareToPlay() call. + + Note that there's no guarantee that prepareToPlay() will actually have been called before + releaseResources(), and it may be called more than once in succession, so make sure your + code is robust and doesn't make any assumptions about when it will be called. + + @see prepareToPlay, getNextAudioBlock + */ + virtual void releaseResources() = 0; + + /** Called repeatedly to fetch subsequent blocks of audio data. + + After calling the prepareToPlay() method, this callback will be made each + time the audio playback hardware (or whatever other destination the audio + data is going to) needs another block of data. + + It will generally be called on a high-priority system thread, or possibly even + an interrupt, so be careful not to do too much work here, as that will cause + audio glitches! + + @see AudioSourceChannelInfo, prepareToPlay, releaseResources + */ + virtual void getNextAudioBlock (const AudioSourceChannelInfo& bufferToFill) = 0; +}; + +} // namespace juce diff --git a/JuceLibraryCode/modules/juce_audio_basics/sources/juce_BufferingAudioSource.cpp b/JuceLibraryCode/modules/juce_audio_basics/sources/juce_BufferingAudioSource.cpp index 740d7ba..5dd781b 100644 --- a/JuceLibraryCode/modules/juce_audio_basics/sources/juce_BufferingAudioSource.cpp +++ b/JuceLibraryCode/modules/juce_audio_basics/sources/juce_BufferingAudioSource.cpp @@ -1,315 +1,315 @@ -/* - ============================================================================== - - This file is part of the JUCE library. - Copyright (c) 2017 - ROLI Ltd. - - JUCE is an open source library subject to commercial or open-source - licensing. - - The code included in this file is provided under the terms of the ISC license - http://www.isc.org/downloads/software-support-policy/isc-license. Permission - To use, copy, modify, and/or distribute this software for any purpose with or - without fee is hereby granted provided that the above copyright notice and - this permission notice appear in all copies. - - JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER - EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE - DISCLAIMED. - - ============================================================================== -*/ - -namespace juce -{ - -BufferingAudioSource::BufferingAudioSource (PositionableAudioSource* s, - TimeSliceThread& thread, - bool deleteSourceWhenDeleted, - int bufferSizeSamples, - int numChannels, - bool prefillBufferOnPrepareToPlay) - : source (s, deleteSourceWhenDeleted), - backgroundThread (thread), - numberOfSamplesToBuffer (jmax (1024, bufferSizeSamples)), - numberOfChannels (numChannels), - prefillBuffer (prefillBufferOnPrepareToPlay) -{ - jassert (source != nullptr); - - jassert (numberOfSamplesToBuffer > 1024); // not much point using this class if you're - // not using a larger buffer.. -} - -BufferingAudioSource::~BufferingAudioSource() -{ - releaseResources(); -} - -//============================================================================== -void BufferingAudioSource::prepareToPlay (int samplesPerBlockExpected, double newSampleRate) -{ - auto bufferSizeNeeded = jmax (samplesPerBlockExpected * 2, numberOfSamplesToBuffer); - - if (newSampleRate != sampleRate - || bufferSizeNeeded != buffer.getNumSamples() - || ! isPrepared) - { - backgroundThread.removeTimeSliceClient (this); - - isPrepared = true; - sampleRate = newSampleRate; - - source->prepareToPlay (samplesPerBlockExpected, newSampleRate); - - buffer.setSize (numberOfChannels, bufferSizeNeeded); - buffer.clear(); - - bufferValidStart = 0; - bufferValidEnd = 0; - - backgroundThread.addTimeSliceClient (this); - - do - { - backgroundThread.moveToFrontOfQueue (this); - Thread::sleep (5); - } - while (prefillBuffer - && (bufferValidEnd - bufferValidStart < jmin (((int) newSampleRate) / 4, buffer.getNumSamples() / 2))); - } -} - -void BufferingAudioSource::releaseResources() -{ - isPrepared = false; - backgroundThread.removeTimeSliceClient (this); - - buffer.setSize (numberOfChannels, 0); - - // MSVC2015 seems to need this if statement to not generate a warning during linking. - // As source is set in the constructor, there is no way that source could - // ever equal this, but it seems to make MSVC2015 happy. - if (source != this) - source->releaseResources(); -} - -void BufferingAudioSource::getNextAudioBlock (const AudioSourceChannelInfo& info) -{ - const ScopedLock sl (bufferStartPosLock); - - auto start = bufferValidStart.load(); - auto end = bufferValidEnd.load(); - auto pos = nextPlayPos.load(); - - auto validStart = (int) (jlimit (start, end, pos) - pos); - auto validEnd = (int) (jlimit (start, end, pos + info.numSamples) - pos); - - if (validStart == validEnd) - { - // total cache miss - info.clearActiveBufferRegion(); - } - else - { - if (validStart > 0) - info.buffer->clear (info.startSample, validStart); // partial cache miss at start - - if (validEnd < info.numSamples) - info.buffer->clear (info.startSample + validEnd, - info.numSamples - validEnd); // partial cache miss at end - - if (validStart < validEnd) - { - for (int chan = jmin (numberOfChannels, info.buffer->getNumChannels()); --chan >= 0;) - { - jassert (buffer.getNumSamples() > 0); - auto startBufferIndex = (int) ((validStart + nextPlayPos) % buffer.getNumSamples()); - auto endBufferIndex = (int) ((validEnd + nextPlayPos) % buffer.getNumSamples()); - - if (startBufferIndex < endBufferIndex) - { - info.buffer->copyFrom (chan, info.startSample + validStart, - buffer, - chan, startBufferIndex, - validEnd - validStart); - } - else - { - auto initialSize = buffer.getNumSamples() - startBufferIndex; - - info.buffer->copyFrom (chan, info.startSample + validStart, - buffer, - chan, startBufferIndex, - initialSize); - - info.buffer->copyFrom (chan, info.startSample + validStart + initialSize, - buffer, - chan, 0, - (validEnd - validStart) - initialSize); - } - } - } - - nextPlayPos += info.numSamples; - } -} - -bool BufferingAudioSource::waitForNextAudioBlockReady (const AudioSourceChannelInfo& info, uint32 timeout) -{ - if (!source || source->getTotalLength() <= 0) - return false; - - if (nextPlayPos + info.numSamples < 0) - return true; - - if (! isLooping() && nextPlayPos > getTotalLength()) - return true; - - auto now = Time::getMillisecondCounter(); - auto startTime = now; - - auto elapsed = (now >= startTime ? now - startTime - : (std::numeric_limits::max() - startTime) + now); - - while (elapsed <= timeout) - { - { - const ScopedLock sl (bufferStartPosLock); - - auto start = bufferValidStart.load(); - auto end = bufferValidEnd.load(); - auto pos = nextPlayPos.load(); - - auto validStart = static_cast (jlimit (start, end, pos) - pos); - auto validEnd = static_cast (jlimit (start, end, pos + info.numSamples) - pos); - - if (validStart <= 0 && validStart < validEnd && validEnd >= info.numSamples) - return true; - } - - if (elapsed < timeout && (! bufferReadyEvent.wait (static_cast (timeout - elapsed)))) - return false; - - now = Time::getMillisecondCounter(); - elapsed = (now >= startTime ? now - startTime - : (std::numeric_limits::max() - startTime) + now); - } - - return false; -} - -int64 BufferingAudioSource::getNextReadPosition() const -{ - jassert (source->getTotalLength() > 0); - auto pos = nextPlayPos.load(); - - return (source->isLooping() && nextPlayPos > 0) - ? pos % source->getTotalLength() - : pos; -} - -void BufferingAudioSource::setNextReadPosition (int64 newPosition) -{ - const ScopedLock sl (bufferStartPosLock); - - nextPlayPos = newPosition; - backgroundThread.moveToFrontOfQueue (this); -} - -bool BufferingAudioSource::readNextBufferChunk() -{ - int64 newBVS, newBVE, sectionToReadStart, sectionToReadEnd; - - { - const ScopedLock sl (bufferStartPosLock); - - if (wasSourceLooping != isLooping()) - { - wasSourceLooping = isLooping(); - bufferValidStart = 0; - bufferValidEnd = 0; - } - - newBVS = jmax ((int64) 0, nextPlayPos.load()); - newBVE = newBVS + buffer.getNumSamples() - 4; - sectionToReadStart = 0; - sectionToReadEnd = 0; - - const int maxChunkSize = 2048; - - if (newBVS < bufferValidStart || newBVS >= bufferValidEnd) - { - newBVE = jmin (newBVE, newBVS + maxChunkSize); - - sectionToReadStart = newBVS; - sectionToReadEnd = newBVE; - - bufferValidStart = 0; - bufferValidEnd = 0; - } - else if (std::abs ((int) (newBVS - bufferValidStart)) > 512 - || std::abs ((int) (newBVE - bufferValidEnd)) > 512) - { - newBVE = jmin (newBVE, bufferValidEnd + maxChunkSize); - - sectionToReadStart = bufferValidEnd; - sectionToReadEnd = newBVE; - - bufferValidStart = newBVS; - bufferValidEnd = jmin (bufferValidEnd.load(), newBVE); - } - } - - if (sectionToReadStart == sectionToReadEnd) - return false; - - jassert (buffer.getNumSamples() > 0); - auto bufferIndexStart = (int) (sectionToReadStart % buffer.getNumSamples()); - auto bufferIndexEnd = (int) (sectionToReadEnd % buffer.getNumSamples()); - - if (bufferIndexStart < bufferIndexEnd) - { - readBufferSection (sectionToReadStart, - (int) (sectionToReadEnd - sectionToReadStart), - bufferIndexStart); - } - else - { - auto initialSize = buffer.getNumSamples() - bufferIndexStart; - - readBufferSection (sectionToReadStart, - initialSize, - bufferIndexStart); - - readBufferSection (sectionToReadStart + initialSize, - (int) (sectionToReadEnd - sectionToReadStart) - initialSize, - 0); - } - - { - const ScopedLock sl2 (bufferStartPosLock); - - bufferValidStart = newBVS; - bufferValidEnd = newBVE; - } - - bufferReadyEvent.signal(); - return true; -} - -void BufferingAudioSource::readBufferSection (int64 start, int length, int bufferOffset) -{ - if (source->getNextReadPosition() != start) - source->setNextReadPosition (start); - - AudioSourceChannelInfo info (&buffer, bufferOffset, length); - source->getNextAudioBlock (info); -} - -int BufferingAudioSource::useTimeSlice() -{ - return readNextBufferChunk() ? 1 : 100; -} - -} // namespace juce +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2017 - ROLI Ltd. + + JUCE is an open source library subject to commercial or open-source + licensing. + + The code included in this file is provided under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license. Permission + To use, copy, modify, and/or distribute this software for any purpose with or + without fee is hereby granted provided that the above copyright notice and + this permission notice appear in all copies. + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +namespace juce +{ + +BufferingAudioSource::BufferingAudioSource (PositionableAudioSource* s, + TimeSliceThread& thread, + bool deleteSourceWhenDeleted, + int bufferSizeSamples, + int numChannels, + bool prefillBufferOnPrepareToPlay) + : source (s, deleteSourceWhenDeleted), + backgroundThread (thread), + numberOfSamplesToBuffer (jmax (1024, bufferSizeSamples)), + numberOfChannels (numChannels), + prefillBuffer (prefillBufferOnPrepareToPlay) +{ + jassert (source != nullptr); + + jassert (numberOfSamplesToBuffer > 1024); // not much point using this class if you're + // not using a larger buffer.. +} + +BufferingAudioSource::~BufferingAudioSource() +{ + releaseResources(); +} + +//============================================================================== +void BufferingAudioSource::prepareToPlay (int samplesPerBlockExpected, double newSampleRate) +{ + auto bufferSizeNeeded = jmax (samplesPerBlockExpected * 2, numberOfSamplesToBuffer); + + if (newSampleRate != sampleRate + || bufferSizeNeeded != buffer.getNumSamples() + || ! isPrepared) + { + backgroundThread.removeTimeSliceClient (this); + + isPrepared = true; + sampleRate = newSampleRate; + + source->prepareToPlay (samplesPerBlockExpected, newSampleRate); + + buffer.setSize (numberOfChannels, bufferSizeNeeded); + buffer.clear(); + + bufferValidStart = 0; + bufferValidEnd = 0; + + backgroundThread.addTimeSliceClient (this); + + do + { + backgroundThread.moveToFrontOfQueue (this); + Thread::sleep (5); + } + while (prefillBuffer + && (bufferValidEnd - bufferValidStart < jmin (((int) newSampleRate) / 4, buffer.getNumSamples() / 2))); + } +} + +void BufferingAudioSource::releaseResources() +{ + isPrepared = false; + backgroundThread.removeTimeSliceClient (this); + + buffer.setSize (numberOfChannels, 0); + + // MSVC2015 seems to need this if statement to not generate a warning during linking. + // As source is set in the constructor, there is no way that source could + // ever equal this, but it seems to make MSVC2015 happy. + if (source != this) + source->releaseResources(); +} + +void BufferingAudioSource::getNextAudioBlock (const AudioSourceChannelInfo& info) +{ + const ScopedLock sl (bufferStartPosLock); + + auto start = bufferValidStart.load(); + auto end = bufferValidEnd.load(); + auto pos = nextPlayPos.load(); + + auto validStart = (int) (jlimit (start, end, pos) - pos); + auto validEnd = (int) (jlimit (start, end, pos + info.numSamples) - pos); + + if (validStart == validEnd) + { + // total cache miss + info.clearActiveBufferRegion(); + } + else + { + if (validStart > 0) + info.buffer->clear (info.startSample, validStart); // partial cache miss at start + + if (validEnd < info.numSamples) + info.buffer->clear (info.startSample + validEnd, + info.numSamples - validEnd); // partial cache miss at end + + if (validStart < validEnd) + { + for (int chan = jmin (numberOfChannels, info.buffer->getNumChannels()); --chan >= 0;) + { + jassert (buffer.getNumSamples() > 0); + auto startBufferIndex = (int) ((validStart + nextPlayPos) % buffer.getNumSamples()); + auto endBufferIndex = (int) ((validEnd + nextPlayPos) % buffer.getNumSamples()); + + if (startBufferIndex < endBufferIndex) + { + info.buffer->copyFrom (chan, info.startSample + validStart, + buffer, + chan, startBufferIndex, + validEnd - validStart); + } + else + { + auto initialSize = buffer.getNumSamples() - startBufferIndex; + + info.buffer->copyFrom (chan, info.startSample + validStart, + buffer, + chan, startBufferIndex, + initialSize); + + info.buffer->copyFrom (chan, info.startSample + validStart + initialSize, + buffer, + chan, 0, + (validEnd - validStart) - initialSize); + } + } + } + + nextPlayPos += info.numSamples; + } +} + +bool BufferingAudioSource::waitForNextAudioBlockReady (const AudioSourceChannelInfo& info, uint32 timeout) +{ + if (!source || source->getTotalLength() <= 0) + return false; + + if (nextPlayPos + info.numSamples < 0) + return true; + + if (! isLooping() && nextPlayPos > getTotalLength()) + return true; + + auto now = Time::getMillisecondCounter(); + auto startTime = now; + + auto elapsed = (now >= startTime ? now - startTime + : (std::numeric_limits::max() - startTime) + now); + + while (elapsed <= timeout) + { + { + const ScopedLock sl (bufferStartPosLock); + + auto start = bufferValidStart.load(); + auto end = bufferValidEnd.load(); + auto pos = nextPlayPos.load(); + + auto validStart = static_cast (jlimit (start, end, pos) - pos); + auto validEnd = static_cast (jlimit (start, end, pos + info.numSamples) - pos); + + if (validStart <= 0 && validStart < validEnd && validEnd >= info.numSamples) + return true; + } + + if (elapsed < timeout && (! bufferReadyEvent.wait (static_cast (timeout - elapsed)))) + return false; + + now = Time::getMillisecondCounter(); + elapsed = (now >= startTime ? now - startTime + : (std::numeric_limits::max() - startTime) + now); + } + + return false; +} + +int64 BufferingAudioSource::getNextReadPosition() const +{ + jassert (source->getTotalLength() > 0); + auto pos = nextPlayPos.load(); + + return (source->isLooping() && nextPlayPos > 0) + ? pos % source->getTotalLength() + : pos; +} + +void BufferingAudioSource::setNextReadPosition (int64 newPosition) +{ + const ScopedLock sl (bufferStartPosLock); + + nextPlayPos = newPosition; + backgroundThread.moveToFrontOfQueue (this); +} + +bool BufferingAudioSource::readNextBufferChunk() +{ + int64 newBVS, newBVE, sectionToReadStart, sectionToReadEnd; + + { + const ScopedLock sl (bufferStartPosLock); + + if (wasSourceLooping != isLooping()) + { + wasSourceLooping = isLooping(); + bufferValidStart = 0; + bufferValidEnd = 0; + } + + newBVS = jmax ((int64) 0, nextPlayPos.load()); + newBVE = newBVS + buffer.getNumSamples() - 4; + sectionToReadStart = 0; + sectionToReadEnd = 0; + + const int maxChunkSize = 2048; + + if (newBVS < bufferValidStart || newBVS >= bufferValidEnd) + { + newBVE = jmin (newBVE, newBVS + maxChunkSize); + + sectionToReadStart = newBVS; + sectionToReadEnd = newBVE; + + bufferValidStart = 0; + bufferValidEnd = 0; + } + else if (std::abs ((int) (newBVS - bufferValidStart)) > 512 + || std::abs ((int) (newBVE - bufferValidEnd)) > 512) + { + newBVE = jmin (newBVE, bufferValidEnd + maxChunkSize); + + sectionToReadStart = bufferValidEnd; + sectionToReadEnd = newBVE; + + bufferValidStart = newBVS; + bufferValidEnd = jmin (bufferValidEnd.load(), newBVE); + } + } + + if (sectionToReadStart == sectionToReadEnd) + return false; + + jassert (buffer.getNumSamples() > 0); + auto bufferIndexStart = (int) (sectionToReadStart % buffer.getNumSamples()); + auto bufferIndexEnd = (int) (sectionToReadEnd % buffer.getNumSamples()); + + if (bufferIndexStart < bufferIndexEnd) + { + readBufferSection (sectionToReadStart, + (int) (sectionToReadEnd - sectionToReadStart), + bufferIndexStart); + } + else + { + auto initialSize = buffer.getNumSamples() - bufferIndexStart; + + readBufferSection (sectionToReadStart, + initialSize, + bufferIndexStart); + + readBufferSection (sectionToReadStart + initialSize, + (int) (sectionToReadEnd - sectionToReadStart) - initialSize, + 0); + } + + { + const ScopedLock sl2 (bufferStartPosLock); + + bufferValidStart = newBVS; + bufferValidEnd = newBVE; + } + + bufferReadyEvent.signal(); + return true; +} + +void BufferingAudioSource::readBufferSection (int64 start, int length, int bufferOffset) +{ + if (source->getNextReadPosition() != start) + source->setNextReadPosition (start); + + AudioSourceChannelInfo info (&buffer, bufferOffset, length); + source->getNextAudioBlock (info); +} + +int BufferingAudioSource::useTimeSlice() +{ + return readNextBufferChunk() ? 1 : 100; +} + +} // namespace juce diff --git a/JuceLibraryCode/modules/juce_audio_basics/sources/juce_BufferingAudioSource.h b/JuceLibraryCode/modules/juce_audio_basics/sources/juce_BufferingAudioSource.h index 44c755d..7ac13d0 100644 --- a/JuceLibraryCode/modules/juce_audio_basics/sources/juce_BufferingAudioSource.h +++ b/JuceLibraryCode/modules/juce_audio_basics/sources/juce_BufferingAudioSource.h @@ -1,119 +1,119 @@ -/* - ============================================================================== - - This file is part of the JUCE library. - Copyright (c) 2017 - ROLI Ltd. - - JUCE is an open source library subject to commercial or open-source - licensing. - - The code included in this file is provided under the terms of the ISC license - http://www.isc.org/downloads/software-support-policy/isc-license. Permission - To use, copy, modify, and/or distribute this software for any purpose with or - without fee is hereby granted provided that the above copyright notice and - this permission notice appear in all copies. - - JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER - EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE - DISCLAIMED. - - ============================================================================== -*/ - -namespace juce -{ - -//============================================================================== -/** - An AudioSource which takes another source as input, and buffers it using a thread. - - Create this as a wrapper around another thread, and it will read-ahead with - a background thread to smooth out playback. You can either create one of these - directly, or use it indirectly using an AudioTransportSource. - - @see PositionableAudioSource, AudioTransportSource - - @tags{Audio} -*/ -class JUCE_API BufferingAudioSource : public PositionableAudioSource, - private TimeSliceClient -{ -public: - //============================================================================== - /** Creates a BufferingAudioSource. - - @param source the input source to read from - @param backgroundThread a background thread that will be used for the - background read-ahead. This object must not be deleted - until after any BufferingAudioSources that are using it - have been deleted! - @param deleteSourceWhenDeleted if true, then the input source object will - be deleted when this object is deleted - @param numberOfSamplesToBuffer the size of buffer to use for reading ahead - @param numberOfChannels the number of channels that will be played - @param prefillBufferOnPrepareToPlay if true, then calling prepareToPlay on this object will - block until the buffer has been filled - */ - BufferingAudioSource (PositionableAudioSource* source, - TimeSliceThread& backgroundThread, - bool deleteSourceWhenDeleted, - int numberOfSamplesToBuffer, - int numberOfChannels = 2, - bool prefillBufferOnPrepareToPlay = true); - - /** Destructor. - - The input source may be deleted depending on whether the deleteSourceWhenDeleted - flag was set in the constructor. - */ - ~BufferingAudioSource() override; - - //============================================================================== - /** Implementation of the AudioSource method. */ - void prepareToPlay (int samplesPerBlockExpected, double sampleRate) override; - - /** Implementation of the AudioSource method. */ - void releaseResources() override; - - /** Implementation of the AudioSource method. */ - void getNextAudioBlock (const AudioSourceChannelInfo&) override; - - //============================================================================== - /** Implements the PositionableAudioSource method. */ - void setNextReadPosition (int64 newPosition) override; - - /** Implements the PositionableAudioSource method. */ - int64 getNextReadPosition() const override; - - /** Implements the PositionableAudioSource method. */ - int64 getTotalLength() const override { return source->getTotalLength(); } - - /** Implements the PositionableAudioSource method. */ - bool isLooping() const override { return source->isLooping(); } - - /** A useful function to block until the next the buffer info can be filled. - - This is useful for offline rendering. - */ - bool waitForNextAudioBlockReady (const AudioSourceChannelInfo& info, const uint32 timeout); - -private: - //============================================================================== - OptionalScopedPointer source; - TimeSliceThread& backgroundThread; - int numberOfSamplesToBuffer, numberOfChannels; - AudioBuffer buffer; - CriticalSection bufferStartPosLock; - WaitableEvent bufferReadyEvent; - std::atomic bufferValidStart { 0 }, bufferValidEnd { 0 }, nextPlayPos { 0 }; - double sampleRate = 0; - bool wasSourceLooping = false, isPrepared = false, prefillBuffer; - - bool readNextBufferChunk(); - void readBufferSection (int64 start, int length, int bufferOffset); - int useTimeSlice() override; - - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (BufferingAudioSource) -}; - -} // namespace juce +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2017 - ROLI Ltd. + + JUCE is an open source library subject to commercial or open-source + licensing. + + The code included in this file is provided under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license. Permission + To use, copy, modify, and/or distribute this software for any purpose with or + without fee is hereby granted provided that the above copyright notice and + this permission notice appear in all copies. + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +namespace juce +{ + +//============================================================================== +/** + An AudioSource which takes another source as input, and buffers it using a thread. + + Create this as a wrapper around another thread, and it will read-ahead with + a background thread to smooth out playback. You can either create one of these + directly, or use it indirectly using an AudioTransportSource. + + @see PositionableAudioSource, AudioTransportSource + + @tags{Audio} +*/ +class JUCE_API BufferingAudioSource : public PositionableAudioSource, + private TimeSliceClient +{ +public: + //============================================================================== + /** Creates a BufferingAudioSource. + + @param source the input source to read from + @param backgroundThread a background thread that will be used for the + background read-ahead. This object must not be deleted + until after any BufferingAudioSources that are using it + have been deleted! + @param deleteSourceWhenDeleted if true, then the input source object will + be deleted when this object is deleted + @param numberOfSamplesToBuffer the size of buffer to use for reading ahead + @param numberOfChannels the number of channels that will be played + @param prefillBufferOnPrepareToPlay if true, then calling prepareToPlay on this object will + block until the buffer has been filled + */ + BufferingAudioSource (PositionableAudioSource* source, + TimeSliceThread& backgroundThread, + bool deleteSourceWhenDeleted, + int numberOfSamplesToBuffer, + int numberOfChannels = 2, + bool prefillBufferOnPrepareToPlay = true); + + /** Destructor. + + The input source may be deleted depending on whether the deleteSourceWhenDeleted + flag was set in the constructor. + */ + ~BufferingAudioSource() override; + + //============================================================================== + /** Implementation of the AudioSource method. */ + void prepareToPlay (int samplesPerBlockExpected, double sampleRate) override; + + /** Implementation of the AudioSource method. */ + void releaseResources() override; + + /** Implementation of the AudioSource method. */ + void getNextAudioBlock (const AudioSourceChannelInfo&) override; + + //============================================================================== + /** Implements the PositionableAudioSource method. */ + void setNextReadPosition (int64 newPosition) override; + + /** Implements the PositionableAudioSource method. */ + int64 getNextReadPosition() const override; + + /** Implements the PositionableAudioSource method. */ + int64 getTotalLength() const override { return source->getTotalLength(); } + + /** Implements the PositionableAudioSource method. */ + bool isLooping() const override { return source->isLooping(); } + + /** A useful function to block until the next the buffer info can be filled. + + This is useful for offline rendering. + */ + bool waitForNextAudioBlockReady (const AudioSourceChannelInfo& info, const uint32 timeout); + +private: + //============================================================================== + OptionalScopedPointer source; + TimeSliceThread& backgroundThread; + int numberOfSamplesToBuffer, numberOfChannels; + AudioBuffer buffer; + CriticalSection bufferStartPosLock; + WaitableEvent bufferReadyEvent; + std::atomic bufferValidStart { 0 }, bufferValidEnd { 0 }, nextPlayPos { 0 }; + double sampleRate = 0; + bool wasSourceLooping = false, isPrepared = false, prefillBuffer; + + bool readNextBufferChunk(); + void readBufferSection (int64 start, int length, int bufferOffset); + int useTimeSlice() override; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (BufferingAudioSource) +}; + +} // namespace juce diff --git a/JuceLibraryCode/modules/juce_audio_basics/sources/juce_ChannelRemappingAudioSource.cpp b/JuceLibraryCode/modules/juce_audio_basics/sources/juce_ChannelRemappingAudioSource.cpp index d5263ac..cfcc6d8 100644 --- a/JuceLibraryCode/modules/juce_audio_basics/sources/juce_ChannelRemappingAudioSource.cpp +++ b/JuceLibraryCode/modules/juce_audio_basics/sources/juce_ChannelRemappingAudioSource.cpp @@ -1,187 +1,187 @@ -/* - ============================================================================== - - This file is part of the JUCE library. - Copyright (c) 2017 - ROLI Ltd. - - JUCE is an open source library subject to commercial or open-source - licensing. - - The code included in this file is provided under the terms of the ISC license - http://www.isc.org/downloads/software-support-policy/isc-license. Permission - To use, copy, modify, and/or distribute this software for any purpose with or - without fee is hereby granted provided that the above copyright notice and - this permission notice appear in all copies. - - JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER - EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE - DISCLAIMED. - - ============================================================================== -*/ - -namespace juce -{ - -ChannelRemappingAudioSource::ChannelRemappingAudioSource (AudioSource* const source_, - const bool deleteSourceWhenDeleted) - : source (source_, deleteSourceWhenDeleted), - requiredNumberOfChannels (2) -{ - remappedInfo.buffer = &buffer; - remappedInfo.startSample = 0; -} - -ChannelRemappingAudioSource::~ChannelRemappingAudioSource() {} - -//============================================================================== -void ChannelRemappingAudioSource::setNumberOfChannelsToProduce (const int requiredNumberOfChannels_) -{ - const ScopedLock sl (lock); - requiredNumberOfChannels = requiredNumberOfChannels_; -} - -void ChannelRemappingAudioSource::clearAllMappings() -{ - const ScopedLock sl (lock); - - remappedInputs.clear(); - remappedOutputs.clear(); -} - -void ChannelRemappingAudioSource::setInputChannelMapping (const int destIndex, const int sourceIndex) -{ - const ScopedLock sl (lock); - - while (remappedInputs.size() < destIndex) - remappedInputs.add (-1); - - remappedInputs.set (destIndex, sourceIndex); -} - -void ChannelRemappingAudioSource::setOutputChannelMapping (const int sourceIndex, const int destIndex) -{ - const ScopedLock sl (lock); - - while (remappedOutputs.size() < sourceIndex) - remappedOutputs.add (-1); - - remappedOutputs.set (sourceIndex, destIndex); -} - -int ChannelRemappingAudioSource::getRemappedInputChannel (const int inputChannelIndex) const -{ - const ScopedLock sl (lock); - - if (inputChannelIndex >= 0 && inputChannelIndex < remappedInputs.size()) - return remappedInputs.getUnchecked (inputChannelIndex); - - return -1; -} - -int ChannelRemappingAudioSource::getRemappedOutputChannel (const int outputChannelIndex) const -{ - const ScopedLock sl (lock); - - if (outputChannelIndex >= 0 && outputChannelIndex < remappedOutputs.size()) - return remappedOutputs .getUnchecked (outputChannelIndex); - - return -1; -} - -//============================================================================== -void ChannelRemappingAudioSource::prepareToPlay (int samplesPerBlockExpected, double sampleRate) -{ - source->prepareToPlay (samplesPerBlockExpected, sampleRate); -} - -void ChannelRemappingAudioSource::releaseResources() -{ - source->releaseResources(); -} - -void ChannelRemappingAudioSource::getNextAudioBlock (const AudioSourceChannelInfo& bufferToFill) -{ - const ScopedLock sl (lock); - - buffer.setSize (requiredNumberOfChannels, bufferToFill.numSamples, false, false, true); - - const int numChans = bufferToFill.buffer->getNumChannels(); - - for (int i = 0; i < buffer.getNumChannels(); ++i) - { - const int remappedChan = getRemappedInputChannel (i); - - if (remappedChan >= 0 && remappedChan < numChans) - { - buffer.copyFrom (i, 0, *bufferToFill.buffer, - remappedChan, - bufferToFill.startSample, - bufferToFill.numSamples); - } - else - { - buffer.clear (i, 0, bufferToFill.numSamples); - } - } - - remappedInfo.numSamples = bufferToFill.numSamples; - - source->getNextAudioBlock (remappedInfo); - - bufferToFill.clearActiveBufferRegion(); - - for (int i = 0; i < requiredNumberOfChannels; ++i) - { - const int remappedChan = getRemappedOutputChannel (i); - - if (remappedChan >= 0 && remappedChan < numChans) - { - bufferToFill.buffer->addFrom (remappedChan, bufferToFill.startSample, - buffer, i, 0, bufferToFill.numSamples); - - } - } -} - -//============================================================================== -std::unique_ptr ChannelRemappingAudioSource::createXml() const -{ - auto e = std::make_unique ("MAPPINGS"); - String ins, outs; - - const ScopedLock sl (lock); - - for (int i = 0; i < remappedInputs.size(); ++i) - ins << remappedInputs.getUnchecked(i) << ' '; - - for (int i = 0; i < remappedOutputs.size(); ++i) - outs << remappedOutputs.getUnchecked(i) << ' '; - - e->setAttribute ("inputs", ins.trimEnd()); - e->setAttribute ("outputs", outs.trimEnd()); - - return e; -} - -void ChannelRemappingAudioSource::restoreFromXml (const XmlElement& e) -{ - if (e.hasTagName ("MAPPINGS")) - { - const ScopedLock sl (lock); - - clearAllMappings(); - - StringArray ins, outs; - ins.addTokens (e.getStringAttribute ("inputs"), false); - outs.addTokens (e.getStringAttribute ("outputs"), false); - - for (int i = 0; i < ins.size(); ++i) - remappedInputs.add (ins[i].getIntValue()); - - for (int i = 0; i < outs.size(); ++i) - remappedOutputs.add (outs[i].getIntValue()); - } -} - -} // namespace juce +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2017 - ROLI Ltd. + + JUCE is an open source library subject to commercial or open-source + licensing. + + The code included in this file is provided under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license. Permission + To use, copy, modify, and/or distribute this software for any purpose with or + without fee is hereby granted provided that the above copyright notice and + this permission notice appear in all copies. + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +namespace juce +{ + +ChannelRemappingAudioSource::ChannelRemappingAudioSource (AudioSource* const source_, + const bool deleteSourceWhenDeleted) + : source (source_, deleteSourceWhenDeleted), + requiredNumberOfChannels (2) +{ + remappedInfo.buffer = &buffer; + remappedInfo.startSample = 0; +} + +ChannelRemappingAudioSource::~ChannelRemappingAudioSource() {} + +//============================================================================== +void ChannelRemappingAudioSource::setNumberOfChannelsToProduce (const int requiredNumberOfChannels_) +{ + const ScopedLock sl (lock); + requiredNumberOfChannels = requiredNumberOfChannels_; +} + +void ChannelRemappingAudioSource::clearAllMappings() +{ + const ScopedLock sl (lock); + + remappedInputs.clear(); + remappedOutputs.clear(); +} + +void ChannelRemappingAudioSource::setInputChannelMapping (const int destIndex, const int sourceIndex) +{ + const ScopedLock sl (lock); + + while (remappedInputs.size() < destIndex) + remappedInputs.add (-1); + + remappedInputs.set (destIndex, sourceIndex); +} + +void ChannelRemappingAudioSource::setOutputChannelMapping (const int sourceIndex, const int destIndex) +{ + const ScopedLock sl (lock); + + while (remappedOutputs.size() < sourceIndex) + remappedOutputs.add (-1); + + remappedOutputs.set (sourceIndex, destIndex); +} + +int ChannelRemappingAudioSource::getRemappedInputChannel (const int inputChannelIndex) const +{ + const ScopedLock sl (lock); + + if (inputChannelIndex >= 0 && inputChannelIndex < remappedInputs.size()) + return remappedInputs.getUnchecked (inputChannelIndex); + + return -1; +} + +int ChannelRemappingAudioSource::getRemappedOutputChannel (const int outputChannelIndex) const +{ + const ScopedLock sl (lock); + + if (outputChannelIndex >= 0 && outputChannelIndex < remappedOutputs.size()) + return remappedOutputs .getUnchecked (outputChannelIndex); + + return -1; +} + +//============================================================================== +void ChannelRemappingAudioSource::prepareToPlay (int samplesPerBlockExpected, double sampleRate) +{ + source->prepareToPlay (samplesPerBlockExpected, sampleRate); +} + +void ChannelRemappingAudioSource::releaseResources() +{ + source->releaseResources(); +} + +void ChannelRemappingAudioSource::getNextAudioBlock (const AudioSourceChannelInfo& bufferToFill) +{ + const ScopedLock sl (lock); + + buffer.setSize (requiredNumberOfChannels, bufferToFill.numSamples, false, false, true); + + const int numChans = bufferToFill.buffer->getNumChannels(); + + for (int i = 0; i < buffer.getNumChannels(); ++i) + { + const int remappedChan = getRemappedInputChannel (i); + + if (remappedChan >= 0 && remappedChan < numChans) + { + buffer.copyFrom (i, 0, *bufferToFill.buffer, + remappedChan, + bufferToFill.startSample, + bufferToFill.numSamples); + } + else + { + buffer.clear (i, 0, bufferToFill.numSamples); + } + } + + remappedInfo.numSamples = bufferToFill.numSamples; + + source->getNextAudioBlock (remappedInfo); + + bufferToFill.clearActiveBufferRegion(); + + for (int i = 0; i < requiredNumberOfChannels; ++i) + { + const int remappedChan = getRemappedOutputChannel (i); + + if (remappedChan >= 0 && remappedChan < numChans) + { + bufferToFill.buffer->addFrom (remappedChan, bufferToFill.startSample, + buffer, i, 0, bufferToFill.numSamples); + + } + } +} + +//============================================================================== +std::unique_ptr ChannelRemappingAudioSource::createXml() const +{ + auto e = std::make_unique ("MAPPINGS"); + String ins, outs; + + const ScopedLock sl (lock); + + for (int i = 0; i < remappedInputs.size(); ++i) + ins << remappedInputs.getUnchecked(i) << ' '; + + for (int i = 0; i < remappedOutputs.size(); ++i) + outs << remappedOutputs.getUnchecked(i) << ' '; + + e->setAttribute ("inputs", ins.trimEnd()); + e->setAttribute ("outputs", outs.trimEnd()); + + return e; +} + +void ChannelRemappingAudioSource::restoreFromXml (const XmlElement& e) +{ + if (e.hasTagName ("MAPPINGS")) + { + const ScopedLock sl (lock); + + clearAllMappings(); + + StringArray ins, outs; + ins.addTokens (e.getStringAttribute ("inputs"), false); + outs.addTokens (e.getStringAttribute ("outputs"), false); + + for (int i = 0; i < ins.size(); ++i) + remappedInputs.add (ins[i].getIntValue()); + + for (int i = 0; i < outs.size(); ++i) + remappedOutputs.add (outs[i].getIntValue()); + } +} + +} // namespace juce diff --git a/JuceLibraryCode/modules/juce_audio_basics/sources/juce_ChannelRemappingAudioSource.h b/JuceLibraryCode/modules/juce_audio_basics/sources/juce_ChannelRemappingAudioSource.h index 928a20c..710f335 100644 --- a/JuceLibraryCode/modules/juce_audio_basics/sources/juce_ChannelRemappingAudioSource.h +++ b/JuceLibraryCode/modules/juce_audio_basics/sources/juce_ChannelRemappingAudioSource.h @@ -1,141 +1,141 @@ -/* - ============================================================================== - - This file is part of the JUCE library. - Copyright (c) 2017 - ROLI Ltd. - - JUCE is an open source library subject to commercial or open-source - licensing. - - The code included in this file is provided under the terms of the ISC license - http://www.isc.org/downloads/software-support-policy/isc-license. Permission - To use, copy, modify, and/or distribute this software for any purpose with or - without fee is hereby granted provided that the above copyright notice and - this permission notice appear in all copies. - - JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER - EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE - DISCLAIMED. - - ============================================================================== -*/ - -namespace juce -{ - -//============================================================================== -/** - An AudioSource that takes the audio from another source, and re-maps its - input and output channels to a different arrangement. - - You can use this to increase or decrease the number of channels that an - audio source uses, or to re-order those channels. - - Call the reset() method before using it to set up a default mapping, and then - the setInputChannelMapping() and setOutputChannelMapping() methods to - create an appropriate mapping, otherwise no channels will be connected and - it'll produce silence. - - @see AudioSource - - @tags{Audio} -*/ -class ChannelRemappingAudioSource : public AudioSource -{ -public: - //============================================================================== - /** Creates a remapping source that will pass on audio from the given input. - - @param source the input source to use. Make sure that this doesn't - get deleted before the ChannelRemappingAudioSource object - @param deleteSourceWhenDeleted if true, the input source will be deleted - when this object is deleted, if false, the caller is - responsible for its deletion - */ - ChannelRemappingAudioSource (AudioSource* source, - bool deleteSourceWhenDeleted); - - /** Destructor. */ - ~ChannelRemappingAudioSource() override; - - //============================================================================== - /** Specifies a number of channels that this audio source must produce from its - getNextAudioBlock() callback. - */ - void setNumberOfChannelsToProduce (int requiredNumberOfChannels); - - /** Clears any mapped channels. - - After this, no channels are mapped, so this object will produce silence. Create - some mappings with setInputChannelMapping() and setOutputChannelMapping(). - */ - void clearAllMappings(); - - /** Creates an input channel mapping. - - When the getNextAudioBlock() method is called, the data in channel sourceChannelIndex of the incoming - data will be sent to destChannelIndex of our input source. - - @param destChannelIndex the index of an input channel in our input audio source (i.e. the - source specified when this object was created). - @param sourceChannelIndex the index of the input channel in the incoming audio data buffer - during our getNextAudioBlock() callback - */ - void setInputChannelMapping (int destChannelIndex, - int sourceChannelIndex); - - /** Creates an output channel mapping. - - When the getNextAudioBlock() method is called, the data returned in channel sourceChannelIndex by - our input audio source will be copied to channel destChannelIndex of the final buffer. - - @param sourceChannelIndex the index of an output channel coming from our input audio source - (i.e. the source specified when this object was created). - @param destChannelIndex the index of the output channel in the incoming audio data buffer - during our getNextAudioBlock() callback - */ - void setOutputChannelMapping (int sourceChannelIndex, - int destChannelIndex); - - /** Returns the channel from our input that will be sent to channel inputChannelIndex of - our input audio source. - */ - int getRemappedInputChannel (int inputChannelIndex) const; - - /** Returns the output channel to which channel outputChannelIndex of our input audio - source will be sent to. - */ - int getRemappedOutputChannel (int outputChannelIndex) const; - - - //============================================================================== - /** Returns an XML object to encapsulate the state of the mappings. - @see restoreFromXml - */ - std::unique_ptr createXml() const; - - /** Restores the mappings from an XML object created by createXML(). - @see createXml - */ - void restoreFromXml (const XmlElement&); - - //============================================================================== - void prepareToPlay (int samplesPerBlockExpected, double sampleRate) override; - void releaseResources() override; - void getNextAudioBlock (const AudioSourceChannelInfo&) override; - - -private: - //============================================================================== - OptionalScopedPointer source; - Array remappedInputs, remappedOutputs; - int requiredNumberOfChannels; - - AudioBuffer buffer; - AudioSourceChannelInfo remappedInfo; - CriticalSection lock; - - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ChannelRemappingAudioSource) -}; - -} // namespace juce +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2017 - ROLI Ltd. + + JUCE is an open source library subject to commercial or open-source + licensing. + + The code included in this file is provided under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license. Permission + To use, copy, modify, and/or distribute this software for any purpose with or + without fee is hereby granted provided that the above copyright notice and + this permission notice appear in all copies. + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +namespace juce +{ + +//============================================================================== +/** + An AudioSource that takes the audio from another source, and re-maps its + input and output channels to a different arrangement. + + You can use this to increase or decrease the number of channels that an + audio source uses, or to re-order those channels. + + Call the reset() method before using it to set up a default mapping, and then + the setInputChannelMapping() and setOutputChannelMapping() methods to + create an appropriate mapping, otherwise no channels will be connected and + it'll produce silence. + + @see AudioSource + + @tags{Audio} +*/ +class ChannelRemappingAudioSource : public AudioSource +{ +public: + //============================================================================== + /** Creates a remapping source that will pass on audio from the given input. + + @param source the input source to use. Make sure that this doesn't + get deleted before the ChannelRemappingAudioSource object + @param deleteSourceWhenDeleted if true, the input source will be deleted + when this object is deleted, if false, the caller is + responsible for its deletion + */ + ChannelRemappingAudioSource (AudioSource* source, + bool deleteSourceWhenDeleted); + + /** Destructor. */ + ~ChannelRemappingAudioSource() override; + + //============================================================================== + /** Specifies a number of channels that this audio source must produce from its + getNextAudioBlock() callback. + */ + void setNumberOfChannelsToProduce (int requiredNumberOfChannels); + + /** Clears any mapped channels. + + After this, no channels are mapped, so this object will produce silence. Create + some mappings with setInputChannelMapping() and setOutputChannelMapping(). + */ + void clearAllMappings(); + + /** Creates an input channel mapping. + + When the getNextAudioBlock() method is called, the data in channel sourceChannelIndex of the incoming + data will be sent to destChannelIndex of our input source. + + @param destChannelIndex the index of an input channel in our input audio source (i.e. the + source specified when this object was created). + @param sourceChannelIndex the index of the input channel in the incoming audio data buffer + during our getNextAudioBlock() callback + */ + void setInputChannelMapping (int destChannelIndex, + int sourceChannelIndex); + + /** Creates an output channel mapping. + + When the getNextAudioBlock() method is called, the data returned in channel sourceChannelIndex by + our input audio source will be copied to channel destChannelIndex of the final buffer. + + @param sourceChannelIndex the index of an output channel coming from our input audio source + (i.e. the source specified when this object was created). + @param destChannelIndex the index of the output channel in the incoming audio data buffer + during our getNextAudioBlock() callback + */ + void setOutputChannelMapping (int sourceChannelIndex, + int destChannelIndex); + + /** Returns the channel from our input that will be sent to channel inputChannelIndex of + our input audio source. + */ + int getRemappedInputChannel (int inputChannelIndex) const; + + /** Returns the output channel to which channel outputChannelIndex of our input audio + source will be sent to. + */ + int getRemappedOutputChannel (int outputChannelIndex) const; + + + //============================================================================== + /** Returns an XML object to encapsulate the state of the mappings. + @see restoreFromXml + */ + std::unique_ptr createXml() const; + + /** Restores the mappings from an XML object created by createXML(). + @see createXml + */ + void restoreFromXml (const XmlElement&); + + //============================================================================== + void prepareToPlay (int samplesPerBlockExpected, double sampleRate) override; + void releaseResources() override; + void getNextAudioBlock (const AudioSourceChannelInfo&) override; + + +private: + //============================================================================== + OptionalScopedPointer source; + Array remappedInputs, remappedOutputs; + int requiredNumberOfChannels; + + AudioBuffer buffer; + AudioSourceChannelInfo remappedInfo; + CriticalSection lock; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ChannelRemappingAudioSource) +}; + +} // namespace juce diff --git a/JuceLibraryCode/modules/juce_audio_basics/sources/juce_IIRFilterAudioSource.cpp b/JuceLibraryCode/modules/juce_audio_basics/sources/juce_IIRFilterAudioSource.cpp index cbea3b8..dc4ac46 100644 --- a/JuceLibraryCode/modules/juce_audio_basics/sources/juce_IIRFilterAudioSource.cpp +++ b/JuceLibraryCode/modules/juce_audio_basics/sources/juce_IIRFilterAudioSource.cpp @@ -1,80 +1,80 @@ -/* - ============================================================================== - - This file is part of the JUCE library. - Copyright (c) 2017 - ROLI Ltd. - - JUCE is an open source library subject to commercial or open-source - licensing. - - The code included in this file is provided under the terms of the ISC license - http://www.isc.org/downloads/software-support-policy/isc-license. Permission - To use, copy, modify, and/or distribute this software for any purpose with or - without fee is hereby granted provided that the above copyright notice and - this permission notice appear in all copies. - - JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER - EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE - DISCLAIMED. - - ============================================================================== -*/ - -namespace juce -{ - -IIRFilterAudioSource::IIRFilterAudioSource (AudioSource* const inputSource, - const bool deleteInputWhenDeleted) - : input (inputSource, deleteInputWhenDeleted) -{ - jassert (inputSource != nullptr); - - for (int i = 2; --i >= 0;) - iirFilters.add (new IIRFilter()); -} - -IIRFilterAudioSource::~IIRFilterAudioSource() {} - -//============================================================================== -void IIRFilterAudioSource::setCoefficients (const IIRCoefficients& newCoefficients) -{ - for (int i = iirFilters.size(); --i >= 0;) - iirFilters.getUnchecked(i)->setCoefficients (newCoefficients); -} - -void IIRFilterAudioSource::makeInactive() -{ - for (int i = iirFilters.size(); --i >= 0;) - iirFilters.getUnchecked(i)->makeInactive(); -} - -//============================================================================== -void IIRFilterAudioSource::prepareToPlay (int samplesPerBlockExpected, double sampleRate) -{ - input->prepareToPlay (samplesPerBlockExpected, sampleRate); - - for (int i = iirFilters.size(); --i >= 0;) - iirFilters.getUnchecked(i)->reset(); -} - -void IIRFilterAudioSource::releaseResources() -{ - input->releaseResources(); -} - -void IIRFilterAudioSource::getNextAudioBlock (const AudioSourceChannelInfo& bufferToFill) -{ - input->getNextAudioBlock (bufferToFill); - - const int numChannels = bufferToFill.buffer->getNumChannels(); - - while (numChannels > iirFilters.size()) - iirFilters.add (new IIRFilter (*iirFilters.getUnchecked (0))); - - for (int i = 0; i < numChannels; ++i) - iirFilters.getUnchecked(i) - ->processSamples (bufferToFill.buffer->getWritePointer (i, bufferToFill.startSample), - bufferToFill.numSamples); -} - -} // namespace juce +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2017 - ROLI Ltd. + + JUCE is an open source library subject to commercial or open-source + licensing. + + The code included in this file is provided under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license. Permission + To use, copy, modify, and/or distribute this software for any purpose with or + without fee is hereby granted provided that the above copyright notice and + this permission notice appear in all copies. + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +namespace juce +{ + +IIRFilterAudioSource::IIRFilterAudioSource (AudioSource* const inputSource, + const bool deleteInputWhenDeleted) + : input (inputSource, deleteInputWhenDeleted) +{ + jassert (inputSource != nullptr); + + for (int i = 2; --i >= 0;) + iirFilters.add (new IIRFilter()); +} + +IIRFilterAudioSource::~IIRFilterAudioSource() {} + +//============================================================================== +void IIRFilterAudioSource::setCoefficients (const IIRCoefficients& newCoefficients) +{ + for (int i = iirFilters.size(); --i >= 0;) + iirFilters.getUnchecked(i)->setCoefficients (newCoefficients); +} + +void IIRFilterAudioSource::makeInactive() +{ + for (int i = iirFilters.size(); --i >= 0;) + iirFilters.getUnchecked(i)->makeInactive(); +} + +//============================================================================== +void IIRFilterAudioSource::prepareToPlay (int samplesPerBlockExpected, double sampleRate) +{ + input->prepareToPlay (samplesPerBlockExpected, sampleRate); + + for (int i = iirFilters.size(); --i >= 0;) + iirFilters.getUnchecked(i)->reset(); +} + +void IIRFilterAudioSource::releaseResources() +{ + input->releaseResources(); +} + +void IIRFilterAudioSource::getNextAudioBlock (const AudioSourceChannelInfo& bufferToFill) +{ + input->getNextAudioBlock (bufferToFill); + + const int numChannels = bufferToFill.buffer->getNumChannels(); + + while (numChannels > iirFilters.size()) + iirFilters.add (new IIRFilter (*iirFilters.getUnchecked (0))); + + for (int i = 0; i < numChannels; ++i) + iirFilters.getUnchecked(i) + ->processSamples (bufferToFill.buffer->getWritePointer (i, bufferToFill.startSample), + bufferToFill.numSamples); +} + +} // namespace juce diff --git a/JuceLibraryCode/modules/juce_audio_basics/sources/juce_IIRFilterAudioSource.h b/JuceLibraryCode/modules/juce_audio_basics/sources/juce_IIRFilterAudioSource.h index e47e9e8..7b2f320 100644 --- a/JuceLibraryCode/modules/juce_audio_basics/sources/juce_IIRFilterAudioSource.h +++ b/JuceLibraryCode/modules/juce_audio_basics/sources/juce_IIRFilterAudioSource.h @@ -1,68 +1,68 @@ -/* - ============================================================================== - - This file is part of the JUCE library. - Copyright (c) 2017 - ROLI Ltd. - - JUCE is an open source library subject to commercial or open-source - licensing. - - The code included in this file is provided under the terms of the ISC license - http://www.isc.org/downloads/software-support-policy/isc-license. Permission - To use, copy, modify, and/or distribute this software for any purpose with or - without fee is hereby granted provided that the above copyright notice and - this permission notice appear in all copies. - - JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER - EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE - DISCLAIMED. - - ============================================================================== -*/ - -namespace juce -{ - -//============================================================================== -/** - An AudioSource that performs an IIR filter on another source. - - @tags{Audio} -*/ -class JUCE_API IIRFilterAudioSource : public AudioSource -{ -public: - //============================================================================== - /** Creates a IIRFilterAudioSource for a given input source. - - @param inputSource the input source to read from - this must not be null - @param deleteInputWhenDeleted if true, the input source will be deleted when - this object is deleted - */ - IIRFilterAudioSource (AudioSource* inputSource, - bool deleteInputWhenDeleted); - - /** Destructor. */ - ~IIRFilterAudioSource() override; - - //============================================================================== - /** Changes the filter to use the same parameters as the one being passed in. */ - void setCoefficients (const IIRCoefficients& newCoefficients); - - /** Calls IIRFilter::makeInactive() on all the filters being used internally. */ - void makeInactive(); - - //============================================================================== - void prepareToPlay (int samplesPerBlockExpected, double sampleRate) override; - void releaseResources() override; - void getNextAudioBlock (const AudioSourceChannelInfo&) override; - -private: - //============================================================================== - OptionalScopedPointer input; - OwnedArray iirFilters; - - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (IIRFilterAudioSource) -}; - -} // namespace juce +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2017 - ROLI Ltd. + + JUCE is an open source library subject to commercial or open-source + licensing. + + The code included in this file is provided under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license. Permission + To use, copy, modify, and/or distribute this software for any purpose with or + without fee is hereby granted provided that the above copyright notice and + this permission notice appear in all copies. + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +namespace juce +{ + +//============================================================================== +/** + An AudioSource that performs an IIR filter on another source. + + @tags{Audio} +*/ +class JUCE_API IIRFilterAudioSource : public AudioSource +{ +public: + //============================================================================== + /** Creates a IIRFilterAudioSource for a given input source. + + @param inputSource the input source to read from - this must not be null + @param deleteInputWhenDeleted if true, the input source will be deleted when + this object is deleted + */ + IIRFilterAudioSource (AudioSource* inputSource, + bool deleteInputWhenDeleted); + + /** Destructor. */ + ~IIRFilterAudioSource() override; + + //============================================================================== + /** Changes the filter to use the same parameters as the one being passed in. */ + void setCoefficients (const IIRCoefficients& newCoefficients); + + /** Calls IIRFilter::makeInactive() on all the filters being used internally. */ + void makeInactive(); + + //============================================================================== + void prepareToPlay (int samplesPerBlockExpected, double sampleRate) override; + void releaseResources() override; + void getNextAudioBlock (const AudioSourceChannelInfo&) override; + +private: + //============================================================================== + OptionalScopedPointer input; + OwnedArray iirFilters; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (IIRFilterAudioSource) +}; + +} // namespace juce diff --git a/JuceLibraryCode/modules/juce_audio_basics/sources/juce_MemoryAudioSource.cpp b/JuceLibraryCode/modules/juce_audio_basics/sources/juce_MemoryAudioSource.cpp index a987d7f..ac0aa22 100644 --- a/JuceLibraryCode/modules/juce_audio_basics/sources/juce_MemoryAudioSource.cpp +++ b/JuceLibraryCode/modules/juce_audio_basics/sources/juce_MemoryAudioSource.cpp @@ -1,73 +1,73 @@ -/* - ============================================================================== - - This file is part of the JUCE library. - Copyright (c) 2017 - ROLI Ltd. - - JUCE is an open source library subject to commercial or open-source - licensing. - - The code included in this file is provided under the terms of the ISC license - http://www.isc.org/downloads/software-support-policy/isc-license. Permission - To use, copy, modify, and/or distribute this software for any purpose with or - without fee is hereby granted provided that the above copyright notice and - this permission notice appear in all copies. - - JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER - EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE - DISCLAIMED. - - ============================================================================== -*/ - -namespace juce -{ - -MemoryAudioSource::MemoryAudioSource (AudioBuffer& bufferToUse, bool copyMemory, bool shouldLoop) - : isLooping (shouldLoop) -{ - if (copyMemory) - buffer.makeCopyOf (bufferToUse); - else - buffer.setDataToReferTo (bufferToUse.getArrayOfWritePointers(), - bufferToUse.getNumChannels(), - bufferToUse.getNumSamples()); -} - -//============================================================================== -void MemoryAudioSource::prepareToPlay (int /*samplesPerBlockExpected*/, double /*sampleRate*/) -{ - position = 0; -} - -void MemoryAudioSource::releaseResources() {} - -void MemoryAudioSource::getNextAudioBlock (const AudioSourceChannelInfo& bufferToFill) -{ - auto& dst = *bufferToFill.buffer; - auto channels = jmin (dst.getNumChannels(), buffer.getNumChannels()); - auto max = 0, pos = 0; - auto n = buffer.getNumSamples(), m = bufferToFill.numSamples; - - int i; - for (i = position; (i < n || isLooping) && (pos < m); i += max) - { - max = jmin (m - pos, n - (i % n)); - - int ch = 0; - for (; ch < channels; ++ch) - dst.copyFrom (ch, bufferToFill.startSample + pos, buffer, ch, i % n, max); - - for (; ch < dst.getNumChannels(); ++ch) - dst.clear (ch, bufferToFill.startSample + pos, max); - - pos += max; - } - - if (pos < m) - dst.clear (bufferToFill.startSample + pos, m - pos); - - position = (i % n); -} - -} // namespace juce +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2017 - ROLI Ltd. + + JUCE is an open source library subject to commercial or open-source + licensing. + + The code included in this file is provided under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license. Permission + To use, copy, modify, and/or distribute this software for any purpose with or + without fee is hereby granted provided that the above copyright notice and + this permission notice appear in all copies. + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +namespace juce +{ + +MemoryAudioSource::MemoryAudioSource (AudioBuffer& bufferToUse, bool copyMemory, bool shouldLoop) + : isLooping (shouldLoop) +{ + if (copyMemory) + buffer.makeCopyOf (bufferToUse); + else + buffer.setDataToReferTo (bufferToUse.getArrayOfWritePointers(), + bufferToUse.getNumChannels(), + bufferToUse.getNumSamples()); +} + +//============================================================================== +void MemoryAudioSource::prepareToPlay (int /*samplesPerBlockExpected*/, double /*sampleRate*/) +{ + position = 0; +} + +void MemoryAudioSource::releaseResources() {} + +void MemoryAudioSource::getNextAudioBlock (const AudioSourceChannelInfo& bufferToFill) +{ + auto& dst = *bufferToFill.buffer; + auto channels = jmin (dst.getNumChannels(), buffer.getNumChannels()); + auto max = 0, pos = 0; + auto n = buffer.getNumSamples(), m = bufferToFill.numSamples; + + int i; + for (i = position; (i < n || isLooping) && (pos < m); i += max) + { + max = jmin (m - pos, n - (i % n)); + + int ch = 0; + for (; ch < channels; ++ch) + dst.copyFrom (ch, bufferToFill.startSample + pos, buffer, ch, i % n, max); + + for (; ch < dst.getNumChannels(); ++ch) + dst.clear (ch, bufferToFill.startSample + pos, max); + + pos += max; + } + + if (pos < m) + dst.clear (bufferToFill.startSample + pos, m - pos); + + position = (i % n); +} + +} // namespace juce diff --git a/JuceLibraryCode/modules/juce_audio_basics/sources/juce_MemoryAudioSource.h b/JuceLibraryCode/modules/juce_audio_basics/sources/juce_MemoryAudioSource.h index d46bd4f..b898682 100644 --- a/JuceLibraryCode/modules/juce_audio_basics/sources/juce_MemoryAudioSource.h +++ b/JuceLibraryCode/modules/juce_audio_basics/sources/juce_MemoryAudioSource.h @@ -1,65 +1,65 @@ -/* - ============================================================================== - - This file is part of the JUCE library. - Copyright (c) 2017 - ROLI Ltd. - - JUCE is an open source library subject to commercial or open-source - licensing. - - The code included in this file is provided under the terms of the ISC license - http://www.isc.org/downloads/software-support-policy/isc-license. Permission - To use, copy, modify, and/or distribute this software for any purpose with or - without fee is hereby granted provided that the above copyright notice and - this permission notice appear in all copies. - - JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER - EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE - DISCLAIMED. - - ============================================================================== -*/ - -namespace juce -{ - -//============================================================================== -/** - An AudioSource which takes some float audio data as an input. - - @tags{Audio} -*/ -class JUCE_API MemoryAudioSource : public AudioSource -{ -public: - //============================================================================== - /** Creates a MemoryAudioSource by providing an audio buffer. - - If copyMemory is true then the buffer will be copied into an internal - buffer which will be owned by the MemoryAudioSource. If copyMemory is - false, then you must ensure that the lifetime of the audio buffer is - at least as long as the MemoryAudioSource. - */ - MemoryAudioSource (AudioBuffer& audioBuffer, bool copyMemory, bool shouldLoop = false); - - //============================================================================== - /** Implementation of the AudioSource method. */ - void prepareToPlay (int samplesPerBlockExpected, double sampleRate) override; - - /** Implementation of the AudioSource method. */ - void releaseResources() override; - - /** Implementation of the AudioSource method. */ - void getNextAudioBlock (const AudioSourceChannelInfo& bufferToFill) override; - -private: - //============================================================================== - AudioBuffer buffer; - int position = 0; - bool isLooping; - - //============================================================================== - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MemoryAudioSource) -}; - -} // namespace juce +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2017 - ROLI Ltd. + + JUCE is an open source library subject to commercial or open-source + licensing. + + The code included in this file is provided under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license. Permission + To use, copy, modify, and/or distribute this software for any purpose with or + without fee is hereby granted provided that the above copyright notice and + this permission notice appear in all copies. + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +namespace juce +{ + +//============================================================================== +/** + An AudioSource which takes some float audio data as an input. + + @tags{Audio} +*/ +class JUCE_API MemoryAudioSource : public AudioSource +{ +public: + //============================================================================== + /** Creates a MemoryAudioSource by providing an audio buffer. + + If copyMemory is true then the buffer will be copied into an internal + buffer which will be owned by the MemoryAudioSource. If copyMemory is + false, then you must ensure that the lifetime of the audio buffer is + at least as long as the MemoryAudioSource. + */ + MemoryAudioSource (AudioBuffer& audioBuffer, bool copyMemory, bool shouldLoop = false); + + //============================================================================== + /** Implementation of the AudioSource method. */ + void prepareToPlay (int samplesPerBlockExpected, double sampleRate) override; + + /** Implementation of the AudioSource method. */ + void releaseResources() override; + + /** Implementation of the AudioSource method. */ + void getNextAudioBlock (const AudioSourceChannelInfo& bufferToFill) override; + +private: + //============================================================================== + AudioBuffer buffer; + int position = 0; + bool isLooping; + + //============================================================================== + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MemoryAudioSource) +}; + +} // namespace juce diff --git a/JuceLibraryCode/modules/juce_audio_basics/sources/juce_MixerAudioSource.cpp b/JuceLibraryCode/modules/juce_audio_basics/sources/juce_MixerAudioSource.cpp index 96d9fe8..5d33d92 100644 --- a/JuceLibraryCode/modules/juce_audio_basics/sources/juce_MixerAudioSource.cpp +++ b/JuceLibraryCode/modules/juce_audio_basics/sources/juce_MixerAudioSource.cpp @@ -1,158 +1,158 @@ -/* - ============================================================================== - - This file is part of the JUCE library. - Copyright (c) 2017 - ROLI Ltd. - - JUCE is an open source library subject to commercial or open-source - licensing. - - The code included in this file is provided under the terms of the ISC license - http://www.isc.org/downloads/software-support-policy/isc-license. Permission - To use, copy, modify, and/or distribute this software for any purpose with or - without fee is hereby granted provided that the above copyright notice and - this permission notice appear in all copies. - - JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER - EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE - DISCLAIMED. - - ============================================================================== -*/ - -namespace juce -{ - -MixerAudioSource::MixerAudioSource() - : currentSampleRate (0.0), bufferSizeExpected (0) -{ -} - -MixerAudioSource::~MixerAudioSource() -{ - removeAllInputs(); -} - -//============================================================================== -void MixerAudioSource::addInputSource (AudioSource* input, const bool deleteWhenRemoved) -{ - if (input != nullptr && ! inputs.contains (input)) - { - double localRate; - int localBufferSize; - - { - const ScopedLock sl (lock); - localRate = currentSampleRate; - localBufferSize = bufferSizeExpected; - } - - if (localRate > 0.0) - input->prepareToPlay (localBufferSize, localRate); - - const ScopedLock sl (lock); - - inputsToDelete.setBit (inputs.size(), deleteWhenRemoved); - inputs.add (input); - } -} - -void MixerAudioSource::removeInputSource (AudioSource* const input) -{ - if (input != nullptr) - { - std::unique_ptr toDelete; - - { - const ScopedLock sl (lock); - const int index = inputs.indexOf (input); - - if (index < 0) - return; - - if (inputsToDelete [index]) - toDelete.reset (input); - - inputsToDelete.shiftBits (-1, index); - inputs.remove (index); - } - - input->releaseResources(); - } -} - -void MixerAudioSource::removeAllInputs() -{ - OwnedArray toDelete; - - { - const ScopedLock sl (lock); - - for (int i = inputs.size(); --i >= 0;) - if (inputsToDelete[i]) - toDelete.add (inputs.getUnchecked(i)); - - inputs.clear(); - } - - for (int i = toDelete.size(); --i >= 0;) - toDelete.getUnchecked(i)->releaseResources(); -} - -void MixerAudioSource::prepareToPlay (int samplesPerBlockExpected, double sampleRate) -{ - tempBuffer.setSize (2, samplesPerBlockExpected); - - const ScopedLock sl (lock); - - currentSampleRate = sampleRate; - bufferSizeExpected = samplesPerBlockExpected; - - for (int i = inputs.size(); --i >= 0;) - inputs.getUnchecked(i)->prepareToPlay (samplesPerBlockExpected, sampleRate); -} - -void MixerAudioSource::releaseResources() -{ - const ScopedLock sl (lock); - - for (int i = inputs.size(); --i >= 0;) - inputs.getUnchecked(i)->releaseResources(); - - tempBuffer.setSize (2, 0); - - currentSampleRate = 0; - bufferSizeExpected = 0; -} - -void MixerAudioSource::getNextAudioBlock (const AudioSourceChannelInfo& info) -{ - const ScopedLock sl (lock); - - if (inputs.size() > 0) - { - inputs.getUnchecked(0)->getNextAudioBlock (info); - - if (inputs.size() > 1) - { - tempBuffer.setSize (jmax (1, info.buffer->getNumChannels()), - info.buffer->getNumSamples()); - - AudioSourceChannelInfo info2 (&tempBuffer, 0, info.numSamples); - - for (int i = 1; i < inputs.size(); ++i) - { - inputs.getUnchecked(i)->getNextAudioBlock (info2); - - for (int chan = 0; chan < info.buffer->getNumChannels(); ++chan) - info.buffer->addFrom (chan, info.startSample, tempBuffer, chan, 0, info.numSamples); - } - } - } - else - { - info.clearActiveBufferRegion(); - } -} - -} // namespace juce +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2017 - ROLI Ltd. + + JUCE is an open source library subject to commercial or open-source + licensing. + + The code included in this file is provided under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license. Permission + To use, copy, modify, and/or distribute this software for any purpose with or + without fee is hereby granted provided that the above copyright notice and + this permission notice appear in all copies. + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +namespace juce +{ + +MixerAudioSource::MixerAudioSource() + : currentSampleRate (0.0), bufferSizeExpected (0) +{ +} + +MixerAudioSource::~MixerAudioSource() +{ + removeAllInputs(); +} + +//============================================================================== +void MixerAudioSource::addInputSource (AudioSource* input, const bool deleteWhenRemoved) +{ + if (input != nullptr && ! inputs.contains (input)) + { + double localRate; + int localBufferSize; + + { + const ScopedLock sl (lock); + localRate = currentSampleRate; + localBufferSize = bufferSizeExpected; + } + + if (localRate > 0.0) + input->prepareToPlay (localBufferSize, localRate); + + const ScopedLock sl (lock); + + inputsToDelete.setBit (inputs.size(), deleteWhenRemoved); + inputs.add (input); + } +} + +void MixerAudioSource::removeInputSource (AudioSource* const input) +{ + if (input != nullptr) + { + std::unique_ptr toDelete; + + { + const ScopedLock sl (lock); + const int index = inputs.indexOf (input); + + if (index < 0) + return; + + if (inputsToDelete [index]) + toDelete.reset (input); + + inputsToDelete.shiftBits (-1, index); + inputs.remove (index); + } + + input->releaseResources(); + } +} + +void MixerAudioSource::removeAllInputs() +{ + OwnedArray toDelete; + + { + const ScopedLock sl (lock); + + for (int i = inputs.size(); --i >= 0;) + if (inputsToDelete[i]) + toDelete.add (inputs.getUnchecked(i)); + + inputs.clear(); + } + + for (int i = toDelete.size(); --i >= 0;) + toDelete.getUnchecked(i)->releaseResources(); +} + +void MixerAudioSource::prepareToPlay (int samplesPerBlockExpected, double sampleRate) +{ + tempBuffer.setSize (2, samplesPerBlockExpected); + + const ScopedLock sl (lock); + + currentSampleRate = sampleRate; + bufferSizeExpected = samplesPerBlockExpected; + + for (int i = inputs.size(); --i >= 0;) + inputs.getUnchecked(i)->prepareToPlay (samplesPerBlockExpected, sampleRate); +} + +void MixerAudioSource::releaseResources() +{ + const ScopedLock sl (lock); + + for (int i = inputs.size(); --i >= 0;) + inputs.getUnchecked(i)->releaseResources(); + + tempBuffer.setSize (2, 0); + + currentSampleRate = 0; + bufferSizeExpected = 0; +} + +void MixerAudioSource::getNextAudioBlock (const AudioSourceChannelInfo& info) +{ + const ScopedLock sl (lock); + + if (inputs.size() > 0) + { + inputs.getUnchecked(0)->getNextAudioBlock (info); + + if (inputs.size() > 1) + { + tempBuffer.setSize (jmax (1, info.buffer->getNumChannels()), + info.buffer->getNumSamples()); + + AudioSourceChannelInfo info2 (&tempBuffer, 0, info.numSamples); + + for (int i = 1; i < inputs.size(); ++i) + { + inputs.getUnchecked(i)->getNextAudioBlock (info2); + + for (int chan = 0; chan < info.buffer->getNumChannels(); ++chan) + info.buffer->addFrom (chan, info.startSample, tempBuffer, chan, 0, info.numSamples); + } + } + } + else + { + info.clearActiveBufferRegion(); + } +} + +} // namespace juce diff --git a/JuceLibraryCode/modules/juce_audio_basics/sources/juce_MixerAudioSource.h b/JuceLibraryCode/modules/juce_audio_basics/sources/juce_MixerAudioSource.h index d3766f9..635a519 100644 --- a/JuceLibraryCode/modules/juce_audio_basics/sources/juce_MixerAudioSource.h +++ b/JuceLibraryCode/modules/juce_audio_basics/sources/juce_MixerAudioSource.h @@ -1,99 +1,99 @@ -/* - ============================================================================== - - This file is part of the JUCE library. - Copyright (c) 2017 - ROLI Ltd. - - JUCE is an open source library subject to commercial or open-source - licensing. - - The code included in this file is provided under the terms of the ISC license - http://www.isc.org/downloads/software-support-policy/isc-license. Permission - To use, copy, modify, and/or distribute this software for any purpose with or - without fee is hereby granted provided that the above copyright notice and - this permission notice appear in all copies. - - JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER - EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE - DISCLAIMED. - - ============================================================================== -*/ - -namespace juce -{ - -//============================================================================== -/** - An AudioSource that mixes together the output of a set of other AudioSources. - - Input sources can be added and removed while the mixer is running as long as their - prepareToPlay() and releaseResources() methods are called before and after adding - them to the mixer. - - @tags{Audio} -*/ -class JUCE_API MixerAudioSource : public AudioSource -{ -public: - //============================================================================== - /** Creates a MixerAudioSource. */ - MixerAudioSource(); - - /** Destructor. */ - ~MixerAudioSource() override; - - //============================================================================== - /** Adds an input source to the mixer. - - If the mixer is running you'll need to make sure that the input source - is ready to play by calling its prepareToPlay() method before adding it. - If the mixer is stopped, then its input sources will be automatically - prepared when the mixer's prepareToPlay() method is called. - - @param newInput the source to add to the mixer - @param deleteWhenRemoved if true, then this source will be deleted when - no longer needed by the mixer. - */ - void addInputSource (AudioSource* newInput, bool deleteWhenRemoved); - - /** Removes an input source. - If the source was added by calling addInputSource() with the deleteWhenRemoved - flag set, it will be deleted by this method. - */ - void removeInputSource (AudioSource* input); - - /** Removes all the input sources. - Any sources which were added by calling addInputSource() with the deleteWhenRemoved - flag set will be deleted by this method. - */ - void removeAllInputs(); - - //============================================================================== - /** Implementation of the AudioSource method. - This will call prepareToPlay() on all its input sources. - */ - void prepareToPlay (int samplesPerBlockExpected, double sampleRate) override; - - /** Implementation of the AudioSource method. - This will call releaseResources() on all its input sources. - */ - void releaseResources() override; - - /** Implementation of the AudioSource method. */ - void getNextAudioBlock (const AudioSourceChannelInfo&) override; - - -private: - //============================================================================== - Array inputs; - BigInteger inputsToDelete; - CriticalSection lock; - AudioBuffer tempBuffer; - double currentSampleRate; - int bufferSizeExpected; - - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MixerAudioSource) -}; - -} // namespace juce +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2017 - ROLI Ltd. + + JUCE is an open source library subject to commercial or open-source + licensing. + + The code included in this file is provided under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license. Permission + To use, copy, modify, and/or distribute this software for any purpose with or + without fee is hereby granted provided that the above copyright notice and + this permission notice appear in all copies. + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +namespace juce +{ + +//============================================================================== +/** + An AudioSource that mixes together the output of a set of other AudioSources. + + Input sources can be added and removed while the mixer is running as long as their + prepareToPlay() and releaseResources() methods are called before and after adding + them to the mixer. + + @tags{Audio} +*/ +class JUCE_API MixerAudioSource : public AudioSource +{ +public: + //============================================================================== + /** Creates a MixerAudioSource. */ + MixerAudioSource(); + + /** Destructor. */ + ~MixerAudioSource() override; + + //============================================================================== + /** Adds an input source to the mixer. + + If the mixer is running you'll need to make sure that the input source + is ready to play by calling its prepareToPlay() method before adding it. + If the mixer is stopped, then its input sources will be automatically + prepared when the mixer's prepareToPlay() method is called. + + @param newInput the source to add to the mixer + @param deleteWhenRemoved if true, then this source will be deleted when + no longer needed by the mixer. + */ + void addInputSource (AudioSource* newInput, bool deleteWhenRemoved); + + /** Removes an input source. + If the source was added by calling addInputSource() with the deleteWhenRemoved + flag set, it will be deleted by this method. + */ + void removeInputSource (AudioSource* input); + + /** Removes all the input sources. + Any sources which were added by calling addInputSource() with the deleteWhenRemoved + flag set will be deleted by this method. + */ + void removeAllInputs(); + + //============================================================================== + /** Implementation of the AudioSource method. + This will call prepareToPlay() on all its input sources. + */ + void prepareToPlay (int samplesPerBlockExpected, double sampleRate) override; + + /** Implementation of the AudioSource method. + This will call releaseResources() on all its input sources. + */ + void releaseResources() override; + + /** Implementation of the AudioSource method. */ + void getNextAudioBlock (const AudioSourceChannelInfo&) override; + + +private: + //============================================================================== + Array inputs; + BigInteger inputsToDelete; + CriticalSection lock; + AudioBuffer tempBuffer; + double currentSampleRate; + int bufferSizeExpected; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MixerAudioSource) +}; + +} // namespace juce diff --git a/JuceLibraryCode/modules/juce_audio_basics/sources/juce_PositionableAudioSource.h b/JuceLibraryCode/modules/juce_audio_basics/sources/juce_PositionableAudioSource.h index df7ea36..d9667b9 100644 --- a/JuceLibraryCode/modules/juce_audio_basics/sources/juce_PositionableAudioSource.h +++ b/JuceLibraryCode/modules/juce_audio_basics/sources/juce_PositionableAudioSource.h @@ -1,76 +1,76 @@ -/* - ============================================================================== - - This file is part of the JUCE library. - Copyright (c) 2017 - ROLI Ltd. - - JUCE is an open source library subject to commercial or open-source - licensing. - - The code included in this file is provided under the terms of the ISC license - http://www.isc.org/downloads/software-support-policy/isc-license. Permission - To use, copy, modify, and/or distribute this software for any purpose with or - without fee is hereby granted provided that the above copyright notice and - this permission notice appear in all copies. - - JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER - EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE - DISCLAIMED. - - ============================================================================== -*/ - -namespace juce -{ - -//============================================================================== -/** - A type of AudioSource which can be repositioned. - - The basic AudioSource just streams continuously with no idea of a current - time or length, so the PositionableAudioSource is used for a finite stream - that has a current read position. - - @see AudioSource, AudioTransportSource - - @tags{Audio} -*/ -class JUCE_API PositionableAudioSource : public AudioSource -{ -protected: - //============================================================================== - /** Creates the PositionableAudioSource. */ - PositionableAudioSource() = default; - -public: - /** Destructor */ - ~PositionableAudioSource() override = default; - - //============================================================================== - /** Tells the stream to move to a new position. - - Calling this indicates that the next call to AudioSource::getNextAudioBlock() - should return samples from this position. - - Note that this may be called on a different thread to getNextAudioBlock(), - so the subclass should make sure it's synchronised. - */ - virtual void setNextReadPosition (int64 newPosition) = 0; - - /** Returns the position from which the next block will be returned. - - @see setNextReadPosition - */ - virtual int64 getNextReadPosition() const = 0; - - /** Returns the total length of the stream (in samples). */ - virtual int64 getTotalLength() const = 0; - - /** Returns true if this source is actually playing in a loop. */ - virtual bool isLooping() const = 0; - - /** Tells the source whether you'd like it to play in a loop. */ - virtual void setLooping (bool shouldLoop) { ignoreUnused (shouldLoop); } -}; - -} // namespace juce +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2017 - ROLI Ltd. + + JUCE is an open source library subject to commercial or open-source + licensing. + + The code included in this file is provided under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license. Permission + To use, copy, modify, and/or distribute this software for any purpose with or + without fee is hereby granted provided that the above copyright notice and + this permission notice appear in all copies. + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +namespace juce +{ + +//============================================================================== +/** + A type of AudioSource which can be repositioned. + + The basic AudioSource just streams continuously with no idea of a current + time or length, so the PositionableAudioSource is used for a finite stream + that has a current read position. + + @see AudioSource, AudioTransportSource + + @tags{Audio} +*/ +class JUCE_API PositionableAudioSource : public AudioSource +{ +protected: + //============================================================================== + /** Creates the PositionableAudioSource. */ + PositionableAudioSource() = default; + +public: + /** Destructor */ + ~PositionableAudioSource() override = default; + + //============================================================================== + /** Tells the stream to move to a new position. + + Calling this indicates that the next call to AudioSource::getNextAudioBlock() + should return samples from this position. + + Note that this may be called on a different thread to getNextAudioBlock(), + so the subclass should make sure it's synchronised. + */ + virtual void setNextReadPosition (int64 newPosition) = 0; + + /** Returns the position from which the next block will be returned. + + @see setNextReadPosition + */ + virtual int64 getNextReadPosition() const = 0; + + /** Returns the total length of the stream (in samples). */ + virtual int64 getTotalLength() const = 0; + + /** Returns true if this source is actually playing in a loop. */ + virtual bool isLooping() const = 0; + + /** Tells the source whether you'd like it to play in a loop. */ + virtual void setLooping (bool shouldLoop) { ignoreUnused (shouldLoop); } +}; + +} // namespace juce diff --git a/JuceLibraryCode/modules/juce_audio_basics/sources/juce_ResamplingAudioSource.cpp b/JuceLibraryCode/modules/juce_audio_basics/sources/juce_ResamplingAudioSource.cpp index d123e4c..1b59d95 100644 --- a/JuceLibraryCode/modules/juce_audio_basics/sources/juce_ResamplingAudioSource.cpp +++ b/JuceLibraryCode/modules/juce_audio_basics/sources/juce_ResamplingAudioSource.cpp @@ -1,265 +1,265 @@ -/* - ============================================================================== - - This file is part of the JUCE library. - Copyright (c) 2017 - ROLI Ltd. - - JUCE is an open source library subject to commercial or open-source - licensing. - - The code included in this file is provided under the terms of the ISC license - http://www.isc.org/downloads/software-support-policy/isc-license. Permission - To use, copy, modify, and/or distribute this software for any purpose with or - without fee is hereby granted provided that the above copyright notice and - this permission notice appear in all copies. - - JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER - EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE - DISCLAIMED. - - ============================================================================== -*/ - -namespace juce -{ - -ResamplingAudioSource::ResamplingAudioSource (AudioSource* const inputSource, - const bool deleteInputWhenDeleted, - const int channels) - : input (inputSource, deleteInputWhenDeleted), - numChannels (channels) -{ - jassert (input != nullptr); - zeromem (coefficients, sizeof (coefficients)); -} - -ResamplingAudioSource::~ResamplingAudioSource() {} - -void ResamplingAudioSource::setResamplingRatio (const double samplesInPerOutputSample) -{ - jassert (samplesInPerOutputSample > 0); - - const SpinLock::ScopedLockType sl (ratioLock); - ratio = jmax (0.0, samplesInPerOutputSample); -} - -void ResamplingAudioSource::prepareToPlay (int samplesPerBlockExpected, double sampleRate) -{ - const SpinLock::ScopedLockType sl (ratioLock); - - auto scaledBlockSize = roundToInt (samplesPerBlockExpected * ratio); - input->prepareToPlay (scaledBlockSize, sampleRate * ratio); - - buffer.setSize (numChannels, scaledBlockSize + 32); - - filterStates.calloc (numChannels); - srcBuffers.calloc (numChannels); - destBuffers.calloc (numChannels); - createLowPass (ratio); - - flushBuffers(); -} - -void ResamplingAudioSource::flushBuffers() -{ - const ScopedLock sl (callbackLock); - - buffer.clear(); - bufferPos = 0; - sampsInBuffer = 0; - subSampleOffset = 0.0; - resetFilters(); -} - -void ResamplingAudioSource::releaseResources() -{ - input->releaseResources(); - buffer.setSize (numChannels, 0); -} - -void ResamplingAudioSource::getNextAudioBlock (const AudioSourceChannelInfo& info) -{ - const ScopedLock sl (callbackLock); - - double localRatio; - - { - const SpinLock::ScopedLockType ratioSl (ratioLock); - localRatio = ratio; - } - - if (lastRatio != localRatio) - { - createLowPass (localRatio); - lastRatio = localRatio; - } - - const int sampsNeeded = roundToInt (info.numSamples * localRatio) + 3; - - int bufferSize = buffer.getNumSamples(); - - if (bufferSize < sampsNeeded + 8) - { - bufferPos %= bufferSize; - bufferSize = sampsNeeded + 32; - buffer.setSize (buffer.getNumChannels(), bufferSize, true, true); - } - - bufferPos %= bufferSize; - - int endOfBufferPos = bufferPos + sampsInBuffer; - const int channelsToProcess = jmin (numChannels, info.buffer->getNumChannels()); - - while (sampsNeeded > sampsInBuffer) - { - endOfBufferPos %= bufferSize; - - int numToDo = jmin (sampsNeeded - sampsInBuffer, - bufferSize - endOfBufferPos); - - AudioSourceChannelInfo readInfo (&buffer, endOfBufferPos, numToDo); - input->getNextAudioBlock (readInfo); - - if (localRatio > 1.0001) - { - // for down-sampling, pre-apply the filter.. - - for (int i = channelsToProcess; --i >= 0;) - applyFilter (buffer.getWritePointer (i, endOfBufferPos), numToDo, filterStates[i]); - } - - sampsInBuffer += numToDo; - endOfBufferPos += numToDo; - } - - for (int channel = 0; channel < channelsToProcess; ++channel) - { - destBuffers[channel] = info.buffer->getWritePointer (channel, info.startSample); - srcBuffers[channel] = buffer.getReadPointer (channel); - } - - int nextPos = (bufferPos + 1) % bufferSize; - - for (int m = info.numSamples; --m >= 0;) - { - jassert (sampsInBuffer > 0 && nextPos != endOfBufferPos); - - const float alpha = (float) subSampleOffset; - - for (int channel = 0; channel < channelsToProcess; ++channel) - *destBuffers[channel]++ = srcBuffers[channel][bufferPos] - + alpha * (srcBuffers[channel][nextPos] - srcBuffers[channel][bufferPos]); - - subSampleOffset += localRatio; - - while (subSampleOffset >= 1.0) - { - if (++bufferPos >= bufferSize) - bufferPos = 0; - - --sampsInBuffer; - - nextPos = (bufferPos + 1) % bufferSize; - subSampleOffset -= 1.0; - } - } - - if (localRatio < 0.9999) - { - // for up-sampling, apply the filter after transposing.. - for (int i = channelsToProcess; --i >= 0;) - applyFilter (info.buffer->getWritePointer (i, info.startSample), info.numSamples, filterStates[i]); - } - else if (localRatio <= 1.0001 && info.numSamples > 0) - { - // if the filter's not currently being applied, keep it stoked with the last couple of samples to avoid discontinuities - for (int i = channelsToProcess; --i >= 0;) - { - const float* const endOfBuffer = info.buffer->getReadPointer (i, info.startSample + info.numSamples - 1); - FilterState& fs = filterStates[i]; - - if (info.numSamples > 1) - { - fs.y2 = fs.x2 = *(endOfBuffer - 1); - } - else - { - fs.y2 = fs.y1; - fs.x2 = fs.x1; - } - - fs.y1 = fs.x1 = *endOfBuffer; - } - } - - jassert (sampsInBuffer >= 0); -} - -void ResamplingAudioSource::createLowPass (const double frequencyRatio) -{ - const double proportionalRate = (frequencyRatio > 1.0) ? 0.5 / frequencyRatio - : 0.5 * frequencyRatio; - - const double n = 1.0 / std::tan (MathConstants::pi * jmax (0.001, proportionalRate)); - const double nSquared = n * n; - const double c1 = 1.0 / (1.0 + MathConstants::sqrt2 * n + nSquared); - - setFilterCoefficients (c1, - c1 * 2.0f, - c1, - 1.0, - c1 * 2.0 * (1.0 - nSquared), - c1 * (1.0 - MathConstants::sqrt2 * n + nSquared)); -} - -void ResamplingAudioSource::setFilterCoefficients (double c1, double c2, double c3, double c4, double c5, double c6) -{ - const double a = 1.0 / c4; - - c1 *= a; - c2 *= a; - c3 *= a; - c5 *= a; - c6 *= a; - - coefficients[0] = c1; - coefficients[1] = c2; - coefficients[2] = c3; - coefficients[3] = c4; - coefficients[4] = c5; - coefficients[5] = c6; -} - -void ResamplingAudioSource::resetFilters() -{ - if (filterStates != nullptr) - filterStates.clear ((size_t) numChannels); -} - -void ResamplingAudioSource::applyFilter (float* samples, int num, FilterState& fs) -{ - while (--num >= 0) - { - const double in = *samples; - - double out = coefficients[0] * in - + coefficients[1] * fs.x1 - + coefficients[2] * fs.x2 - - coefficients[4] * fs.y1 - - coefficients[5] * fs.y2; - - #if JUCE_INTEL - if (! (out < -1.0e-8 || out > 1.0e-8)) - out = 0; - #endif - - fs.x2 = fs.x1; - fs.x1 = in; - fs.y2 = fs.y1; - fs.y1 = out; - - *samples++ = (float) out; - } -} - -} // namespace juce +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2017 - ROLI Ltd. + + JUCE is an open source library subject to commercial or open-source + licensing. + + The code included in this file is provided under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license. Permission + To use, copy, modify, and/or distribute this software for any purpose with or + without fee is hereby granted provided that the above copyright notice and + this permission notice appear in all copies. + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +namespace juce +{ + +ResamplingAudioSource::ResamplingAudioSource (AudioSource* const inputSource, + const bool deleteInputWhenDeleted, + const int channels) + : input (inputSource, deleteInputWhenDeleted), + numChannels (channels) +{ + jassert (input != nullptr); + zeromem (coefficients, sizeof (coefficients)); +} + +ResamplingAudioSource::~ResamplingAudioSource() {} + +void ResamplingAudioSource::setResamplingRatio (const double samplesInPerOutputSample) +{ + jassert (samplesInPerOutputSample > 0); + + const SpinLock::ScopedLockType sl (ratioLock); + ratio = jmax (0.0, samplesInPerOutputSample); +} + +void ResamplingAudioSource::prepareToPlay (int samplesPerBlockExpected, double sampleRate) +{ + const SpinLock::ScopedLockType sl (ratioLock); + + auto scaledBlockSize = roundToInt (samplesPerBlockExpected * ratio); + input->prepareToPlay (scaledBlockSize, sampleRate * ratio); + + buffer.setSize (numChannels, scaledBlockSize + 32); + + filterStates.calloc (numChannels); + srcBuffers.calloc (numChannels); + destBuffers.calloc (numChannels); + createLowPass (ratio); + + flushBuffers(); +} + +void ResamplingAudioSource::flushBuffers() +{ + const ScopedLock sl (callbackLock); + + buffer.clear(); + bufferPos = 0; + sampsInBuffer = 0; + subSampleOffset = 0.0; + resetFilters(); +} + +void ResamplingAudioSource::releaseResources() +{ + input->releaseResources(); + buffer.setSize (numChannels, 0); +} + +void ResamplingAudioSource::getNextAudioBlock (const AudioSourceChannelInfo& info) +{ + const ScopedLock sl (callbackLock); + + double localRatio; + + { + const SpinLock::ScopedLockType ratioSl (ratioLock); + localRatio = ratio; + } + + if (lastRatio != localRatio) + { + createLowPass (localRatio); + lastRatio = localRatio; + } + + const int sampsNeeded = roundToInt (info.numSamples * localRatio) + 3; + + int bufferSize = buffer.getNumSamples(); + + if (bufferSize < sampsNeeded + 8) + { + bufferPos %= bufferSize; + bufferSize = sampsNeeded + 32; + buffer.setSize (buffer.getNumChannels(), bufferSize, true, true); + } + + bufferPos %= bufferSize; + + int endOfBufferPos = bufferPos + sampsInBuffer; + const int channelsToProcess = jmin (numChannels, info.buffer->getNumChannels()); + + while (sampsNeeded > sampsInBuffer) + { + endOfBufferPos %= bufferSize; + + int numToDo = jmin (sampsNeeded - sampsInBuffer, + bufferSize - endOfBufferPos); + + AudioSourceChannelInfo readInfo (&buffer, endOfBufferPos, numToDo); + input->getNextAudioBlock (readInfo); + + if (localRatio > 1.0001) + { + // for down-sampling, pre-apply the filter.. + + for (int i = channelsToProcess; --i >= 0;) + applyFilter (buffer.getWritePointer (i, endOfBufferPos), numToDo, filterStates[i]); + } + + sampsInBuffer += numToDo; + endOfBufferPos += numToDo; + } + + for (int channel = 0; channel < channelsToProcess; ++channel) + { + destBuffers[channel] = info.buffer->getWritePointer (channel, info.startSample); + srcBuffers[channel] = buffer.getReadPointer (channel); + } + + int nextPos = (bufferPos + 1) % bufferSize; + + for (int m = info.numSamples; --m >= 0;) + { + jassert (sampsInBuffer > 0 && nextPos != endOfBufferPos); + + const float alpha = (float) subSampleOffset; + + for (int channel = 0; channel < channelsToProcess; ++channel) + *destBuffers[channel]++ = srcBuffers[channel][bufferPos] + + alpha * (srcBuffers[channel][nextPos] - srcBuffers[channel][bufferPos]); + + subSampleOffset += localRatio; + + while (subSampleOffset >= 1.0) + { + if (++bufferPos >= bufferSize) + bufferPos = 0; + + --sampsInBuffer; + + nextPos = (bufferPos + 1) % bufferSize; + subSampleOffset -= 1.0; + } + } + + if (localRatio < 0.9999) + { + // for up-sampling, apply the filter after transposing.. + for (int i = channelsToProcess; --i >= 0;) + applyFilter (info.buffer->getWritePointer (i, info.startSample), info.numSamples, filterStates[i]); + } + else if (localRatio <= 1.0001 && info.numSamples > 0) + { + // if the filter's not currently being applied, keep it stoked with the last couple of samples to avoid discontinuities + for (int i = channelsToProcess; --i >= 0;) + { + const float* const endOfBuffer = info.buffer->getReadPointer (i, info.startSample + info.numSamples - 1); + FilterState& fs = filterStates[i]; + + if (info.numSamples > 1) + { + fs.y2 = fs.x2 = *(endOfBuffer - 1); + } + else + { + fs.y2 = fs.y1; + fs.x2 = fs.x1; + } + + fs.y1 = fs.x1 = *endOfBuffer; + } + } + + jassert (sampsInBuffer >= 0); +} + +void ResamplingAudioSource::createLowPass (const double frequencyRatio) +{ + const double proportionalRate = (frequencyRatio > 1.0) ? 0.5 / frequencyRatio + : 0.5 * frequencyRatio; + + const double n = 1.0 / std::tan (MathConstants::pi * jmax (0.001, proportionalRate)); + const double nSquared = n * n; + const double c1 = 1.0 / (1.0 + MathConstants::sqrt2 * n + nSquared); + + setFilterCoefficients (c1, + c1 * 2.0f, + c1, + 1.0, + c1 * 2.0 * (1.0 - nSquared), + c1 * (1.0 - MathConstants::sqrt2 * n + nSquared)); +} + +void ResamplingAudioSource::setFilterCoefficients (double c1, double c2, double c3, double c4, double c5, double c6) +{ + const double a = 1.0 / c4; + + c1 *= a; + c2 *= a; + c3 *= a; + c5 *= a; + c6 *= a; + + coefficients[0] = c1; + coefficients[1] = c2; + coefficients[2] = c3; + coefficients[3] = c4; + coefficients[4] = c5; + coefficients[5] = c6; +} + +void ResamplingAudioSource::resetFilters() +{ + if (filterStates != nullptr) + filterStates.clear ((size_t) numChannels); +} + +void ResamplingAudioSource::applyFilter (float* samples, int num, FilterState& fs) +{ + while (--num >= 0) + { + const double in = *samples; + + double out = coefficients[0] * in + + coefficients[1] * fs.x1 + + coefficients[2] * fs.x2 + - coefficients[4] * fs.y1 + - coefficients[5] * fs.y2; + + #if JUCE_INTEL + if (! (out < -1.0e-8 || out > 1.0e-8)) + out = 0; + #endif + + fs.x2 = fs.x1; + fs.x1 = in; + fs.y2 = fs.y1; + fs.y1 = out; + + *samples++ = (float) out; + } +} + +} // namespace juce diff --git a/JuceLibraryCode/modules/juce_audio_basics/sources/juce_ResamplingAudioSource.h b/JuceLibraryCode/modules/juce_audio_basics/sources/juce_ResamplingAudioSource.h index 49b4974..56b3d26 100644 --- a/JuceLibraryCode/modules/juce_audio_basics/sources/juce_ResamplingAudioSource.h +++ b/JuceLibraryCode/modules/juce_audio_basics/sources/juce_ResamplingAudioSource.h @@ -1,106 +1,106 @@ -/* - ============================================================================== - - This file is part of the JUCE library. - Copyright (c) 2017 - ROLI Ltd. - - JUCE is an open source library subject to commercial or open-source - licensing. - - The code included in this file is provided under the terms of the ISC license - http://www.isc.org/downloads/software-support-policy/isc-license. Permission - To use, copy, modify, and/or distribute this software for any purpose with or - without fee is hereby granted provided that the above copyright notice and - this permission notice appear in all copies. - - JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER - EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE - DISCLAIMED. - - ============================================================================== -*/ - -namespace juce -{ - -//============================================================================== -/** - A type of AudioSource that takes an input source and changes its sample rate. - - @see AudioSource, LagrangeInterpolator, CatmullRomInterpolator - - @tags{Audio} -*/ -class JUCE_API ResamplingAudioSource : public AudioSource -{ -public: - //============================================================================== - /** Creates a ResamplingAudioSource for a given input source. - - @param inputSource the input source to read from - @param deleteInputWhenDeleted if true, the input source will be deleted when - this object is deleted - @param numChannels the number of channels to process - */ - ResamplingAudioSource (AudioSource* inputSource, - bool deleteInputWhenDeleted, - int numChannels = 2); - - /** Destructor. */ - ~ResamplingAudioSource() override; - - /** Changes the resampling ratio. - - (This value can be changed at any time, even while the source is running). - - @param samplesInPerOutputSample if set to 1.0, the input is passed through; higher - values will speed it up; lower values will slow it - down. The ratio must be greater than 0 - */ - void setResamplingRatio (double samplesInPerOutputSample); - - /** Returns the current resampling ratio. - - This is the value that was set by setResamplingRatio(). - */ - double getResamplingRatio() const noexcept { return ratio; } - - /** Clears any buffers and filters that the resampler is using. */ - void flushBuffers(); - - //============================================================================== - void prepareToPlay (int samplesPerBlockExpected, double sampleRate) override; - void releaseResources() override; - void getNextAudioBlock (const AudioSourceChannelInfo&) override; - -private: - //============================================================================== - OptionalScopedPointer input; - double ratio = 1.0, lastRatio = 1.0; - AudioBuffer buffer; - int bufferPos = 0, sampsInBuffer = 0; - double subSampleOffset = 0.0; - double coefficients[6]; - SpinLock ratioLock; - CriticalSection callbackLock; - const int numChannels; - HeapBlock destBuffers; - HeapBlock srcBuffers; - - void setFilterCoefficients (double c1, double c2, double c3, double c4, double c5, double c6); - void createLowPass (double proportionalRate); - - struct FilterState - { - double x1, x2, y1, y2; - }; - - HeapBlock filterStates; - void resetFilters(); - - void applyFilter (float* samples, int num, FilterState& fs); - - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ResamplingAudioSource) -}; - -} // namespace juce +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2017 - ROLI Ltd. + + JUCE is an open source library subject to commercial or open-source + licensing. + + The code included in this file is provided under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license. Permission + To use, copy, modify, and/or distribute this software for any purpose with or + without fee is hereby granted provided that the above copyright notice and + this permission notice appear in all copies. + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +namespace juce +{ + +//============================================================================== +/** + A type of AudioSource that takes an input source and changes its sample rate. + + @see AudioSource, LagrangeInterpolator, CatmullRomInterpolator + + @tags{Audio} +*/ +class JUCE_API ResamplingAudioSource : public AudioSource +{ +public: + //============================================================================== + /** Creates a ResamplingAudioSource for a given input source. + + @param inputSource the input source to read from + @param deleteInputWhenDeleted if true, the input source will be deleted when + this object is deleted + @param numChannels the number of channels to process + */ + ResamplingAudioSource (AudioSource* inputSource, + bool deleteInputWhenDeleted, + int numChannels = 2); + + /** Destructor. */ + ~ResamplingAudioSource() override; + + /** Changes the resampling ratio. + + (This value can be changed at any time, even while the source is running). + + @param samplesInPerOutputSample if set to 1.0, the input is passed through; higher + values will speed it up; lower values will slow it + down. The ratio must be greater than 0 + */ + void setResamplingRatio (double samplesInPerOutputSample); + + /** Returns the current resampling ratio. + + This is the value that was set by setResamplingRatio(). + */ + double getResamplingRatio() const noexcept { return ratio; } + + /** Clears any buffers and filters that the resampler is using. */ + void flushBuffers(); + + //============================================================================== + void prepareToPlay (int samplesPerBlockExpected, double sampleRate) override; + void releaseResources() override; + void getNextAudioBlock (const AudioSourceChannelInfo&) override; + +private: + //============================================================================== + OptionalScopedPointer input; + double ratio = 1.0, lastRatio = 1.0; + AudioBuffer buffer; + int bufferPos = 0, sampsInBuffer = 0; + double subSampleOffset = 0.0; + double coefficients[6]; + SpinLock ratioLock; + CriticalSection callbackLock; + const int numChannels; + HeapBlock destBuffers; + HeapBlock srcBuffers; + + void setFilterCoefficients (double c1, double c2, double c3, double c4, double c5, double c6); + void createLowPass (double proportionalRate); + + struct FilterState + { + double x1, x2, y1, y2; + }; + + HeapBlock filterStates; + void resetFilters(); + + void applyFilter (float* samples, int num, FilterState& fs); + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ResamplingAudioSource) +}; + +} // namespace juce diff --git a/JuceLibraryCode/modules/juce_audio_basics/sources/juce_ReverbAudioSource.cpp b/JuceLibraryCode/modules/juce_audio_basics/sources/juce_ReverbAudioSource.cpp index dd4e7ab..51c1216 100644 --- a/JuceLibraryCode/modules/juce_audio_basics/sources/juce_ReverbAudioSource.cpp +++ b/JuceLibraryCode/modules/juce_audio_basics/sources/juce_ReverbAudioSource.cpp @@ -1,83 +1,83 @@ -/* - ============================================================================== - - This file is part of the JUCE library. - Copyright (c) 2017 - ROLI Ltd. - - JUCE is an open source library subject to commercial or open-source - licensing. - - The code included in this file is provided under the terms of the ISC license - http://www.isc.org/downloads/software-support-policy/isc-license. Permission - To use, copy, modify, and/or distribute this software for any purpose with or - without fee is hereby granted provided that the above copyright notice and - this permission notice appear in all copies. - - JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER - EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE - DISCLAIMED. - - ============================================================================== -*/ - -namespace juce -{ - -ReverbAudioSource::ReverbAudioSource (AudioSource* const inputSource, const bool deleteInputWhenDeleted) - : input (inputSource, deleteInputWhenDeleted), - bypass (false) -{ - jassert (inputSource != nullptr); -} - -ReverbAudioSource::~ReverbAudioSource() {} - -void ReverbAudioSource::prepareToPlay (int samplesPerBlockExpected, double sampleRate) -{ - const ScopedLock sl (lock); - input->prepareToPlay (samplesPerBlockExpected, sampleRate); - reverb.setSampleRate (sampleRate); -} - -void ReverbAudioSource::releaseResources() {} - -void ReverbAudioSource::getNextAudioBlock (const AudioSourceChannelInfo& bufferToFill) -{ - const ScopedLock sl (lock); - - input->getNextAudioBlock (bufferToFill); - - if (! bypass) - { - float* const firstChannel = bufferToFill.buffer->getWritePointer (0, bufferToFill.startSample); - - if (bufferToFill.buffer->getNumChannels() > 1) - { - reverb.processStereo (firstChannel, - bufferToFill.buffer->getWritePointer (1, bufferToFill.startSample), - bufferToFill.numSamples); - } - else - { - reverb.processMono (firstChannel, bufferToFill.numSamples); - } - } -} - -void ReverbAudioSource::setParameters (const Reverb::Parameters& newParams) -{ - const ScopedLock sl (lock); - reverb.setParameters (newParams); -} - -void ReverbAudioSource::setBypassed (bool b) noexcept -{ - if (bypass != b) - { - const ScopedLock sl (lock); - bypass = b; - reverb.reset(); - } -} - -} // namespace juce +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2017 - ROLI Ltd. + + JUCE is an open source library subject to commercial or open-source + licensing. + + The code included in this file is provided under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license. Permission + To use, copy, modify, and/or distribute this software for any purpose with or + without fee is hereby granted provided that the above copyright notice and + this permission notice appear in all copies. + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +namespace juce +{ + +ReverbAudioSource::ReverbAudioSource (AudioSource* const inputSource, const bool deleteInputWhenDeleted) + : input (inputSource, deleteInputWhenDeleted), + bypass (false) +{ + jassert (inputSource != nullptr); +} + +ReverbAudioSource::~ReverbAudioSource() {} + +void ReverbAudioSource::prepareToPlay (int samplesPerBlockExpected, double sampleRate) +{ + const ScopedLock sl (lock); + input->prepareToPlay (samplesPerBlockExpected, sampleRate); + reverb.setSampleRate (sampleRate); +} + +void ReverbAudioSource::releaseResources() {} + +void ReverbAudioSource::getNextAudioBlock (const AudioSourceChannelInfo& bufferToFill) +{ + const ScopedLock sl (lock); + + input->getNextAudioBlock (bufferToFill); + + if (! bypass) + { + float* const firstChannel = bufferToFill.buffer->getWritePointer (0, bufferToFill.startSample); + + if (bufferToFill.buffer->getNumChannels() > 1) + { + reverb.processStereo (firstChannel, + bufferToFill.buffer->getWritePointer (1, bufferToFill.startSample), + bufferToFill.numSamples); + } + else + { + reverb.processMono (firstChannel, bufferToFill.numSamples); + } + } +} + +void ReverbAudioSource::setParameters (const Reverb::Parameters& newParams) +{ + const ScopedLock sl (lock); + reverb.setParameters (newParams); +} + +void ReverbAudioSource::setBypassed (bool b) noexcept +{ + if (bypass != b) + { + const ScopedLock sl (lock); + bypass = b; + reverb.reset(); + } +} + +} // namespace juce diff --git a/JuceLibraryCode/modules/juce_audio_basics/sources/juce_ReverbAudioSource.h b/JuceLibraryCode/modules/juce_audio_basics/sources/juce_ReverbAudioSource.h index a22f419..0de651c 100644 --- a/JuceLibraryCode/modules/juce_audio_basics/sources/juce_ReverbAudioSource.h +++ b/JuceLibraryCode/modules/juce_audio_basics/sources/juce_ReverbAudioSource.h @@ -1,74 +1,74 @@ -/* - ============================================================================== - - This file is part of the JUCE library. - Copyright (c) 2017 - ROLI Ltd. - - JUCE is an open source library subject to commercial or open-source - licensing. - - The code included in this file is provided under the terms of the ISC license - http://www.isc.org/downloads/software-support-policy/isc-license. Permission - To use, copy, modify, and/or distribute this software for any purpose with or - without fee is hereby granted provided that the above copyright notice and - this permission notice appear in all copies. - - JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER - EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE - DISCLAIMED. - - ============================================================================== -*/ - -namespace juce -{ - -//============================================================================== -/** - An AudioSource that uses the Reverb class to apply a reverb to another AudioSource. - - @see Reverb - - @tags{Audio} -*/ -class JUCE_API ReverbAudioSource : public AudioSource -{ -public: - /** Creates a ReverbAudioSource to process a given input source. - - @param inputSource the input source to read from - this must not be null - @param deleteInputWhenDeleted if true, the input source will be deleted when - this object is deleted - */ - ReverbAudioSource (AudioSource* inputSource, - bool deleteInputWhenDeleted); - - /** Destructor. */ - ~ReverbAudioSource() override; - - //============================================================================== - /** Returns the parameters from the reverb. */ - const Reverb::Parameters& getParameters() const noexcept { return reverb.getParameters(); } - - /** Changes the reverb's parameters. */ - void setParameters (const Reverb::Parameters& newParams); - - void setBypassed (bool isBypassed) noexcept; - bool isBypassed() const noexcept { return bypass; } - - //============================================================================== - void prepareToPlay (int samplesPerBlockExpected, double sampleRate) override; - void releaseResources() override; - void getNextAudioBlock (const AudioSourceChannelInfo&) override; - -private: - //============================================================================== - CriticalSection lock; - OptionalScopedPointer input; - Reverb reverb; - std::atomic bypass; - - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ReverbAudioSource) -}; - -} // namespace juce +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2017 - ROLI Ltd. + + JUCE is an open source library subject to commercial or open-source + licensing. + + The code included in this file is provided under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license. Permission + To use, copy, modify, and/or distribute this software for any purpose with or + without fee is hereby granted provided that the above copyright notice and + this permission notice appear in all copies. + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +namespace juce +{ + +//============================================================================== +/** + An AudioSource that uses the Reverb class to apply a reverb to another AudioSource. + + @see Reverb + + @tags{Audio} +*/ +class JUCE_API ReverbAudioSource : public AudioSource +{ +public: + /** Creates a ReverbAudioSource to process a given input source. + + @param inputSource the input source to read from - this must not be null + @param deleteInputWhenDeleted if true, the input source will be deleted when + this object is deleted + */ + ReverbAudioSource (AudioSource* inputSource, + bool deleteInputWhenDeleted); + + /** Destructor. */ + ~ReverbAudioSource() override; + + //============================================================================== + /** Returns the parameters from the reverb. */ + const Reverb::Parameters& getParameters() const noexcept { return reverb.getParameters(); } + + /** Changes the reverb's parameters. */ + void setParameters (const Reverb::Parameters& newParams); + + void setBypassed (bool isBypassed) noexcept; + bool isBypassed() const noexcept { return bypass; } + + //============================================================================== + void prepareToPlay (int samplesPerBlockExpected, double sampleRate) override; + void releaseResources() override; + void getNextAudioBlock (const AudioSourceChannelInfo&) override; + +private: + //============================================================================== + CriticalSection lock; + OptionalScopedPointer input; + Reverb reverb; + std::atomic bypass; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ReverbAudioSource) +}; + +} // namespace juce diff --git a/JuceLibraryCode/modules/juce_audio_basics/sources/juce_ToneGeneratorAudioSource.cpp b/JuceLibraryCode/modules/juce_audio_basics/sources/juce_ToneGeneratorAudioSource.cpp index 1707db7..54c8321 100644 --- a/JuceLibraryCode/modules/juce_audio_basics/sources/juce_ToneGeneratorAudioSource.cpp +++ b/JuceLibraryCode/modules/juce_audio_basics/sources/juce_ToneGeneratorAudioSource.cpp @@ -1,78 +1,78 @@ -/* - ============================================================================== - - This file is part of the JUCE library. - Copyright (c) 2017 - ROLI Ltd. - - JUCE is an open source library subject to commercial or open-source - licensing. - - The code included in this file is provided under the terms of the ISC license - http://www.isc.org/downloads/software-support-policy/isc-license. Permission - To use, copy, modify, and/or distribute this software for any purpose with or - without fee is hereby granted provided that the above copyright notice and - this permission notice appear in all copies. - - JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER - EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE - DISCLAIMED. - - ============================================================================== -*/ - -namespace juce -{ - -ToneGeneratorAudioSource::ToneGeneratorAudioSource() - : frequency (1000.0), - sampleRate (44100.0), - currentPhase (0.0), - phasePerSample (0.0), - amplitude (0.5f) -{ -} - -ToneGeneratorAudioSource::~ToneGeneratorAudioSource() -{ -} - -//============================================================================== -void ToneGeneratorAudioSource::setAmplitude (const float newAmplitude) -{ - amplitude = newAmplitude; -} - -void ToneGeneratorAudioSource::setFrequency (const double newFrequencyHz) -{ - frequency = newFrequencyHz; - phasePerSample = 0.0; -} - -//============================================================================== -void ToneGeneratorAudioSource::prepareToPlay (int /*samplesPerBlockExpected*/, double rate) -{ - currentPhase = 0.0; - phasePerSample = 0.0; - sampleRate = rate; -} - -void ToneGeneratorAudioSource::releaseResources() -{ -} - -void ToneGeneratorAudioSource::getNextAudioBlock (const AudioSourceChannelInfo& info) -{ - if (phasePerSample == 0.0) - phasePerSample = MathConstants::twoPi / (sampleRate / frequency); - - for (int i = 0; i < info.numSamples; ++i) - { - const float sample = amplitude * (float) std::sin (currentPhase); - currentPhase += phasePerSample; - - for (int j = info.buffer->getNumChannels(); --j >= 0;) - info.buffer->setSample (j, info.startSample + i, sample); - } -} - -} // namespace juce +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2017 - ROLI Ltd. + + JUCE is an open source library subject to commercial or open-source + licensing. + + The code included in this file is provided under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license. Permission + To use, copy, modify, and/or distribute this software for any purpose with or + without fee is hereby granted provided that the above copyright notice and + this permission notice appear in all copies. + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +namespace juce +{ + +ToneGeneratorAudioSource::ToneGeneratorAudioSource() + : frequency (1000.0), + sampleRate (44100.0), + currentPhase (0.0), + phasePerSample (0.0), + amplitude (0.5f) +{ +} + +ToneGeneratorAudioSource::~ToneGeneratorAudioSource() +{ +} + +//============================================================================== +void ToneGeneratorAudioSource::setAmplitude (const float newAmplitude) +{ + amplitude = newAmplitude; +} + +void ToneGeneratorAudioSource::setFrequency (const double newFrequencyHz) +{ + frequency = newFrequencyHz; + phasePerSample = 0.0; +} + +//============================================================================== +void ToneGeneratorAudioSource::prepareToPlay (int /*samplesPerBlockExpected*/, double rate) +{ + currentPhase = 0.0; + phasePerSample = 0.0; + sampleRate = rate; +} + +void ToneGeneratorAudioSource::releaseResources() +{ +} + +void ToneGeneratorAudioSource::getNextAudioBlock (const AudioSourceChannelInfo& info) +{ + if (phasePerSample == 0.0) + phasePerSample = MathConstants::twoPi / (sampleRate / frequency); + + for (int i = 0; i < info.numSamples; ++i) + { + const float sample = amplitude * (float) std::sin (currentPhase); + currentPhase += phasePerSample; + + for (int j = info.buffer->getNumChannels(); --j >= 0;) + info.buffer->setSample (j, info.startSample + i, sample); + } +} + +} // namespace juce diff --git a/JuceLibraryCode/modules/juce_audio_basics/sources/juce_ToneGeneratorAudioSource.h b/JuceLibraryCode/modules/juce_audio_basics/sources/juce_ToneGeneratorAudioSource.h index df04c89..5a03167 100644 --- a/JuceLibraryCode/modules/juce_audio_basics/sources/juce_ToneGeneratorAudioSource.h +++ b/JuceLibraryCode/modules/juce_audio_basics/sources/juce_ToneGeneratorAudioSource.h @@ -1,71 +1,71 @@ -/* - ============================================================================== - - This file is part of the JUCE library. - Copyright (c) 2017 - ROLI Ltd. - - JUCE is an open source library subject to commercial or open-source - licensing. - - The code included in this file is provided under the terms of the ISC license - http://www.isc.org/downloads/software-support-policy/isc-license. Permission - To use, copy, modify, and/or distribute this software for any purpose with or - without fee is hereby granted provided that the above copyright notice and - this permission notice appear in all copies. - - JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER - EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE - DISCLAIMED. - - ============================================================================== -*/ - -namespace juce -{ - -//============================================================================== -/** - A simple AudioSource that generates a sine wave. - - - @tags{Audio} -*/ -class JUCE_API ToneGeneratorAudioSource : public AudioSource -{ -public: - //============================================================================== - /** Creates a ToneGeneratorAudioSource. */ - ToneGeneratorAudioSource(); - - /** Destructor. */ - ~ToneGeneratorAudioSource() override; - - //============================================================================== - /** Sets the signal's amplitude. */ - void setAmplitude (float newAmplitude); - - /** Sets the signal's frequency. */ - void setFrequency (double newFrequencyHz); - - - //============================================================================== - /** Implementation of the AudioSource method. */ - void prepareToPlay (int samplesPerBlockExpected, double sampleRate) override; - - /** Implementation of the AudioSource method. */ - void releaseResources() override; - - /** Implementation of the AudioSource method. */ - void getNextAudioBlock (const AudioSourceChannelInfo&) override; - - -private: - //============================================================================== - double frequency, sampleRate; - double currentPhase, phasePerSample; - float amplitude; - - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ToneGeneratorAudioSource) -}; - -} // namespace juce +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2017 - ROLI Ltd. + + JUCE is an open source library subject to commercial or open-source + licensing. + + The code included in this file is provided under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license. Permission + To use, copy, modify, and/or distribute this software for any purpose with or + without fee is hereby granted provided that the above copyright notice and + this permission notice appear in all copies. + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +namespace juce +{ + +//============================================================================== +/** + A simple AudioSource that generates a sine wave. + + + @tags{Audio} +*/ +class JUCE_API ToneGeneratorAudioSource : public AudioSource +{ +public: + //============================================================================== + /** Creates a ToneGeneratorAudioSource. */ + ToneGeneratorAudioSource(); + + /** Destructor. */ + ~ToneGeneratorAudioSource() override; + + //============================================================================== + /** Sets the signal's amplitude. */ + void setAmplitude (float newAmplitude); + + /** Sets the signal's frequency. */ + void setFrequency (double newFrequencyHz); + + + //============================================================================== + /** Implementation of the AudioSource method. */ + void prepareToPlay (int samplesPerBlockExpected, double sampleRate) override; + + /** Implementation of the AudioSource method. */ + void releaseResources() override; + + /** Implementation of the AudioSource method. */ + void getNextAudioBlock (const AudioSourceChannelInfo&) override; + + +private: + //============================================================================== + double frequency, sampleRate; + double currentPhase, phasePerSample; + float amplitude; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ToneGeneratorAudioSource) +}; + +} // namespace juce diff --git a/JuceLibraryCode/modules/juce_audio_basics/synthesisers/juce_Synthesiser.cpp b/JuceLibraryCode/modules/juce_audio_basics/synthesisers/juce_Synthesiser.cpp index 8c02bf2..414bcf4 100644 --- a/JuceLibraryCode/modules/juce_audio_basics/synthesisers/juce_Synthesiser.cpp +++ b/JuceLibraryCode/modules/juce_audio_basics/synthesisers/juce_Synthesiser.cpp @@ -1,586 +1,586 @@ -/* - ============================================================================== - - This file is part of the JUCE library. - Copyright (c) 2017 - ROLI Ltd. - - JUCE is an open source library subject to commercial or open-source - licensing. - - The code included in this file is provided under the terms of the ISC license - http://www.isc.org/downloads/software-support-policy/isc-license. Permission - To use, copy, modify, and/or distribute this software for any purpose with or - without fee is hereby granted provided that the above copyright notice and - this permission notice appear in all copies. - - JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER - EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE - DISCLAIMED. - - ============================================================================== -*/ - -namespace juce -{ - -SynthesiserSound::SynthesiserSound() {} -SynthesiserSound::~SynthesiserSound() {} - -//============================================================================== -SynthesiserVoice::SynthesiserVoice() {} -SynthesiserVoice::~SynthesiserVoice() {} - -bool SynthesiserVoice::isPlayingChannel (const int midiChannel) const -{ - return currentPlayingMidiChannel == midiChannel; -} - -void SynthesiserVoice::setCurrentPlaybackSampleRate (const double newRate) -{ - currentSampleRate = newRate; -} - -bool SynthesiserVoice::isVoiceActive() const -{ - return getCurrentlyPlayingNote() >= 0; -} - -void SynthesiserVoice::clearCurrentNote() -{ - currentlyPlayingNote = -1; - currentlyPlayingSound = nullptr; - currentPlayingMidiChannel = 0; -} - -void SynthesiserVoice::aftertouchChanged (int) {} -void SynthesiserVoice::channelPressureChanged (int) {} - -bool SynthesiserVoice::wasStartedBefore (const SynthesiserVoice& other) const noexcept -{ - return noteOnTime < other.noteOnTime; -} - -void SynthesiserVoice::renderNextBlock (AudioBuffer& outputBuffer, - int startSample, int numSamples) -{ - AudioBuffer subBuffer (outputBuffer.getArrayOfWritePointers(), - outputBuffer.getNumChannels(), - startSample, numSamples); - - tempBuffer.makeCopyOf (subBuffer, true); - renderNextBlock (tempBuffer, 0, numSamples); - subBuffer.makeCopyOf (tempBuffer, true); -} - -//============================================================================== -Synthesiser::Synthesiser() -{ - for (int i = 0; i < numElementsInArray (lastPitchWheelValues); ++i) - lastPitchWheelValues[i] = 0x2000; -} - -Synthesiser::~Synthesiser() -{ -} - -//============================================================================== -SynthesiserVoice* Synthesiser::getVoice (const int index) const -{ - const ScopedLock sl (lock); - return voices [index]; -} - -void Synthesiser::clearVoices() -{ - const ScopedLock sl (lock); - voices.clear(); -} - -SynthesiserVoice* Synthesiser::addVoice (SynthesiserVoice* const newVoice) -{ - const ScopedLock sl (lock); - newVoice->setCurrentPlaybackSampleRate (sampleRate); - return voices.add (newVoice); -} - -void Synthesiser::removeVoice (const int index) -{ - const ScopedLock sl (lock); - voices.remove (index); -} - -void Synthesiser::clearSounds() -{ - const ScopedLock sl (lock); - sounds.clear(); -} - -SynthesiserSound* Synthesiser::addSound (const SynthesiserSound::Ptr& newSound) -{ - const ScopedLock sl (lock); - return sounds.add (newSound); -} - -void Synthesiser::removeSound (const int index) -{ - const ScopedLock sl (lock); - sounds.remove (index); -} - -void Synthesiser::setNoteStealingEnabled (const bool shouldSteal) -{ - shouldStealNotes = shouldSteal; -} - -void Synthesiser::setMinimumRenderingSubdivisionSize (int numSamples, bool shouldBeStrict) noexcept -{ - jassert (numSamples > 0); // it wouldn't make much sense for this to be less than 1 - minimumSubBlockSize = numSamples; - subBlockSubdivisionIsStrict = shouldBeStrict; -} - -//============================================================================== -void Synthesiser::setCurrentPlaybackSampleRate (const double newRate) -{ - if (sampleRate != newRate) - { - const ScopedLock sl (lock); - allNotesOff (0, false); - sampleRate = newRate; - - for (auto* voice : voices) - voice->setCurrentPlaybackSampleRate (newRate); - } -} - -template -void Synthesiser::processNextBlock (AudioBuffer& outputAudio, - const MidiBuffer& midiData, - int startSample, - int numSamples) -{ - // must set the sample rate before using this! - jassert (sampleRate != 0); - const int targetChannels = outputAudio.getNumChannels(); - - MidiBuffer::Iterator midiIterator (midiData); - midiIterator.setNextSamplePosition (startSample); - - bool firstEvent = true; - int midiEventPos; - MidiMessage m; - - const ScopedLock sl (lock); - - while (numSamples > 0) - { - if (! midiIterator.getNextEvent (m, midiEventPos)) - { - if (targetChannels > 0) - renderVoices (outputAudio, startSample, numSamples); - - return; - } - - const int samplesToNextMidiMessage = midiEventPos - startSample; - - if (samplesToNextMidiMessage >= numSamples) - { - if (targetChannels > 0) - renderVoices (outputAudio, startSample, numSamples); - - handleMidiEvent (m); - break; - } - - if (samplesToNextMidiMessage < ((firstEvent && ! subBlockSubdivisionIsStrict) ? 1 : minimumSubBlockSize)) - { - handleMidiEvent (m); - continue; - } - - firstEvent = false; - - if (targetChannels > 0) - renderVoices (outputAudio, startSample, samplesToNextMidiMessage); - - handleMidiEvent (m); - startSample += samplesToNextMidiMessage; - numSamples -= samplesToNextMidiMessage; - } - - while (midiIterator.getNextEvent (m, midiEventPos)) - handleMidiEvent (m); -} - -// explicit template instantiation -template void Synthesiser::processNextBlock (AudioBuffer&, const MidiBuffer&, int, int); -template void Synthesiser::processNextBlock (AudioBuffer&, const MidiBuffer&, int, int); - -void Synthesiser::renderNextBlock (AudioBuffer& outputAudio, const MidiBuffer& inputMidi, - int startSample, int numSamples) -{ - processNextBlock (outputAudio, inputMidi, startSample, numSamples); -} - -void Synthesiser::renderNextBlock (AudioBuffer& outputAudio, const MidiBuffer& inputMidi, - int startSample, int numSamples) -{ - processNextBlock (outputAudio, inputMidi, startSample, numSamples); -} - -void Synthesiser::renderVoices (AudioBuffer& buffer, int startSample, int numSamples) -{ - for (auto* voice : voices) - voice->renderNextBlock (buffer, startSample, numSamples); -} - -void Synthesiser::renderVoices (AudioBuffer& buffer, int startSample, int numSamples) -{ - for (auto* voice : voices) - voice->renderNextBlock (buffer, startSample, numSamples); -} - -void Synthesiser::handleMidiEvent (const MidiMessage& m) -{ - const int channel = m.getChannel(); - - if (m.isNoteOn()) - { - noteOn (channel, m.getNoteNumber(), m.getFloatVelocity()); - } - else if (m.isNoteOff()) - { - noteOff (channel, m.getNoteNumber(), m.getFloatVelocity(), true); - } - else if (m.isAllNotesOff() || m.isAllSoundOff()) - { - allNotesOff (channel, true); - } - else if (m.isPitchWheel()) - { - const int wheelPos = m.getPitchWheelValue(); - lastPitchWheelValues [channel - 1] = wheelPos; - handlePitchWheel (channel, wheelPos); - } - else if (m.isAftertouch()) - { - handleAftertouch (channel, m.getNoteNumber(), m.getAfterTouchValue()); - } - else if (m.isChannelPressure()) - { - handleChannelPressure (channel, m.getChannelPressureValue()); - } - else if (m.isController()) - { - handleController (channel, m.getControllerNumber(), m.getControllerValue()); - } - else if (m.isProgramChange()) - { - handleProgramChange (channel, m.getProgramChangeNumber()); - } -} - -//============================================================================== -void Synthesiser::noteOn (const int midiChannel, - const int midiNoteNumber, - const float velocity) -{ - const ScopedLock sl (lock); - - for (auto* sound : sounds) - { - if (sound->appliesToNote (midiNoteNumber) && sound->appliesToChannel (midiChannel)) - { - // If hitting a note that's still ringing, stop it first (it could be - // still playing because of the sustain or sostenuto pedal). - for (auto* voice : voices) - if (voice->getCurrentlyPlayingNote() == midiNoteNumber && voice->isPlayingChannel (midiChannel)) - stopVoice (voice, 1.0f, true); - - startVoice (findFreeVoice (sound, midiChannel, midiNoteNumber, shouldStealNotes), - sound, midiChannel, midiNoteNumber, velocity); - } - } -} - -void Synthesiser::startVoice (SynthesiserVoice* const voice, - SynthesiserSound* const sound, - const int midiChannel, - const int midiNoteNumber, - const float velocity) -{ - if (voice != nullptr && sound != nullptr) - { - if (voice->currentlyPlayingSound != nullptr) - voice->stopNote (0.0f, false); - - voice->currentlyPlayingNote = midiNoteNumber; - voice->currentPlayingMidiChannel = midiChannel; - voice->noteOnTime = ++lastNoteOnCounter; - voice->currentlyPlayingSound = sound; - voice->setKeyDown (true); - voice->setSostenutoPedalDown (false); - voice->setSustainPedalDown (sustainPedalsDown[midiChannel]); - - voice->startNote (midiNoteNumber, velocity, sound, - lastPitchWheelValues [midiChannel - 1]); - } -} - -void Synthesiser::stopVoice (SynthesiserVoice* voice, float velocity, const bool allowTailOff) -{ - jassert (voice != nullptr); - - voice->stopNote (velocity, allowTailOff); - - // the subclass MUST call clearCurrentNote() if it's not tailing off! RTFM for stopNote()! - jassert (allowTailOff || (voice->getCurrentlyPlayingNote() < 0 && voice->getCurrentlyPlayingSound() == nullptr)); -} - -void Synthesiser::noteOff (const int midiChannel, - const int midiNoteNumber, - const float velocity, - const bool allowTailOff) -{ - const ScopedLock sl (lock); - - for (auto* voice : voices) - { - if (voice->getCurrentlyPlayingNote() == midiNoteNumber - && voice->isPlayingChannel (midiChannel)) - { - if (auto sound = voice->getCurrentlyPlayingSound()) - { - if (sound->appliesToNote (midiNoteNumber) - && sound->appliesToChannel (midiChannel)) - { - jassert (! voice->keyIsDown || voice->isSustainPedalDown() == sustainPedalsDown [midiChannel]); - - voice->setKeyDown (false); - - if (! (voice->isSustainPedalDown() || voice->isSostenutoPedalDown())) - stopVoice (voice, velocity, allowTailOff); - } - } - } - } -} - -void Synthesiser::allNotesOff (const int midiChannel, const bool allowTailOff) -{ - const ScopedLock sl (lock); - - for (auto* voice : voices) - if (midiChannel <= 0 || voice->isPlayingChannel (midiChannel)) - voice->stopNote (1.0f, allowTailOff); - - sustainPedalsDown.clear(); -} - -void Synthesiser::handlePitchWheel (const int midiChannel, const int wheelValue) -{ - const ScopedLock sl (lock); - - for (auto* voice : voices) - if (midiChannel <= 0 || voice->isPlayingChannel (midiChannel)) - voice->pitchWheelMoved (wheelValue); -} - -void Synthesiser::handleController (const int midiChannel, - const int controllerNumber, - const int controllerValue) -{ - switch (controllerNumber) - { - case 0x40: handleSustainPedal (midiChannel, controllerValue >= 64); break; - case 0x42: handleSostenutoPedal (midiChannel, controllerValue >= 64); break; - case 0x43: handleSoftPedal (midiChannel, controllerValue >= 64); break; - default: break; - } - - const ScopedLock sl (lock); - - for (auto* voice : voices) - if (midiChannel <= 0 || voice->isPlayingChannel (midiChannel)) - voice->controllerMoved (controllerNumber, controllerValue); -} - -void Synthesiser::handleAftertouch (int midiChannel, int midiNoteNumber, int aftertouchValue) -{ - const ScopedLock sl (lock); - - for (auto* voice : voices) - if (voice->getCurrentlyPlayingNote() == midiNoteNumber - && (midiChannel <= 0 || voice->isPlayingChannel (midiChannel))) - voice->aftertouchChanged (aftertouchValue); -} - -void Synthesiser::handleChannelPressure (int midiChannel, int channelPressureValue) -{ - const ScopedLock sl (lock); - - for (auto* voice : voices) - if (midiChannel <= 0 || voice->isPlayingChannel (midiChannel)) - voice->channelPressureChanged (channelPressureValue); -} - -void Synthesiser::handleSustainPedal (int midiChannel, bool isDown) -{ - jassert (midiChannel > 0 && midiChannel <= 16); - const ScopedLock sl (lock); - - if (isDown) - { - sustainPedalsDown.setBit (midiChannel); - - for (auto* voice : voices) - if (voice->isPlayingChannel (midiChannel) && voice->isKeyDown()) - voice->setSustainPedalDown (true); - } - else - { - for (auto* voice : voices) - { - if (voice->isPlayingChannel (midiChannel)) - { - voice->setSustainPedalDown (false); - - if (! (voice->isKeyDown() || voice->isSostenutoPedalDown())) - stopVoice (voice, 1.0f, true); - } - } - - sustainPedalsDown.clearBit (midiChannel); - } -} - -void Synthesiser::handleSostenutoPedal (int midiChannel, bool isDown) -{ - jassert (midiChannel > 0 && midiChannel <= 16); - const ScopedLock sl (lock); - - for (auto* voice : voices) - { - if (voice->isPlayingChannel (midiChannel)) - { - if (isDown) - voice->setSostenutoPedalDown (true); - else if (voice->isSostenutoPedalDown()) - stopVoice (voice, 1.0f, true); - } - } -} - -void Synthesiser::handleSoftPedal (int midiChannel, bool /*isDown*/) -{ - ignoreUnused (midiChannel); - jassert (midiChannel > 0 && midiChannel <= 16); -} - -void Synthesiser::handleProgramChange (int midiChannel, int programNumber) -{ - ignoreUnused (midiChannel, programNumber); - jassert (midiChannel > 0 && midiChannel <= 16); -} - -//============================================================================== -SynthesiserVoice* Synthesiser::findFreeVoice (SynthesiserSound* soundToPlay, - int midiChannel, int midiNoteNumber, - const bool stealIfNoneAvailable) const -{ - const ScopedLock sl (lock); - - for (auto* voice : voices) - if ((! voice->isVoiceActive()) && voice->canPlaySound (soundToPlay)) - return voice; - - if (stealIfNoneAvailable) - return findVoiceToSteal (soundToPlay, midiChannel, midiNoteNumber); - - return nullptr; -} - -SynthesiserVoice* Synthesiser::findVoiceToSteal (SynthesiserSound* soundToPlay, - int /*midiChannel*/, int midiNoteNumber) const -{ - // This voice-stealing algorithm applies the following heuristics: - // - Re-use the oldest notes first - // - Protect the lowest & topmost notes, even if sustained, but not if they've been released. - - // apparently you are trying to render audio without having any voices... - jassert (! voices.isEmpty()); - - // These are the voices we want to protect (ie: only steal if unavoidable) - SynthesiserVoice* low = nullptr; // Lowest sounding note, might be sustained, but NOT in release phase - SynthesiserVoice* top = nullptr; // Highest sounding note, might be sustained, but NOT in release phase - - // this is a list of voices we can steal, sorted by how long they've been running - Array usableVoices; - usableVoices.ensureStorageAllocated (voices.size()); - - for (auto* voice : voices) - { - if (voice->canPlaySound (soundToPlay)) - { - jassert (voice->isVoiceActive()); // We wouldn't be here otherwise - - usableVoices.add (voice); - - // NB: Using a functor rather than a lambda here due to scare-stories about - // compilers generating code containing heap allocations.. - struct Sorter - { - bool operator() (const SynthesiserVoice* a, const SynthesiserVoice* b) const noexcept { return a->wasStartedBefore (*b); } - }; - - std::sort (usableVoices.begin(), usableVoices.end(), Sorter()); - - if (! voice->isPlayingButReleased()) // Don't protect released notes - { - auto note = voice->getCurrentlyPlayingNote(); - - if (low == nullptr || note < low->getCurrentlyPlayingNote()) - low = voice; - - if (top == nullptr || note > top->getCurrentlyPlayingNote()) - top = voice; - } - } - } - - // Eliminate pathological cases (ie: only 1 note playing): we always give precedence to the lowest note(s) - if (top == low) - top = nullptr; - - // The oldest note that's playing with the target pitch is ideal.. - for (auto* voice : usableVoices) - if (voice->getCurrentlyPlayingNote() == midiNoteNumber) - return voice; - - // Oldest voice that has been released (no finger on it and not held by sustain pedal) - for (auto* voice : usableVoices) - if (voice != low && voice != top && voice->isPlayingButReleased()) - return voice; - - // Oldest voice that doesn't have a finger on it: - for (auto* voice : usableVoices) - if (voice != low && voice != top && ! voice->isKeyDown()) - return voice; - - // Oldest voice that isn't protected - for (auto* voice : usableVoices) - if (voice != low && voice != top) - return voice; - - // We've only got "protected" voices now: lowest note takes priority - jassert (low != nullptr); - - // Duophonic synth: give priority to the bass note: - if (top != nullptr) - return top; - - return low; -} - -} // namespace juce +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2017 - ROLI Ltd. + + JUCE is an open source library subject to commercial or open-source + licensing. + + The code included in this file is provided under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license. Permission + To use, copy, modify, and/or distribute this software for any purpose with or + without fee is hereby granted provided that the above copyright notice and + this permission notice appear in all copies. + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +namespace juce +{ + +SynthesiserSound::SynthesiserSound() {} +SynthesiserSound::~SynthesiserSound() {} + +//============================================================================== +SynthesiserVoice::SynthesiserVoice() {} +SynthesiserVoice::~SynthesiserVoice() {} + +bool SynthesiserVoice::isPlayingChannel (const int midiChannel) const +{ + return currentPlayingMidiChannel == midiChannel; +} + +void SynthesiserVoice::setCurrentPlaybackSampleRate (const double newRate) +{ + currentSampleRate = newRate; +} + +bool SynthesiserVoice::isVoiceActive() const +{ + return getCurrentlyPlayingNote() >= 0; +} + +void SynthesiserVoice::clearCurrentNote() +{ + currentlyPlayingNote = -1; + currentlyPlayingSound = nullptr; + currentPlayingMidiChannel = 0; +} + +void SynthesiserVoice::aftertouchChanged (int) {} +void SynthesiserVoice::channelPressureChanged (int) {} + +bool SynthesiserVoice::wasStartedBefore (const SynthesiserVoice& other) const noexcept +{ + return noteOnTime < other.noteOnTime; +} + +void SynthesiserVoice::renderNextBlock (AudioBuffer& outputBuffer, + int startSample, int numSamples) +{ + AudioBuffer subBuffer (outputBuffer.getArrayOfWritePointers(), + outputBuffer.getNumChannels(), + startSample, numSamples); + + tempBuffer.makeCopyOf (subBuffer, true); + renderNextBlock (tempBuffer, 0, numSamples); + subBuffer.makeCopyOf (tempBuffer, true); +} + +//============================================================================== +Synthesiser::Synthesiser() +{ + for (int i = 0; i < numElementsInArray (lastPitchWheelValues); ++i) + lastPitchWheelValues[i] = 0x2000; +} + +Synthesiser::~Synthesiser() +{ +} + +//============================================================================== +SynthesiserVoice* Synthesiser::getVoice (const int index) const +{ + const ScopedLock sl (lock); + return voices [index]; +} + +void Synthesiser::clearVoices() +{ + const ScopedLock sl (lock); + voices.clear(); +} + +SynthesiserVoice* Synthesiser::addVoice (SynthesiserVoice* const newVoice) +{ + const ScopedLock sl (lock); + newVoice->setCurrentPlaybackSampleRate (sampleRate); + return voices.add (newVoice); +} + +void Synthesiser::removeVoice (const int index) +{ + const ScopedLock sl (lock); + voices.remove (index); +} + +void Synthesiser::clearSounds() +{ + const ScopedLock sl (lock); + sounds.clear(); +} + +SynthesiserSound* Synthesiser::addSound (const SynthesiserSound::Ptr& newSound) +{ + const ScopedLock sl (lock); + return sounds.add (newSound); +} + +void Synthesiser::removeSound (const int index) +{ + const ScopedLock sl (lock); + sounds.remove (index); +} + +void Synthesiser::setNoteStealingEnabled (const bool shouldSteal) +{ + shouldStealNotes = shouldSteal; +} + +void Synthesiser::setMinimumRenderingSubdivisionSize (int numSamples, bool shouldBeStrict) noexcept +{ + jassert (numSamples > 0); // it wouldn't make much sense for this to be less than 1 + minimumSubBlockSize = numSamples; + subBlockSubdivisionIsStrict = shouldBeStrict; +} + +//============================================================================== +void Synthesiser::setCurrentPlaybackSampleRate (const double newRate) +{ + if (sampleRate != newRate) + { + const ScopedLock sl (lock); + allNotesOff (0, false); + sampleRate = newRate; + + for (auto* voice : voices) + voice->setCurrentPlaybackSampleRate (newRate); + } +} + +template +void Synthesiser::processNextBlock (AudioBuffer& outputAudio, + const MidiBuffer& midiData, + int startSample, + int numSamples) +{ + // must set the sample rate before using this! + jassert (sampleRate != 0); + const int targetChannels = outputAudio.getNumChannels(); + + MidiBuffer::Iterator midiIterator (midiData); + midiIterator.setNextSamplePosition (startSample); + + bool firstEvent = true; + int midiEventPos; + MidiMessage m; + + const ScopedLock sl (lock); + + while (numSamples > 0) + { + if (! midiIterator.getNextEvent (m, midiEventPos)) + { + if (targetChannels > 0) + renderVoices (outputAudio, startSample, numSamples); + + return; + } + + const int samplesToNextMidiMessage = midiEventPos - startSample; + + if (samplesToNextMidiMessage >= numSamples) + { + if (targetChannels > 0) + renderVoices (outputAudio, startSample, numSamples); + + handleMidiEvent (m); + break; + } + + if (samplesToNextMidiMessage < ((firstEvent && ! subBlockSubdivisionIsStrict) ? 1 : minimumSubBlockSize)) + { + handleMidiEvent (m); + continue; + } + + firstEvent = false; + + if (targetChannels > 0) + renderVoices (outputAudio, startSample, samplesToNextMidiMessage); + + handleMidiEvent (m); + startSample += samplesToNextMidiMessage; + numSamples -= samplesToNextMidiMessage; + } + + while (midiIterator.getNextEvent (m, midiEventPos)) + handleMidiEvent (m); +} + +// explicit template instantiation +template void Synthesiser::processNextBlock (AudioBuffer&, const MidiBuffer&, int, int); +template void Synthesiser::processNextBlock (AudioBuffer&, const MidiBuffer&, int, int); + +void Synthesiser::renderNextBlock (AudioBuffer& outputAudio, const MidiBuffer& inputMidi, + int startSample, int numSamples) +{ + processNextBlock (outputAudio, inputMidi, startSample, numSamples); +} + +void Synthesiser::renderNextBlock (AudioBuffer& outputAudio, const MidiBuffer& inputMidi, + int startSample, int numSamples) +{ + processNextBlock (outputAudio, inputMidi, startSample, numSamples); +} + +void Synthesiser::renderVoices (AudioBuffer& buffer, int startSample, int numSamples) +{ + for (auto* voice : voices) + voice->renderNextBlock (buffer, startSample, numSamples); +} + +void Synthesiser::renderVoices (AudioBuffer& buffer, int startSample, int numSamples) +{ + for (auto* voice : voices) + voice->renderNextBlock (buffer, startSample, numSamples); +} + +void Synthesiser::handleMidiEvent (const MidiMessage& m) +{ + const int channel = m.getChannel(); + + if (m.isNoteOn()) + { + noteOn (channel, m.getNoteNumber(), m.getFloatVelocity()); + } + else if (m.isNoteOff()) + { + noteOff (channel, m.getNoteNumber(), m.getFloatVelocity(), true); + } + else if (m.isAllNotesOff() || m.isAllSoundOff()) + { + allNotesOff (channel, true); + } + else if (m.isPitchWheel()) + { + const int wheelPos = m.getPitchWheelValue(); + lastPitchWheelValues [channel - 1] = wheelPos; + handlePitchWheel (channel, wheelPos); + } + else if (m.isAftertouch()) + { + handleAftertouch (channel, m.getNoteNumber(), m.getAfterTouchValue()); + } + else if (m.isChannelPressure()) + { + handleChannelPressure (channel, m.getChannelPressureValue()); + } + else if (m.isController()) + { + handleController (channel, m.getControllerNumber(), m.getControllerValue()); + } + else if (m.isProgramChange()) + { + handleProgramChange (channel, m.getProgramChangeNumber()); + } +} + +//============================================================================== +void Synthesiser::noteOn (const int midiChannel, + const int midiNoteNumber, + const float velocity) +{ + const ScopedLock sl (lock); + + for (auto* sound : sounds) + { + if (sound->appliesToNote (midiNoteNumber) && sound->appliesToChannel (midiChannel)) + { + // If hitting a note that's still ringing, stop it first (it could be + // still playing because of the sustain or sostenuto pedal). + for (auto* voice : voices) + if (voice->getCurrentlyPlayingNote() == midiNoteNumber && voice->isPlayingChannel (midiChannel)) + stopVoice (voice, 1.0f, true); + + startVoice (findFreeVoice (sound, midiChannel, midiNoteNumber, shouldStealNotes), + sound, midiChannel, midiNoteNumber, velocity); + } + } +} + +void Synthesiser::startVoice (SynthesiserVoice* const voice, + SynthesiserSound* const sound, + const int midiChannel, + const int midiNoteNumber, + const float velocity) +{ + if (voice != nullptr && sound != nullptr) + { + if (voice->currentlyPlayingSound != nullptr) + voice->stopNote (0.0f, false); + + voice->currentlyPlayingNote = midiNoteNumber; + voice->currentPlayingMidiChannel = midiChannel; + voice->noteOnTime = ++lastNoteOnCounter; + voice->currentlyPlayingSound = sound; + voice->setKeyDown (true); + voice->setSostenutoPedalDown (false); + voice->setSustainPedalDown (sustainPedalsDown[midiChannel]); + + voice->startNote (midiNoteNumber, velocity, sound, + lastPitchWheelValues [midiChannel - 1]); + } +} + +void Synthesiser::stopVoice (SynthesiserVoice* voice, float velocity, const bool allowTailOff) +{ + jassert (voice != nullptr); + + voice->stopNote (velocity, allowTailOff); + + // the subclass MUST call clearCurrentNote() if it's not tailing off! RTFM for stopNote()! + jassert (allowTailOff || (voice->getCurrentlyPlayingNote() < 0 && voice->getCurrentlyPlayingSound() == nullptr)); +} + +void Synthesiser::noteOff (const int midiChannel, + const int midiNoteNumber, + const float velocity, + const bool allowTailOff) +{ + const ScopedLock sl (lock); + + for (auto* voice : voices) + { + if (voice->getCurrentlyPlayingNote() == midiNoteNumber + && voice->isPlayingChannel (midiChannel)) + { + if (auto sound = voice->getCurrentlyPlayingSound()) + { + if (sound->appliesToNote (midiNoteNumber) + && sound->appliesToChannel (midiChannel)) + { + jassert (! voice->keyIsDown || voice->isSustainPedalDown() == sustainPedalsDown [midiChannel]); + + voice->setKeyDown (false); + + if (! (voice->isSustainPedalDown() || voice->isSostenutoPedalDown())) + stopVoice (voice, velocity, allowTailOff); + } + } + } + } +} + +void Synthesiser::allNotesOff (const int midiChannel, const bool allowTailOff) +{ + const ScopedLock sl (lock); + + for (auto* voice : voices) + if (midiChannel <= 0 || voice->isPlayingChannel (midiChannel)) + voice->stopNote (1.0f, allowTailOff); + + sustainPedalsDown.clear(); +} + +void Synthesiser::handlePitchWheel (const int midiChannel, const int wheelValue) +{ + const ScopedLock sl (lock); + + for (auto* voice : voices) + if (midiChannel <= 0 || voice->isPlayingChannel (midiChannel)) + voice->pitchWheelMoved (wheelValue); +} + +void Synthesiser::handleController (const int midiChannel, + const int controllerNumber, + const int controllerValue) +{ + switch (controllerNumber) + { + case 0x40: handleSustainPedal (midiChannel, controllerValue >= 64); break; + case 0x42: handleSostenutoPedal (midiChannel, controllerValue >= 64); break; + case 0x43: handleSoftPedal (midiChannel, controllerValue >= 64); break; + default: break; + } + + const ScopedLock sl (lock); + + for (auto* voice : voices) + if (midiChannel <= 0 || voice->isPlayingChannel (midiChannel)) + voice->controllerMoved (controllerNumber, controllerValue); +} + +void Synthesiser::handleAftertouch (int midiChannel, int midiNoteNumber, int aftertouchValue) +{ + const ScopedLock sl (lock); + + for (auto* voice : voices) + if (voice->getCurrentlyPlayingNote() == midiNoteNumber + && (midiChannel <= 0 || voice->isPlayingChannel (midiChannel))) + voice->aftertouchChanged (aftertouchValue); +} + +void Synthesiser::handleChannelPressure (int midiChannel, int channelPressureValue) +{ + const ScopedLock sl (lock); + + for (auto* voice : voices) + if (midiChannel <= 0 || voice->isPlayingChannel (midiChannel)) + voice->channelPressureChanged (channelPressureValue); +} + +void Synthesiser::handleSustainPedal (int midiChannel, bool isDown) +{ + jassert (midiChannel > 0 && midiChannel <= 16); + const ScopedLock sl (lock); + + if (isDown) + { + sustainPedalsDown.setBit (midiChannel); + + for (auto* voice : voices) + if (voice->isPlayingChannel (midiChannel) && voice->isKeyDown()) + voice->setSustainPedalDown (true); + } + else + { + for (auto* voice : voices) + { + if (voice->isPlayingChannel (midiChannel)) + { + voice->setSustainPedalDown (false); + + if (! (voice->isKeyDown() || voice->isSostenutoPedalDown())) + stopVoice (voice, 1.0f, true); + } + } + + sustainPedalsDown.clearBit (midiChannel); + } +} + +void Synthesiser::handleSostenutoPedal (int midiChannel, bool isDown) +{ + jassert (midiChannel > 0 && midiChannel <= 16); + const ScopedLock sl (lock); + + for (auto* voice : voices) + { + if (voice->isPlayingChannel (midiChannel)) + { + if (isDown) + voice->setSostenutoPedalDown (true); + else if (voice->isSostenutoPedalDown()) + stopVoice (voice, 1.0f, true); + } + } +} + +void Synthesiser::handleSoftPedal (int midiChannel, bool /*isDown*/) +{ + ignoreUnused (midiChannel); + jassert (midiChannel > 0 && midiChannel <= 16); +} + +void Synthesiser::handleProgramChange (int midiChannel, int programNumber) +{ + ignoreUnused (midiChannel, programNumber); + jassert (midiChannel > 0 && midiChannel <= 16); +} + +//============================================================================== +SynthesiserVoice* Synthesiser::findFreeVoice (SynthesiserSound* soundToPlay, + int midiChannel, int midiNoteNumber, + const bool stealIfNoneAvailable) const +{ + const ScopedLock sl (lock); + + for (auto* voice : voices) + if ((! voice->isVoiceActive()) && voice->canPlaySound (soundToPlay)) + return voice; + + if (stealIfNoneAvailable) + return findVoiceToSteal (soundToPlay, midiChannel, midiNoteNumber); + + return nullptr; +} + +SynthesiserVoice* Synthesiser::findVoiceToSteal (SynthesiserSound* soundToPlay, + int /*midiChannel*/, int midiNoteNumber) const +{ + // This voice-stealing algorithm applies the following heuristics: + // - Re-use the oldest notes first + // - Protect the lowest & topmost notes, even if sustained, but not if they've been released. + + // apparently you are trying to render audio without having any voices... + jassert (! voices.isEmpty()); + + // These are the voices we want to protect (ie: only steal if unavoidable) + SynthesiserVoice* low = nullptr; // Lowest sounding note, might be sustained, but NOT in release phase + SynthesiserVoice* top = nullptr; // Highest sounding note, might be sustained, but NOT in release phase + + // this is a list of voices we can steal, sorted by how long they've been running + Array usableVoices; + usableVoices.ensureStorageAllocated (voices.size()); + + for (auto* voice : voices) + { + if (voice->canPlaySound (soundToPlay)) + { + jassert (voice->isVoiceActive()); // We wouldn't be here otherwise + + usableVoices.add (voice); + + // NB: Using a functor rather than a lambda here due to scare-stories about + // compilers generating code containing heap allocations.. + struct Sorter + { + bool operator() (const SynthesiserVoice* a, const SynthesiserVoice* b) const noexcept { return a->wasStartedBefore (*b); } + }; + + std::sort (usableVoices.begin(), usableVoices.end(), Sorter()); + + if (! voice->isPlayingButReleased()) // Don't protect released notes + { + auto note = voice->getCurrentlyPlayingNote(); + + if (low == nullptr || note < low->getCurrentlyPlayingNote()) + low = voice; + + if (top == nullptr || note > top->getCurrentlyPlayingNote()) + top = voice; + } + } + } + + // Eliminate pathological cases (ie: only 1 note playing): we always give precedence to the lowest note(s) + if (top == low) + top = nullptr; + + // The oldest note that's playing with the target pitch is ideal.. + for (auto* voice : usableVoices) + if (voice->getCurrentlyPlayingNote() == midiNoteNumber) + return voice; + + // Oldest voice that has been released (no finger on it and not held by sustain pedal) + for (auto* voice : usableVoices) + if (voice != low && voice != top && voice->isPlayingButReleased()) + return voice; + + // Oldest voice that doesn't have a finger on it: + for (auto* voice : usableVoices) + if (voice != low && voice != top && ! voice->isKeyDown()) + return voice; + + // Oldest voice that isn't protected + for (auto* voice : usableVoices) + if (voice != low && voice != top) + return voice; + + // We've only got "protected" voices now: lowest note takes priority + jassert (low != nullptr); + + // Duophonic synth: give priority to the bass note: + if (top != nullptr) + return top; + + return low; +} + +} // namespace juce diff --git a/JuceLibraryCode/modules/juce_audio_basics/synthesisers/juce_Synthesiser.h b/JuceLibraryCode/modules/juce_audio_basics/synthesisers/juce_Synthesiser.h index 7c39c81..f1836e1 100644 --- a/JuceLibraryCode/modules/juce_audio_basics/synthesisers/juce_Synthesiser.h +++ b/JuceLibraryCode/modules/juce_audio_basics/synthesisers/juce_Synthesiser.h @@ -1,645 +1,645 @@ -/* - ============================================================================== - - This file is part of the JUCE library. - Copyright (c) 2017 - ROLI Ltd. - - JUCE is an open source library subject to commercial or open-source - licensing. - - The code included in this file is provided under the terms of the ISC license - http://www.isc.org/downloads/software-support-policy/isc-license. Permission - To use, copy, modify, and/or distribute this software for any purpose with or - without fee is hereby granted provided that the above copyright notice and - this permission notice appear in all copies. - - JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER - EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE - DISCLAIMED. - - ============================================================================== -*/ - -namespace juce -{ - -//============================================================================== -/** - Describes one of the sounds that a Synthesiser can play. - - A synthesiser can contain one or more sounds, and a sound can choose which - midi notes and channels can trigger it. - - The SynthesiserSound is a passive class that just describes what the sound is - - the actual audio rendering for a sound is done by a SynthesiserVoice. This allows - more than one SynthesiserVoice to play the same sound at the same time. - - @see Synthesiser, SynthesiserVoice - - @tags{Audio} -*/ -class JUCE_API SynthesiserSound : public ReferenceCountedObject -{ -protected: - //============================================================================== - SynthesiserSound(); - -public: - /** Destructor. */ - ~SynthesiserSound() override; - - //============================================================================== - /** Returns true if this sound should be played when a given midi note is pressed. - - The Synthesiser will use this information when deciding which sounds to trigger - for a given note. - */ - virtual bool appliesToNote (int midiNoteNumber) = 0; - - /** Returns true if the sound should be triggered by midi events on a given channel. - - The Synthesiser will use this information when deciding which sounds to trigger - for a given note. - */ - virtual bool appliesToChannel (int midiChannel) = 0; - - /** The class is reference-counted, so this is a handy pointer class for it. */ - using Ptr = ReferenceCountedObjectPtr; - - -private: - //============================================================================== - JUCE_LEAK_DETECTOR (SynthesiserSound) -}; - - -//============================================================================== -/** - Represents a voice that a Synthesiser can use to play a SynthesiserSound. - - A voice plays a single sound at a time, and a synthesiser holds an array of - voices so that it can play polyphonically. - - @see Synthesiser, SynthesiserSound - - @tags{Audio} -*/ -class JUCE_API SynthesiserVoice -{ -public: - //============================================================================== - /** Creates a voice. */ - SynthesiserVoice(); - - /** Destructor. */ - virtual ~SynthesiserVoice(); - - //============================================================================== - /** Returns the midi note that this voice is currently playing. - Returns a value less than 0 if no note is playing. - */ - int getCurrentlyPlayingNote() const noexcept { return currentlyPlayingNote; } - - /** Returns the sound that this voice is currently playing. - Returns nullptr if it's not playing. - */ - SynthesiserSound::Ptr getCurrentlyPlayingSound() const noexcept { return currentlyPlayingSound; } - - /** Must return true if this voice object is capable of playing the given sound. - - If there are different classes of sound, and different classes of voice, a voice can - choose which ones it wants to take on. - - A typical implementation of this method may just return true if there's only one type - of voice and sound, or it might check the type of the sound object passed-in and - see if it's one that it understands. - */ - virtual bool canPlaySound (SynthesiserSound*) = 0; - - /** Called to start a new note. - This will be called during the rendering callback, so must be fast and thread-safe. - */ - virtual void startNote (int midiNoteNumber, - float velocity, - SynthesiserSound* sound, - int currentPitchWheelPosition) = 0; - - /** Called to stop a note. - - This will be called during the rendering callback, so must be fast and thread-safe. - - The velocity indicates how quickly the note was released - 0 is slowly, 1 is quickly. - - If allowTailOff is false or the voice doesn't want to tail-off, then it must stop all - sound immediately, and must call clearCurrentNote() to reset the state of this voice - and allow the synth to reassign it another sound. - - If allowTailOff is true and the voice decides to do a tail-off, then it's allowed to - begin fading out its sound, and it can stop playing until it's finished. As soon as it - finishes playing (during the rendering callback), it must make sure that it calls - clearCurrentNote(). - */ - virtual void stopNote (float velocity, bool allowTailOff) = 0; - - /** Returns true if this voice is currently busy playing a sound. - By default this just checks the getCurrentlyPlayingNote() value, but can - be overridden for more advanced checking. - */ - virtual bool isVoiceActive() const; - - /** Called to let the voice know that the pitch wheel has been moved. - This will be called during the rendering callback, so must be fast and thread-safe. - */ - virtual void pitchWheelMoved (int newPitchWheelValue) = 0; - - /** Called to let the voice know that a midi controller has been moved. - This will be called during the rendering callback, so must be fast and thread-safe. - */ - virtual void controllerMoved (int controllerNumber, int newControllerValue) = 0; - - /** Called to let the voice know that the aftertouch has changed. - This will be called during the rendering callback, so must be fast and thread-safe. - */ - virtual void aftertouchChanged (int newAftertouchValue); - - /** Called to let the voice know that the channel pressure has changed. - This will be called during the rendering callback, so must be fast and thread-safe. - */ - virtual void channelPressureChanged (int newChannelPressureValue); - - //============================================================================== - /** Renders the next block of data for this voice. - - The output audio data must be added to the current contents of the buffer provided. - Only the region of the buffer between startSample and (startSample + numSamples) - should be altered by this method. - - If the voice is currently silent, it should just return without doing anything. - - If the sound that the voice is playing finishes during the course of this rendered - block, it must call clearCurrentNote(), to tell the synthesiser that it has finished. - - The size of the blocks that are rendered can change each time it is called, and may - involve rendering as little as 1 sample at a time. In between rendering callbacks, - the voice's methods will be called to tell it about note and controller events. - */ - virtual void renderNextBlock (AudioBuffer& outputBuffer, - int startSample, - int numSamples) = 0; - - /** A double-precision version of renderNextBlock() */ - virtual void renderNextBlock (AudioBuffer& outputBuffer, - int startSample, - int numSamples); - - /** Changes the voice's reference sample rate. - - The rate is set so that subclasses know the output rate and can set their pitch - accordingly. - - This method is called by the synth, and subclasses can access the current rate with - the currentSampleRate member. - */ - virtual void setCurrentPlaybackSampleRate (double newRate); - - /** Returns true if the voice is currently playing a sound which is mapped to the given - midi channel. - - If it's not currently playing, this will return false. - */ - virtual bool isPlayingChannel (int midiChannel) const; - - /** Returns the current target sample rate at which rendering is being done. - Subclasses may need to know this so that they can pitch things correctly. - */ - double getSampleRate() const noexcept { return currentSampleRate; } - - /** Returns true if the key that triggered this voice is still held down. - Note that the voice may still be playing after the key was released (e.g because the - sostenuto pedal is down). - */ - bool isKeyDown() const noexcept { return keyIsDown; } - - /** Allows you to modify the flag indicating that the key that triggered this voice is still held down. - @see isKeyDown - */ - void setKeyDown (bool isNowDown) noexcept { keyIsDown = isNowDown; } - - /** Returns true if the sustain pedal is currently active for this voice. */ - bool isSustainPedalDown() const noexcept { return sustainPedalDown; } - - /** Modifies the sustain pedal flag. */ - void setSustainPedalDown (bool isNowDown) noexcept { sustainPedalDown = isNowDown; } - - /** Returns true if the sostenuto pedal is currently active for this voice. */ - bool isSostenutoPedalDown() const noexcept { return sostenutoPedalDown; } - - /** Modifies the sostenuto pedal flag. */ - void setSostenutoPedalDown (bool isNowDown) noexcept { sostenutoPedalDown = isNowDown; } - - /** Returns true if a voice is sounding in its release phase **/ - bool isPlayingButReleased() const noexcept - { - return isVoiceActive() && ! (isKeyDown() || isSostenutoPedalDown() || isSustainPedalDown()); - } - - /** Returns true if this voice started playing its current note before the other voice did. */ - bool wasStartedBefore (const SynthesiserVoice& other) const noexcept; - -protected: - /** Resets the state of this voice after a sound has finished playing. - - The subclass must call this when it finishes playing a note and becomes available - to play new ones. - - It must either call it in the stopNote() method, or if the voice is tailing off, - then it should call it later during the renderNextBlock method, as soon as it - finishes its tail-off. - - It can also be called at any time during the render callback if the sound happens - to have finished, e.g. if it's playing a sample and the sample finishes. - */ - void clearCurrentNote(); - - -private: - //============================================================================== - friend class Synthesiser; - - double currentSampleRate = 44100.0; - int currentlyPlayingNote = -1, currentPlayingMidiChannel = 0; - uint32 noteOnTime = 0; - SynthesiserSound::Ptr currentlyPlayingSound; - bool keyIsDown = false, sustainPedalDown = false, sostenutoPedalDown = false; - - AudioBuffer tempBuffer; - - JUCE_LEAK_DETECTOR (SynthesiserVoice) -}; - - -//============================================================================== -/** - Base class for a musical device that can play sounds. - - To create a synthesiser, you'll need to create a subclass of SynthesiserSound - to describe each sound available to your synth, and a subclass of SynthesiserVoice - which can play back one of these sounds. - - Then you can use the addVoice() and addSound() methods to give the synthesiser a - set of sounds, and a set of voices it can use to play them. If you only give it - one voice it will be monophonic - the more voices it has, the more polyphony it'll - have available. - - Then repeatedly call the renderNextBlock() method to produce the audio. Any midi - events that go in will be scanned for note on/off messages, and these are used to - start and stop the voices playing the appropriate sounds. - - While it's playing, you can also cause notes to be triggered by calling the noteOn(), - noteOff() and other controller methods. - - Before rendering, be sure to call the setCurrentPlaybackSampleRate() to tell it - what the target playback rate is. This value is passed on to the voices so that - they can pitch their output correctly. - - @tags{Audio} -*/ -class JUCE_API Synthesiser -{ -public: - //============================================================================== - /** Creates a new synthesiser. - You'll need to add some sounds and voices before it'll make any sound. - */ - Synthesiser(); - - /** Destructor. */ - virtual ~Synthesiser(); - - //============================================================================== - /** Deletes all voices. */ - void clearVoices(); - - /** Returns the number of voices that have been added. */ - int getNumVoices() const noexcept { return voices.size(); } - - /** Returns one of the voices that have been added. */ - SynthesiserVoice* getVoice (int index) const; - - /** Adds a new voice to the synth. - - All the voices should be the same class of object and are treated equally. - - The object passed in will be managed by the synthesiser, which will delete - it later on when no longer needed. The caller should not retain a pointer to the - voice. - */ - SynthesiserVoice* addVoice (SynthesiserVoice* newVoice); - - /** Deletes one of the voices. */ - void removeVoice (int index); - - //============================================================================== - /** Deletes all sounds. */ - void clearSounds(); - - /** Returns the number of sounds that have been added to the synth. */ - int getNumSounds() const noexcept { return sounds.size(); } - - /** Returns one of the sounds. */ - SynthesiserSound::Ptr getSound (int index) const noexcept { return sounds[index]; } - - /** Adds a new sound to the synthesiser. - - The object passed in is reference counted, so will be deleted when the - synthesiser and all voices are no longer using it. - */ - SynthesiserSound* addSound (const SynthesiserSound::Ptr& newSound); - - /** Removes and deletes one of the sounds. */ - void removeSound (int index); - - //============================================================================== - /** If set to true, then the synth will try to take over an existing voice if - it runs out and needs to play another note. - - The value of this boolean is passed into findFreeVoice(), so the result will - depend on the implementation of this method. - */ - void setNoteStealingEnabled (bool shouldStealNotes); - - /** Returns true if note-stealing is enabled. - @see setNoteStealingEnabled - */ - bool isNoteStealingEnabled() const noexcept { return shouldStealNotes; } - - //============================================================================== - /** Triggers a note-on event. - - The default method here will find all the sounds that want to be triggered by - this note/channel. For each sound, it'll try to find a free voice, and use the - voice to start playing the sound. - - Subclasses might want to override this if they need a more complex algorithm. - - This method will be called automatically according to the midi data passed into - renderNextBlock(), but may be called explicitly too. - - The midiChannel parameter is the channel, between 1 and 16 inclusive. - */ - virtual void noteOn (int midiChannel, - int midiNoteNumber, - float velocity); - - /** Triggers a note-off event. - - This will turn off any voices that are playing a sound for the given note/channel. - - If allowTailOff is true, the voices will be allowed to fade out the notes gracefully - (if they can do). If this is false, the notes will all be cut off immediately. - - This method will be called automatically according to the midi data passed into - renderNextBlock(), but may be called explicitly too. - - The midiChannel parameter is the channel, between 1 and 16 inclusive. - */ - virtual void noteOff (int midiChannel, - int midiNoteNumber, - float velocity, - bool allowTailOff); - - /** Turns off all notes. - - This will turn off any voices that are playing a sound on the given midi channel. - - If midiChannel is 0 or less, then all voices will be turned off, regardless of - which channel they're playing. Otherwise it represents a valid midi channel, from - 1 to 16 inclusive. - - If allowTailOff is true, the voices will be allowed to fade out the notes gracefully - (if they can do). If this is false, the notes will all be cut off immediately. - - This method will be called automatically according to the midi data passed into - renderNextBlock(), but may be called explicitly too. - */ - virtual void allNotesOff (int midiChannel, - bool allowTailOff); - - /** Sends a pitch-wheel message to any active voices. - - This will send a pitch-wheel message to any voices that are playing sounds on - the given midi channel. - - This method will be called automatically according to the midi data passed into - renderNextBlock(), but may be called explicitly too. - - @param midiChannel the midi channel, from 1 to 16 inclusive - @param wheelValue the wheel position, from 0 to 0x3fff, as returned by MidiMessage::getPitchWheelValue() - */ - virtual void handlePitchWheel (int midiChannel, - int wheelValue); - - /** Sends a midi controller message to any active voices. - - This will send a midi controller message to any voices that are playing sounds on - the given midi channel. - - This method will be called automatically according to the midi data passed into - renderNextBlock(), but may be called explicitly too. - - @param midiChannel the midi channel, from 1 to 16 inclusive - @param controllerNumber the midi controller type, as returned by MidiMessage::getControllerNumber() - @param controllerValue the midi controller value, between 0 and 127, as returned by MidiMessage::getControllerValue() - */ - virtual void handleController (int midiChannel, - int controllerNumber, - int controllerValue); - - /** Sends an aftertouch message. - - This will send an aftertouch message to any voices that are playing sounds on - the given midi channel and note number. - - This method will be called automatically according to the midi data passed into - renderNextBlock(), but may be called explicitly too. - - @param midiChannel the midi channel, from 1 to 16 inclusive - @param midiNoteNumber the midi note number, 0 to 127 - @param aftertouchValue the aftertouch value, between 0 and 127, - as returned by MidiMessage::getAftertouchValue() - */ - virtual void handleAftertouch (int midiChannel, int midiNoteNumber, int aftertouchValue); - - /** Sends a channel pressure message. - - This will send a channel pressure message to any voices that are playing sounds on - the given midi channel. - - This method will be called automatically according to the midi data passed into - renderNextBlock(), but may be called explicitly too. - - @param midiChannel the midi channel, from 1 to 16 inclusive - @param channelPressureValue the pressure value, between 0 and 127, as returned - by MidiMessage::getChannelPressureValue() - */ - virtual void handleChannelPressure (int midiChannel, int channelPressureValue); - - /** Handles a sustain pedal event. */ - virtual void handleSustainPedal (int midiChannel, bool isDown); - - /** Handles a sostenuto pedal event. */ - virtual void handleSostenutoPedal (int midiChannel, bool isDown); - - /** Can be overridden to handle soft pedal events. */ - virtual void handleSoftPedal (int midiChannel, bool isDown); - - /** Can be overridden to handle an incoming program change message. - The base class implementation of this has no effect, but you may want to make your - own synth react to program changes. - */ - virtual void handleProgramChange (int midiChannel, - int programNumber); - - //============================================================================== - /** Tells the synthesiser what the sample rate is for the audio it's being used to render. - - This value is propagated to the voices so that they can use it to render the correct - pitches. - */ - virtual void setCurrentPlaybackSampleRate (double sampleRate); - - /** Creates the next block of audio output. - - This will process the next numSamples of data from all the voices, and add that output - to the audio block supplied, starting from the offset specified. Note that the - data will be added to the current contents of the buffer, so you should clear it - before calling this method if necessary. - - The midi events in the inputMidi buffer are parsed for note and controller events, - and these are used to trigger the voices. Note that the startSample offset applies - both to the audio output buffer and the midi input buffer, so any midi events - with timestamps outside the specified region will be ignored. - */ - void renderNextBlock (AudioBuffer& outputAudio, - const MidiBuffer& inputMidi, - int startSample, - int numSamples); - - void renderNextBlock (AudioBuffer& outputAudio, - const MidiBuffer& inputMidi, - int startSample, - int numSamples); - - /** Returns the current target sample rate at which rendering is being done. - Subclasses may need to know this so that they can pitch things correctly. - */ - double getSampleRate() const noexcept { return sampleRate; } - - /** Sets a minimum limit on the size to which audio sub-blocks will be divided when rendering. - - When rendering, the audio blocks that are passed into renderNextBlock() will be split up - into smaller blocks that lie between all the incoming midi messages, and it is these smaller - sub-blocks that are rendered with multiple calls to renderVoices(). - - Obviously in a pathological case where there are midi messages on every sample, then - renderVoices() could be called once per sample and lead to poor performance, so this - setting allows you to set a lower limit on the block size. - - The default setting is 32, which means that midi messages are accurate to about < 1ms - accuracy, which is probably fine for most purposes, but you may want to increase or - decrease this value for your synth. - - If shouldBeStrict is true, the audio sub-blocks will strictly never be smaller than numSamples. - - If shouldBeStrict is false (default), the first audio sub-block in the buffer is allowed - to be smaller, to make sure that the first MIDI event in a buffer will always be sample-accurate - (this can sometimes help to avoid quantisation or phasing issues). - */ - void setMinimumRenderingSubdivisionSize (int numSamples, bool shouldBeStrict = false) noexcept; - -protected: - //============================================================================== - /** This is used to control access to the rendering callback and the note trigger methods. */ - CriticalSection lock; - - OwnedArray voices; - ReferenceCountedArray sounds; - - /** The last pitch-wheel values for each midi channel. */ - int lastPitchWheelValues [16]; - - /** Renders the voices for the given range. - By default this just calls renderNextBlock() on each voice, but you may need - to override it to handle custom cases. - */ - virtual void renderVoices (AudioBuffer& outputAudio, - int startSample, int numSamples); - virtual void renderVoices (AudioBuffer& outputAudio, - int startSample, int numSamples); - - /** Searches through the voices to find one that's not currently playing, and - which can play the given sound. - - Returns nullptr if all voices are busy and stealing isn't enabled. - - To implement a custom note-stealing algorithm, you can either override this - method, or (preferably) override findVoiceToSteal(). - */ - virtual SynthesiserVoice* findFreeVoice (SynthesiserSound* soundToPlay, - int midiChannel, - int midiNoteNumber, - bool stealIfNoneAvailable) const; - - /** Chooses a voice that is most suitable for being re-used. - The default method will attempt to find the oldest voice that isn't the - bottom or top note being played. If that's not suitable for your synth, - you can override this method and do something more cunning instead. - */ - virtual SynthesiserVoice* findVoiceToSteal (SynthesiserSound* soundToPlay, - int midiChannel, - int midiNoteNumber) const; - - /** Starts a specified voice playing a particular sound. - You'll probably never need to call this, it's used internally by noteOn(), but - may be needed by subclasses for custom behaviours. - */ - void startVoice (SynthesiserVoice* voice, - SynthesiserSound* sound, - int midiChannel, - int midiNoteNumber, - float velocity); - - /** Stops a given voice. - You should never need to call this, it's used internally by noteOff, but is protected - in case it's useful for some custom subclasses. It basically just calls through to - SynthesiserVoice::stopNote(), and has some assertions to sanity-check a few things. - */ - void stopVoice (SynthesiserVoice*, float velocity, bool allowTailOff); - - /** Can be overridden to do custom handling of incoming midi events. */ - virtual void handleMidiEvent (const MidiMessage&); - -private: - //============================================================================== - double sampleRate = 0; - uint32 lastNoteOnCounter = 0; - int minimumSubBlockSize = 32; - bool subBlockSubdivisionIsStrict = false; - bool shouldStealNotes = true; - BigInteger sustainPedalsDown; - - template - void processNextBlock (AudioBuffer&, const MidiBuffer&, int startSample, int numSamples); - - #if JUCE_CATCH_DEPRECATED_CODE_MISUSE - // Note the new parameters for these methods. - virtual int findFreeVoice (const bool) const { return 0; } - virtual int noteOff (int, int, int) { return 0; } - virtual int findFreeVoice (SynthesiserSound*, const bool) { return 0; } - virtual int findVoiceToSteal (SynthesiserSound*) const { return 0; } - #endif - - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Synthesiser) -}; - -} // namespace juce +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2017 - ROLI Ltd. + + JUCE is an open source library subject to commercial or open-source + licensing. + + The code included in this file is provided under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license. Permission + To use, copy, modify, and/or distribute this software for any purpose with or + without fee is hereby granted provided that the above copyright notice and + this permission notice appear in all copies. + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +namespace juce +{ + +//============================================================================== +/** + Describes one of the sounds that a Synthesiser can play. + + A synthesiser can contain one or more sounds, and a sound can choose which + midi notes and channels can trigger it. + + The SynthesiserSound is a passive class that just describes what the sound is - + the actual audio rendering for a sound is done by a SynthesiserVoice. This allows + more than one SynthesiserVoice to play the same sound at the same time. + + @see Synthesiser, SynthesiserVoice + + @tags{Audio} +*/ +class JUCE_API SynthesiserSound : public ReferenceCountedObject +{ +protected: + //============================================================================== + SynthesiserSound(); + +public: + /** Destructor. */ + ~SynthesiserSound() override; + + //============================================================================== + /** Returns true if this sound should be played when a given midi note is pressed. + + The Synthesiser will use this information when deciding which sounds to trigger + for a given note. + */ + virtual bool appliesToNote (int midiNoteNumber) = 0; + + /** Returns true if the sound should be triggered by midi events on a given channel. + + The Synthesiser will use this information when deciding which sounds to trigger + for a given note. + */ + virtual bool appliesToChannel (int midiChannel) = 0; + + /** The class is reference-counted, so this is a handy pointer class for it. */ + using Ptr = ReferenceCountedObjectPtr; + + +private: + //============================================================================== + JUCE_LEAK_DETECTOR (SynthesiserSound) +}; + + +//============================================================================== +/** + Represents a voice that a Synthesiser can use to play a SynthesiserSound. + + A voice plays a single sound at a time, and a synthesiser holds an array of + voices so that it can play polyphonically. + + @see Synthesiser, SynthesiserSound + + @tags{Audio} +*/ +class JUCE_API SynthesiserVoice +{ +public: + //============================================================================== + /** Creates a voice. */ + SynthesiserVoice(); + + /** Destructor. */ + virtual ~SynthesiserVoice(); + + //============================================================================== + /** Returns the midi note that this voice is currently playing. + Returns a value less than 0 if no note is playing. + */ + int getCurrentlyPlayingNote() const noexcept { return currentlyPlayingNote; } + + /** Returns the sound that this voice is currently playing. + Returns nullptr if it's not playing. + */ + SynthesiserSound::Ptr getCurrentlyPlayingSound() const noexcept { return currentlyPlayingSound; } + + /** Must return true if this voice object is capable of playing the given sound. + + If there are different classes of sound, and different classes of voice, a voice can + choose which ones it wants to take on. + + A typical implementation of this method may just return true if there's only one type + of voice and sound, or it might check the type of the sound object passed-in and + see if it's one that it understands. + */ + virtual bool canPlaySound (SynthesiserSound*) = 0; + + /** Called to start a new note. + This will be called during the rendering callback, so must be fast and thread-safe. + */ + virtual void startNote (int midiNoteNumber, + float velocity, + SynthesiserSound* sound, + int currentPitchWheelPosition) = 0; + + /** Called to stop a note. + + This will be called during the rendering callback, so must be fast and thread-safe. + + The velocity indicates how quickly the note was released - 0 is slowly, 1 is quickly. + + If allowTailOff is false or the voice doesn't want to tail-off, then it must stop all + sound immediately, and must call clearCurrentNote() to reset the state of this voice + and allow the synth to reassign it another sound. + + If allowTailOff is true and the voice decides to do a tail-off, then it's allowed to + begin fading out its sound, and it can stop playing until it's finished. As soon as it + finishes playing (during the rendering callback), it must make sure that it calls + clearCurrentNote(). + */ + virtual void stopNote (float velocity, bool allowTailOff) = 0; + + /** Returns true if this voice is currently busy playing a sound. + By default this just checks the getCurrentlyPlayingNote() value, but can + be overridden for more advanced checking. + */ + virtual bool isVoiceActive() const; + + /** Called to let the voice know that the pitch wheel has been moved. + This will be called during the rendering callback, so must be fast and thread-safe. + */ + virtual void pitchWheelMoved (int newPitchWheelValue) = 0; + + /** Called to let the voice know that a midi controller has been moved. + This will be called during the rendering callback, so must be fast and thread-safe. + */ + virtual void controllerMoved (int controllerNumber, int newControllerValue) = 0; + + /** Called to let the voice know that the aftertouch has changed. + This will be called during the rendering callback, so must be fast and thread-safe. + */ + virtual void aftertouchChanged (int newAftertouchValue); + + /** Called to let the voice know that the channel pressure has changed. + This will be called during the rendering callback, so must be fast and thread-safe. + */ + virtual void channelPressureChanged (int newChannelPressureValue); + + //============================================================================== + /** Renders the next block of data for this voice. + + The output audio data must be added to the current contents of the buffer provided. + Only the region of the buffer between startSample and (startSample + numSamples) + should be altered by this method. + + If the voice is currently silent, it should just return without doing anything. + + If the sound that the voice is playing finishes during the course of this rendered + block, it must call clearCurrentNote(), to tell the synthesiser that it has finished. + + The size of the blocks that are rendered can change each time it is called, and may + involve rendering as little as 1 sample at a time. In between rendering callbacks, + the voice's methods will be called to tell it about note and controller events. + */ + virtual void renderNextBlock (AudioBuffer& outputBuffer, + int startSample, + int numSamples) = 0; + + /** A double-precision version of renderNextBlock() */ + virtual void renderNextBlock (AudioBuffer& outputBuffer, + int startSample, + int numSamples); + + /** Changes the voice's reference sample rate. + + The rate is set so that subclasses know the output rate and can set their pitch + accordingly. + + This method is called by the synth, and subclasses can access the current rate with + the currentSampleRate member. + */ + virtual void setCurrentPlaybackSampleRate (double newRate); + + /** Returns true if the voice is currently playing a sound which is mapped to the given + midi channel. + + If it's not currently playing, this will return false. + */ + virtual bool isPlayingChannel (int midiChannel) const; + + /** Returns the current target sample rate at which rendering is being done. + Subclasses may need to know this so that they can pitch things correctly. + */ + double getSampleRate() const noexcept { return currentSampleRate; } + + /** Returns true if the key that triggered this voice is still held down. + Note that the voice may still be playing after the key was released (e.g because the + sostenuto pedal is down). + */ + bool isKeyDown() const noexcept { return keyIsDown; } + + /** Allows you to modify the flag indicating that the key that triggered this voice is still held down. + @see isKeyDown + */ + void setKeyDown (bool isNowDown) noexcept { keyIsDown = isNowDown; } + + /** Returns true if the sustain pedal is currently active for this voice. */ + bool isSustainPedalDown() const noexcept { return sustainPedalDown; } + + /** Modifies the sustain pedal flag. */ + void setSustainPedalDown (bool isNowDown) noexcept { sustainPedalDown = isNowDown; } + + /** Returns true if the sostenuto pedal is currently active for this voice. */ + bool isSostenutoPedalDown() const noexcept { return sostenutoPedalDown; } + + /** Modifies the sostenuto pedal flag. */ + void setSostenutoPedalDown (bool isNowDown) noexcept { sostenutoPedalDown = isNowDown; } + + /** Returns true if a voice is sounding in its release phase **/ + bool isPlayingButReleased() const noexcept + { + return isVoiceActive() && ! (isKeyDown() || isSostenutoPedalDown() || isSustainPedalDown()); + } + + /** Returns true if this voice started playing its current note before the other voice did. */ + bool wasStartedBefore (const SynthesiserVoice& other) const noexcept; + +protected: + /** Resets the state of this voice after a sound has finished playing. + + The subclass must call this when it finishes playing a note and becomes available + to play new ones. + + It must either call it in the stopNote() method, or if the voice is tailing off, + then it should call it later during the renderNextBlock method, as soon as it + finishes its tail-off. + + It can also be called at any time during the render callback if the sound happens + to have finished, e.g. if it's playing a sample and the sample finishes. + */ + void clearCurrentNote(); + + +private: + //============================================================================== + friend class Synthesiser; + + double currentSampleRate = 44100.0; + int currentlyPlayingNote = -1, currentPlayingMidiChannel = 0; + uint32 noteOnTime = 0; + SynthesiserSound::Ptr currentlyPlayingSound; + bool keyIsDown = false, sustainPedalDown = false, sostenutoPedalDown = false; + + AudioBuffer tempBuffer; + + JUCE_LEAK_DETECTOR (SynthesiserVoice) +}; + + +//============================================================================== +/** + Base class for a musical device that can play sounds. + + To create a synthesiser, you'll need to create a subclass of SynthesiserSound + to describe each sound available to your synth, and a subclass of SynthesiserVoice + which can play back one of these sounds. + + Then you can use the addVoice() and addSound() methods to give the synthesiser a + set of sounds, and a set of voices it can use to play them. If you only give it + one voice it will be monophonic - the more voices it has, the more polyphony it'll + have available. + + Then repeatedly call the renderNextBlock() method to produce the audio. Any midi + events that go in will be scanned for note on/off messages, and these are used to + start and stop the voices playing the appropriate sounds. + + While it's playing, you can also cause notes to be triggered by calling the noteOn(), + noteOff() and other controller methods. + + Before rendering, be sure to call the setCurrentPlaybackSampleRate() to tell it + what the target playback rate is. This value is passed on to the voices so that + they can pitch their output correctly. + + @tags{Audio} +*/ +class JUCE_API Synthesiser +{ +public: + //============================================================================== + /** Creates a new synthesiser. + You'll need to add some sounds and voices before it'll make any sound. + */ + Synthesiser(); + + /** Destructor. */ + virtual ~Synthesiser(); + + //============================================================================== + /** Deletes all voices. */ + void clearVoices(); + + /** Returns the number of voices that have been added. */ + int getNumVoices() const noexcept { return voices.size(); } + + /** Returns one of the voices that have been added. */ + SynthesiserVoice* getVoice (int index) const; + + /** Adds a new voice to the synth. + + All the voices should be the same class of object and are treated equally. + + The object passed in will be managed by the synthesiser, which will delete + it later on when no longer needed. The caller should not retain a pointer to the + voice. + */ + SynthesiserVoice* addVoice (SynthesiserVoice* newVoice); + + /** Deletes one of the voices. */ + void removeVoice (int index); + + //============================================================================== + /** Deletes all sounds. */ + void clearSounds(); + + /** Returns the number of sounds that have been added to the synth. */ + int getNumSounds() const noexcept { return sounds.size(); } + + /** Returns one of the sounds. */ + SynthesiserSound::Ptr getSound (int index) const noexcept { return sounds[index]; } + + /** Adds a new sound to the synthesiser. + + The object passed in is reference counted, so will be deleted when the + synthesiser and all voices are no longer using it. + */ + SynthesiserSound* addSound (const SynthesiserSound::Ptr& newSound); + + /** Removes and deletes one of the sounds. */ + void removeSound (int index); + + //============================================================================== + /** If set to true, then the synth will try to take over an existing voice if + it runs out and needs to play another note. + + The value of this boolean is passed into findFreeVoice(), so the result will + depend on the implementation of this method. + */ + void setNoteStealingEnabled (bool shouldStealNotes); + + /** Returns true if note-stealing is enabled. + @see setNoteStealingEnabled + */ + bool isNoteStealingEnabled() const noexcept { return shouldStealNotes; } + + //============================================================================== + /** Triggers a note-on event. + + The default method here will find all the sounds that want to be triggered by + this note/channel. For each sound, it'll try to find a free voice, and use the + voice to start playing the sound. + + Subclasses might want to override this if they need a more complex algorithm. + + This method will be called automatically according to the midi data passed into + renderNextBlock(), but may be called explicitly too. + + The midiChannel parameter is the channel, between 1 and 16 inclusive. + */ + virtual void noteOn (int midiChannel, + int midiNoteNumber, + float velocity); + + /** Triggers a note-off event. + + This will turn off any voices that are playing a sound for the given note/channel. + + If allowTailOff is true, the voices will be allowed to fade out the notes gracefully + (if they can do). If this is false, the notes will all be cut off immediately. + + This method will be called automatically according to the midi data passed into + renderNextBlock(), but may be called explicitly too. + + The midiChannel parameter is the channel, between 1 and 16 inclusive. + */ + virtual void noteOff (int midiChannel, + int midiNoteNumber, + float velocity, + bool allowTailOff); + + /** Turns off all notes. + + This will turn off any voices that are playing a sound on the given midi channel. + + If midiChannel is 0 or less, then all voices will be turned off, regardless of + which channel they're playing. Otherwise it represents a valid midi channel, from + 1 to 16 inclusive. + + If allowTailOff is true, the voices will be allowed to fade out the notes gracefully + (if they can do). If this is false, the notes will all be cut off immediately. + + This method will be called automatically according to the midi data passed into + renderNextBlock(), but may be called explicitly too. + */ + virtual void allNotesOff (int midiChannel, + bool allowTailOff); + + /** Sends a pitch-wheel message to any active voices. + + This will send a pitch-wheel message to any voices that are playing sounds on + the given midi channel. + + This method will be called automatically according to the midi data passed into + renderNextBlock(), but may be called explicitly too. + + @param midiChannel the midi channel, from 1 to 16 inclusive + @param wheelValue the wheel position, from 0 to 0x3fff, as returned by MidiMessage::getPitchWheelValue() + */ + virtual void handlePitchWheel (int midiChannel, + int wheelValue); + + /** Sends a midi controller message to any active voices. + + This will send a midi controller message to any voices that are playing sounds on + the given midi channel. + + This method will be called automatically according to the midi data passed into + renderNextBlock(), but may be called explicitly too. + + @param midiChannel the midi channel, from 1 to 16 inclusive + @param controllerNumber the midi controller type, as returned by MidiMessage::getControllerNumber() + @param controllerValue the midi controller value, between 0 and 127, as returned by MidiMessage::getControllerValue() + */ + virtual void handleController (int midiChannel, + int controllerNumber, + int controllerValue); + + /** Sends an aftertouch message. + + This will send an aftertouch message to any voices that are playing sounds on + the given midi channel and note number. + + This method will be called automatically according to the midi data passed into + renderNextBlock(), but may be called explicitly too. + + @param midiChannel the midi channel, from 1 to 16 inclusive + @param midiNoteNumber the midi note number, 0 to 127 + @param aftertouchValue the aftertouch value, between 0 and 127, + as returned by MidiMessage::getAftertouchValue() + */ + virtual void handleAftertouch (int midiChannel, int midiNoteNumber, int aftertouchValue); + + /** Sends a channel pressure message. + + This will send a channel pressure message to any voices that are playing sounds on + the given midi channel. + + This method will be called automatically according to the midi data passed into + renderNextBlock(), but may be called explicitly too. + + @param midiChannel the midi channel, from 1 to 16 inclusive + @param channelPressureValue the pressure value, between 0 and 127, as returned + by MidiMessage::getChannelPressureValue() + */ + virtual void handleChannelPressure (int midiChannel, int channelPressureValue); + + /** Handles a sustain pedal event. */ + virtual void handleSustainPedal (int midiChannel, bool isDown); + + /** Handles a sostenuto pedal event. */ + virtual void handleSostenutoPedal (int midiChannel, bool isDown); + + /** Can be overridden to handle soft pedal events. */ + virtual void handleSoftPedal (int midiChannel, bool isDown); + + /** Can be overridden to handle an incoming program change message. + The base class implementation of this has no effect, but you may want to make your + own synth react to program changes. + */ + virtual void handleProgramChange (int midiChannel, + int programNumber); + + //============================================================================== + /** Tells the synthesiser what the sample rate is for the audio it's being used to render. + + This value is propagated to the voices so that they can use it to render the correct + pitches. + */ + virtual void setCurrentPlaybackSampleRate (double sampleRate); + + /** Creates the next block of audio output. + + This will process the next numSamples of data from all the voices, and add that output + to the audio block supplied, starting from the offset specified. Note that the + data will be added to the current contents of the buffer, so you should clear it + before calling this method if necessary. + + The midi events in the inputMidi buffer are parsed for note and controller events, + and these are used to trigger the voices. Note that the startSample offset applies + both to the audio output buffer and the midi input buffer, so any midi events + with timestamps outside the specified region will be ignored. + */ + void renderNextBlock (AudioBuffer& outputAudio, + const MidiBuffer& inputMidi, + int startSample, + int numSamples); + + void renderNextBlock (AudioBuffer& outputAudio, + const MidiBuffer& inputMidi, + int startSample, + int numSamples); + + /** Returns the current target sample rate at which rendering is being done. + Subclasses may need to know this so that they can pitch things correctly. + */ + double getSampleRate() const noexcept { return sampleRate; } + + /** Sets a minimum limit on the size to which audio sub-blocks will be divided when rendering. + + When rendering, the audio blocks that are passed into renderNextBlock() will be split up + into smaller blocks that lie between all the incoming midi messages, and it is these smaller + sub-blocks that are rendered with multiple calls to renderVoices(). + + Obviously in a pathological case where there are midi messages on every sample, then + renderVoices() could be called once per sample and lead to poor performance, so this + setting allows you to set a lower limit on the block size. + + The default setting is 32, which means that midi messages are accurate to about < 1ms + accuracy, which is probably fine for most purposes, but you may want to increase or + decrease this value for your synth. + + If shouldBeStrict is true, the audio sub-blocks will strictly never be smaller than numSamples. + + If shouldBeStrict is false (default), the first audio sub-block in the buffer is allowed + to be smaller, to make sure that the first MIDI event in a buffer will always be sample-accurate + (this can sometimes help to avoid quantisation or phasing issues). + */ + void setMinimumRenderingSubdivisionSize (int numSamples, bool shouldBeStrict = false) noexcept; + +protected: + //============================================================================== + /** This is used to control access to the rendering callback and the note trigger methods. */ + CriticalSection lock; + + OwnedArray voices; + ReferenceCountedArray sounds; + + /** The last pitch-wheel values for each midi channel. */ + int lastPitchWheelValues [16]; + + /** Renders the voices for the given range. + By default this just calls renderNextBlock() on each voice, but you may need + to override it to handle custom cases. + */ + virtual void renderVoices (AudioBuffer& outputAudio, + int startSample, int numSamples); + virtual void renderVoices (AudioBuffer& outputAudio, + int startSample, int numSamples); + + /** Searches through the voices to find one that's not currently playing, and + which can play the given sound. + + Returns nullptr if all voices are busy and stealing isn't enabled. + + To implement a custom note-stealing algorithm, you can either override this + method, or (preferably) override findVoiceToSteal(). + */ + virtual SynthesiserVoice* findFreeVoice (SynthesiserSound* soundToPlay, + int midiChannel, + int midiNoteNumber, + bool stealIfNoneAvailable) const; + + /** Chooses a voice that is most suitable for being re-used. + The default method will attempt to find the oldest voice that isn't the + bottom or top note being played. If that's not suitable for your synth, + you can override this method and do something more cunning instead. + */ + virtual SynthesiserVoice* findVoiceToSteal (SynthesiserSound* soundToPlay, + int midiChannel, + int midiNoteNumber) const; + + /** Starts a specified voice playing a particular sound. + You'll probably never need to call this, it's used internally by noteOn(), but + may be needed by subclasses for custom behaviours. + */ + void startVoice (SynthesiserVoice* voice, + SynthesiserSound* sound, + int midiChannel, + int midiNoteNumber, + float velocity); + + /** Stops a given voice. + You should never need to call this, it's used internally by noteOff, but is protected + in case it's useful for some custom subclasses. It basically just calls through to + SynthesiserVoice::stopNote(), and has some assertions to sanity-check a few things. + */ + void stopVoice (SynthesiserVoice*, float velocity, bool allowTailOff); + + /** Can be overridden to do custom handling of incoming midi events. */ + virtual void handleMidiEvent (const MidiMessage&); + +private: + //============================================================================== + double sampleRate = 0; + uint32 lastNoteOnCounter = 0; + int minimumSubBlockSize = 32; + bool subBlockSubdivisionIsStrict = false; + bool shouldStealNotes = true; + BigInteger sustainPedalsDown; + + template + void processNextBlock (AudioBuffer&, const MidiBuffer&, int startSample, int numSamples); + + #if JUCE_CATCH_DEPRECATED_CODE_MISUSE + // Note the new parameters for these methods. + virtual int findFreeVoice (const bool) const { return 0; } + virtual int noteOff (int, int, int) { return 0; } + virtual int findFreeVoice (SynthesiserSound*, const bool) { return 0; } + virtual int findVoiceToSteal (SynthesiserSound*) const { return 0; } + #endif + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Synthesiser) +}; + +} // namespace juce diff --git a/JuceLibraryCode/modules/juce_audio_basics/utilities/juce_ADSR.h b/JuceLibraryCode/modules/juce_audio_basics/utilities/juce_ADSR.h index 8f50cee..88b3701 100644 --- a/JuceLibraryCode/modules/juce_audio_basics/utilities/juce_ADSR.h +++ b/JuceLibraryCode/modules/juce_audio_basics/utilities/juce_ADSR.h @@ -1,248 +1,248 @@ -/* - ============================================================================== - - This file is part of the JUCE library. - Copyright (c) 2017 - ROLI Ltd. - - JUCE is an open source library subject to commercial or open-source - licensing. - - The code included in this file is provided under the terms of the ISC license - http://www.isc.org/downloads/software-support-policy/isc-license. Permission - To use, copy, modify, and/or distribute this software for any purpose with or - without fee is hereby granted provided that the above copyright notice and - this permission notice appear in all copies. - - JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER - EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE - DISCLAIMED. - - ============================================================================== -*/ - -namespace juce -{ - -//============================================================================== -/** - A very simple ADSR envelope class. - - To use it, call setSampleRate() with the current sample rate and give it some parameters - with setParameters() then call getNextSample() to get the envelope value to be applied - to each audio sample or applyEnvelopeToBuffer() to apply the envelope to a whole buffer. - - @tags{Audio} -*/ -class ADSR -{ -public: - //============================================================================== - ADSR() - { - setSampleRate (44100.0); - setParameters ({}); - } - - //============================================================================== - /** - Holds the parameters being used by an ADSR object. - - @tags{Audio} - */ - struct Parameters - { - /** Attack time in seconds. */ - float attack = 0.1f; - - /** Decay time in seconds. */ - float decay = 0.1f; - - /** Sustain level. */ - float sustain = 1.0f; - - /** Release time in seconds. */ - float release = 0.1f; - }; - - /** Sets the parameters that will be used by an ADSR object. - - You must have called setSampleRate() with the correct sample rate before - this otherwise the values may be incorrect! - - @see getParameters - */ - void setParameters (const Parameters& newParameters) - { - currentParameters = newParameters; - - sustainLevel = newParameters.sustain; - calculateRates (newParameters); - - if (currentState != State::idle) - checkCurrentState(); - } - - /** Returns the parameters currently being used by an ADSR object. - - @see setParameters - */ - const Parameters& getParameters() const { return currentParameters; } - - /** Returns true if the envelope is in its attack, decay, sustain or release stage. */ - bool isActive() const noexcept { return currentState != State::idle; } - - //============================================================================== - /** Sets the sample rate that will be used for the envelope. - - This must be called before the getNextSample() or setParameters() methods. - */ - void setSampleRate (double sampleRate) - { - jassert (sampleRate > 0.0); - sr = sampleRate; - } - - //============================================================================== - /** Resets the envelope to an idle state. */ - void reset() - { - envelopeVal = 0.0f; - currentState = State::idle; - } - - /** Starts the attack phase of the envelope. */ - void noteOn() - { - if (attackRate > 0.0f) - { - currentState = State::attack; - } - else if (decayRate > 0.0f) - { - envelopeVal = 1.0f; - currentState = State::decay; - } - else - { - currentState = State::sustain; - } - } - - /** Starts the release phase of the envelope. */ - void noteOff() - { - if (currentState != State::idle) - { - if (currentParameters.release > 0.0f) - { - releaseRate = static_cast (envelopeVal / (currentParameters.release * sr)); - currentState = State::release; - } - else - { - reset(); - } - } - } - - //============================================================================== - /** Returns the next sample value for an ADSR object. - - @see applyEnvelopeToBuffer - */ - float getNextSample() - { - if (currentState == State::idle) - return 0.0f; - - if (currentState == State::attack) - { - envelopeVal += attackRate; - - if (envelopeVal >= 1.0f) - { - envelopeVal = 1.0f; - - if (decayRate > 0.0f) - currentState = State::decay; - else - currentState = State::sustain; - } - } - else if (currentState == State::decay) - { - envelopeVal -= decayRate; - - if (envelopeVal <= sustainLevel) - { - envelopeVal = sustainLevel; - currentState = State::sustain; - } - } - else if (currentState == State::sustain) - { - envelopeVal = sustainLevel; - } - else if (currentState == State::release) - { - envelopeVal -= releaseRate; - - if (envelopeVal <= 0.0f) - reset(); - } - - return envelopeVal; - } - - /** This method will conveniently apply the next numSamples number of envelope values - to an AudioBuffer. - - @see getNextSample - */ - template - void applyEnvelopeToBuffer (AudioBuffer& buffer, int startSample, int numSamples) - { - jassert (startSample + numSamples <= buffer.getNumSamples()); - - auto numChannels = buffer.getNumChannels(); - - while (--numSamples >= 0) - { - auto env = getNextSample(); - - for (int i = 0; i < numChannels; ++i) - buffer.getWritePointer (i)[startSample] *= env; - - ++startSample; - } - } - -private: - //============================================================================== - void calculateRates (const Parameters& parameters) - { - // need to call setSampleRate() first! - jassert (sr > 0.0); - - attackRate = (parameters.attack > 0.0f ? static_cast (1.0f / (parameters.attack * sr)) : -1.0f); - decayRate = (parameters.decay > 0.0f ? static_cast ((1.0f - sustainLevel) / (parameters.decay * sr)) : -1.0f); - } - - void checkCurrentState() - { - if (currentState == State::attack && attackRate <= 0.0f) currentState = decayRate > 0.0f ? State::decay : State::sustain; - else if (currentState == State::decay && decayRate <= 0.0f) currentState = State::sustain; - else if (currentState == State::release && releaseRate <= 0.0f) reset(); - } - - //============================================================================== - enum class State { idle, attack, decay, sustain, release }; - - State currentState = State::idle; - Parameters currentParameters; - - double sr = 0.0; - float envelopeVal = 0.0f, sustainLevel = 0.0f, attackRate = 0.0f, decayRate = 0.0f, releaseRate = 0.0f; -}; - -} // namespace juce +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2017 - ROLI Ltd. + + JUCE is an open source library subject to commercial or open-source + licensing. + + The code included in this file is provided under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license. Permission + To use, copy, modify, and/or distribute this software for any purpose with or + without fee is hereby granted provided that the above copyright notice and + this permission notice appear in all copies. + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +namespace juce +{ + +//============================================================================== +/** + A very simple ADSR envelope class. + + To use it, call setSampleRate() with the current sample rate and give it some parameters + with setParameters() then call getNextSample() to get the envelope value to be applied + to each audio sample or applyEnvelopeToBuffer() to apply the envelope to a whole buffer. + + @tags{Audio} +*/ +class ADSR +{ +public: + //============================================================================== + ADSR() + { + setSampleRate (44100.0); + setParameters ({}); + } + + //============================================================================== + /** + Holds the parameters being used by an ADSR object. + + @tags{Audio} + */ + struct Parameters + { + /** Attack time in seconds. */ + float attack = 0.1f; + + /** Decay time in seconds. */ + float decay = 0.1f; + + /** Sustain level. */ + float sustain = 1.0f; + + /** Release time in seconds. */ + float release = 0.1f; + }; + + /** Sets the parameters that will be used by an ADSR object. + + You must have called setSampleRate() with the correct sample rate before + this otherwise the values may be incorrect! + + @see getParameters + */ + void setParameters (const Parameters& newParameters) + { + currentParameters = newParameters; + + sustainLevel = newParameters.sustain; + calculateRates (newParameters); + + if (currentState != State::idle) + checkCurrentState(); + } + + /** Returns the parameters currently being used by an ADSR object. + + @see setParameters + */ + const Parameters& getParameters() const { return currentParameters; } + + /** Returns true if the envelope is in its attack, decay, sustain or release stage. */ + bool isActive() const noexcept { return currentState != State::idle; } + + //============================================================================== + /** Sets the sample rate that will be used for the envelope. + + This must be called before the getNextSample() or setParameters() methods. + */ + void setSampleRate (double sampleRate) + { + jassert (sampleRate > 0.0); + sr = sampleRate; + } + + //============================================================================== + /** Resets the envelope to an idle state. */ + void reset() + { + envelopeVal = 0.0f; + currentState = State::idle; + } + + /** Starts the attack phase of the envelope. */ + void noteOn() + { + if (attackRate > 0.0f) + { + currentState = State::attack; + } + else if (decayRate > 0.0f) + { + envelopeVal = 1.0f; + currentState = State::decay; + } + else + { + currentState = State::sustain; + } + } + + /** Starts the release phase of the envelope. */ + void noteOff() + { + if (currentState != State::idle) + { + if (currentParameters.release > 0.0f) + { + releaseRate = static_cast (envelopeVal / (currentParameters.release * sr)); + currentState = State::release; + } + else + { + reset(); + } + } + } + + //============================================================================== + /** Returns the next sample value for an ADSR object. + + @see applyEnvelopeToBuffer + */ + float getNextSample() + { + if (currentState == State::idle) + return 0.0f; + + if (currentState == State::attack) + { + envelopeVal += attackRate; + + if (envelopeVal >= 1.0f) + { + envelopeVal = 1.0f; + + if (decayRate > 0.0f) + currentState = State::decay; + else + currentState = State::sustain; + } + } + else if (currentState == State::decay) + { + envelopeVal -= decayRate; + + if (envelopeVal <= sustainLevel) + { + envelopeVal = sustainLevel; + currentState = State::sustain; + } + } + else if (currentState == State::sustain) + { + envelopeVal = sustainLevel; + } + else if (currentState == State::release) + { + envelopeVal -= releaseRate; + + if (envelopeVal <= 0.0f) + reset(); + } + + return envelopeVal; + } + + /** This method will conveniently apply the next numSamples number of envelope values + to an AudioBuffer. + + @see getNextSample + */ + template + void applyEnvelopeToBuffer (AudioBuffer& buffer, int startSample, int numSamples) + { + jassert (startSample + numSamples <= buffer.getNumSamples()); + + auto numChannels = buffer.getNumChannels(); + + while (--numSamples >= 0) + { + auto env = getNextSample(); + + for (int i = 0; i < numChannels; ++i) + buffer.getWritePointer (i)[startSample] *= env; + + ++startSample; + } + } + +private: + //============================================================================== + void calculateRates (const Parameters& parameters) + { + // need to call setSampleRate() first! + jassert (sr > 0.0); + + attackRate = (parameters.attack > 0.0f ? static_cast (1.0f / (parameters.attack * sr)) : -1.0f); + decayRate = (parameters.decay > 0.0f ? static_cast ((1.0f - sustainLevel) / (parameters.decay * sr)) : -1.0f); + } + + void checkCurrentState() + { + if (currentState == State::attack && attackRate <= 0.0f) currentState = decayRate > 0.0f ? State::decay : State::sustain; + else if (currentState == State::decay && decayRate <= 0.0f) currentState = State::sustain; + else if (currentState == State::release && releaseRate <= 0.0f) reset(); + } + + //============================================================================== + enum class State { idle, attack, decay, sustain, release }; + + State currentState = State::idle; + Parameters currentParameters; + + double sr = 0.0; + float envelopeVal = 0.0f, sustainLevel = 0.0f, attackRate = 0.0f, decayRate = 0.0f, releaseRate = 0.0f; +}; + +} // namespace juce diff --git a/JuceLibraryCode/modules/juce_audio_basics/utilities/juce_CatmullRomInterpolator.cpp b/JuceLibraryCode/modules/juce_audio_basics/utilities/juce_CatmullRomInterpolator.cpp index c6f59d4..dbe986c 100644 --- a/JuceLibraryCode/modules/juce_audio_basics/utilities/juce_CatmullRomInterpolator.cpp +++ b/JuceLibraryCode/modules/juce_audio_basics/utilities/juce_CatmullRomInterpolator.cpp @@ -1,75 +1,75 @@ -/* - ============================================================================== - - This file is part of the JUCE library. - Copyright (c) 2017 - ROLI Ltd. - - JUCE is an open source library subject to commercial or open-source - licensing. - - The code included in this file is provided under the terms of the ISC license - http://www.isc.org/downloads/software-support-policy/isc-license. Permission - To use, copy, modify, and/or distribute this software for any purpose with or - without fee is hereby granted provided that the above copyright notice and - this permission notice appear in all copies. - - JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER - EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE - DISCLAIMED. - - ============================================================================== -*/ - -namespace juce -{ - -struct CatmullRomAlgorithm -{ - static forcedinline float valueAtOffset (const float* const inputs, const float offset) noexcept - { - auto y0 = inputs[3]; - auto y1 = inputs[2]; - auto y2 = inputs[1]; - auto y3 = inputs[0]; - - auto halfY0 = 0.5f * y0; - auto halfY3 = 0.5f * y3; - - return y1 + offset * ((0.5f * y2 - halfY0) - + (offset * (((y0 + 2.0f * y2) - (halfY3 + 2.5f * y1)) - + (offset * ((halfY3 + 1.5f * y1) - (halfY0 + 1.5f * y2)))))); - } -}; - -CatmullRomInterpolator::CatmullRomInterpolator() noexcept { reset(); } -CatmullRomInterpolator::~CatmullRomInterpolator() noexcept {} - -void CatmullRomInterpolator::reset() noexcept -{ - subSamplePos = 1.0; - - for (auto& s : lastInputSamples) - s = 0; -} - -int CatmullRomInterpolator::process (double actualRatio, const float* in, float* out, int numOut, int available, int wrap) noexcept -{ - return interpolate (lastInputSamples, subSamplePos, actualRatio, in, out, numOut, available, wrap); -} - -int CatmullRomInterpolator::process (double actualRatio, const float* in, float* out, int numOut) noexcept -{ - return interpolate (lastInputSamples, subSamplePos, actualRatio, in, out, numOut); -} - -int CatmullRomInterpolator::processAdding (double actualRatio, const float* in, float* out, int numOut, int available, int wrap, float gain) noexcept -{ - return interpolateAdding (lastInputSamples, subSamplePos, actualRatio, in, out, numOut, available, wrap, gain); -} - -int CatmullRomInterpolator::processAdding (double actualRatio, const float* in, float* out, int numOut, float gain) noexcept -{ - return interpolateAdding (lastInputSamples, subSamplePos, actualRatio, in, out, numOut, gain); -} - -} // namespace juce +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2017 - ROLI Ltd. + + JUCE is an open source library subject to commercial or open-source + licensing. + + The code included in this file is provided under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license. Permission + To use, copy, modify, and/or distribute this software for any purpose with or + without fee is hereby granted provided that the above copyright notice and + this permission notice appear in all copies. + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +namespace juce +{ + +struct CatmullRomAlgorithm +{ + static forcedinline float valueAtOffset (const float* const inputs, const float offset) noexcept + { + auto y0 = inputs[3]; + auto y1 = inputs[2]; + auto y2 = inputs[1]; + auto y3 = inputs[0]; + + auto halfY0 = 0.5f * y0; + auto halfY3 = 0.5f * y3; + + return y1 + offset * ((0.5f * y2 - halfY0) + + (offset * (((y0 + 2.0f * y2) - (halfY3 + 2.5f * y1)) + + (offset * ((halfY3 + 1.5f * y1) - (halfY0 + 1.5f * y2)))))); + } +}; + +CatmullRomInterpolator::CatmullRomInterpolator() noexcept { reset(); } +CatmullRomInterpolator::~CatmullRomInterpolator() noexcept {} + +void CatmullRomInterpolator::reset() noexcept +{ + subSamplePos = 1.0; + + for (auto& s : lastInputSamples) + s = 0; +} + +int CatmullRomInterpolator::process (double actualRatio, const float* in, float* out, int numOut, int available, int wrap) noexcept +{ + return interpolate (lastInputSamples, subSamplePos, actualRatio, in, out, numOut, available, wrap); +} + +int CatmullRomInterpolator::process (double actualRatio, const float* in, float* out, int numOut) noexcept +{ + return interpolate (lastInputSamples, subSamplePos, actualRatio, in, out, numOut); +} + +int CatmullRomInterpolator::processAdding (double actualRatio, const float* in, float* out, int numOut, int available, int wrap, float gain) noexcept +{ + return interpolateAdding (lastInputSamples, subSamplePos, actualRatio, in, out, numOut, available, wrap, gain); +} + +int CatmullRomInterpolator::processAdding (double actualRatio, const float* in, float* out, int numOut, float gain) noexcept +{ + return interpolateAdding (lastInputSamples, subSamplePos, actualRatio, in, out, numOut, gain); +} + +} // namespace juce diff --git a/JuceLibraryCode/modules/juce_audio_basics/utilities/juce_CatmullRomInterpolator.h b/JuceLibraryCode/modules/juce_audio_basics/utilities/juce_CatmullRomInterpolator.h index 2d89a34..0972acb 100644 --- a/JuceLibraryCode/modules/juce_audio_basics/utilities/juce_CatmullRomInterpolator.h +++ b/JuceLibraryCode/modules/juce_audio_basics/utilities/juce_CatmullRomInterpolator.h @@ -1,146 +1,146 @@ -/* - ============================================================================== - - This file is part of the JUCE library. - Copyright (c) 2017 - ROLI Ltd. - - JUCE is an open source library subject to commercial or open-source - licensing. - - The code included in this file is provided under the terms of the ISC license - http://www.isc.org/downloads/software-support-policy/isc-license. Permission - To use, copy, modify, and/or distribute this software for any purpose with or - without fee is hereby granted provided that the above copyright notice and - this permission notice appear in all copies. - - JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER - EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE - DISCLAIMED. - - ============================================================================== -*/ - -namespace juce -{ - -/** - Interpolator for resampling a stream of floats using Catmull-Rom interpolation. - - Note that the resampler is stateful, so when there's a break in the continuity - of the input stream you're feeding it, you should call reset() before feeding - it any new data. And like with any other stateful filter, if you're resampling - multiple channels, make sure each one uses its own CatmullRomInterpolator - object. - - @see LagrangeInterpolator - - @tags{Audio} -*/ -class JUCE_API CatmullRomInterpolator -{ -public: - CatmullRomInterpolator() noexcept; - ~CatmullRomInterpolator() noexcept; - - CatmullRomInterpolator (CatmullRomInterpolator&&) noexcept = default; - CatmullRomInterpolator& operator= (CatmullRomInterpolator&&) noexcept = default; - - /** Resets the state of the interpolator. - Call this when there's a break in the continuity of the input data stream. - */ - void reset() noexcept; - - /** Resamples a stream of samples. - - @param speedRatio the number of input samples to use for each output sample - @param inputSamples the source data to read from. This must contain at - least (speedRatio * numOutputSamplesToProduce) samples. - @param outputSamples the buffer to write the results into - @param numOutputSamplesToProduce the number of output samples that should be created - - @returns the actual number of input samples that were used - */ - int process (double speedRatio, - const float* inputSamples, - float* outputSamples, - int numOutputSamplesToProduce) noexcept; - - /** Resamples a stream of samples. - - @param speedRatio the number of input samples to use for each output sample - @param inputSamples the source data to read from. This must contain at - least (speedRatio * numOutputSamplesToProduce) samples. - @param outputSamples the buffer to write the results into - @param numOutputSamplesToProduce the number of output samples that should be created - @param available the number of available input samples. If it needs more samples - than available, it either wraps back for wrapAround samples, or - it feeds zeroes - @param wrapAround if the stream exceeds available samples, it wraps back for - wrapAround samples. If wrapAround is set to 0, it will feed zeroes. - - @returns the actual number of input samples that were used - */ - int process (double speedRatio, - const float* inputSamples, - float* outputSamples, - int numOutputSamplesToProduce, - int available, - int wrapAround) noexcept; - - /** Resamples a stream of samples, adding the results to the output data - with a gain. - - @param speedRatio the number of input samples to use for each output sample - @param inputSamples the source data to read from. This must contain at - least (speedRatio * numOutputSamplesToProduce) samples. - @param outputSamples the buffer to write the results to - the result values will be added - to any pre-existing data in this buffer after being multiplied by - the gain factor - @param numOutputSamplesToProduce the number of output samples that should be created - @param gain a gain factor to multiply the resulting samples by before - adding them to the destination buffer - - @returns the actual number of input samples that were used - */ - int processAdding (double speedRatio, - const float* inputSamples, - float* outputSamples, - int numOutputSamplesToProduce, - float gain) noexcept; - - /** Resamples a stream of samples, adding the results to the output data - with a gain. - - @param speedRatio the number of input samples to use for each output sample - @param inputSamples the source data to read from. This must contain at - least (speedRatio * numOutputSamplesToProduce) samples. - @param outputSamples the buffer to write the results to - the result values will be added - to any pre-existing data in this buffer after being multiplied by - the gain factor - @param numOutputSamplesToProduce the number of output samples that should be created - @param available the number of available input samples. If it needs more samples - than available, it either wraps back for wrapAround samples, or - it feeds zeroes - @param wrapAround if the stream exceeds available samples, it wraps back for - wrapAround samples. If wrapAround is set to 0, it will feed zeroes. - @param gain a gain factor to multiply the resulting samples by before - adding them to the destination buffer - - @returns the actual number of input samples that were used - */ - int processAdding (double speedRatio, - const float* inputSamples, - float* outputSamples, - int numOutputSamplesToProduce, - int available, - int wrapAround, - float gain) noexcept; - -private: - float lastInputSamples[5]; - double subSamplePos; - - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CatmullRomInterpolator) -}; - -} // namespace juce +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2017 - ROLI Ltd. + + JUCE is an open source library subject to commercial or open-source + licensing. + + The code included in this file is provided under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license. Permission + To use, copy, modify, and/or distribute this software for any purpose with or + without fee is hereby granted provided that the above copyright notice and + this permission notice appear in all copies. + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +namespace juce +{ + +/** + Interpolator for resampling a stream of floats using Catmull-Rom interpolation. + + Note that the resampler is stateful, so when there's a break in the continuity + of the input stream you're feeding it, you should call reset() before feeding + it any new data. And like with any other stateful filter, if you're resampling + multiple channels, make sure each one uses its own CatmullRomInterpolator + object. + + @see LagrangeInterpolator + + @tags{Audio} +*/ +class JUCE_API CatmullRomInterpolator +{ +public: + CatmullRomInterpolator() noexcept; + ~CatmullRomInterpolator() noexcept; + + CatmullRomInterpolator (CatmullRomInterpolator&&) noexcept = default; + CatmullRomInterpolator& operator= (CatmullRomInterpolator&&) noexcept = default; + + /** Resets the state of the interpolator. + Call this when there's a break in the continuity of the input data stream. + */ + void reset() noexcept; + + /** Resamples a stream of samples. + + @param speedRatio the number of input samples to use for each output sample + @param inputSamples the source data to read from. This must contain at + least (speedRatio * numOutputSamplesToProduce) samples. + @param outputSamples the buffer to write the results into + @param numOutputSamplesToProduce the number of output samples that should be created + + @returns the actual number of input samples that were used + */ + int process (double speedRatio, + const float* inputSamples, + float* outputSamples, + int numOutputSamplesToProduce) noexcept; + + /** Resamples a stream of samples. + + @param speedRatio the number of input samples to use for each output sample + @param inputSamples the source data to read from. This must contain at + least (speedRatio * numOutputSamplesToProduce) samples. + @param outputSamples the buffer to write the results into + @param numOutputSamplesToProduce the number of output samples that should be created + @param available the number of available input samples. If it needs more samples + than available, it either wraps back for wrapAround samples, or + it feeds zeroes + @param wrapAround if the stream exceeds available samples, it wraps back for + wrapAround samples. If wrapAround is set to 0, it will feed zeroes. + + @returns the actual number of input samples that were used + */ + int process (double speedRatio, + const float* inputSamples, + float* outputSamples, + int numOutputSamplesToProduce, + int available, + int wrapAround) noexcept; + + /** Resamples a stream of samples, adding the results to the output data + with a gain. + + @param speedRatio the number of input samples to use for each output sample + @param inputSamples the source data to read from. This must contain at + least (speedRatio * numOutputSamplesToProduce) samples. + @param outputSamples the buffer to write the results to - the result values will be added + to any pre-existing data in this buffer after being multiplied by + the gain factor + @param numOutputSamplesToProduce the number of output samples that should be created + @param gain a gain factor to multiply the resulting samples by before + adding them to the destination buffer + + @returns the actual number of input samples that were used + */ + int processAdding (double speedRatio, + const float* inputSamples, + float* outputSamples, + int numOutputSamplesToProduce, + float gain) noexcept; + + /** Resamples a stream of samples, adding the results to the output data + with a gain. + + @param speedRatio the number of input samples to use for each output sample + @param inputSamples the source data to read from. This must contain at + least (speedRatio * numOutputSamplesToProduce) samples. + @param outputSamples the buffer to write the results to - the result values will be added + to any pre-existing data in this buffer after being multiplied by + the gain factor + @param numOutputSamplesToProduce the number of output samples that should be created + @param available the number of available input samples. If it needs more samples + than available, it either wraps back for wrapAround samples, or + it feeds zeroes + @param wrapAround if the stream exceeds available samples, it wraps back for + wrapAround samples. If wrapAround is set to 0, it will feed zeroes. + @param gain a gain factor to multiply the resulting samples by before + adding them to the destination buffer + + @returns the actual number of input samples that were used + */ + int processAdding (double speedRatio, + const float* inputSamples, + float* outputSamples, + int numOutputSamplesToProduce, + int available, + int wrapAround, + float gain) noexcept; + +private: + float lastInputSamples[5]; + double subSamplePos; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CatmullRomInterpolator) +}; + +} // namespace juce diff --git a/JuceLibraryCode/modules/juce_audio_basics/utilities/juce_Decibels.h b/JuceLibraryCode/modules/juce_audio_basics/utilities/juce_Decibels.h index f1aa5eb..999ec55 100644 --- a/JuceLibraryCode/modules/juce_audio_basics/utilities/juce_Decibels.h +++ b/JuceLibraryCode/modules/juce_audio_basics/utilities/juce_Decibels.h @@ -1,112 +1,112 @@ -/* - ============================================================================== - - This file is part of the JUCE library. - Copyright (c) 2017 - ROLI Ltd. - - JUCE is an open source library subject to commercial or open-source - licensing. - - The code included in this file is provided under the terms of the ISC license - http://www.isc.org/downloads/software-support-policy/isc-license. Permission - To use, copy, modify, and/or distribute this software for any purpose with or - without fee is hereby granted provided that the above copyright notice and - this permission notice appear in all copies. - - JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER - EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE - DISCLAIMED. - - ============================================================================== -*/ - -namespace juce -{ - -//============================================================================== -/** - This class contains some helpful static methods for dealing with decibel values. - - @tags{Audio} -*/ -class Decibels -{ -public: - //============================================================================== - /** Converts a dBFS value to its equivalent gain level. - - A gain of 1.0 = 0 dB, and lower gains map onto negative decibel values. Any - decibel value lower than minusInfinityDb will return a gain of 0. - */ - template - static Type decibelsToGain (Type decibels, - Type minusInfinityDb = Type (defaultMinusInfinitydB)) - { - return decibels > minusInfinityDb ? std::pow (Type (10.0), decibels * Type (0.05)) - : Type(); - } - - /** Converts a gain level into a dBFS value. - - A gain of 1.0 = 0 dB, and lower gains map onto negative decibel values. - If the gain is 0 (or negative), then the method will return the value - provided as minusInfinityDb. - */ - template - static Type gainToDecibels (Type gain, - Type minusInfinityDb = Type (defaultMinusInfinitydB)) - { - return gain > Type() ? jmax (minusInfinityDb, static_cast (std::log10 (gain)) * Type (20.0)) - : minusInfinityDb; - } - - //============================================================================== - /** Converts a decibel reading to a string. - - By default the returned string will have the 'dB' suffix added, but this can be removed by - setting the shouldIncludeSuffix argument to false. If a customMinusInfinityString argument - is provided this will be returned if the value is lower than minusInfinityDb, otherwise - the return value will be "-INF". - */ - template - static String toString (Type decibels, - int decimalPlaces = 2, - Type minusInfinityDb = Type (defaultMinusInfinitydB), - bool shouldIncludeSuffix = true, - StringRef customMinusInfinityString = {}) - { - String s; - s.preallocateBytes (20); - - if (decibels <= minusInfinityDb) - { - if (customMinusInfinityString.isEmpty()) - s << "-INF"; - else - s << customMinusInfinityString; - } - else - { - if (decibels >= Type()) - s << '+'; - - if (decimalPlaces <= 0) - s << roundToInt (decibels); - else - s << String (decibels, decimalPlaces); - } - - if (shouldIncludeSuffix) - s << " dB"; - - return s; - } - -private: - //============================================================================== - enum { defaultMinusInfinitydB = -100 }; - - Decibels() = delete; // This class can't be instantiated, it's just a holder for static methods.. -}; - -} // namespace juce +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2017 - ROLI Ltd. + + JUCE is an open source library subject to commercial or open-source + licensing. + + The code included in this file is provided under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license. Permission + To use, copy, modify, and/or distribute this software for any purpose with or + without fee is hereby granted provided that the above copyright notice and + this permission notice appear in all copies. + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +namespace juce +{ + +//============================================================================== +/** + This class contains some helpful static methods for dealing with decibel values. + + @tags{Audio} +*/ +class Decibels +{ +public: + //============================================================================== + /** Converts a dBFS value to its equivalent gain level. + + A gain of 1.0 = 0 dB, and lower gains map onto negative decibel values. Any + decibel value lower than minusInfinityDb will return a gain of 0. + */ + template + static Type decibelsToGain (Type decibels, + Type minusInfinityDb = Type (defaultMinusInfinitydB)) + { + return decibels > minusInfinityDb ? std::pow (Type (10.0), decibels * Type (0.05)) + : Type(); + } + + /** Converts a gain level into a dBFS value. + + A gain of 1.0 = 0 dB, and lower gains map onto negative decibel values. + If the gain is 0 (or negative), then the method will return the value + provided as minusInfinityDb. + */ + template + static Type gainToDecibels (Type gain, + Type minusInfinityDb = Type (defaultMinusInfinitydB)) + { + return gain > Type() ? jmax (minusInfinityDb, static_cast (std::log10 (gain)) * Type (20.0)) + : minusInfinityDb; + } + + //============================================================================== + /** Converts a decibel reading to a string. + + By default the returned string will have the 'dB' suffix added, but this can be removed by + setting the shouldIncludeSuffix argument to false. If a customMinusInfinityString argument + is provided this will be returned if the value is lower than minusInfinityDb, otherwise + the return value will be "-INF". + */ + template + static String toString (Type decibels, + int decimalPlaces = 2, + Type minusInfinityDb = Type (defaultMinusInfinitydB), + bool shouldIncludeSuffix = true, + StringRef customMinusInfinityString = {}) + { + String s; + s.preallocateBytes (20); + + if (decibels <= minusInfinityDb) + { + if (customMinusInfinityString.isEmpty()) + s << "-INF"; + else + s << customMinusInfinityString; + } + else + { + if (decibels >= Type()) + s << '+'; + + if (decimalPlaces <= 0) + s << roundToInt (decibels); + else + s << String (decibels, decimalPlaces); + } + + if (shouldIncludeSuffix) + s << " dB"; + + return s; + } + +private: + //============================================================================== + enum { defaultMinusInfinitydB = -100 }; + + Decibels() = delete; // This class can't be instantiated, it's just a holder for static methods.. +}; + +} // namespace juce diff --git a/JuceLibraryCode/modules/juce_audio_basics/utilities/juce_IIRFilter.cpp b/JuceLibraryCode/modules/juce_audio_basics/utilities/juce_IIRFilter.cpp index 392ccfc..3938a6b 100644 --- a/JuceLibraryCode/modules/juce_audio_basics/utilities/juce_IIRFilter.cpp +++ b/JuceLibraryCode/modules/juce_audio_basics/utilities/juce_IIRFilter.cpp @@ -1,336 +1,336 @@ -/* - ============================================================================== - - This file is part of the JUCE library. - Copyright (c) 2017 - ROLI Ltd. - - JUCE is an open source library subject to commercial or open-source - licensing. - - The code included in this file is provided under the terms of the ISC license - http://www.isc.org/downloads/software-support-policy/isc-license. Permission - To use, copy, modify, and/or distribute this software for any purpose with or - without fee is hereby granted provided that the above copyright notice and - this permission notice appear in all copies. - - JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER - EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE - DISCLAIMED. - - ============================================================================== -*/ - -namespace juce -{ - -IIRCoefficients::IIRCoefficients() noexcept -{ - zeromem (coefficients, sizeof (coefficients)); -} - -IIRCoefficients::~IIRCoefficients() noexcept {} - -IIRCoefficients::IIRCoefficients (const IIRCoefficients& other) noexcept -{ - memcpy (coefficients, other.coefficients, sizeof (coefficients)); -} - -IIRCoefficients& IIRCoefficients::operator= (const IIRCoefficients& other) noexcept -{ - memcpy (coefficients, other.coefficients, sizeof (coefficients)); - return *this; -} - -IIRCoefficients::IIRCoefficients (double c1, double c2, double c3, - double c4, double c5, double c6) noexcept -{ - auto a = 1.0 / c4; - - coefficients[0] = (float) (c1 * a); - coefficients[1] = (float) (c2 * a); - coefficients[2] = (float) (c3 * a); - coefficients[3] = (float) (c5 * a); - coefficients[4] = (float) (c6 * a); -} - -IIRCoefficients IIRCoefficients::makeLowPass (double sampleRate, - double frequency) noexcept -{ - return makeLowPass (sampleRate, frequency, 1.0 / MathConstants::sqrt2); -} - -IIRCoefficients IIRCoefficients::makeLowPass (double sampleRate, - double frequency, - double Q) noexcept -{ - jassert (sampleRate > 0.0); - jassert (frequency > 0.0 && frequency <= sampleRate * 0.5); - jassert (Q > 0.0); - - auto n = 1.0 / std::tan (MathConstants::pi * frequency / sampleRate); - auto nSquared = n * n; - auto c1 = 1.0 / (1.0 + 1.0 / Q * n + nSquared); - - return IIRCoefficients (c1, - c1 * 2.0, - c1, - 1.0, - c1 * 2.0 * (1.0 - nSquared), - c1 * (1.0 - 1.0 / Q * n + nSquared)); -} - -IIRCoefficients IIRCoefficients::makeHighPass (double sampleRate, - double frequency) noexcept -{ - return makeHighPass (sampleRate, frequency, 1.0 / std::sqrt(2.0)); -} - -IIRCoefficients IIRCoefficients::makeHighPass (double sampleRate, - double frequency, - double Q) noexcept -{ - jassert (sampleRate > 0.0); - jassert (frequency > 0.0 && frequency <= sampleRate * 0.5); - jassert (Q > 0.0); - - auto n = std::tan (MathConstants::pi * frequency / sampleRate); - auto nSquared = n * n; - auto c1 = 1.0 / (1.0 + 1.0 / Q * n + nSquared); - - return IIRCoefficients (c1, - c1 * -2.0, - c1, - 1.0, - c1 * 2.0 * (nSquared - 1.0), - c1 * (1.0 - 1.0 / Q * n + nSquared)); -} - -IIRCoefficients IIRCoefficients::makeBandPass (double sampleRate, - double frequency) noexcept -{ - return makeBandPass (sampleRate, frequency, 1.0 / MathConstants::sqrt2); -} - -IIRCoefficients IIRCoefficients::makeBandPass (double sampleRate, - double frequency, - double Q) noexcept -{ - jassert (sampleRate > 0.0); - jassert (frequency > 0.0 && frequency <= sampleRate * 0.5); - jassert (Q > 0.0); - - auto n = 1.0 / std::tan (MathConstants::pi * frequency / sampleRate); - auto nSquared = n * n; - auto c1 = 1.0 / (1.0 + 1.0 / Q * n + nSquared); - - return IIRCoefficients (c1 * n / Q, - 0.0, - -c1 * n / Q, - 1.0, - c1 * 2.0 * (1.0 - nSquared), - c1 * (1.0 - 1.0 / Q * n + nSquared)); -} - -IIRCoefficients IIRCoefficients::makeNotchFilter (double sampleRate, - double frequency) noexcept -{ - return makeNotchFilter (sampleRate, frequency, 1.0 / MathConstants::sqrt2); -} - -IIRCoefficients IIRCoefficients::makeNotchFilter (double sampleRate, - double frequency, - double Q) noexcept -{ - jassert (sampleRate > 0.0); - jassert (frequency > 0.0 && frequency <= sampleRate * 0.5); - jassert (Q > 0.0); - - auto n = 1.0 / std::tan (MathConstants::pi * frequency / sampleRate); - auto nSquared = n * n; - auto c1 = 1.0 / (1.0 + n / Q + nSquared); - - return IIRCoefficients (c1 * (1.0 + nSquared), - 2.0 * c1 * (1.0 - nSquared), - c1 * (1.0 + nSquared), - 1.0, - c1 * 2.0 * (1.0 - nSquared), - c1 * (1.0 - n / Q + nSquared)); -} - -IIRCoefficients IIRCoefficients::makeAllPass (double sampleRate, - double frequency) noexcept -{ - return makeAllPass (sampleRate, frequency, 1.0 / MathConstants::sqrt2); -} - -IIRCoefficients IIRCoefficients::makeAllPass (double sampleRate, - double frequency, - double Q) noexcept -{ - jassert (sampleRate > 0.0); - jassert (frequency > 0.0 && frequency <= sampleRate * 0.5); - jassert (Q > 0.0); - - auto n = 1.0 / std::tan (MathConstants::pi * frequency / sampleRate); - auto nSquared = n * n; - auto c1 = 1.0 / (1.0 + 1.0 / Q * n + nSquared); - - return IIRCoefficients (c1 * (1.0 - n / Q + nSquared), - c1 * 2.0 * (1.0 - nSquared), - 1.0, - 1.0, - c1 * 2.0 * (1.0 - nSquared), - c1 * (1.0 - n / Q + nSquared)); -} - -IIRCoefficients IIRCoefficients::makeLowShelf (double sampleRate, - double cutOffFrequency, - double Q, - float gainFactor) noexcept -{ - jassert (sampleRate > 0.0); - jassert (cutOffFrequency > 0.0 && cutOffFrequency <= sampleRate * 0.5); - jassert (Q > 0.0); - - auto A = jmax (0.0f, std::sqrt (gainFactor)); - auto aminus1 = A - 1.0; - auto aplus1 = A + 1.0; - auto omega = (MathConstants::twoPi * jmax (cutOffFrequency, 2.0)) / sampleRate; - auto coso = std::cos (omega); - auto beta = std::sin (omega) * std::sqrt (A) / Q; - auto aminus1TimesCoso = aminus1 * coso; - - return IIRCoefficients (A * (aplus1 - aminus1TimesCoso + beta), - A * 2.0 * (aminus1 - aplus1 * coso), - A * (aplus1 - aminus1TimesCoso - beta), - aplus1 + aminus1TimesCoso + beta, - -2.0 * (aminus1 + aplus1 * coso), - aplus1 + aminus1TimesCoso - beta); -} - -IIRCoefficients IIRCoefficients::makeHighShelf (double sampleRate, - double cutOffFrequency, - double Q, - float gainFactor) noexcept -{ - jassert (sampleRate > 0.0); - jassert (cutOffFrequency > 0.0 && cutOffFrequency <= sampleRate * 0.5); - jassert (Q > 0.0); - - auto A = jmax (0.0f, std::sqrt (gainFactor)); - auto aminus1 = A - 1.0; - auto aplus1 = A + 1.0; - auto omega = (MathConstants::twoPi * jmax (cutOffFrequency, 2.0)) / sampleRate; - auto coso = std::cos (omega); - auto beta = std::sin (omega) * std::sqrt (A) / Q; - auto aminus1TimesCoso = aminus1 * coso; - - return IIRCoefficients (A * (aplus1 + aminus1TimesCoso + beta), - A * -2.0 * (aminus1 + aplus1 * coso), - A * (aplus1 + aminus1TimesCoso - beta), - aplus1 - aminus1TimesCoso + beta, - 2.0 * (aminus1 - aplus1 * coso), - aplus1 - aminus1TimesCoso - beta); -} - -IIRCoefficients IIRCoefficients::makePeakFilter (double sampleRate, - double frequency, - double Q, - float gainFactor) noexcept -{ - jassert (sampleRate > 0.0); - jassert (frequency > 0.0 && frequency <= sampleRate * 0.5); - jassert (Q > 0.0); - - auto A = jmax (0.0f, std::sqrt (gainFactor)); - auto omega = (MathConstants::twoPi * jmax (frequency, 2.0)) / sampleRate; - auto alpha = 0.5 * std::sin (omega) / Q; - auto c2 = -2.0 * std::cos (omega); - auto alphaTimesA = alpha * A; - auto alphaOverA = alpha / A; - - return IIRCoefficients (1.0 + alphaTimesA, - c2, - 1.0 - alphaTimesA, - 1.0 + alphaOverA, - c2, - 1.0 - alphaOverA); -} - -//============================================================================== -IIRFilter::IIRFilter() noexcept -{ -} - -IIRFilter::IIRFilter (const IIRFilter& other) noexcept : active (other.active) -{ - const SpinLock::ScopedLockType sl (other.processLock); - coefficients = other.coefficients; -} - -IIRFilter::~IIRFilter() noexcept -{ -} - -//============================================================================== -void IIRFilter::makeInactive() noexcept -{ - const SpinLock::ScopedLockType sl (processLock); - active = false; -} - -void IIRFilter::setCoefficients (const IIRCoefficients& newCoefficients) noexcept -{ - const SpinLock::ScopedLockType sl (processLock); - coefficients = newCoefficients; - active = true; -} - -//============================================================================== -void IIRFilter::reset() noexcept -{ - const SpinLock::ScopedLockType sl (processLock); - v1 = v2 = 0.0; -} - -float IIRFilter::processSingleSampleRaw (float in) noexcept -{ - auto out = coefficients.coefficients[0] * in + v1; - - JUCE_SNAP_TO_ZERO (out); - - v1 = coefficients.coefficients[1] * in - coefficients.coefficients[3] * out + v2; - v2 = coefficients.coefficients[2] * in - coefficients.coefficients[4] * out; - - return out; -} - -void IIRFilter::processSamples (float* const samples, const int numSamples) noexcept -{ - const SpinLock::ScopedLockType sl (processLock); - - if (active) - { - auto c0 = coefficients.coefficients[0]; - auto c1 = coefficients.coefficients[1]; - auto c2 = coefficients.coefficients[2]; - auto c3 = coefficients.coefficients[3]; - auto c4 = coefficients.coefficients[4]; - auto lv1 = v1, lv2 = v2; - - for (int i = 0; i < numSamples; ++i) - { - auto in = samples[i]; - auto out = c0 * in + lv1; - samples[i] = out; - - lv1 = c1 * in - c3 * out + lv2; - lv2 = c2 * in - c4 * out; - } - - JUCE_SNAP_TO_ZERO (lv1); v1 = lv1; - JUCE_SNAP_TO_ZERO (lv2); v2 = lv2; - } -} - -} // namespace juce +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2017 - ROLI Ltd. + + JUCE is an open source library subject to commercial or open-source + licensing. + + The code included in this file is provided under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license. Permission + To use, copy, modify, and/or distribute this software for any purpose with or + without fee is hereby granted provided that the above copyright notice and + this permission notice appear in all copies. + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +namespace juce +{ + +IIRCoefficients::IIRCoefficients() noexcept +{ + zeromem (coefficients, sizeof (coefficients)); +} + +IIRCoefficients::~IIRCoefficients() noexcept {} + +IIRCoefficients::IIRCoefficients (const IIRCoefficients& other) noexcept +{ + memcpy (coefficients, other.coefficients, sizeof (coefficients)); +} + +IIRCoefficients& IIRCoefficients::operator= (const IIRCoefficients& other) noexcept +{ + memcpy (coefficients, other.coefficients, sizeof (coefficients)); + return *this; +} + +IIRCoefficients::IIRCoefficients (double c1, double c2, double c3, + double c4, double c5, double c6) noexcept +{ + auto a = 1.0 / c4; + + coefficients[0] = (float) (c1 * a); + coefficients[1] = (float) (c2 * a); + coefficients[2] = (float) (c3 * a); + coefficients[3] = (float) (c5 * a); + coefficients[4] = (float) (c6 * a); +} + +IIRCoefficients IIRCoefficients::makeLowPass (double sampleRate, + double frequency) noexcept +{ + return makeLowPass (sampleRate, frequency, 1.0 / MathConstants::sqrt2); +} + +IIRCoefficients IIRCoefficients::makeLowPass (double sampleRate, + double frequency, + double Q) noexcept +{ + jassert (sampleRate > 0.0); + jassert (frequency > 0.0 && frequency <= sampleRate * 0.5); + jassert (Q > 0.0); + + auto n = 1.0 / std::tan (MathConstants::pi * frequency / sampleRate); + auto nSquared = n * n; + auto c1 = 1.0 / (1.0 + 1.0 / Q * n + nSquared); + + return IIRCoefficients (c1, + c1 * 2.0, + c1, + 1.0, + c1 * 2.0 * (1.0 - nSquared), + c1 * (1.0 - 1.0 / Q * n + nSquared)); +} + +IIRCoefficients IIRCoefficients::makeHighPass (double sampleRate, + double frequency) noexcept +{ + return makeHighPass (sampleRate, frequency, 1.0 / std::sqrt(2.0)); +} + +IIRCoefficients IIRCoefficients::makeHighPass (double sampleRate, + double frequency, + double Q) noexcept +{ + jassert (sampleRate > 0.0); + jassert (frequency > 0.0 && frequency <= sampleRate * 0.5); + jassert (Q > 0.0); + + auto n = std::tan (MathConstants::pi * frequency / sampleRate); + auto nSquared = n * n; + auto c1 = 1.0 / (1.0 + 1.0 / Q * n + nSquared); + + return IIRCoefficients (c1, + c1 * -2.0, + c1, + 1.0, + c1 * 2.0 * (nSquared - 1.0), + c1 * (1.0 - 1.0 / Q * n + nSquared)); +} + +IIRCoefficients IIRCoefficients::makeBandPass (double sampleRate, + double frequency) noexcept +{ + return makeBandPass (sampleRate, frequency, 1.0 / MathConstants::sqrt2); +} + +IIRCoefficients IIRCoefficients::makeBandPass (double sampleRate, + double frequency, + double Q) noexcept +{ + jassert (sampleRate > 0.0); + jassert (frequency > 0.0 && frequency <= sampleRate * 0.5); + jassert (Q > 0.0); + + auto n = 1.0 / std::tan (MathConstants::pi * frequency / sampleRate); + auto nSquared = n * n; + auto c1 = 1.0 / (1.0 + 1.0 / Q * n + nSquared); + + return IIRCoefficients (c1 * n / Q, + 0.0, + -c1 * n / Q, + 1.0, + c1 * 2.0 * (1.0 - nSquared), + c1 * (1.0 - 1.0 / Q * n + nSquared)); +} + +IIRCoefficients IIRCoefficients::makeNotchFilter (double sampleRate, + double frequency) noexcept +{ + return makeNotchFilter (sampleRate, frequency, 1.0 / MathConstants::sqrt2); +} + +IIRCoefficients IIRCoefficients::makeNotchFilter (double sampleRate, + double frequency, + double Q) noexcept +{ + jassert (sampleRate > 0.0); + jassert (frequency > 0.0 && frequency <= sampleRate * 0.5); + jassert (Q > 0.0); + + auto n = 1.0 / std::tan (MathConstants::pi * frequency / sampleRate); + auto nSquared = n * n; + auto c1 = 1.0 / (1.0 + n / Q + nSquared); + + return IIRCoefficients (c1 * (1.0 + nSquared), + 2.0 * c1 * (1.0 - nSquared), + c1 * (1.0 + nSquared), + 1.0, + c1 * 2.0 * (1.0 - nSquared), + c1 * (1.0 - n / Q + nSquared)); +} + +IIRCoefficients IIRCoefficients::makeAllPass (double sampleRate, + double frequency) noexcept +{ + return makeAllPass (sampleRate, frequency, 1.0 / MathConstants::sqrt2); +} + +IIRCoefficients IIRCoefficients::makeAllPass (double sampleRate, + double frequency, + double Q) noexcept +{ + jassert (sampleRate > 0.0); + jassert (frequency > 0.0 && frequency <= sampleRate * 0.5); + jassert (Q > 0.0); + + auto n = 1.0 / std::tan (MathConstants::pi * frequency / sampleRate); + auto nSquared = n * n; + auto c1 = 1.0 / (1.0 + 1.0 / Q * n + nSquared); + + return IIRCoefficients (c1 * (1.0 - n / Q + nSquared), + c1 * 2.0 * (1.0 - nSquared), + 1.0, + 1.0, + c1 * 2.0 * (1.0 - nSquared), + c1 * (1.0 - n / Q + nSquared)); +} + +IIRCoefficients IIRCoefficients::makeLowShelf (double sampleRate, + double cutOffFrequency, + double Q, + float gainFactor) noexcept +{ + jassert (sampleRate > 0.0); + jassert (cutOffFrequency > 0.0 && cutOffFrequency <= sampleRate * 0.5); + jassert (Q > 0.0); + + auto A = jmax (0.0f, std::sqrt (gainFactor)); + auto aminus1 = A - 1.0; + auto aplus1 = A + 1.0; + auto omega = (MathConstants::twoPi * jmax (cutOffFrequency, 2.0)) / sampleRate; + auto coso = std::cos (omega); + auto beta = std::sin (omega) * std::sqrt (A) / Q; + auto aminus1TimesCoso = aminus1 * coso; + + return IIRCoefficients (A * (aplus1 - aminus1TimesCoso + beta), + A * 2.0 * (aminus1 - aplus1 * coso), + A * (aplus1 - aminus1TimesCoso - beta), + aplus1 + aminus1TimesCoso + beta, + -2.0 * (aminus1 + aplus1 * coso), + aplus1 + aminus1TimesCoso - beta); +} + +IIRCoefficients IIRCoefficients::makeHighShelf (double sampleRate, + double cutOffFrequency, + double Q, + float gainFactor) noexcept +{ + jassert (sampleRate > 0.0); + jassert (cutOffFrequency > 0.0 && cutOffFrequency <= sampleRate * 0.5); + jassert (Q > 0.0); + + auto A = jmax (0.0f, std::sqrt (gainFactor)); + auto aminus1 = A - 1.0; + auto aplus1 = A + 1.0; + auto omega = (MathConstants::twoPi * jmax (cutOffFrequency, 2.0)) / sampleRate; + auto coso = std::cos (omega); + auto beta = std::sin (omega) * std::sqrt (A) / Q; + auto aminus1TimesCoso = aminus1 * coso; + + return IIRCoefficients (A * (aplus1 + aminus1TimesCoso + beta), + A * -2.0 * (aminus1 + aplus1 * coso), + A * (aplus1 + aminus1TimesCoso - beta), + aplus1 - aminus1TimesCoso + beta, + 2.0 * (aminus1 - aplus1 * coso), + aplus1 - aminus1TimesCoso - beta); +} + +IIRCoefficients IIRCoefficients::makePeakFilter (double sampleRate, + double frequency, + double Q, + float gainFactor) noexcept +{ + jassert (sampleRate > 0.0); + jassert (frequency > 0.0 && frequency <= sampleRate * 0.5); + jassert (Q > 0.0); + + auto A = jmax (0.0f, std::sqrt (gainFactor)); + auto omega = (MathConstants::twoPi * jmax (frequency, 2.0)) / sampleRate; + auto alpha = 0.5 * std::sin (omega) / Q; + auto c2 = -2.0 * std::cos (omega); + auto alphaTimesA = alpha * A; + auto alphaOverA = alpha / A; + + return IIRCoefficients (1.0 + alphaTimesA, + c2, + 1.0 - alphaTimesA, + 1.0 + alphaOverA, + c2, + 1.0 - alphaOverA); +} + +//============================================================================== +IIRFilter::IIRFilter() noexcept +{ +} + +IIRFilter::IIRFilter (const IIRFilter& other) noexcept : active (other.active) +{ + const SpinLock::ScopedLockType sl (other.processLock); + coefficients = other.coefficients; +} + +IIRFilter::~IIRFilter() noexcept +{ +} + +//============================================================================== +void IIRFilter::makeInactive() noexcept +{ + const SpinLock::ScopedLockType sl (processLock); + active = false; +} + +void IIRFilter::setCoefficients (const IIRCoefficients& newCoefficients) noexcept +{ + const SpinLock::ScopedLockType sl (processLock); + coefficients = newCoefficients; + active = true; +} + +//============================================================================== +void IIRFilter::reset() noexcept +{ + const SpinLock::ScopedLockType sl (processLock); + v1 = v2 = 0.0; +} + +float IIRFilter::processSingleSampleRaw (float in) noexcept +{ + auto out = coefficients.coefficients[0] * in + v1; + + JUCE_SNAP_TO_ZERO (out); + + v1 = coefficients.coefficients[1] * in - coefficients.coefficients[3] * out + v2; + v2 = coefficients.coefficients[2] * in - coefficients.coefficients[4] * out; + + return out; +} + +void IIRFilter::processSamples (float* const samples, const int numSamples) noexcept +{ + const SpinLock::ScopedLockType sl (processLock); + + if (active) + { + auto c0 = coefficients.coefficients[0]; + auto c1 = coefficients.coefficients[1]; + auto c2 = coefficients.coefficients[2]; + auto c3 = coefficients.coefficients[3]; + auto c4 = coefficients.coefficients[4]; + auto lv1 = v1, lv2 = v2; + + for (int i = 0; i < numSamples; ++i) + { + auto in = samples[i]; + auto out = c0 * in + lv1; + samples[i] = out; + + lv1 = c1 * in - c3 * out + lv2; + lv2 = c2 * in - c4 * out; + } + + JUCE_SNAP_TO_ZERO (lv1); v1 = lv1; + JUCE_SNAP_TO_ZERO (lv2); v2 = lv2; + } +} + +} // namespace juce diff --git a/JuceLibraryCode/modules/juce_audio_basics/utilities/juce_IIRFilter.h b/JuceLibraryCode/modules/juce_audio_basics/utilities/juce_IIRFilter.h index 3b6e8cf..5eed4e2 100644 --- a/JuceLibraryCode/modules/juce_audio_basics/utilities/juce_IIRFilter.h +++ b/JuceLibraryCode/modules/juce_audio_basics/utilities/juce_IIRFilter.h @@ -1,217 +1,217 @@ -/* - ============================================================================== - - This file is part of the JUCE library. - Copyright (c) 2017 - ROLI Ltd. - - JUCE is an open source library subject to commercial or open-source - licensing. - - The code included in this file is provided under the terms of the ISC license - http://www.isc.org/downloads/software-support-policy/isc-license. Permission - To use, copy, modify, and/or distribute this software for any purpose with or - without fee is hereby granted provided that the above copyright notice and - this permission notice appear in all copies. - - JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER - EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE - DISCLAIMED. - - ============================================================================== -*/ - -namespace juce -{ - -class IIRFilter; - -//============================================================================== -/** - A set of coefficients for use in an IIRFilter object. - - @see IIRFilter - - @tags{Audio} -*/ -class JUCE_API IIRCoefficients -{ -public: - //============================================================================== - /** Creates a null set of coefficients (which will produce silence). */ - IIRCoefficients() noexcept; - - /** Directly constructs an object from the raw coefficients. - Most people will want to use the static methods instead of this, but - the constructor is public to allow tinkerers to create their own custom - filters! - */ - IIRCoefficients (double c1, double c2, double c3, - double c4, double c5, double c6) noexcept; - - /** Creates a copy of another filter. */ - IIRCoefficients (const IIRCoefficients&) noexcept; - /** Creates a copy of another filter. */ - IIRCoefficients& operator= (const IIRCoefficients&) noexcept; - /** Destructor. */ - ~IIRCoefficients() noexcept; - - //============================================================================== - /** Returns the coefficients for a low-pass filter. */ - static IIRCoefficients makeLowPass (double sampleRate, - double frequency) noexcept; - - /** Returns the coefficients for a low-pass filter with variable Q. */ - static IIRCoefficients makeLowPass (double sampleRate, - double frequency, - double Q) noexcept; - - //============================================================================== - /** Returns the coefficients for a high-pass filter. */ - static IIRCoefficients makeHighPass (double sampleRate, - double frequency) noexcept; - - /** Returns the coefficients for a high-pass filter with variable Q. */ - static IIRCoefficients makeHighPass (double sampleRate, - double frequency, - double Q) noexcept; - - //============================================================================== - /** Returns the coefficients for a band-pass filter. */ - static IIRCoefficients makeBandPass (double sampleRate, double frequency) noexcept; - - /** Returns the coefficients for a band-pass filter with variable Q. */ - static IIRCoefficients makeBandPass (double sampleRate, - double frequency, - double Q) noexcept; - - //============================================================================== - /** Returns the coefficients for a notch filter. */ - static IIRCoefficients makeNotchFilter (double sampleRate, double frequency) noexcept; - - /** Returns the coefficients for a notch filter with variable Q. */ - static IIRCoefficients makeNotchFilter (double sampleRate, - double frequency, - double Q) noexcept; - - //============================================================================== - /** Returns the coefficients for an all-pass filter. */ - static IIRCoefficients makeAllPass (double sampleRate, double frequency) noexcept; - - /** Returns the coefficients for an all-pass filter with variable Q. */ - static IIRCoefficients makeAllPass (double sampleRate, - double frequency, - double Q) noexcept; - - //============================================================================== - /** Returns the coefficients for a low-pass shelf filter with variable Q and gain. - - The gain is a scale factor that the low frequencies are multiplied by, so values - greater than 1.0 will boost the low frequencies, values less than 1.0 will - attenuate them. - */ - static IIRCoefficients makeLowShelf (double sampleRate, - double cutOffFrequency, - double Q, - float gainFactor) noexcept; - - /** Returns the coefficients for a high-pass shelf filter with variable Q and gain. - - The gain is a scale factor that the high frequencies are multiplied by, so values - greater than 1.0 will boost the high frequencies, values less than 1.0 will - attenuate them. - */ - static IIRCoefficients makeHighShelf (double sampleRate, - double cutOffFrequency, - double Q, - float gainFactor) noexcept; - - /** Returns the coefficients for a peak filter centred around a - given frequency, with a variable Q and gain. - - The gain is a scale factor that the centre frequencies are multiplied by, so - values greater than 1.0 will boost the centre frequencies, values less than - 1.0 will attenuate them. - */ - static IIRCoefficients makePeakFilter (double sampleRate, - double centreFrequency, - double Q, - float gainFactor) noexcept; - - //============================================================================== - /** The raw coefficients. - You should leave these numbers alone unless you really know what you're doing. - */ - float coefficients[5]; -}; - -//============================================================================== -/** - An IIR filter that can perform low, high, or band-pass filtering on an - audio signal. - - @see IIRCoefficient, IIRFilterAudioSource - - @tags{Audio} -*/ -class JUCE_API IIRFilter -{ -public: - //============================================================================== - /** Creates a filter. - - Initially the filter is inactive, so will have no effect on samples that - you process with it. Use the setCoefficients() method to turn it into the - type of filter needed. - */ - IIRFilter() noexcept; - - /** Creates a copy of another filter. */ - IIRFilter (const IIRFilter&) noexcept; - - /** Destructor. */ - ~IIRFilter() noexcept; - - //============================================================================== - /** Clears the filter so that any incoming data passes through unchanged. */ - void makeInactive() noexcept; - - /** Applies a set of coefficients to this filter. */ - void setCoefficients (const IIRCoefficients& newCoefficients) noexcept; - - /** Returns the coefficients that this filter is using. */ - IIRCoefficients getCoefficients() const noexcept { return coefficients; } - - //============================================================================== - /** Resets the filter's processing pipeline, ready to start a new stream of data. - - Note that this clears the processing state, but the type of filter and - its coefficients aren't changed. To put a filter into an inactive state, use - the makeInactive() method. - */ - void reset() noexcept; - - /** Performs the filter operation on the given set of samples. */ - void processSamples (float* samples, int numSamples) noexcept; - - /** Processes a single sample, without any locking or checking. - - Use this if you need fast processing of a single value, but be aware that - this isn't thread-safe in the way that processSamples() is. - */ - float processSingleSampleRaw (float sample) noexcept; - -protected: - //============================================================================== - SpinLock processLock; - IIRCoefficients coefficients; - float v1 = 0, v2 = 0; - bool active = false; - - // The exact meaning of an assignment operator would be ambiguous since the filters are - // stateful. If you want to copy the coefficients, then just use setCoefficients(). - IIRFilter& operator= (const IIRFilter&) = delete; - - JUCE_LEAK_DETECTOR (IIRFilter) -}; - -} // namespace juce +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2017 - ROLI Ltd. + + JUCE is an open source library subject to commercial or open-source + licensing. + + The code included in this file is provided under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license. Permission + To use, copy, modify, and/or distribute this software for any purpose with or + without fee is hereby granted provided that the above copyright notice and + this permission notice appear in all copies. + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +namespace juce +{ + +class IIRFilter; + +//============================================================================== +/** + A set of coefficients for use in an IIRFilter object. + + @see IIRFilter + + @tags{Audio} +*/ +class JUCE_API IIRCoefficients +{ +public: + //============================================================================== + /** Creates a null set of coefficients (which will produce silence). */ + IIRCoefficients() noexcept; + + /** Directly constructs an object from the raw coefficients. + Most people will want to use the static methods instead of this, but + the constructor is public to allow tinkerers to create their own custom + filters! + */ + IIRCoefficients (double c1, double c2, double c3, + double c4, double c5, double c6) noexcept; + + /** Creates a copy of another filter. */ + IIRCoefficients (const IIRCoefficients&) noexcept; + /** Creates a copy of another filter. */ + IIRCoefficients& operator= (const IIRCoefficients&) noexcept; + /** Destructor. */ + ~IIRCoefficients() noexcept; + + //============================================================================== + /** Returns the coefficients for a low-pass filter. */ + static IIRCoefficients makeLowPass (double sampleRate, + double frequency) noexcept; + + /** Returns the coefficients for a low-pass filter with variable Q. */ + static IIRCoefficients makeLowPass (double sampleRate, + double frequency, + double Q) noexcept; + + //============================================================================== + /** Returns the coefficients for a high-pass filter. */ + static IIRCoefficients makeHighPass (double sampleRate, + double frequency) noexcept; + + /** Returns the coefficients for a high-pass filter with variable Q. */ + static IIRCoefficients makeHighPass (double sampleRate, + double frequency, + double Q) noexcept; + + //============================================================================== + /** Returns the coefficients for a band-pass filter. */ + static IIRCoefficients makeBandPass (double sampleRate, double frequency) noexcept; + + /** Returns the coefficients for a band-pass filter with variable Q. */ + static IIRCoefficients makeBandPass (double sampleRate, + double frequency, + double Q) noexcept; + + //============================================================================== + /** Returns the coefficients for a notch filter. */ + static IIRCoefficients makeNotchFilter (double sampleRate, double frequency) noexcept; + + /** Returns the coefficients for a notch filter with variable Q. */ + static IIRCoefficients makeNotchFilter (double sampleRate, + double frequency, + double Q) noexcept; + + //============================================================================== + /** Returns the coefficients for an all-pass filter. */ + static IIRCoefficients makeAllPass (double sampleRate, double frequency) noexcept; + + /** Returns the coefficients for an all-pass filter with variable Q. */ + static IIRCoefficients makeAllPass (double sampleRate, + double frequency, + double Q) noexcept; + + //============================================================================== + /** Returns the coefficients for a low-pass shelf filter with variable Q and gain. + + The gain is a scale factor that the low frequencies are multiplied by, so values + greater than 1.0 will boost the low frequencies, values less than 1.0 will + attenuate them. + */ + static IIRCoefficients makeLowShelf (double sampleRate, + double cutOffFrequency, + double Q, + float gainFactor) noexcept; + + /** Returns the coefficients for a high-pass shelf filter with variable Q and gain. + + The gain is a scale factor that the high frequencies are multiplied by, so values + greater than 1.0 will boost the high frequencies, values less than 1.0 will + attenuate them. + */ + static IIRCoefficients makeHighShelf (double sampleRate, + double cutOffFrequency, + double Q, + float gainFactor) noexcept; + + /** Returns the coefficients for a peak filter centred around a + given frequency, with a variable Q and gain. + + The gain is a scale factor that the centre frequencies are multiplied by, so + values greater than 1.0 will boost the centre frequencies, values less than + 1.0 will attenuate them. + */ + static IIRCoefficients makePeakFilter (double sampleRate, + double centreFrequency, + double Q, + float gainFactor) noexcept; + + //============================================================================== + /** The raw coefficients. + You should leave these numbers alone unless you really know what you're doing. + */ + float coefficients[5]; +}; + +//============================================================================== +/** + An IIR filter that can perform low, high, or band-pass filtering on an + audio signal. + + @see IIRCoefficient, IIRFilterAudioSource + + @tags{Audio} +*/ +class JUCE_API IIRFilter +{ +public: + //============================================================================== + /** Creates a filter. + + Initially the filter is inactive, so will have no effect on samples that + you process with it. Use the setCoefficients() method to turn it into the + type of filter needed. + */ + IIRFilter() noexcept; + + /** Creates a copy of another filter. */ + IIRFilter (const IIRFilter&) noexcept; + + /** Destructor. */ + ~IIRFilter() noexcept; + + //============================================================================== + /** Clears the filter so that any incoming data passes through unchanged. */ + void makeInactive() noexcept; + + /** Applies a set of coefficients to this filter. */ + void setCoefficients (const IIRCoefficients& newCoefficients) noexcept; + + /** Returns the coefficients that this filter is using. */ + IIRCoefficients getCoefficients() const noexcept { return coefficients; } + + //============================================================================== + /** Resets the filter's processing pipeline, ready to start a new stream of data. + + Note that this clears the processing state, but the type of filter and + its coefficients aren't changed. To put a filter into an inactive state, use + the makeInactive() method. + */ + void reset() noexcept; + + /** Performs the filter operation on the given set of samples. */ + void processSamples (float* samples, int numSamples) noexcept; + + /** Processes a single sample, without any locking or checking. + + Use this if you need fast processing of a single value, but be aware that + this isn't thread-safe in the way that processSamples() is. + */ + float processSingleSampleRaw (float sample) noexcept; + +protected: + //============================================================================== + SpinLock processLock; + IIRCoefficients coefficients; + float v1 = 0, v2 = 0; + bool active = false; + + // The exact meaning of an assignment operator would be ambiguous since the filters are + // stateful. If you want to copy the coefficients, then just use setCoefficients(). + IIRFilter& operator= (const IIRFilter&) = delete; + + JUCE_LEAK_DETECTOR (IIRFilter) +}; + +} // namespace juce diff --git a/JuceLibraryCode/modules/juce_audio_basics/utilities/juce_LagrangeInterpolator.cpp b/JuceLibraryCode/modules/juce_audio_basics/utilities/juce_LagrangeInterpolator.cpp index 952327c..16af330 100644 --- a/JuceLibraryCode/modules/juce_audio_basics/utilities/juce_LagrangeInterpolator.cpp +++ b/JuceLibraryCode/modules/juce_audio_basics/utilities/juce_LagrangeInterpolator.cpp @@ -1,467 +1,467 @@ -/* - ============================================================================== - - This file is part of the JUCE library. - Copyright (c) 2017 - ROLI Ltd. - - JUCE is an open source library subject to commercial or open-source - licensing. - - The code included in this file is provided under the terms of the ISC license - http://www.isc.org/downloads/software-support-policy/isc-license. Permission - To use, copy, modify, and/or distribute this software for any purpose with or - without fee is hereby granted provided that the above copyright notice and - this permission notice appear in all copies. - - JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER - EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE - DISCLAIMED. - - ============================================================================== -*/ - -namespace juce -{ - -namespace -{ - static forcedinline void pushInterpolationSample (float* lastInputSamples, float newValue) noexcept - { - lastInputSamples[4] = lastInputSamples[3]; - lastInputSamples[3] = lastInputSamples[2]; - lastInputSamples[2] = lastInputSamples[1]; - lastInputSamples[1] = lastInputSamples[0]; - lastInputSamples[0] = newValue; - } - - static forcedinline void pushInterpolationSamples (float* lastInputSamples, const float* input, int numOut) noexcept - { - if (numOut >= 5) - { - for (int i = 0; i < 5; ++i) - lastInputSamples[i] = input[--numOut]; - } - else - { - for (int i = 0; i < numOut; ++i) - pushInterpolationSample (lastInputSamples, input[i]); - } - } - - static forcedinline void pushInterpolationSamples (float* lastInputSamples, const float* input, - int numOut, int available, int wrapAround) noexcept - { - if (numOut >= 5) - { - if (available >= 5) - { - for (int i = 0; i < 5; ++i) - lastInputSamples[i] = input[--numOut]; - } - else - { - for (int i = 0; i < available; ++i) - lastInputSamples[i] = input[--numOut]; - - if (wrapAround > 0) - { - numOut -= wrapAround; - - for (int i = available; i < 5; ++i) - lastInputSamples[i] = input[--numOut]; - } - else - { - for (int i = available; i < 5; ++i) - lastInputSamples[i] = 0.0f; - } - } - } - else - { - if (numOut > available) - { - for (int i = 0; i < available; ++i) - pushInterpolationSample (lastInputSamples, input[i]); - - if (wrapAround > 0) - { - for (int i = 0; i < numOut - available; ++i) - pushInterpolationSample (lastInputSamples, input[i + available - wrapAround]); - } - else - { - for (int i = 0; i < numOut - available; ++i) - pushInterpolationSample (lastInputSamples, 0); - } - } - else - { - for (int i = 0; i < numOut; ++i) - pushInterpolationSample (lastInputSamples, input[i]); - } - } - } - - template - static int interpolate (float* lastInputSamples, double& subSamplePos, double actualRatio, - const float* in, float* out, int numOut) noexcept - { - auto pos = subSamplePos; - - if (actualRatio == 1.0 && pos == 1.0) - { - memcpy (out, in, (size_t) numOut * sizeof (float)); - pushInterpolationSamples (lastInputSamples, in, numOut); - return numOut; - } - - int numUsed = 0; - - while (numOut > 0) - { - while (pos >= 1.0) - { - pushInterpolationSample (lastInputSamples, in[numUsed++]); - pos -= 1.0; - } - - *out++ = InterpolatorType::valueAtOffset (lastInputSamples, (float) pos); - pos += actualRatio; - --numOut; - } - - subSamplePos = pos; - return numUsed; - } - - template - static int interpolate (float* lastInputSamples, double& subSamplePos, double actualRatio, - const float* in, float* out, int numOut, int available, int wrap) noexcept - { - if (actualRatio == 1.0) - { - if (available >= numOut) - { - memcpy (out, in, (size_t) numOut * sizeof (float)); - pushInterpolationSamples (lastInputSamples, in, numOut, available, wrap); - } - else - { - memcpy (out, in, (size_t) available * sizeof (float)); - pushInterpolationSamples (lastInputSamples, in, numOut, available, wrap); - - if (wrap > 0) - { - memcpy (out + available, in + available - wrap, (size_t) (numOut - available) * sizeof (float)); - pushInterpolationSamples (lastInputSamples, in, numOut, available, wrap); - } - else - { - for (int i = 0; i < numOut - available; ++i) - pushInterpolationSample (lastInputSamples, 0); - } - } - - return numOut; - } - - auto originalIn = in; - auto pos = subSamplePos; - bool exceeded = false; - - if (actualRatio < 1.0) - { - for (int i = numOut; --i >= 0;) - { - if (pos >= 1.0) - { - if (exceeded) - { - pushInterpolationSample (lastInputSamples, 0); - } - else - { - pushInterpolationSample (lastInputSamples, *in++); - - if (--available <= 0) - { - if (wrap > 0) - { - in -= wrap; - available += wrap; - } - else - { - exceeded = true; - } - } - } - - pos -= 1.0; - } - - *out++ = InterpolatorType::valueAtOffset (lastInputSamples, (float) pos); - pos += actualRatio; - } - } - else - { - for (int i = numOut; --i >= 0;) - { - while (pos < actualRatio) - { - if (exceeded) - { - pushInterpolationSample (lastInputSamples, 0); - } - else - { - pushInterpolationSample (lastInputSamples, *in++); - - if (--available <= 0) - { - if (wrap > 0) - { - in -= wrap; - available += wrap; - } - else - { - exceeded = true; - } - } - } - - pos += 1.0; - } - - pos -= actualRatio; - *out++ = InterpolatorType::valueAtOffset (lastInputSamples, jmax (0.0f, 1.0f - (float) pos)); - } - } - - subSamplePos = pos; - - if (wrap == 0) - return (int) (in - originalIn); - - return ((int) (in - originalIn) + wrap) % wrap; - } - - template - static int interpolateAdding (float* lastInputSamples, double& subSamplePos, double actualRatio, - const float* in, float* out, int numOut, - int available, int wrap, float gain) noexcept - { - if (actualRatio == 1.0) - { - if (available >= numOut) - { - FloatVectorOperations::addWithMultiply (out, in, gain, numOut); - pushInterpolationSamples (lastInputSamples, in, numOut, available, wrap); - } - else - { - FloatVectorOperations::addWithMultiply (out, in, gain, available); - pushInterpolationSamples (lastInputSamples, in, available, available, wrap); - - if (wrap > 0) - { - FloatVectorOperations::addWithMultiply (out, in - wrap, gain, numOut - available); - pushInterpolationSamples (lastInputSamples, in - wrap, numOut - available, available, wrap); - } - else - { - for (int i = 0; i < numOut-available; ++i) - pushInterpolationSample (lastInputSamples, 0.0); - } - } - - return numOut; - } - - auto originalIn = in; - auto pos = subSamplePos; - bool exceeded = false; - - if (actualRatio < 1.0) - { - for (int i = numOut; --i >= 0;) - { - if (pos >= 1.0) - { - if (exceeded) - { - pushInterpolationSample (lastInputSamples, 0.0); - } - else - { - pushInterpolationSample (lastInputSamples, *in++); - - if (--available <= 0) - { - if (wrap > 0) - { - in -= wrap; - available += wrap; - } - else - { - exceeded = true; - } - } - } - - pos -= 1.0; - } - - *out++ += gain * InterpolatorType::valueAtOffset (lastInputSamples, (float) pos); - pos += actualRatio; - } - } - else - { - for (int i = numOut; --i >= 0;) - { - while (pos < actualRatio) - { - if (exceeded) - { - pushInterpolationSample (lastInputSamples, 0.0); - } - else - { - pushInterpolationSample (lastInputSamples, *in++); - - if (--available <= 0) - { - if (wrap > 0) - { - in -= wrap; - available += wrap; - } - else - { - exceeded = true; - } - } - } - - pos += 1.0; - } - - pos -= actualRatio; - *out++ += gain * InterpolatorType::valueAtOffset (lastInputSamples, jmax (0.0f, 1.0f - (float) pos)); - } - } - - subSamplePos = pos; - - if (wrap == 0) - return (int) (in - originalIn); - - return ((int) (in - originalIn) + wrap) % wrap; - } - - template - static int interpolateAdding (float* lastInputSamples, double& subSamplePos, double actualRatio, - const float* in, float* out, int numOut, float gain) noexcept - { - auto pos = subSamplePos; - - if (actualRatio == 1.0 && pos == 1.0) - { - FloatVectorOperations::addWithMultiply (out, in, gain, numOut); - pushInterpolationSamples (lastInputSamples, in, numOut); - return numOut; - } - - int numUsed = 0; - - while (numOut > 0) - { - while (pos >= 1.0) - { - pushInterpolationSample (lastInputSamples, in[numUsed++]); - pos -= 1.0; - } - - *out++ += gain * InterpolatorType::valueAtOffset (lastInputSamples, (float) pos); - pos += actualRatio; - --numOut; - } - - subSamplePos = pos; - return numUsed; - } -} - -//============================================================================== -template -struct LagrangeResampleHelper -{ - static forcedinline void calc (float& a, float b) noexcept { a *= b * (1.0f / k); } -}; - -template<> -struct LagrangeResampleHelper<0> -{ - static forcedinline void calc (float&, float) noexcept {} -}; - -struct LagrangeAlgorithm -{ - static forcedinline float valueAtOffset (const float* inputs, float offset) noexcept - { - return calcCoefficient<0> (inputs[4], offset) - + calcCoefficient<1> (inputs[3], offset) - + calcCoefficient<2> (inputs[2], offset) - + calcCoefficient<3> (inputs[1], offset) - + calcCoefficient<4> (inputs[0], offset); - } - - template - static forcedinline float calcCoefficient (float input, float offset) noexcept - { - LagrangeResampleHelper<0 - k>::calc (input, -2.0f - offset); - LagrangeResampleHelper<1 - k>::calc (input, -1.0f - offset); - LagrangeResampleHelper<2 - k>::calc (input, 0.0f - offset); - LagrangeResampleHelper<3 - k>::calc (input, 1.0f - offset); - LagrangeResampleHelper<4 - k>::calc (input, 2.0f - offset); - return input; - } -}; - -LagrangeInterpolator::LagrangeInterpolator() noexcept { reset(); } -LagrangeInterpolator::~LagrangeInterpolator() noexcept {} - -void LagrangeInterpolator::reset() noexcept -{ - subSamplePos = 1.0; - - for (auto& s : lastInputSamples) - s = 0; -} - -int LagrangeInterpolator::process (double actualRatio, const float* in, float* out, int numOut, int available, int wrap) noexcept -{ - return interpolate (lastInputSamples, subSamplePos, actualRatio, in, out, numOut, available, wrap); -} - -int LagrangeInterpolator::process (double actualRatio, const float* in, float* out, int numOut) noexcept -{ - return interpolate (lastInputSamples, subSamplePos, actualRatio, in, out, numOut); -} - -int LagrangeInterpolator::processAdding (double actualRatio, const float* in, float* out, int numOut, int available, int wrap, float gain) noexcept -{ - return interpolateAdding (lastInputSamples, subSamplePos, actualRatio, in, out, numOut, available, wrap, gain); -} - -int LagrangeInterpolator::processAdding (double actualRatio, const float* in, float* out, int numOut, float gain) noexcept -{ - return interpolateAdding (lastInputSamples, subSamplePos, actualRatio, in, out, numOut, gain); -} - -} // namespace juce +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2017 - ROLI Ltd. + + JUCE is an open source library subject to commercial or open-source + licensing. + + The code included in this file is provided under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license. Permission + To use, copy, modify, and/or distribute this software for any purpose with or + without fee is hereby granted provided that the above copyright notice and + this permission notice appear in all copies. + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +namespace juce +{ + +namespace +{ + static forcedinline void pushInterpolationSample (float* lastInputSamples, float newValue) noexcept + { + lastInputSamples[4] = lastInputSamples[3]; + lastInputSamples[3] = lastInputSamples[2]; + lastInputSamples[2] = lastInputSamples[1]; + lastInputSamples[1] = lastInputSamples[0]; + lastInputSamples[0] = newValue; + } + + static forcedinline void pushInterpolationSamples (float* lastInputSamples, const float* input, int numOut) noexcept + { + if (numOut >= 5) + { + for (int i = 0; i < 5; ++i) + lastInputSamples[i] = input[--numOut]; + } + else + { + for (int i = 0; i < numOut; ++i) + pushInterpolationSample (lastInputSamples, input[i]); + } + } + + static forcedinline void pushInterpolationSamples (float* lastInputSamples, const float* input, + int numOut, int available, int wrapAround) noexcept + { + if (numOut >= 5) + { + if (available >= 5) + { + for (int i = 0; i < 5; ++i) + lastInputSamples[i] = input[--numOut]; + } + else + { + for (int i = 0; i < available; ++i) + lastInputSamples[i] = input[--numOut]; + + if (wrapAround > 0) + { + numOut -= wrapAround; + + for (int i = available; i < 5; ++i) + lastInputSamples[i] = input[--numOut]; + } + else + { + for (int i = available; i < 5; ++i) + lastInputSamples[i] = 0.0f; + } + } + } + else + { + if (numOut > available) + { + for (int i = 0; i < available; ++i) + pushInterpolationSample (lastInputSamples, input[i]); + + if (wrapAround > 0) + { + for (int i = 0; i < numOut - available; ++i) + pushInterpolationSample (lastInputSamples, input[i + available - wrapAround]); + } + else + { + for (int i = 0; i < numOut - available; ++i) + pushInterpolationSample (lastInputSamples, 0); + } + } + else + { + for (int i = 0; i < numOut; ++i) + pushInterpolationSample (lastInputSamples, input[i]); + } + } + } + + template + static int interpolate (float* lastInputSamples, double& subSamplePos, double actualRatio, + const float* in, float* out, int numOut) noexcept + { + auto pos = subSamplePos; + + if (actualRatio == 1.0 && pos == 1.0) + { + memcpy (out, in, (size_t) numOut * sizeof (float)); + pushInterpolationSamples (lastInputSamples, in, numOut); + return numOut; + } + + int numUsed = 0; + + while (numOut > 0) + { + while (pos >= 1.0) + { + pushInterpolationSample (lastInputSamples, in[numUsed++]); + pos -= 1.0; + } + + *out++ = InterpolatorType::valueAtOffset (lastInputSamples, (float) pos); + pos += actualRatio; + --numOut; + } + + subSamplePos = pos; + return numUsed; + } + + template + static int interpolate (float* lastInputSamples, double& subSamplePos, double actualRatio, + const float* in, float* out, int numOut, int available, int wrap) noexcept + { + if (actualRatio == 1.0) + { + if (available >= numOut) + { + memcpy (out, in, (size_t) numOut * sizeof (float)); + pushInterpolationSamples (lastInputSamples, in, numOut, available, wrap); + } + else + { + memcpy (out, in, (size_t) available * sizeof (float)); + pushInterpolationSamples (lastInputSamples, in, numOut, available, wrap); + + if (wrap > 0) + { + memcpy (out + available, in + available - wrap, (size_t) (numOut - available) * sizeof (float)); + pushInterpolationSamples (lastInputSamples, in, numOut, available, wrap); + } + else + { + for (int i = 0; i < numOut - available; ++i) + pushInterpolationSample (lastInputSamples, 0); + } + } + + return numOut; + } + + auto originalIn = in; + auto pos = subSamplePos; + bool exceeded = false; + + if (actualRatio < 1.0) + { + for (int i = numOut; --i >= 0;) + { + if (pos >= 1.0) + { + if (exceeded) + { + pushInterpolationSample (lastInputSamples, 0); + } + else + { + pushInterpolationSample (lastInputSamples, *in++); + + if (--available <= 0) + { + if (wrap > 0) + { + in -= wrap; + available += wrap; + } + else + { + exceeded = true; + } + } + } + + pos -= 1.0; + } + + *out++ = InterpolatorType::valueAtOffset (lastInputSamples, (float) pos); + pos += actualRatio; + } + } + else + { + for (int i = numOut; --i >= 0;) + { + while (pos < actualRatio) + { + if (exceeded) + { + pushInterpolationSample (lastInputSamples, 0); + } + else + { + pushInterpolationSample (lastInputSamples, *in++); + + if (--available <= 0) + { + if (wrap > 0) + { + in -= wrap; + available += wrap; + } + else + { + exceeded = true; + } + } + } + + pos += 1.0; + } + + pos -= actualRatio; + *out++ = InterpolatorType::valueAtOffset (lastInputSamples, jmax (0.0f, 1.0f - (float) pos)); + } + } + + subSamplePos = pos; + + if (wrap == 0) + return (int) (in - originalIn); + + return ((int) (in - originalIn) + wrap) % wrap; + } + + template + static int interpolateAdding (float* lastInputSamples, double& subSamplePos, double actualRatio, + const float* in, float* out, int numOut, + int available, int wrap, float gain) noexcept + { + if (actualRatio == 1.0) + { + if (available >= numOut) + { + FloatVectorOperations::addWithMultiply (out, in, gain, numOut); + pushInterpolationSamples (lastInputSamples, in, numOut, available, wrap); + } + else + { + FloatVectorOperations::addWithMultiply (out, in, gain, available); + pushInterpolationSamples (lastInputSamples, in, available, available, wrap); + + if (wrap > 0) + { + FloatVectorOperations::addWithMultiply (out, in - wrap, gain, numOut - available); + pushInterpolationSamples (lastInputSamples, in - wrap, numOut - available, available, wrap); + } + else + { + for (int i = 0; i < numOut-available; ++i) + pushInterpolationSample (lastInputSamples, 0.0); + } + } + + return numOut; + } + + auto originalIn = in; + auto pos = subSamplePos; + bool exceeded = false; + + if (actualRatio < 1.0) + { + for (int i = numOut; --i >= 0;) + { + if (pos >= 1.0) + { + if (exceeded) + { + pushInterpolationSample (lastInputSamples, 0.0); + } + else + { + pushInterpolationSample (lastInputSamples, *in++); + + if (--available <= 0) + { + if (wrap > 0) + { + in -= wrap; + available += wrap; + } + else + { + exceeded = true; + } + } + } + + pos -= 1.0; + } + + *out++ += gain * InterpolatorType::valueAtOffset (lastInputSamples, (float) pos); + pos += actualRatio; + } + } + else + { + for (int i = numOut; --i >= 0;) + { + while (pos < actualRatio) + { + if (exceeded) + { + pushInterpolationSample (lastInputSamples, 0.0); + } + else + { + pushInterpolationSample (lastInputSamples, *in++); + + if (--available <= 0) + { + if (wrap > 0) + { + in -= wrap; + available += wrap; + } + else + { + exceeded = true; + } + } + } + + pos += 1.0; + } + + pos -= actualRatio; + *out++ += gain * InterpolatorType::valueAtOffset (lastInputSamples, jmax (0.0f, 1.0f - (float) pos)); + } + } + + subSamplePos = pos; + + if (wrap == 0) + return (int) (in - originalIn); + + return ((int) (in - originalIn) + wrap) % wrap; + } + + template + static int interpolateAdding (float* lastInputSamples, double& subSamplePos, double actualRatio, + const float* in, float* out, int numOut, float gain) noexcept + { + auto pos = subSamplePos; + + if (actualRatio == 1.0 && pos == 1.0) + { + FloatVectorOperations::addWithMultiply (out, in, gain, numOut); + pushInterpolationSamples (lastInputSamples, in, numOut); + return numOut; + } + + int numUsed = 0; + + while (numOut > 0) + { + while (pos >= 1.0) + { + pushInterpolationSample (lastInputSamples, in[numUsed++]); + pos -= 1.0; + } + + *out++ += gain * InterpolatorType::valueAtOffset (lastInputSamples, (float) pos); + pos += actualRatio; + --numOut; + } + + subSamplePos = pos; + return numUsed; + } +} + +//============================================================================== +template +struct LagrangeResampleHelper +{ + static forcedinline void calc (float& a, float b) noexcept { a *= b * (1.0f / k); } +}; + +template<> +struct LagrangeResampleHelper<0> +{ + static forcedinline void calc (float&, float) noexcept {} +}; + +struct LagrangeAlgorithm +{ + static forcedinline float valueAtOffset (const float* inputs, float offset) noexcept + { + return calcCoefficient<0> (inputs[4], offset) + + calcCoefficient<1> (inputs[3], offset) + + calcCoefficient<2> (inputs[2], offset) + + calcCoefficient<3> (inputs[1], offset) + + calcCoefficient<4> (inputs[0], offset); + } + + template + static forcedinline float calcCoefficient (float input, float offset) noexcept + { + LagrangeResampleHelper<0 - k>::calc (input, -2.0f - offset); + LagrangeResampleHelper<1 - k>::calc (input, -1.0f - offset); + LagrangeResampleHelper<2 - k>::calc (input, 0.0f - offset); + LagrangeResampleHelper<3 - k>::calc (input, 1.0f - offset); + LagrangeResampleHelper<4 - k>::calc (input, 2.0f - offset); + return input; + } +}; + +LagrangeInterpolator::LagrangeInterpolator() noexcept { reset(); } +LagrangeInterpolator::~LagrangeInterpolator() noexcept {} + +void LagrangeInterpolator::reset() noexcept +{ + subSamplePos = 1.0; + + for (auto& s : lastInputSamples) + s = 0; +} + +int LagrangeInterpolator::process (double actualRatio, const float* in, float* out, int numOut, int available, int wrap) noexcept +{ + return interpolate (lastInputSamples, subSamplePos, actualRatio, in, out, numOut, available, wrap); +} + +int LagrangeInterpolator::process (double actualRatio, const float* in, float* out, int numOut) noexcept +{ + return interpolate (lastInputSamples, subSamplePos, actualRatio, in, out, numOut); +} + +int LagrangeInterpolator::processAdding (double actualRatio, const float* in, float* out, int numOut, int available, int wrap, float gain) noexcept +{ + return interpolateAdding (lastInputSamples, subSamplePos, actualRatio, in, out, numOut, available, wrap, gain); +} + +int LagrangeInterpolator::processAdding (double actualRatio, const float* in, float* out, int numOut, float gain) noexcept +{ + return interpolateAdding (lastInputSamples, subSamplePos, actualRatio, in, out, numOut, gain); +} + +} // namespace juce diff --git a/JuceLibraryCode/modules/juce_audio_basics/utilities/juce_LagrangeInterpolator.h b/JuceLibraryCode/modules/juce_audio_basics/utilities/juce_LagrangeInterpolator.h index 25598e7..3489795 100644 --- a/JuceLibraryCode/modules/juce_audio_basics/utilities/juce_LagrangeInterpolator.h +++ b/JuceLibraryCode/modules/juce_audio_basics/utilities/juce_LagrangeInterpolator.h @@ -1,146 +1,146 @@ -/* - ============================================================================== - - This file is part of the JUCE library. - Copyright (c) 2017 - ROLI Ltd. - - JUCE is an open source library subject to commercial or open-source - licensing. - - The code included in this file is provided under the terms of the ISC license - http://www.isc.org/downloads/software-support-policy/isc-license. Permission - To use, copy, modify, and/or distribute this software for any purpose with or - without fee is hereby granted provided that the above copyright notice and - this permission notice appear in all copies. - - JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER - EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE - DISCLAIMED. - - ============================================================================== -*/ - -namespace juce -{ - -/** - Interpolator for resampling a stream of floats using 4-point lagrange interpolation. - - Note that the resampler is stateful, so when there's a break in the continuity - of the input stream you're feeding it, you should call reset() before feeding - it any new data. And like with any other stateful filter, if you're resampling - multiple channels, make sure each one uses its own LagrangeInterpolator - object. - - @see CatmullRomInterpolator - - @tags{Audio} -*/ -class JUCE_API LagrangeInterpolator -{ -public: - LagrangeInterpolator() noexcept; - ~LagrangeInterpolator() noexcept; - - LagrangeInterpolator (LagrangeInterpolator&&) noexcept = default; - LagrangeInterpolator& operator= (LagrangeInterpolator&&) noexcept = default; - - /** Resets the state of the interpolator. - Call this when there's a break in the continuity of the input data stream. - */ - void reset() noexcept; - - /** Resamples a stream of samples. - - @param speedRatio the number of input samples to use for each output sample - @param inputSamples the source data to read from. This must contain at - least (speedRatio * numOutputSamplesToProduce) samples. - @param outputSamples the buffer to write the results into - @param numOutputSamplesToProduce the number of output samples that should be created - - @returns the actual number of input samples that were used - */ - int process (double speedRatio, - const float* inputSamples, - float* outputSamples, - int numOutputSamplesToProduce) noexcept; - - /** Resamples a stream of samples. - - @param speedRatio the number of input samples to use for each output sample - @param inputSamples the source data to read from. This must contain at - least (speedRatio * numOutputSamplesToProduce) samples. - @param outputSamples the buffer to write the results into - @param numOutputSamplesToProduce the number of output samples that should be created - @param available the number of available input samples. If it needs more samples - than available, it either wraps back for wrapAround samples, or - it feeds zeroes - @param wrapAround if the stream exceeds available samples, it wraps back for - wrapAround samples. If wrapAround is set to 0, it will feed zeroes. - - @returns the actual number of input samples that were used - */ - int process (double speedRatio, - const float* inputSamples, - float* outputSamples, - int numOutputSamplesToProduce, - int available, - int wrapAround) noexcept; - - /** Resamples a stream of samples, adding the results to the output data - with a gain. - - @param speedRatio the number of input samples to use for each output sample - @param inputSamples the source data to read from. This must contain at - least (speedRatio * numOutputSamplesToProduce) samples. - @param outputSamples the buffer to write the results to - the result values will be added - to any pre-existing data in this buffer after being multiplied by - the gain factor - @param numOutputSamplesToProduce the number of output samples that should be created - @param gain a gain factor to multiply the resulting samples by before - adding them to the destination buffer - - @returns the actual number of input samples that were used - */ - int processAdding (double speedRatio, - const float* inputSamples, - float* outputSamples, - int numOutputSamplesToProduce, - float gain) noexcept; - - /** Resamples a stream of samples, adding the results to the output data - with a gain. - - @param speedRatio the number of input samples to use for each output sample - @param inputSamples the source data to read from. This must contain at - least (speedRatio * numOutputSamplesToProduce) samples. - @param outputSamples the buffer to write the results to - the result values will be added - to any pre-existing data in this buffer after being multiplied by - the gain factor - @param numOutputSamplesToProduce the number of output samples that should be created - @param available the number of available input samples. If it needs more samples - than available, it either wraps back for wrapAround samples, or - it feeds zeroes - @param wrapAround if the stream exceeds available samples, it wraps back for - wrapAround samples. If wrapAround is set to 0, it will feed zeroes. - @param gain a gain factor to multiply the resulting samples by before - adding them to the destination buffer - - @returns the actual number of input samples that were used - */ - int processAdding (double speedRatio, - const float* inputSamples, - float* outputSamples, - int numOutputSamplesToProduce, - int available, - int wrapAround, - float gain) noexcept; - -private: - float lastInputSamples[5]; - double subSamplePos; - - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (LagrangeInterpolator) -}; - -} // namespace juce +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2017 - ROLI Ltd. + + JUCE is an open source library subject to commercial or open-source + licensing. + + The code included in this file is provided under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license. Permission + To use, copy, modify, and/or distribute this software for any purpose with or + without fee is hereby granted provided that the above copyright notice and + this permission notice appear in all copies. + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +namespace juce +{ + +/** + Interpolator for resampling a stream of floats using 4-point lagrange interpolation. + + Note that the resampler is stateful, so when there's a break in the continuity + of the input stream you're feeding it, you should call reset() before feeding + it any new data. And like with any other stateful filter, if you're resampling + multiple channels, make sure each one uses its own LagrangeInterpolator + object. + + @see CatmullRomInterpolator + + @tags{Audio} +*/ +class JUCE_API LagrangeInterpolator +{ +public: + LagrangeInterpolator() noexcept; + ~LagrangeInterpolator() noexcept; + + LagrangeInterpolator (LagrangeInterpolator&&) noexcept = default; + LagrangeInterpolator& operator= (LagrangeInterpolator&&) noexcept = default; + + /** Resets the state of the interpolator. + Call this when there's a break in the continuity of the input data stream. + */ + void reset() noexcept; + + /** Resamples a stream of samples. + + @param speedRatio the number of input samples to use for each output sample + @param inputSamples the source data to read from. This must contain at + least (speedRatio * numOutputSamplesToProduce) samples. + @param outputSamples the buffer to write the results into + @param numOutputSamplesToProduce the number of output samples that should be created + + @returns the actual number of input samples that were used + */ + int process (double speedRatio, + const float* inputSamples, + float* outputSamples, + int numOutputSamplesToProduce) noexcept; + + /** Resamples a stream of samples. + + @param speedRatio the number of input samples to use for each output sample + @param inputSamples the source data to read from. This must contain at + least (speedRatio * numOutputSamplesToProduce) samples. + @param outputSamples the buffer to write the results into + @param numOutputSamplesToProduce the number of output samples that should be created + @param available the number of available input samples. If it needs more samples + than available, it either wraps back for wrapAround samples, or + it feeds zeroes + @param wrapAround if the stream exceeds available samples, it wraps back for + wrapAround samples. If wrapAround is set to 0, it will feed zeroes. + + @returns the actual number of input samples that were used + */ + int process (double speedRatio, + const float* inputSamples, + float* outputSamples, + int numOutputSamplesToProduce, + int available, + int wrapAround) noexcept; + + /** Resamples a stream of samples, adding the results to the output data + with a gain. + + @param speedRatio the number of input samples to use for each output sample + @param inputSamples the source data to read from. This must contain at + least (speedRatio * numOutputSamplesToProduce) samples. + @param outputSamples the buffer to write the results to - the result values will be added + to any pre-existing data in this buffer after being multiplied by + the gain factor + @param numOutputSamplesToProduce the number of output samples that should be created + @param gain a gain factor to multiply the resulting samples by before + adding them to the destination buffer + + @returns the actual number of input samples that were used + */ + int processAdding (double speedRatio, + const float* inputSamples, + float* outputSamples, + int numOutputSamplesToProduce, + float gain) noexcept; + + /** Resamples a stream of samples, adding the results to the output data + with a gain. + + @param speedRatio the number of input samples to use for each output sample + @param inputSamples the source data to read from. This must contain at + least (speedRatio * numOutputSamplesToProduce) samples. + @param outputSamples the buffer to write the results to - the result values will be added + to any pre-existing data in this buffer after being multiplied by + the gain factor + @param numOutputSamplesToProduce the number of output samples that should be created + @param available the number of available input samples. If it needs more samples + than available, it either wraps back for wrapAround samples, or + it feeds zeroes + @param wrapAround if the stream exceeds available samples, it wraps back for + wrapAround samples. If wrapAround is set to 0, it will feed zeroes. + @param gain a gain factor to multiply the resulting samples by before + adding them to the destination buffer + + @returns the actual number of input samples that were used + */ + int processAdding (double speedRatio, + const float* inputSamples, + float* outputSamples, + int numOutputSamplesToProduce, + int available, + int wrapAround, + float gain) noexcept; + +private: + float lastInputSamples[5]; + double subSamplePos; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (LagrangeInterpolator) +}; + +} // namespace juce diff --git a/JuceLibraryCode/modules/juce_audio_basics/utilities/juce_Reverb.h b/JuceLibraryCode/modules/juce_audio_basics/utilities/juce_Reverb.h index 0ed72b8..b88c7f7 100644 --- a/JuceLibraryCode/modules/juce_audio_basics/utilities/juce_Reverb.h +++ b/JuceLibraryCode/modules/juce_audio_basics/utilities/juce_Reverb.h @@ -1,313 +1,313 @@ -/* - ============================================================================== - - This file is part of the JUCE library. - Copyright (c) 2017 - ROLI Ltd. - - JUCE is an open source library subject to commercial or open-source - licensing. - - The code included in this file is provided under the terms of the ISC license - http://www.isc.org/downloads/software-support-policy/isc-license. Permission - To use, copy, modify, and/or distribute this software for any purpose with or - without fee is hereby granted provided that the above copyright notice and - this permission notice appear in all copies. - - JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER - EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE - DISCLAIMED. - - ============================================================================== -*/ - -namespace juce -{ - -//============================================================================== -/** - Performs a simple reverb effect on a stream of audio data. - - This is a simple stereo reverb, based on the technique and tunings used in FreeVerb. - Use setSampleRate() to prepare it, and then call processStereo() or processMono() to - apply the reverb to your audio data. - - @see ReverbAudioSource - - @tags{Audio} -*/ -class Reverb -{ -public: - //============================================================================== - Reverb() - { - setParameters (Parameters()); - setSampleRate (44100.0); - } - - //============================================================================== - /** Holds the parameters being used by a Reverb object. */ - struct Parameters - { - float roomSize = 0.5f; /**< Room size, 0 to 1.0, where 1.0 is big, 0 is small. */ - float damping = 0.5f; /**< Damping, 0 to 1.0, where 0 is not damped, 1.0 is fully damped. */ - float wetLevel = 0.33f; /**< Wet level, 0 to 1.0 */ - float dryLevel = 0.4f; /**< Dry level, 0 to 1.0 */ - float width = 1.0f; /**< Reverb width, 0 to 1.0, where 1.0 is very wide. */ - float freezeMode = 0.0f; /**< Freeze mode - values < 0.5 are "normal" mode, values > 0.5 - put the reverb into a continuous feedback loop. */ - }; - - //============================================================================== - /** Returns the reverb's current parameters. */ - const Parameters& getParameters() const noexcept { return parameters; } - - /** Applies a new set of parameters to the reverb. - Note that this doesn't attempt to lock the reverb, so if you call this in parallel with - the process method, you may get artifacts. - */ - void setParameters (const Parameters& newParams) - { - const float wetScaleFactor = 3.0f; - const float dryScaleFactor = 2.0f; - - const float wet = newParams.wetLevel * wetScaleFactor; - dryGain.setTargetValue (newParams.dryLevel * dryScaleFactor); - wetGain1.setTargetValue (0.5f * wet * (1.0f + newParams.width)); - wetGain2.setTargetValue (0.5f * wet * (1.0f - newParams.width)); - - gain = isFrozen (newParams.freezeMode) ? 0.0f : 0.015f; - parameters = newParams; - updateDamping(); - } - - //============================================================================== - /** Sets the sample rate that will be used for the reverb. - You must call this before the process methods, in order to tell it the correct sample rate. - */ - void setSampleRate (const double sampleRate) - { - jassert (sampleRate > 0); - - static const short combTunings[] = { 1116, 1188, 1277, 1356, 1422, 1491, 1557, 1617 }; // (at 44100Hz) - static const short allPassTunings[] = { 556, 441, 341, 225 }; - const int stereoSpread = 23; - const int intSampleRate = (int) sampleRate; - - for (int i = 0; i < numCombs; ++i) - { - comb[0][i].setSize ((intSampleRate * combTunings[i]) / 44100); - comb[1][i].setSize ((intSampleRate * (combTunings[i] + stereoSpread)) / 44100); - } - - for (int i = 0; i < numAllPasses; ++i) - { - allPass[0][i].setSize ((intSampleRate * allPassTunings[i]) / 44100); - allPass[1][i].setSize ((intSampleRate * (allPassTunings[i] + stereoSpread)) / 44100); - } - - const double smoothTime = 0.01; - damping .reset (sampleRate, smoothTime); - feedback.reset (sampleRate, smoothTime); - dryGain .reset (sampleRate, smoothTime); - wetGain1.reset (sampleRate, smoothTime); - wetGain2.reset (sampleRate, smoothTime); - } - - /** Clears the reverb's buffers. */ - void reset() - { - for (int j = 0; j < numChannels; ++j) - { - for (int i = 0; i < numCombs; ++i) - comb[j][i].clear(); - - for (int i = 0; i < numAllPasses; ++i) - allPass[j][i].clear(); - } - } - - //============================================================================== - /** Applies the reverb to two stereo channels of audio data. */ - void processStereo (float* const left, float* const right, const int numSamples) noexcept - { - jassert (left != nullptr && right != nullptr); - - for (int i = 0; i < numSamples; ++i) - { - const float input = (left[i] + right[i]) * gain; - float outL = 0, outR = 0; - - const float damp = damping.getNextValue(); - const float feedbck = feedback.getNextValue(); - - for (int j = 0; j < numCombs; ++j) // accumulate the comb filters in parallel - { - outL += comb[0][j].process (input, damp, feedbck); - outR += comb[1][j].process (input, damp, feedbck); - } - - for (int j = 0; j < numAllPasses; ++j) // run the allpass filters in series - { - outL = allPass[0][j].process (outL); - outR = allPass[1][j].process (outR); - } - - const float dry = dryGain.getNextValue(); - const float wet1 = wetGain1.getNextValue(); - const float wet2 = wetGain2.getNextValue(); - - left[i] = outL * wet1 + outR * wet2 + left[i] * dry; - right[i] = outR * wet1 + outL * wet2 + right[i] * dry; - } - } - - /** Applies the reverb to a single mono channel of audio data. */ - void processMono (float* const samples, const int numSamples) noexcept - { - jassert (samples != nullptr); - - for (int i = 0; i < numSamples; ++i) - { - const float input = samples[i] * gain; - float output = 0; - - const float damp = damping.getNextValue(); - const float feedbck = feedback.getNextValue(); - - for (int j = 0; j < numCombs; ++j) // accumulate the comb filters in parallel - output += comb[0][j].process (input, damp, feedbck); - - for (int j = 0; j < numAllPasses; ++j) // run the allpass filters in series - output = allPass[0][j].process (output); - - const float dry = dryGain.getNextValue(); - const float wet1 = wetGain1.getNextValue(); - - samples[i] = output * wet1 + samples[i] * dry; - } - } - -private: - //============================================================================== - static bool isFrozen (const float freezeMode) noexcept { return freezeMode >= 0.5f; } - - void updateDamping() noexcept - { - const float roomScaleFactor = 0.28f; - const float roomOffset = 0.7f; - const float dampScaleFactor = 0.4f; - - if (isFrozen (parameters.freezeMode)) - setDamping (0.0f, 1.0f); - else - setDamping (parameters.damping * dampScaleFactor, - parameters.roomSize * roomScaleFactor + roomOffset); - } - - void setDamping (const float dampingToUse, const float roomSizeToUse) noexcept - { - damping.setTargetValue (dampingToUse); - feedback.setTargetValue (roomSizeToUse); - } - - //============================================================================== - class CombFilter - { - public: - CombFilter() noexcept {} - - void setSize (const int size) - { - if (size != bufferSize) - { - bufferIndex = 0; - buffer.malloc (size); - bufferSize = size; - } - - clear(); - } - - void clear() noexcept - { - last = 0; - buffer.clear ((size_t) bufferSize); - } - - float process (const float input, const float damp, const float feedbackLevel) noexcept - { - const float output = buffer[bufferIndex]; - last = (output * (1.0f - damp)) + (last * damp); - JUCE_UNDENORMALISE (last); - - float temp = input + (last * feedbackLevel); - JUCE_UNDENORMALISE (temp); - buffer[bufferIndex] = temp; - bufferIndex = (bufferIndex + 1) % bufferSize; - return output; - } - - private: - HeapBlock buffer; - int bufferSize = 0, bufferIndex = 0; - float last = 0.0f; - - JUCE_DECLARE_NON_COPYABLE (CombFilter) - }; - - //============================================================================== - class AllPassFilter - { - public: - AllPassFilter() noexcept {} - - void setSize (const int size) - { - if (size != bufferSize) - { - bufferIndex = 0; - buffer.malloc (size); - bufferSize = size; - } - - clear(); - } - - void clear() noexcept - { - buffer.clear ((size_t) bufferSize); - } - - float process (const float input) noexcept - { - const float bufferedValue = buffer [bufferIndex]; - float temp = input + (bufferedValue * 0.5f); - JUCE_UNDENORMALISE (temp); - buffer [bufferIndex] = temp; - bufferIndex = (bufferIndex + 1) % bufferSize; - return bufferedValue - input; - } - - private: - HeapBlock buffer; - int bufferSize = 0, bufferIndex = 0; - - JUCE_DECLARE_NON_COPYABLE (AllPassFilter) - }; - - //============================================================================== - enum { numCombs = 8, numAllPasses = 4, numChannels = 2 }; - - Parameters parameters; - float gain; - - CombFilter comb [numChannels][numCombs]; - AllPassFilter allPass [numChannels][numAllPasses]; - - SmoothedValue damping, feedback, dryGain, wetGain1, wetGain2; - - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Reverb) -}; - -} // namespace juce +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2017 - ROLI Ltd. + + JUCE is an open source library subject to commercial or open-source + licensing. + + The code included in this file is provided under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license. Permission + To use, copy, modify, and/or distribute this software for any purpose with or + without fee is hereby granted provided that the above copyright notice and + this permission notice appear in all copies. + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +namespace juce +{ + +//============================================================================== +/** + Performs a simple reverb effect on a stream of audio data. + + This is a simple stereo reverb, based on the technique and tunings used in FreeVerb. + Use setSampleRate() to prepare it, and then call processStereo() or processMono() to + apply the reverb to your audio data. + + @see ReverbAudioSource + + @tags{Audio} +*/ +class Reverb +{ +public: + //============================================================================== + Reverb() + { + setParameters (Parameters()); + setSampleRate (44100.0); + } + + //============================================================================== + /** Holds the parameters being used by a Reverb object. */ + struct Parameters + { + float roomSize = 0.5f; /**< Room size, 0 to 1.0, where 1.0 is big, 0 is small. */ + float damping = 0.5f; /**< Damping, 0 to 1.0, where 0 is not damped, 1.0 is fully damped. */ + float wetLevel = 0.33f; /**< Wet level, 0 to 1.0 */ + float dryLevel = 0.4f; /**< Dry level, 0 to 1.0 */ + float width = 1.0f; /**< Reverb width, 0 to 1.0, where 1.0 is very wide. */ + float freezeMode = 0.0f; /**< Freeze mode - values < 0.5 are "normal" mode, values > 0.5 + put the reverb into a continuous feedback loop. */ + }; + + //============================================================================== + /** Returns the reverb's current parameters. */ + const Parameters& getParameters() const noexcept { return parameters; } + + /** Applies a new set of parameters to the reverb. + Note that this doesn't attempt to lock the reverb, so if you call this in parallel with + the process method, you may get artifacts. + */ + void setParameters (const Parameters& newParams) + { + const float wetScaleFactor = 3.0f; + const float dryScaleFactor = 2.0f; + + const float wet = newParams.wetLevel * wetScaleFactor; + dryGain.setTargetValue (newParams.dryLevel * dryScaleFactor); + wetGain1.setTargetValue (0.5f * wet * (1.0f + newParams.width)); + wetGain2.setTargetValue (0.5f * wet * (1.0f - newParams.width)); + + gain = isFrozen (newParams.freezeMode) ? 0.0f : 0.015f; + parameters = newParams; + updateDamping(); + } + + //============================================================================== + /** Sets the sample rate that will be used for the reverb. + You must call this before the process methods, in order to tell it the correct sample rate. + */ + void setSampleRate (const double sampleRate) + { + jassert (sampleRate > 0); + + static const short combTunings[] = { 1116, 1188, 1277, 1356, 1422, 1491, 1557, 1617 }; // (at 44100Hz) + static const short allPassTunings[] = { 556, 441, 341, 225 }; + const int stereoSpread = 23; + const int intSampleRate = (int) sampleRate; + + for (int i = 0; i < numCombs; ++i) + { + comb[0][i].setSize ((intSampleRate * combTunings[i]) / 44100); + comb[1][i].setSize ((intSampleRate * (combTunings[i] + stereoSpread)) / 44100); + } + + for (int i = 0; i < numAllPasses; ++i) + { + allPass[0][i].setSize ((intSampleRate * allPassTunings[i]) / 44100); + allPass[1][i].setSize ((intSampleRate * (allPassTunings[i] + stereoSpread)) / 44100); + } + + const double smoothTime = 0.01; + damping .reset (sampleRate, smoothTime); + feedback.reset (sampleRate, smoothTime); + dryGain .reset (sampleRate, smoothTime); + wetGain1.reset (sampleRate, smoothTime); + wetGain2.reset (sampleRate, smoothTime); + } + + /** Clears the reverb's buffers. */ + void reset() + { + for (int j = 0; j < numChannels; ++j) + { + for (int i = 0; i < numCombs; ++i) + comb[j][i].clear(); + + for (int i = 0; i < numAllPasses; ++i) + allPass[j][i].clear(); + } + } + + //============================================================================== + /** Applies the reverb to two stereo channels of audio data. */ + void processStereo (float* const left, float* const right, const int numSamples) noexcept + { + jassert (left != nullptr && right != nullptr); + + for (int i = 0; i < numSamples; ++i) + { + const float input = (left[i] + right[i]) * gain; + float outL = 0, outR = 0; + + const float damp = damping.getNextValue(); + const float feedbck = feedback.getNextValue(); + + for (int j = 0; j < numCombs; ++j) // accumulate the comb filters in parallel + { + outL += comb[0][j].process (input, damp, feedbck); + outR += comb[1][j].process (input, damp, feedbck); + } + + for (int j = 0; j < numAllPasses; ++j) // run the allpass filters in series + { + outL = allPass[0][j].process (outL); + outR = allPass[1][j].process (outR); + } + + const float dry = dryGain.getNextValue(); + const float wet1 = wetGain1.getNextValue(); + const float wet2 = wetGain2.getNextValue(); + + left[i] = outL * wet1 + outR * wet2 + left[i] * dry; + right[i] = outR * wet1 + outL * wet2 + right[i] * dry; + } + } + + /** Applies the reverb to a single mono channel of audio data. */ + void processMono (float* const samples, const int numSamples) noexcept + { + jassert (samples != nullptr); + + for (int i = 0; i < numSamples; ++i) + { + const float input = samples[i] * gain; + float output = 0; + + const float damp = damping.getNextValue(); + const float feedbck = feedback.getNextValue(); + + for (int j = 0; j < numCombs; ++j) // accumulate the comb filters in parallel + output += comb[0][j].process (input, damp, feedbck); + + for (int j = 0; j < numAllPasses; ++j) // run the allpass filters in series + output = allPass[0][j].process (output); + + const float dry = dryGain.getNextValue(); + const float wet1 = wetGain1.getNextValue(); + + samples[i] = output * wet1 + samples[i] * dry; + } + } + +private: + //============================================================================== + static bool isFrozen (const float freezeMode) noexcept { return freezeMode >= 0.5f; } + + void updateDamping() noexcept + { + const float roomScaleFactor = 0.28f; + const float roomOffset = 0.7f; + const float dampScaleFactor = 0.4f; + + if (isFrozen (parameters.freezeMode)) + setDamping (0.0f, 1.0f); + else + setDamping (parameters.damping * dampScaleFactor, + parameters.roomSize * roomScaleFactor + roomOffset); + } + + void setDamping (const float dampingToUse, const float roomSizeToUse) noexcept + { + damping.setTargetValue (dampingToUse); + feedback.setTargetValue (roomSizeToUse); + } + + //============================================================================== + class CombFilter + { + public: + CombFilter() noexcept {} + + void setSize (const int size) + { + if (size != bufferSize) + { + bufferIndex = 0; + buffer.malloc (size); + bufferSize = size; + } + + clear(); + } + + void clear() noexcept + { + last = 0; + buffer.clear ((size_t) bufferSize); + } + + float process (const float input, const float damp, const float feedbackLevel) noexcept + { + const float output = buffer[bufferIndex]; + last = (output * (1.0f - damp)) + (last * damp); + JUCE_UNDENORMALISE (last); + + float temp = input + (last * feedbackLevel); + JUCE_UNDENORMALISE (temp); + buffer[bufferIndex] = temp; + bufferIndex = (bufferIndex + 1) % bufferSize; + return output; + } + + private: + HeapBlock buffer; + int bufferSize = 0, bufferIndex = 0; + float last = 0.0f; + + JUCE_DECLARE_NON_COPYABLE (CombFilter) + }; + + //============================================================================== + class AllPassFilter + { + public: + AllPassFilter() noexcept {} + + void setSize (const int size) + { + if (size != bufferSize) + { + bufferIndex = 0; + buffer.malloc (size); + bufferSize = size; + } + + clear(); + } + + void clear() noexcept + { + buffer.clear ((size_t) bufferSize); + } + + float process (const float input) noexcept + { + const float bufferedValue = buffer [bufferIndex]; + float temp = input + (bufferedValue * 0.5f); + JUCE_UNDENORMALISE (temp); + buffer [bufferIndex] = temp; + bufferIndex = (bufferIndex + 1) % bufferSize; + return bufferedValue - input; + } + + private: + HeapBlock buffer; + int bufferSize = 0, bufferIndex = 0; + + JUCE_DECLARE_NON_COPYABLE (AllPassFilter) + }; + + //============================================================================== + enum { numCombs = 8, numAllPasses = 4, numChannels = 2 }; + + Parameters parameters; + float gain; + + CombFilter comb [numChannels][numCombs]; + AllPassFilter allPass [numChannels][numAllPasses]; + + SmoothedValue damping, feedback, dryGain, wetGain1, wetGain2; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Reverb) +}; + +} // namespace juce diff --git a/JuceLibraryCode/modules/juce_audio_basics/utilities/juce_SmoothedValue.cpp b/JuceLibraryCode/modules/juce_audio_basics/utilities/juce_SmoothedValue.cpp index 643dfd1..6c7d516 100644 --- a/JuceLibraryCode/modules/juce_audio_basics/utilities/juce_SmoothedValue.cpp +++ b/JuceLibraryCode/modules/juce_audio_basics/utilities/juce_SmoothedValue.cpp @@ -1,92 +1,92 @@ -/* - ============================================================================== - - This file is part of the JUCE library. - Copyright (c) 2018 - ROLI Ltd. - - JUCE is an open source library subject to commercial or open-source - licensing. - - The code included in this file is provided under the terms of the ISC license - http://www.isc.org/downloads/software-support-policy/isc-license. Permission - To use, copy, modify, and/or distribute this software for any purpose with or - without fee is hereby granted provided that the above copyright notice and - this permission notice appear in all copies. - - JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER - EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE - DISCLAIMED. - - ============================================================================== -*/ - -namespace juce -{ - -#if JUCE_UNIT_TESTS - -static CommonSmoothedValueTests > commonLinearSmoothedValueTests; -static CommonSmoothedValueTests > commonMultiplicativeSmoothedValueTests; - -class SmoothedValueTests : public UnitTest -{ -public: - SmoothedValueTests() - : UnitTest ("SmoothedValueTests", UnitTestCategories::smoothedValues) - {} - - void runTest() override - { - beginTest ("Linear moving target"); - { - SmoothedValue sv; - - sv.reset (12); - float initialValue = 0.0f; - sv.setCurrentAndTargetValue (initialValue); - sv.setTargetValue (1.0f); - - auto delta = sv.getNextValue() - initialValue; - - sv.skip (6); - - auto newInitialValue = sv.getCurrentValue(); - sv.setTargetValue (newInitialValue + 2.0f); - auto doubleDelta = sv.getNextValue() - newInitialValue; - - expectWithinAbsoluteError (doubleDelta, delta * 2.0f, 1.0e-7f); - } - - beginTest ("Multiplicative curve"); - { - SmoothedValue sv; - - auto numSamples = 12; - AudioBuffer values (2, numSamples + 1); - - sv.reset (numSamples); - sv.setCurrentAndTargetValue (1.0); - sv.setTargetValue (2.0f); - - values.setSample (0, 0, sv.getCurrentValue()); - - for (int i = 1; i < values.getNumSamples(); ++i) - values.setSample (0, i, sv.getNextValue()); - - sv.setTargetValue (1.0f); - values.setSample (1, values.getNumSamples() - 1, sv.getCurrentValue()); - - for (int i = values.getNumSamples() - 2; i >= 0 ; --i) - values.setSample (1, i, sv.getNextValue()); - - for (int i = 0; i < values.getNumSamples(); ++i) - expectWithinAbsoluteError (values.getSample (0, i), values.getSample (1, i), 1.0e-9); - } - } -}; - -static SmoothedValueTests smoothedValueTests; - -#endif - -} // namespace juce +/* + ============================================================================== + + This file is part of the JUCE library. + Copyright (c) 2018 - ROLI Ltd. + + JUCE is an open source library subject to commercial or open-source + licensing. + + The code included in this file is provided under the terms of the ISC license + http://www.isc.org/downloads/software-support-policy/isc-license. Permission + To use, copy, modify, and/or distribute this software for any purpose with or + without fee is hereby granted provided that the above copyright notice and + this permission notice appear in all copies. + + JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER + EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE + DISCLAIMED. + + ============================================================================== +*/ + +namespace juce +{ + +#if JUCE_UNIT_TESTS + +static CommonSmoothedValueTests > commonLinearSmoothedValueTests; +static CommonSmoothedValueTests > commonMultiplicativeSmoothedValueTests; + +class SmoothedValueTests : public UnitTest +{ +public: + SmoothedValueTests() + : UnitTest ("SmoothedValueTests", UnitTestCategories::smoothedValues) + {} + + void runTest() override + { + beginTest ("Linear moving target"); + { + SmoothedValue sv; + + sv.reset (12); + float initialValue = 0.0f; + sv.setCurrentAndTargetValue (initialValue); + sv.setTargetValue (1.0f); + + auto delta = sv.getNextValue() - initialValue; + + sv.skip (6); + + auto newInitialValue = sv.getCurrentValue(); + sv.setTargetValue (newInitialValue + 2.0f); + auto doubleDelta = sv.getNextValue() - newInitialValue; + + expectWithinAbsoluteError (doubleDelta, delta * 2.0f, 1.0e-7f); + } + + beginTest ("Multiplicative curve"); + { + SmoothedValue sv; + + auto numSamples = 12; + AudioBuffer values (2, numSamples + 1); + + sv.reset (numSamples); + sv.setCurrentAndTargetValue (1.0); + sv.setTargetValue (2.0f); + + values.setSample (0, 0, sv.getCurrentValue()); + + for (int i = 1; i < values.getNumSamples(); ++i) + values.setSample (0, i, sv.getNextValue()); + + sv.setTargetValue (1.0f); + values.setSample (1, values.getNumSamples() - 1, sv.getCurrentValue()); + + for (int i = values.getNumSamples() - 2; i >= 0 ; --i) + values.setSample (1, i, sv.getNextValue()); + + for (int i = 0; i < values.getNumSamples(); ++i) + expectWithinAbsoluteError (values.getSample (0, i), values.getSample (1, i), 1.0e-9); + } + } +}; + +static SmoothedValueTests smoothedValueTests; + +#endif + +} // namespace juce diff --git a/JuceLibraryCode/modules/juce_audio_basics/utilities/juce_SmoothedValue.h b/JuceLibraryCode/modules/juce_audio_basics/utilities/juce_SmoothedValue.h index a472fb0..36570aa 100644 --- a/JuceLibraryCode/modules/juce_audio_basics/utilities/juce_SmoothedValue.h +++ b/JuceLibraryCode/modules/juce_audio_basics/utilities/juce_SmoothedValue.h @@ -1,630 +1,630 @@ -/* - ============================================================================== - - This file is part of the JUCE library. - Copyright (c) 2017 - ROLI Ltd. - - JUCE is an open source library subject to commercial or open-source - licensing. - - The code included in this file is provided under the terms of the ISC license - http://www.isc.org/downloads/software-support-policy/isc-license. Permission - To use, copy, modify, and/or distribute this software for any purpose with or - without fee is hereby granted provided that the above copyright notice and - this permission notice appear in all copies. - - JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER - EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE - DISCLAIMED. - - ============================================================================== -*/ - -namespace juce -{ - -//============================================================================== -/** - A base class for the smoothed value classes. - - This class is used to provide common functionality to the SmoothedValue and - dsp::LogRampedValue classes. - - @tags{Audio} -*/ -template -class SmoothedValueBase -{ -private: - //============================================================================== - template struct FloatTypeHelper; - - template