27 : instrument (defaultInstrument)
103template <
typename floatType>
114 auto prevSample = startSample;
115 const auto endSample = startSample + numSamples;
119 const auto metadata = *it;
121 if (metadata.samplePosition >= endSample)
124 const auto smallBlockAllowed = (prevSample == startSample && ! subBlockSubdivisionIsStrict);
125 const auto thisBlockSize = smallBlockAllowed ? 1 : minimumSubBlockSize;
127 if (metadata.samplePosition >= prevSample + thisBlockSize)
130 prevSample = metadata.samplePosition;
136 if (prevSample < endSample)
151 sampleRate = newRate;
159 minimumSubBlockSize = numSamples;
160 subBlockSubdivisionIsStrict = shouldBeStrict;
167 class MpeSynthesiserBaseTests final :
public UnitTest
169 enum class CallbackKind { process, midi };
171 struct StartAndLength
173 StartAndLength (
int s,
int l) : start (s), length (l) {}
180 bool operator== (
const StartAndLength& other)
const noexcept {
return tie() == other.tie(); }
181 bool operator!= (
const StartAndLength& other)
const noexcept {
return tie() != other.tie(); }
183 bool operator< (
const StartAndLength& other)
const noexcept {
return tie() < other.tie(); }
193 class MockSynthesiser final :
public MPESynthesiserBase
198 void handleMidiEvent (
const MidiMessage& m)
override
201 events.order.emplace_back (CallbackKind::midi);
207 void renderNextSubBlock (AudioBuffer<float>&,
209 int numSamples)
override
211 events.blocks.push_back ({ startSample, numSamples });
212 events.order.emplace_back (CallbackKind::process);
216 static MidiBuffer makeTestBuffer (
const int bufferLength)
220 for (
int i = 0; i != bufferLength; ++i)
221 result.addEvent ({}, i);
227 MpeSynthesiserBaseTests()
228 : UnitTest (
"MPE Synthesiser Base", UnitTestCategories::midi) {}
230 void runTest()
override
234 const auto addBlock = [] (
int acc,
const StartAndLength& info) {
return acc + info.length; };
238 beginTest (
"Rendering sparse subblocks works");
240 const int blockSize = 512;
241 const auto midi = [&] { MidiBuffer b; b.addEvent ({}, blockSize / 2);
return b; }();
242 AudioBuffer<float> audio (1, blockSize);
244 const auto processEvents = [&] (
int start,
int length)
246 MockSynthesiser synth;
247 synth.setMinimumRenderingSubdivisionSize (1,
false);
248 synth.setCurrentPlaybackSampleRate (44100);
249 synth.renderNextBlock (audio, midi, start, length);
254 const auto e = processEvents (0, blockSize);
255 expect (e.blocks.size() == 2);
256 expect (e.messages.size() == 1);
258 expect (sumBlockLengths (e.blocks) == blockSize);
261 CallbackKind::process });
265 beginTest (
"Rendering subblocks processes only contained midi events");
267 const int blockSize = 512;
268 const auto midi = makeTestBuffer (blockSize);
269 AudioBuffer<float> audio (1, blockSize);
271 const auto processEvents = [&] (
int start,
int length)
273 MockSynthesiser synth;
274 synth.setMinimumRenderingSubdivisionSize (1,
false);
275 synth.setCurrentPlaybackSampleRate (44100);
276 synth.renderNextBlock (audio, midi, start, length);
281 const int subBlockLength = 0;
282 const auto e = processEvents (0, subBlockLength);
283 expect (e.blocks.size() == 0);
284 expect (e.messages.size() == 0);
286 expect (sumBlockLengths (e.blocks) == subBlockLength);
290 const int subBlockLength = 0;
291 const auto e = processEvents (1, subBlockLength);
292 expect (e.blocks.size() == 0);
293 expect (e.messages.size() == 0);
295 expect (sumBlockLengths (e.blocks) == subBlockLength);
299 const int subBlockLength = 1;
300 const auto e = processEvents (1, subBlockLength);
301 expect (e.blocks.size() == 1);
302 expect (e.messages.size() == 1);
304 expect (sumBlockLengths (e.blocks) == subBlockLength);
306 CallbackKind::process });
310 const auto e = processEvents (0, blockSize);
311 expect (e.blocks.size() == blockSize);
312 expect (e.messages.size() == blockSize);
314 expect (sumBlockLengths (e.blocks) == blockSize);
315 expect (e.order.front() == CallbackKind::midi);
319 beginTest (
"Subblocks respect their minimum size");
321 const int blockSize = 512;
322 const auto midi = makeTestBuffer (blockSize);
323 AudioBuffer<float> audio (1, blockSize);
327 if (info.
size() <= 1)
330 const auto lengthIsValid = [&] (
const StartAndLength& s) {
return minLength <= s.length; };
336 for (
auto strict : {
false,
true })
338 for (
auto subblockSize : { 1, 16, 32, 64, 1024 })
340 MockSynthesiser synth;
341 synth.setMinimumRenderingSubdivisionSize (subblockSize, strict);
342 synth.setCurrentPlaybackSampleRate (44100);
343 synth.renderNextBlock (audio, midi, 0, blockSize);
345 const auto& e = synth.events;
346 expectWithinAbsoluteError (
float (e.blocks.size()),
347 std::ceil ((
float) blockSize / (
float) subblockSize),
349 expect (e.messages.size() == blockSize);
351 expect (sumBlockLengths (e.blocks) == blockSize);
352 expect (blockLengthsAreValid (e.blocks, subblockSize, strict));
357 MockSynthesiser synth;
358 synth.setMinimumRenderingSubdivisionSize (32,
true);
359 synth.setCurrentPlaybackSampleRate (44100);
360 synth.renderNextBlock (audio, MidiBuffer{}, 0, 16);
364 expect (synth.events.messages.empty());
370 MpeSynthesiserBaseTests mpeSynthesiserBaseTests;
A multi-channel buffer containing floating point audio samples.
Automatically locks and unlocks a mutex object.
This class represents an instrument handling MPE.
void setPitchbendTrackingMode(TrackingMode modeToUse)
Set the MPE tracking mode for the pitchbend dimension.
void setLegacyModeChannelRange(Range< int > channelRange)
Re-sets the range of MIDI channels (1-16) to be used for notes when in legacy mode.
MPEZoneLayout getZoneLayout() const noexcept
Returns the current zone layout of the instrument.
void enableLegacyMode(int pitchbendRange=2, Range< int > channelRange=Range< int >(1, 17))
Puts the instrument into legacy mode.
TrackingMode
The MPE note tracking mode.
void setZoneLayout(MPEZoneLayout newLayout)
Re-sets the zone layout of the instrument to the one passed in.
virtual void processNextMidiEvent(const MidiMessage &message)
Process a MIDI message and trigger the appropriate method calls (noteOn, noteOff etc....
void setLegacyModePitchbendRange(int pitchbendRange)
Re-sets the pitchbend range in semitones (0-96) to be used for notes when in legacy mode.
bool isLegacyModeEnabled() const noexcept
Returns true if the instrument is in legacy mode, false otherwise.
void setPressureTrackingMode(TrackingMode modeToUse)
Set the MPE tracking mode for the pressure dimension.
void addListener(Listener *listenerToAdd)
Adds a listener.
void setTimbreTrackingMode(TrackingMode modeToUse)
Set the MPE tracking mode for the timbre dimension.
void releaseAllNotes()
Discard all currently playing notes.
Range< int > getLegacyModeChannelRange() const noexcept
Returns the range of MIDI channels (1-16) to be used for notes when in legacy mode.
int getLegacyModePitchbendRange() const noexcept
Returns the pitchbend range in semitones (0-96) to be used for notes when in legacy mode.
This class represents the current MPE zone layout of a device capable of handling MPE.
Holds a sequence of time-stamped midi events.
MidiBufferIterator findNextSamplePosition(int samplePosition) const noexcept
Get an iterator pointing to the first event with a timestamp greater-than or equal-to samplePosition.
MidiBufferIterator cend() const noexcept
Get a read-only iterator pointing one past the end of this buffer.
Encapsulates a MIDI message.
A general-purpose range object, that simply represents any linear range with a start and end point.
This is a base class for classes that perform a unit test.
T emplace_back(T... args)
constexpr bool approximatelyEqual(Type a, Type b, Tolerance< Type > tolerance=Tolerance< Type >{} .withAbsolute(std::numeric_limits< Type >::min()) .withRelative(std::numeric_limits< Type >::epsilon()))
Returns true if the two floating-point numbers are approximately equal.
RangedDirectoryIterator begin(const RangedDirectoryIterator &it)
Returns the iterator that was passed in.
void setPitchbendTrackingMode(TrackingMode modeToUse)
Set the MPE tracking mode for the pitchbend dimension.
virtual void handleMidiEvent(const MidiMessage &)
Handle incoming MIDI events (called from renderNextBlock).
bool isLegacyModeEnabled() const noexcept
Returns true if the instrument is in legacy mode, false otherwise.
MPEZoneLayout getZoneLayout() const noexcept
Returns the synthesiser's internal MPE zone layout.
void setZoneLayout(MPEZoneLayout newLayout)
Re-sets the synthesiser's internal MPE zone layout to the one passed in.
int getLegacyModePitchbendRange() const noexcept
Returns the pitchbend range in semitones (0-96) to be used for notes when in legacy mode.
void setLegacyModeChannelRange(Range< int > channelRange)
Re-sets the range of MIDI channels (1-16) to be used for notes when in legacy mode.
void setLegacyModePitchbendRange(int pitchbendRange)
Re-sets the pitchbend range in semitones (0-96) to be used for notes when in legacy mode.
void enableLegacyMode(int pitchbendRange=2, Range< int > channelRange=Range< int >(1, 17))
Puts the synthesiser into legacy mode.
void setTimbreTrackingMode(TrackingMode modeToUse)
Set the MPE tracking mode for the timbre dimension.
void setPressureTrackingMode(TrackingMode modeToUse)
Set the MPE tracking mode for the pressure dimension.
Range< int > getLegacyModeChannelRange() const noexcept
Returns the range of MIDI channels (1-16) to be used for notes when in legacy mode.
virtual void setCurrentPlaybackSampleRate(double sampleRate)
Tells the synthesiser what the sample rate is for the audio it's being used to render.
void renderNextBlock(AudioBuffer< floatType > &outputAudio, const MidiBuffer &inputMidi, int startSample, int numSamples)
Creates the next block of audio output.
void setMinimumRenderingSubdivisionSize(int numSamples, bool shouldBeStrict=false) noexcept
Sets a minimum limit on the size to which audio sub-blocks will be divided when rendering.
virtual void renderNextSubBlock(AudioBuffer< float > &outputAudio, int startSample, int numSamples)=0
Implement this method to render your audio inside.
MPESynthesiserBase()
Constructor.