Krotos Modules 3
Loading...
Searching...
No Matches
KwidgetSynthesiser.cpp
Go to the documentation of this file.
1namespace krotos
2{
3 bool KwidgetSynthSound::appliesToNote(int /*midiNoteNumber*/) { return true; }
4 bool KwidgetSynthSound::appliesToChannel(int /*midiChannel*/) { return true; }
5
6 KwidgetSynthVoice::KwidgetSynthVoice(int idx, int numOutputChannels, std::vector<KwidgetProcessorGraph*> processors)
7 : m_voiceIndex(idx)
8 {
22 m_workingBuffer.setSize(numOutputChannels, 4096);
23
24 for (auto p : processors)
25 addProcessor(p);
26 }
27
28 bool KwidgetSynthVoice::canPlaySound(SynthesiserSound* s)
29 {
30 if (dynamic_cast<KwidgetSynthSound*>(s))
31 return true;
32
33 return false;
34 }
35
36 // Called from Synthesiser::startVoice
37 void KwidgetSynthVoice::startNote(int midiNote, float velocity, SynthesiserSound*, int)
38 {
39 // m_voiceIndex was set when the voice was created - it is a way of identifying this voice
40 for (auto p : m_processors)
41 p->noteOn(m_voiceIndex, midiNote, velocity);
42 }
43
44 void KwidgetSynthVoice::stopNote(float velocity, bool allowTailOff)
45 {
46 for (auto p : m_processors)
47 p->noteOff(m_voiceIndex, velocity);
48
49 if (!allowTailOff) // Are we electing not to wait for the ADSR release phase to time out ?
50 {
51 for (auto p : m_processors) // Then stop everthing playing
52 p->noteCleared(m_voiceIndex);
53 clearCurrentNote(); // Kill this voice
54 }
55 }
56
58
60
61 void KwidgetSynthVoice::renderNextBlock(AudioBuffer<float>& ioBuffer, int startSample, int numSamples)
62 {
63 if (!isVoiceActive())
64 return;
65
66 int workingChannels =
67 ioBuffer.getNumChannels(); // This is the number of channels we will actaully process in this block
68
69 jassert(m_workingBuffer.getNumChannels() >= workingChannels);
70
71 auto tempBufferPtrsArray = m_workingBuffer.getArrayOfWritePointers();
72 auto ioBufferPtrsArray = ioBuffer.getArrayOfWritePointers();
73
74 // Create a temporary audio buffer with workingChannels channels useing memory pre-allocated in m_workingBuffer
75 AudioBuffer<float> tempBuf{tempBufferPtrsArray, workingChannels, startSample, numSamples};
76
77 // Call all the processors in the graph
78 for (auto p : m_processors)
79 {
80 p->processVoice(m_voiceIndex, tempBuf);
81 }
82
83 // Output from the audio graph is now in tempBuffer
84 // Sum the output from the temprary buffer back into our input/ouput buffer
85 for (int j = 0; j < workingChannels; j++)
86 FloatVectorOperations::add(ioBufferPtrsArray[j] + startSample, tempBufferPtrsArray[j] + startSample,
87 numSamples);
88
89 bool active = false;
90 for (auto p : m_processors)
91 {
92 if (p->isActive(m_voiceIndex))
93 {
94 active = true;
95 break;
96 }
97 }
98
99#ifdef USE_VOICE_DEBUG_PRINTOUT
100 if (isActive_old && !active)
101 {
102 DBG("Inactive " + String(m_voiceIndex));
103 }
104 if (!isActive_old && active)
105 {
106 DBG("Active " + String(m_voiceIndex));
107 }
108 isActive_old = active;
109#endif
110
111 // At the moment the only Kwidget which reports back its active state is the Core Engine kwidget
112 // as no other Kwidget in our current configuration contributes to control over the lifetime of the voice.
113 // The core engine takes account of its own children, including amplitude ADSR, whos active state it will take
114 // account of It does this by overriding the Kwidget class isActive() method and providing its own logic. Other
115 // Kwidgets which are not doing that, call the method default and return the value supplied by their associated
116 // processor isActive() method. This means that other Kwidgets may still be active when the Core Engine and its
117 // ADSR goes inactive, and need to be cleared. In the case of the core engine, eg if it goes inactive due to
118 // ADSR timeout, clearing is an important step, as it clears the still playing grain(s) and returns the
119 // resources to the grain pool. Note: If an ADSR kwidget is instantiated in the default manner, ie not as a
120 // child of another kwidget, it's isActive method will be polled directly and contribute directly to the voice
121 // lifetime
122
123 if (!active)
124 {
125 // Clear every processor for the voice we are dealing with
126 for (auto p : m_processors)
127 p->noteCleared(m_voiceIndex);
128
129 clearCurrentNote();
130 }
131 }
132
134 {
135 jassert(isPositiveAndBelow(m_voiceIndex, p->getNumVoices()));
136 m_processors.add(p);
137 }
138
140 int voicesOffset, int forceNumVoices, int numOutputChannels)
141 {
142 auto numVoices = forceNumVoices;
143
144 if (numVoices == 0) // If the caller is not electing to force the number of voices ...
145 {
146 // ... then they are set from the graphs
147 numVoices = mainGraph.getNumVoices();
148 jassert(numVoices == modulatorGraph.getNumVoices());
149 }
150
151 addSound(new KwidgetSynthSound());
152
153 // voicesOffset allows the range of voices index to be continuous globally
154 for (int i = 0; i < numVoices; i++)
155 addVoice(new KwidgetSynthVoice(voicesOffset + i, numOutputChannels, {&modulatorGraph, &mainGraph}));
156 }
157
158 void KwidgetSynth::processBlock(AudioBuffer<float>& buffer, MidiBuffer& midi)
159 {
160 renderNextBlock(buffer, midi, 0, buffer.getNumSamples());
161 }
162
164 {
165 int numActiveVoices = 0;
166 for (int i = 0; i < getNumVoices(); i++)
167 {
168 auto voice = getVoice(i);
169 if (voice->isVoiceActive())
170 numActiveVoices++;
171 }
172 return numActiveVoices;
173 }
174
176
177 SynthesiserVoice* KwidgetSynth::findVoiceToSteal(SynthesiserSound* soundToPlay, int /*midiChannel*/,
178 int midiNoteNumber) const
179 {
180 // This voice-stealing algorithm applies the following heuristics:
181 // - Re-use the oldest notes first
182 // - Protect the lowest & topmost notes, even if sustained, but not if they've been released.
183
184 // apparently you are trying to render audio without having any voices...
185 jassert(!voices.isEmpty());
186
187 // These are the voices we want to protect (ie: only steal if unavoidable)
188 SynthesiserVoice* low = nullptr; // Lowest sounding note, might be sustained, but NOT in release phase
189 SynthesiserVoice* top = nullptr; // Highest sounding note, might be sustained, but NOT in release phase
190
191 // this is a list of voices we can steal, sorted by how long they've been running
192 Array<SynthesiserVoice*> usableVoices;
193 usableVoices.ensureStorageAllocated(voices.size());
194
195 for (auto* voice : voices)
196 {
197 if (voice->canPlaySound(soundToPlay))
198 {
199 jassert(voice->isVoiceActive()); // We wouldn't be here otherwise
200
201 usableVoices.add(voice);
202
203 // NB: Using a functor rather than a lambda here due to scare-stories about
204 // compilers generating code containing heap allocations..
205 struct Sorter
206 {
207 bool operator()(const SynthesiserVoice* a, const SynthesiserVoice* b) const noexcept
208 {
209 return a->wasStartedBefore(*b);
210 }
211 };
212
213 std::sort(usableVoices.begin(), usableVoices.end(), Sorter());
214
215 if (!voice->isPlayingButReleased()) // Don't protect released notes
216 {
217 auto note = voice->getCurrentlyPlayingNote();
218
219 if (low == nullptr || note < low->getCurrentlyPlayingNote())
220 low = voice;
221
222 if (top == nullptr || note > top->getCurrentlyPlayingNote())
223 top = voice;
224 }
225 }
226 }
227
228 // Eliminate pathological cases (ie: only 1 note playing): we always give precedence to the lowest note(s)
229 if (top == low)
230 top = nullptr;
231
232 // The oldest note that's playing with the target pitch is ideal..
233 if (m_samePitchNoteStealingEnabled) // Overriding findVoiceToSteal() and adding this, provided the fix for
234 // KST-1216
235 {
236 for (auto* voice : usableVoices)
237 if (voice->getCurrentlyPlayingNote() == midiNoteNumber)
238 return voice;
239 }
240
241 // Oldest voice that has been released (no finger on it and not held by sustain pedal)
242 for (auto* voice : usableVoices)
243 if (voice != low && voice != top && voice->isPlayingButReleased())
244 return voice;
245
246 // Oldest voice that doesn't have a finger on it:
247 for (auto* voice : usableVoices)
248 if (voice != low && voice != top && !voice->isKeyDown())
249 return voice;
250
251 // Oldest voice that isn't protected
252 for (auto* voice : usableVoices)
253 if (voice != low && voice != top)
254 return voice;
255
256 // We've only got "protected" voices now: lowest note takes priority
257 jassert(low != nullptr);
258
259 // Duophonic synth: give priority to the bass note:
260 if (top != nullptr)
261 return top;
262
263 return low;
264 }
265
266 void KwidgetSynth::noteOff(int midiChannel, int midiNoteNumber, float velocity, bool allowTailOff)
267 {
268 Synthesiser::noteOff(midiChannel, midiNoteNumber, velocity, allowTailOff);
269 if (midiNoteNumber <= MIDI_NOTE_MASK) // If it is a normal MIDI note number
270 {
271 noteOnThenDelayedNoteOff(midiChannel, midiNoteNumber + MIDI_NOTE_FLAG_NOR, velocity, allowTailOff);
272 }
273 }
274
276 {
277 while (!m_messageBuffer.empty())
278 {
279 midiMessage mess = m_messageBuffer.back();
280 m_messageBuffer.pop_back();
281 Synthesiser::noteOff(mess.midiChannel, mess.midiNoteNumber, mess.velocity, mess.allowTailOff);
282 }
283 stopTimer();
284 }
285
286 void KwidgetSynth::noteOnThenDelayedNoteOff(int midiChannel, int midiNoteNumber, float velocity, bool allowTailOff)
287 {
288 Synthesiser::noteOn(midiChannel, midiNoteNumber, velocity);
289 m_messageBuffer.push_back(midiMessage{midiChannel, midiNoteNumber, velocity, allowTailOff});
290 startTimer(NOTE_UP_NOTE_LENGTH_MS);
291 }
292
293} // namespace krotos
Uses juce::AudioProcessorGraph to store Kwidgets and their audio connections, and process audio throu...
Definition KwidgetProcessorGraph.h:18
int getNumVoices() const
Definition KwidgetProcessorGraph.h:35
void processBlock(AudioBuffer< float > &buffer, MidiBuffer &midi)
Definition KwidgetSynthesiser.cpp:158
bool m_samePitchNoteStealingEnabled
Definition KwidgetSynthesiser.h:108
void setSamePitchNoteStealingEnable(bool newState)
Definition KwidgetSynthesiser.cpp:175
SynthesiserVoice * findVoiceToSteal(SynthesiserSound *soundToPlay, int midiChannel, int midiNoteNumber) const override
Definition KwidgetSynthesiser.cpp:177
void noteOnThenDelayedNoteOff(int midiChannel, int midiNoteNumber, float velocity, bool allowTailOff)
Sends a note on message, then after a delay sends a corresponding note-off message.
Definition KwidgetSynthesiser.cpp:286
int getNumActiveVoices()
Definition KwidgetSynthesiser.cpp:163
std::vector< midiMessage > m_messageBuffer
Definition KwidgetSynthesiser.h:106
void timerCallback() override
Definition KwidgetSynthesiser.cpp:275
KwidgetSynth(KwidgetProcessorGraph &mainGraph, KwidgetProcessorGraph &modulatorGraph, int voicesOffset=0, int forceNumVoices=0, int numOutputChannels=10)
Definition KwidgetSynthesiser.cpp:139
const int NOTE_UP_NOTE_LENGTH_MS
Definition KwidgetSynthesiser.h:83
void noteOff(int midiChannel, int midiNoteNumber, float velocity, bool allowTailOff) override
Definition KwidgetSynthesiser.cpp:266
Concrete subclass of juce::SynthesiserSound.
Definition KwidgetSynthesiser.h:8
bool appliesToNote(int midiNoteNumber) override
Definition KwidgetSynthesiser.cpp:3
bool appliesToChannel(int midiChannel) override
Definition KwidgetSynthesiser.cpp:4
Adapter for using KwidgetProcessorGraph objects with juce::Synthesiser.
Definition KwidgetSynthesiser.h:25
void addProcessor(KwidgetProcessorGraph *p)
Definition KwidgetSynthesiser.cpp:133
void renderNextBlock(AudioBuffer< float > &buffer, int startSample, int numSamples) override
Definition KwidgetSynthesiser.cpp:61
const int m_voiceIndex
Definition KwidgetSynthesiser.h:48
KwidgetSynthVoice(int idx, int numOutputChannels, std::vector< KwidgetProcessorGraph * > processors)
Definition KwidgetSynthesiser.cpp:6
Array< KwidgetProcessorGraph * > m_processors
Definition KwidgetSynthesiser.h:51
void startNote(int midiNote, float velocity, SynthesiserSound *sound, int) override
Definition KwidgetSynthesiser.cpp:37
void controllerMoved(int, int) override
Definition KwidgetSynthesiser.cpp:59
bool canPlaySound(SynthesiserSound *sound) override
Definition KwidgetSynthesiser.cpp:28
void stopNote(float velocity, bool allowTailOff) override
Definition KwidgetSynthesiser.cpp:44
void pitchWheelMoved(int) override
Definition KwidgetSynthesiser.cpp:57
AudioBuffer< float > m_workingBuffer
Definition KwidgetSynthesiser.h:49
Definition AirAbsorptionFilter.cpp:2
static const int MIDI_NOTE_MASK
Definition OscillatorUtils.h:21
static const int MIDI_NOTE_FLAG_NOR
Definition OscillatorUtils.h:25
Definition KwidgetSynthesiser.h:98
float velocity
Definition KwidgetSynthesiser.h:101
int midiChannel
Definition KwidgetSynthesiser.h:99
int midiNoteNumber
Definition KwidgetSynthesiser.h:100
bool allowTailOff
Definition KwidgetSynthesiser.h:102