OpenShot Audio Library | OpenShotAudio  0.6.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 
23 namespace juce
24 {
25 
27  : instrument (defaultInstrument)
28 {
29  instrument.addListener (this);
30 }
31 
33  : instrument (inst)
34 {
35  instrument.addListener (this);
36 }
37 
38 //==============================================================================
40 {
41  return instrument.getZoneLayout();
42 }
43 
45 {
46  instrument.setZoneLayout (newLayout);
47 }
48 
49 //==============================================================================
50 void 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 {
77  instrument.setLegacyModePitchbendRange (pitchbendRange);
78 }
79 
80 //==============================================================================
82 {
84 }
85 
87 {
89 }
90 
92 {
94 }
95 
96 //==============================================================================
98 {
100 }
101 
102 //==============================================================================
103 template <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:
141 template void MPESynthesiserBase::renderNextBlock<float> (AudioBuffer<float>&, const MidiBuffer&, int, int);
142 template 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 //==============================================================================
156 void 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 
165 namespace
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