OpenShot Audio Library | OpenShotAudio 0.4.0
juce_MPESynthesiserBase.cpp
1/*
2 ==============================================================================
3
4 This file is part of the JUCE library.
5 Copyright (c) 2022 - Raw Material Software Limited
6
7 JUCE is an open source library subject to commercial or open-source
8 licensing.
9
10 The code included in this file is provided under the terms of the ISC license
11 http://www.isc.org/downloads/software-support-policy/isc-license. Permission
12 To use, copy, modify, and/or distribute this software for any purpose with or
13 without fee is hereby granted provided that the above copyright notice and
14 this permission notice appear in all copies.
15
16 JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
17 EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
18 DISCLAIMED.
19
20 ==============================================================================
21*/
22
23namespace juce
24{
25
27 : instrument (defaultInstrument)
28{
30}
31
33 : instrument (inst)
34{
36}
37
38//==============================================================================
40{
42}
43
45{
46 instrument.setZoneLayout (newLayout);
47}
48
49//==============================================================================
50void MPESynthesiserBase::enableLegacyMode (int pitchbendRange, Range<int> channelRange)
51{
52 instrument.enableLegacyMode (pitchbendRange, channelRange);
53}
54
56{
58}
59
61{
63}
64
66{
68}
69
71{
73}
74
76{
78}
79
80//==============================================================================
82{
84}
85
87{
89}
90
92{
94}
95
96//==============================================================================
98{
100}
101
102//==============================================================================
103template <typename floatType>
105 const MidiBuffer& inputMidi,
106 int startSample,
107 int numSamples)
108{
109 // you must set the sample rate before using this!
110 jassert (! approximatelyEqual (sampleRate, 0.0));
111
112 const ScopedLock sl (noteStateLock);
113
114 auto prevSample = startSample;
115 const auto endSample = startSample + numSamples;
116
117 for (auto it = inputMidi.findNextSamplePosition (startSample); it != inputMidi.cend(); ++it)
118 {
119 const auto metadata = *it;
120
121 if (metadata.samplePosition >= endSample)
122 break;
123
124 const auto smallBlockAllowed = (prevSample == startSample && ! subBlockSubdivisionIsStrict);
125 const auto thisBlockSize = smallBlockAllowed ? 1 : minimumSubBlockSize;
126
127 if (metadata.samplePosition >= prevSample + thisBlockSize)
128 {
129 renderNextSubBlock (outputAudio, prevSample, metadata.samplePosition - prevSample);
130 prevSample = metadata.samplePosition;
131 }
132
133 handleMidiEvent (metadata.getMessage());
134 }
135
136 if (prevSample < endSample)
137 renderNextSubBlock (outputAudio, prevSample, endSample - prevSample);
138}
139
140// explicit instantiation for supported float types:
141template void MPESynthesiserBase::renderNextBlock<float> (AudioBuffer<float>&, const MidiBuffer&, int, int);
142template void MPESynthesiserBase::renderNextBlock<double> (AudioBuffer<double>&, const MidiBuffer&, int, int);
143
144//==============================================================================
146{
147 if (! approximatelyEqual (sampleRate, newRate))
148 {
149 const ScopedLock sl (noteStateLock);
151 sampleRate = newRate;
152 }
153}
154
155//==============================================================================
156void MPESynthesiserBase::setMinimumRenderingSubdivisionSize (int numSamples, bool shouldBeStrict) noexcept
157{
158 jassert (numSamples > 0); // it wouldn't make much sense for this to be less than 1
159 minimumSubBlockSize = numSamples;
160 subBlockSubdivisionIsStrict = shouldBeStrict;
161}
162
163#if JUCE_UNIT_TESTS
164
165namespace
166{
167 class MpeSynthesiserBaseTests final : public UnitTest
168 {
169 enum class CallbackKind { process, midi };
170
171 struct StartAndLength
172 {
173 StartAndLength (int s, int l) : start (s), length (l) {}
174
175 int start = 0;
176 int length = 0;
177
178 std::tuple<const int&, const int&> tie() const noexcept { return std::tie (start, length); }
179
180 bool operator== (const StartAndLength& other) const noexcept { return tie() == other.tie(); }
181 bool operator!= (const StartAndLength& other) const noexcept { return tie() != other.tie(); }
182
183 bool operator< (const StartAndLength& other) const noexcept { return tie() < other.tie(); }
184 };
185
186 struct Events
187 {
188 std::vector<StartAndLength> blocks;
189 std::vector<MidiMessage> messages;
190 std::vector<CallbackKind> order;
191 };
192
193 class MockSynthesiser final : public MPESynthesiserBase
194 {
195 public:
196 Events events;
197
198 void handleMidiEvent (const MidiMessage& m) override
199 {
200 events.messages.emplace_back (m);
201 events.order.emplace_back (CallbackKind::midi);
202 }
203
204 private:
206
207 void renderNextSubBlock (AudioBuffer<float>&,
208 int startSample,
209 int numSamples) override
210 {
211 events.blocks.push_back ({ startSample, numSamples });
212 events.order.emplace_back (CallbackKind::process);
213 }
214 };
215
216 static MidiBuffer makeTestBuffer (const int bufferLength)
217 {
218 MidiBuffer result;
219
220 for (int i = 0; i != bufferLength; ++i)
221 result.addEvent ({}, i);
222
223 return result;
224 }
225
226 public:
227 MpeSynthesiserBaseTests()
228 : UnitTest ("MPE Synthesiser Base", UnitTestCategories::midi) {}
229
230 void runTest() override
231 {
232 const auto sumBlockLengths = [] (const std::vector<StartAndLength>& b)
233 {
234 const auto addBlock = [] (int acc, const StartAndLength& info) { return acc + info.length; };
235 return std::accumulate (b.begin(), b.end(), 0, addBlock);
236 };
237
238 beginTest ("Rendering sparse subblocks works");
239 {
240 const int blockSize = 512;
241 const auto midi = [&] { MidiBuffer b; b.addEvent ({}, blockSize / 2); return b; }();
242 AudioBuffer<float> audio (1, blockSize);
243
244 const auto processEvents = [&] (int start, int length)
245 {
246 MockSynthesiser synth;
247 synth.setMinimumRenderingSubdivisionSize (1, false);
248 synth.setCurrentPlaybackSampleRate (44100);
249 synth.renderNextBlock (audio, midi, start, length);
250 return synth.events;
251 };
252
253 {
254 const auto e = processEvents (0, blockSize);
255 expect (e.blocks.size() == 2);
256 expect (e.messages.size() == 1);
257 expect (std::is_sorted (e.blocks.begin(), e.blocks.end()));
258 expect (sumBlockLengths (e.blocks) == blockSize);
259 expect (e.order == std::vector<CallbackKind> { CallbackKind::process,
260 CallbackKind::midi,
261 CallbackKind::process });
262 }
263 }
264
265 beginTest ("Rendering subblocks processes only contained midi events");
266 {
267 const int blockSize = 512;
268 const auto midi = makeTestBuffer (blockSize);
269 AudioBuffer<float> audio (1, blockSize);
270
271 const auto processEvents = [&] (int start, int length)
272 {
273 MockSynthesiser synth;
274 synth.setMinimumRenderingSubdivisionSize (1, false);
275 synth.setCurrentPlaybackSampleRate (44100);
276 synth.renderNextBlock (audio, midi, start, length);
277 return synth.events;
278 };
279
280 {
281 const int subBlockLength = 0;
282 const auto e = processEvents (0, subBlockLength);
283 expect (e.blocks.size() == 0);
284 expect (e.messages.size() == 0);
285 expect (std::is_sorted (e.blocks.begin(), e.blocks.end()));
286 expect (sumBlockLengths (e.blocks) == subBlockLength);
287 }
288
289 {
290 const int subBlockLength = 0;
291 const auto e = processEvents (1, subBlockLength);
292 expect (e.blocks.size() == 0);
293 expect (e.messages.size() == 0);
294 expect (std::is_sorted (e.blocks.begin(), e.blocks.end()));
295 expect (sumBlockLengths (e.blocks) == subBlockLength);
296 }
297
298 {
299 const int subBlockLength = 1;
300 const auto e = processEvents (1, subBlockLength);
301 expect (e.blocks.size() == 1);
302 expect (e.messages.size() == 1);
303 expect (std::is_sorted (e.blocks.begin(), e.blocks.end()));
304 expect (sumBlockLengths (e.blocks) == subBlockLength);
305 expect (e.order == std::vector<CallbackKind> { CallbackKind::midi,
306 CallbackKind::process });
307 }
308
309 {
310 const auto e = processEvents (0, blockSize);
311 expect (e.blocks.size() == blockSize);
312 expect (e.messages.size() == blockSize);
313 expect (std::is_sorted (e.blocks.begin(), e.blocks.end()));
314 expect (sumBlockLengths (e.blocks) == blockSize);
315 expect (e.order.front() == CallbackKind::midi);
316 }
317 }
318
319 beginTest ("Subblocks respect their minimum size");
320 {
321 const int blockSize = 512;
322 const auto midi = makeTestBuffer (blockSize);
323 AudioBuffer<float> audio (1, blockSize);
324
325 const auto blockLengthsAreValid = [] (const std::vector<StartAndLength>& info, int minLength, bool strict)
326 {
327 if (info.size() <= 1)
328 return true;
329
330 const auto lengthIsValid = [&] (const StartAndLength& s) { return minLength <= s.length; };
331 const auto begin = strict ? info.begin() : std::next (info.begin());
332 // The final block is allowed to be shorter than the minLength
333 return std::all_of (begin, std::prev (info.end()), lengthIsValid);
334 };
335
336 for (auto strict : { false, true })
337 {
338 for (auto subblockSize : { 1, 16, 32, 64, 1024 })
339 {
340 MockSynthesiser synth;
341 synth.setMinimumRenderingSubdivisionSize (subblockSize, strict);
342 synth.setCurrentPlaybackSampleRate (44100);
343 synth.renderNextBlock (audio, midi, 0, blockSize);
344
345 const auto& e = synth.events;
346 expectWithinAbsoluteError (float (e.blocks.size()),
347 std::ceil ((float) blockSize / (float) subblockSize),
348 1.0f);
349 expect (e.messages.size() == blockSize);
350 expect (std::is_sorted (e.blocks.begin(), e.blocks.end()));
351 expect (sumBlockLengths (e.blocks) == blockSize);
352 expect (blockLengthsAreValid (e.blocks, subblockSize, strict));
353 }
354 }
355
356 {
357 MockSynthesiser synth;
358 synth.setMinimumRenderingSubdivisionSize (32, true);
359 synth.setCurrentPlaybackSampleRate (44100);
360 synth.renderNextBlock (audio, MidiBuffer{}, 0, 16);
361
362 expect (synth.events.blocks == std::vector<StartAndLength> { { 0, 16 } });
363 expect (synth.events.order == std::vector<CallbackKind> { CallbackKind::process });
364 expect (synth.events.messages.empty());
365 }
366 }
367 }
368 };
369
370 MpeSynthesiserBaseTests mpeSynthesiserBaseTests;
371}
372
373#endif
374
375} // namespace juce
void setPitchbendTrackingMode(TrackingMode modeToUse)
void setLegacyModeChannelRange(Range< int > channelRange)
MPEZoneLayout getZoneLayout() const noexcept
void enableLegacyMode(int pitchbendRange=2, Range< int > channelRange=Range< int >(1, 17))
void setZoneLayout(MPEZoneLayout newLayout)
virtual void processNextMidiEvent(const MidiMessage &message)
void setLegacyModePitchbendRange(int pitchbendRange)
bool isLegacyModeEnabled() const noexcept
void setPressureTrackingMode(TrackingMode modeToUse)
void addListener(Listener *listenerToAdd)
void setTimbreTrackingMode(TrackingMode modeToUse)
Range< int > getLegacyModeChannelRange() const noexcept
int getLegacyModePitchbendRange() const noexcept
MidiBufferIterator findNextSamplePosition(int samplePosition) const noexcept
MidiBufferIterator cend() const noexcept
void setPitchbendTrackingMode(TrackingMode modeToUse)
virtual void handleMidiEvent(const MidiMessage &)
bool isLegacyModeEnabled() const noexcept
MPEZoneLayout getZoneLayout() const noexcept
void setZoneLayout(MPEZoneLayout newLayout)
int getLegacyModePitchbendRange() const noexcept
void setLegacyModeChannelRange(Range< int > channelRange)
void setLegacyModePitchbendRange(int pitchbendRange)
void enableLegacyMode(int pitchbendRange=2, Range< int > channelRange=Range< int >(1, 17))
void setTimbreTrackingMode(TrackingMode modeToUse)
void setPressureTrackingMode(TrackingMode modeToUse)
Range< int > getLegacyModeChannelRange() const noexcept
virtual void setCurrentPlaybackSampleRate(double sampleRate)
void renderNextBlock(AudioBuffer< floatType > &outputAudio, const MidiBuffer &inputMidi, int startSample, int numSamples)
void setMinimumRenderingSubdivisionSize(int numSamples, bool shouldBeStrict=false) noexcept
virtual void renderNextSubBlock(AudioBuffer< float > &outputAudio, int startSample, int numSamples)=0