Krotos Modules 3
Loading...
Searching...
No Matches
GranularOscillator.cpp
Go to the documentation of this file.
1/*
2==============================================================================
3
4 GranularOscillator.cpp
5 Sample based oscillator / granular engine
6 Author: Sandy
7 Date: 19/08/2020
8
9==============================================================================
10*/
11
12#include "GranularOscillator.h"
13
14namespace krotos
15{
17
23
24 void GranularOscillator::setGranularParameter(size_t paramIndex, const float* newValue)
25 {
26 jassert(paramIndex >= 0 && paramIndex < GranularParam::NUM_PARAMS);
27 m_parameters[paramIndex] = newValue;
28 }
29
30 inline void GranularOscillator::setGranularParameters(std::vector<const float*>& newParams)
31 {
32 jassert(newParams.size() == m_parameters.size());
33 m_parameters = newParams;
34 }
35
36 inline void GranularOscillator::setSampleRate(double sampleRate)
37 {
38 m_samplerate = sampleRate;
39 m_grainTimer.setSampleRate(static_cast<float>(m_samplerate));
41 m_channelCrossfade.setMaxSlewRate(2.f / static_cast<float>(sampleRate));
42 m_directionGatingSmoother.setMaxSlewRate(2.f / static_cast<float>(sampleRate));
43 }
44
46 {
47 m_sampleEngine = sampleEngine;
48 // m_length = sampleEngine->getNumSamples();
49 }
50
51 // Remove all playing grains from the active grains list
53 {
54 for (auto& grain : m_activeGrains)
55 {
56 m_sampleEngine->freeGrain(grain); // Stop playing the grain
57 }
58 m_activeGrains.clear();
59 }
60
61 /*
62 How the frequency calculation works
63 setFrequency( frequency ) is called with the desired output frequency of the sample
64
65 Things we need to calculate:
66
67 1) An increment to be added to the playhead every sample
68 2) A resample ratio to compensate for any difference between the sample's sample rate and the DAW's sample rate
69 3) A ratio to adjust for the frequency of the sampled note (working with C3 at present, pending some analysis or
70 meta-data)
71
72 1 is simply m_playbackHz * m_resampleRatio
73 2 & 3 are combined into m_resampleRatio
74 */
75
76 inline void GranularOscillator::setFrequency(float frequency)
77 {
78 // Clamp within reasonable limits ie frequency >= epsilon, frequency <= nyquist (m_samplerate / 2)
79 m_playbackHz = std::max(std::min(float(m_samplerate * 0.5f), frequency), std::numeric_limits<float>::epsilon());
80 }
81
82 inline float GranularOscillator::cube(float val) { return val * val * val; }
83
84 void GranularOscillator::startNote(int midiNote, float velocity, int voiceIndex)
85 {
86 m_voiceIndex = voiceIndex; // Used finally merely to indicate on the UX the voice index of each playhead
87
88 // Setting the mode of the oscillator - no need to do this every sample, once at note-start is enough
90
92 m_isDrone = (midiNote & MIDI_NOTE_FLAG_DRONE) != 0;
93
94 m_stopped = false;
95 m_keyDown = true;
96 m_velocity = velocity;
98 setFrequency(static_cast<float>(MidiMessage::getMidiNoteInHertz(midiNote & MIDI_NOTE_MASK)));
99 m_grainTimer.triggerNewGrain(); // Trigger a new grain
100 }
101
103
105
107 {
108 m_keyDown = false;
109 if (!m_stopped)
110 {
111 m_stopped = true;
113 }
114 }
115
116 inline void GranularOscillator::setTaggingType(bool isOn) { m_taggingType = isOn; }
117
119
124
126 {
127 if (std::isnan(sample.left))
128 {
129 jassertfalse; // Shouldn't get here
130 sample.left = 0.0f;
131 }
132 if (std::isnan(sample.right))
133 {
134 jassertfalse; // Shouldn't get here
135 sample.right = 0.0f;
136 }
137#ifdef USE_BRICK_WALL_CLIPPING
138 const float clippingVal = 3.0f;
139 if (sample.left > clippingVal)
140 {
141 sample.left = clippingVal;
142 }
143 if (sample.right > clippingVal)
144 {
145 sample.right = clippingVal;
146 }
147#endif
148 }
149
150 //
151 // This is a stereo oscillator, but we return a mono result here
152 //
153 // inline float GranularOscillator::getNextSample()
154 //{
155 // StereoSample sample = getNextStereoSample();
156 // return (sample.left + sample.right) * 0.5f;
157 //}
158
160 {
161 float rand = m_rand.nextFloat() - 0.5f; // rand varies between +- 0.5
163 0.3f); // scaled by FLUX parameter, skewed for more action lower down
164 if (rand > 0.0f)
165 {
166 m_fluxFactor = 1.0f + rand * 6.0f; // scaling ranges between 1.0 -> 4.0
167 }
168 else
169 {
170 m_fluxFactor = 1.0f + rand * 1.5f; // scaling ranges between 1.0 -> 0.25
171 }
172 }
173
174 double GranularOscillator::calculatePlayheadPosition(double startPosition, float halfSpray,
175 float /*referenceGrainsize*/)
176 {
177 // Calculate start and spray
178 // If the combination of start and spray take the spray range off the edge of the sample ...
179 // .. then the spray range is shifted forwards or backwards to keep it inside the sample while preserving the
180 // spray range size eg if spray is set to 1, start will have no effect whatsoever, as the spray range already
181 // covers the entire sample Conversely if spray is set to 0, start will be effective throughout its entire range
182
183 //--------------------- Scale spray range to grain size
184 halfSpray *= 10.0f * m_closestDescriptor.grainSize / float(m_length); // += 10 grains max spray range
185
186 Range<double> sprayRange(startPosition - static_cast<double>(halfSpray),
187 startPosition + static_cast<double>(halfSpray));
188 Range<double> constraintRange(0.f, 1.f);
189 Range<double> useableRange = constraintRange.constrainRange(sprayRange);
190
191 // Randomly place playhead somewhere within useable range
192 return (useableRange.getStart() + m_rand.nextDouble() * useableRange.getLength()) *
193 static_cast<double>(m_length);
194 }
195
196 Grain* GranularOscillator::launchGrain(float paramSize, float paramStart, float paramSpray, Grain::Flavour flavour,
197 float gain)
198 {
199 const float minimumGrainLength{0.1f};
200
201 bool shouldLaunchNewGrain = false;
202 if (m_isDrone)
203 {
205 {
206 shouldLaunchNewGrain = true;
207 }
208 }
209 else
210 {
211 shouldLaunchNewGrain = true;
212 }
213
214 if (shouldLaunchNewGrain)
215 {
216 // Ask the sample engine for a free grain slot, returns non-null if successful
217 Grain* newGrain = m_sampleEngine->allocateGrain();
218 if (newGrain) // If non-null, means a free grain slot was found and newGrain is pointing to it
219 {
220 // Each voice instance (this is a voice instance) keeps its own personal record of every grain it is
221 // playing
222 m_activeGrains.push_back(newGrain);
223
224 int grainLength = int(jmax<float>(minimumGrainLength, m_closestDescriptor.grainSize * paramSize));
225
226 double playHead = calculatePlayheadPosition(paramStart, paramSpray, m_closestDescriptor.grainSize);
228 static_cast<int>(playHead) -
229 grainLength / 2); // Center the playing grain about the start position
230
231 // Calculate jitter - this implementation is an amplitude variation factor of +-1 centered around 1
232 if (gain < 0.f) // If supplied gain < 0, we generate it here
233 {
234 gain = 1.0f +
235 2.f * m_parameters[GranularParam::LevelRand][m_sampleIndex] * (m_rand.nextFloat() - 0.5f);
236 }
237
238 // Calculate drift
239 float pitchRand =
240 1.0f + m_parameters[GranularParam::DetuneRand][m_sampleIndex] * (m_rand.nextFloat() - 0.5f);
241
242 // Now we populate all the properties of the grain ...
243 newGrain->playHead =
244 static_cast<double>(quantisedGrain.grainIndexByTime); // phase cycle index with phase angle 0
245 newGrain->playHeadStart =
246 quantisedGrain.audioIndex; // Keep a record of the start point - used in displaying grain in UI
247 newGrain->windowDelta =
248 1.f /
249 float(grainLength); // Calculate the increment needed to go from 0->1 during lifetime of grain
250 newGrain->grainWindow = 0.f; // Reset the window value
251 newGrain->length = grainLength; // Set the grainLength in samples
253 newGrain->gainRand = gain * gain;
254 newGrain->pitchRand = pitchRand;
255 newGrain->flavour = flavour;
256 }
257
258 return newGrain;
259 }
260
261 return nullptr;
262 }
263
265 {
266 if (m_sampleEngine == nullptr)
267 {
268 return StereoSample{}; // Return silence if no sample data
269 }
270
271 m_length = m_sampleEngine->getNumSamples();
272
273 jassert(sampleIndex >= 0 && sampleIndex < m_length);
274
275 if (m_stopped || sampleIndex < 0 || sampleIndex >= m_length)
276 {
277 return StereoSample{}; // Return silence if engine stopped or sampleIndex out of range
278 }
279
280 m_sampleIndex = sampleIndex;
281
283
285
287 {
290 Decibels::decibelsToGain(m_parameters[GranularParam::VehicleLevel][m_sampleIndex]);
291 break;
293 return getNextStereoGranular() *
294 Decibels::decibelsToGain(m_parameters[GranularParam::GranularLevel][m_sampleIndex]);
295 break;
297 return getNextStereoSampler() *
298 Decibels::decibelsToGain(m_parameters[GranularParam::SamplerLevel][m_sampleIndex]);
299 break;
300 default:
301 return StereoSample(); // Return silence
302 break;
303 }
304 }
305
307 {
308 // Slider names: Rate of grain generation - RATE, Randomisation of grain rate - FLUX
309
310 bool muteUp = m_parameters[GranularParam::MuteUp][0] > 0.5f;
311 bool muteDown = m_parameters[GranularParam::MuteDown][0] > 0.5f;
312 bool synch = m_parameters[GranularParam::PhaseSync][0] > 0.5f;
313
315 {
316 // jassertfalse;
317 return StereoSample{}; // Return silence if no sample data
318 }
319
320 // Declare local variables
321 StereoSample sampleAccumulatorSustained;
322 StereoSample sampleAccumulatorMoving;
323
324 float startSlider = m_parameters[GranularParam::Start][m_sampleIndex] * 0.01f;
325
326 // Find the closest (red line) descriptor to the start slider, but since it involves a search, only if the start
327 // slider has moved
328 if (m_startIndex != startSlider)
329 {
330 float smoothing = powf(m_parameters[GranularParam::Smoothing][m_sampleIndex],
331 0.3f); // powf skewed for for more action towards zero
332
333 float smoothingCoeff = jmap<float>(smoothing, 0.f, 1.f, 0.001f, 0.000001f);
334
335 m_startIndex = (1.f - smoothingCoeff) * m_startIndex + smoothingCoeff * startSlider;
336
337 float difference = m_startIndex - startSlider;
338 float movement = jmin<float>(
340 1.f); // Clamp to <= 1
341
342 m_crossfadeValue = movement;
344
345 // Direction gating
346 if (difference > 0)
347 {
348 m_directionGating = 0.f;
349 }
350 else
351 {
352 m_directionGating = movement;
353 }
354 if (muteUp && muteDown)
355 {
356 m_directionGating = 0.f;
357 }
358 else if (muteUp && !muteDown)
359 {
361 }
362 else if (!muteUp && !muteDown)
363 {
365 }
366
367 // Find the nearest grain
368 if (m_sampleEngine->getGrainDescriptionByTime().size() != 0) // Safety
369 {
370 if (synch)
371 {
372 float freq = jmap<float>(m_startIndex, 0.f, 1.f, 12.f, 60.f);
373 m_closestDescriptor = m_sampleEngine->audioFrequencyToDescriptor(freq); // Navigate by frequency
374 }
375 else
376 {
378 m_sampleEngine->audioPercentToDescriptor(m_startIndex); // Navigate by file position
379 }
380
381 // Set the frequency of the phase generator from the red line
383
385
386 m_startIndexSearched = static_cast<float>(static_cast<double>(m_closestDescriptor.audioIndex) /
387 static_cast<double>(m_length));
388 }
389 }
390
391 float mix = m_channelCrossfade.processSample(jmap<float>(m_crossfadeValue, 0.f, 1.f,
394
395 float smoothedDirectionGating = m_directionGatingSmoother.processSample(m_directionGating);
396 m_sampleEngine->setIndicator2(smoothedDirectionGating);
397
399 static_cast<int>(m_parameters[GranularParam::GrainLim]
400 [m_sampleIndex])); // TODO: this could be done further up the hierarchy
401
402 // Update the phase of this entire voice - all playing grains in this voice are synchronised to this
404
405 // If m_voice phase loop divider has wrapped, then it is time to launch a new sustain grain
407 {
411 }
412
413 // If m_voice phase loop has wrapped past 360 degrees, then it is time to launch a new moving grain
415 {
419 }
420
421 // Sum and return the audio signals from all the playing grains
422 int grainIndex = static_cast<int>(m_activeGrains.size()) - 1;
423 while (grainIndex >= 0)
424 {
425 Grain* playingGrain = m_activeGrains[size_t(grainIndex)];
426
427 // Fetch the audio from the sample buffer using the selected interpolation method
428 // StereoSample sample = m_sampleEngine->getInterpolatedSample(playingGrain->playHead);
430
431 // Calculate shape envelope (TODO: add more shape calculation methodology - cosine ? other ? -
432 // pre-calculate to save CPU ?)
433
434 float grainProgress =
435 playingGrain->grainWindow; // Pick the value off before we increment to allow it to start from 0
436
437 playingGrain->grainWindow += playingGrain->windowDelta; // Increment the grain window - track progress
438 // through grain from 0 to 1 as grain plays
439
440 if (grainProgress > 0.5f)
441 grainProgress = 1.0f - grainProgress;
442 grainProgress *= 2.0f; // Scale so it peaks at 1 - shape now looks like a triangle
444 0.02f); // Don't let shape get too square or it will click / generate NANs
445 grainProgress /= shape;
446 grainProgress = jmin(grainProgress, 1.0f); // Clamp to 1
447 float shapeEnvelope = 1.0f - cube(fabsf(1.0f - grainProgress)); // Curve the straight lines
448 shapeEnvelope *=
449 1.f + shape * AMPLITUDE_FIDDLE_FACTOR; // Compensate for loss of amplitude as shape gets less square
450 playingGrain->envelope = shapeEnvelope; // Copy here for use by the grain display
451
452 // Do width calculation
454 float widthInv = 1.0f - width;
455 float mono = widthInv * 0.5f * (sample.left + sample.right);
456 float left = sample.left * width + mono;
457 float right = sample.right * width + mono;
458
459 // Accumulate audio from all the playing grains
460 float gain = playingGrain->gainRand * shapeEnvelope;
461 float pan = (1.f - m_parameters[GranularParam::Pan][m_sampleIndex]) * 0.5f;
462 StereoSample result;
463
464 result.left = gain * left * powf(pan * 0.8f, 0.5f);
465 result.right = gain * right * powf(0.8f - pan * 0.8f, 0.5f);
466
467 if (playingGrain->flavour == Grain::Flavour::vehicleMoving)
468 {
469 sampleAccumulatorMoving += result;
470 playingGrain->mix = playingGrain->gainRand * mix;
471 }
472 else
473 {
474 sampleAccumulatorSustained += result;
475 playingGrain->mix = playingGrain->gainRand * (1.0f - mix);
476 }
477
478 // Move the playhead forwards, synchronising with the voice phase
479 m_grainTimer.synchronise(playingGrain->playHead);
480
481 // Has the grain come to the end of its life ?
482 if (playingGrain->grainWindow >= 1.0f)
483 {
484 // If so, return it to the pool
485 m_sampleEngine->freeGrain(playingGrain);
486 // and remove it from our local list of active grains
487 m_activeGrains.erase(m_activeGrains.begin() + grainIndex);
488 }
489
490 grainIndex--;
491 }
492
493 float gain = Decibels::decibelsToGain(m_parameters[GranularParam::Level][m_sampleIndex]);
494 StereoSample output = (sampleAccumulatorMoving * mix + sampleAccumulatorSustained * (1.f - mix)) * gain;
495
497 output = output * smoothedDirectionGating;
498
499 killNANs(output);
500 return output;
501 }
502
504 {
505 // The main routine for Granular mode
506
507 StereoSample sampleAccumulator;
508
509 // Calculate Pitch playBack
512 float playbackHzDetunedMod;
513 if (m_detuneModVal == 0)
514 playbackHzDetunedMod = m_playbackHz;
515 else
516 playbackHzDetunedMod =
517 (m_playbackHz * powf(2.0, m_detuneModVal / static_cast<float>(SEMITONES_PER_OCTAVE)));
518
519 if (detuneVal == 0)
520 m_playbackHzDetuned = playbackHzDetunedMod;
521 else
523 playbackHzDetunedMod * powf(2.0, detuneVal / static_cast<float>(SEMITONES_PER_OCTAVE));
524
525 // Tick the grain timer
527
528 // It is time to launch a new grain if m_grainTimer has wrapped and we can obtain a granular slot
529 // m_grainTimer is an instance of the class PhaseGenerator which can be used to keep track of
530 // the progression in time though the samples that comprise a complete cycle of a waveform.
531 // It is used here simply as a timer to regulate the rate of grain generation.
532 // Every sample time the phase is incremented by a delta based on the RATE setting.
533 // When the phase angle reaches 1, it wraps back to 0, and sets a flag which
534 // can be read by the hasWrapped() method. This is the signal to launch a new grain
535 // The sample engine has a finite number of slots available for playing grains, so allocateGrain()
536 // can return with a null pointer if all slots are busy. In this case the launching of a new grain
537 // is aborted.
538 Grain* newGrain = nullptr;
539
540 if (m_grainTimer.hasWrapped()) // Time for new grain
541 {
542 bool shouldLaunchNewGrain = false;
543 if (m_isDrone)
544 {
546 {
547 shouldLaunchNewGrain = true;
548 }
549 }
550 else
551 {
552 shouldLaunchNewGrain = true;
553 }
554
555 if (shouldLaunchNewGrain)
556 {
557 m_sampleEngine->setGrainLimit(static_cast<int>(
559 [m_sampleIndex])); // TODO: this could be done further up the hierarchy
560
562 false) // If key is up, quench mode disables new grain generation
563 {
564 newGrain = m_sampleEngine->allocateGrain(); // Ask the sample engine for a free grain slot
566 }
567 }
568 }
569
570 if (newGrain)
571 {
572 // New flux value is generated every new grain time
573 // Slider names: Rate of grain generation - RATE, Randomisation of grain rate - FLUX
574 float rand = m_rand.nextFloat() - 0.5f; // rand varies between +- 0.5
576 0.3f); // scaled by FLUX parameter, skewed for more action lower down
577 if (rand > 0.0f)
578 {
579 m_fluxFactor = 1.0f + rand * 6.0f; // scaling ranges between 1.0 -> 4.0
580 }
581 else
582 {
583 m_fluxFactor = 1.0f + rand * 1.5f; // scaling ranges between 1.0 -> 0.25
584 }
585 }
586
587 if (m_loopingEnable)
588 {
589 m_grainRate = 0.f;
591 }
592 else
593 {
594 // The grain launch timer is updated when GranularParam::Rate value changes or isNewGrainTime
596 {
599 }
600 }
601
602 // Time to launch a grain ?
603 if (newGrain) // If non-null, means a free grain slot was found and returned
604 {
605 // The segment index is not incremented in granular mode, but it can be set to a specific index to allow
606 // choosing which segment to play.
607 int selectedSegment = m_sampleEngine->getNextSegmentIndex();
608 int selectionLength = jmax<int>(1, m_sampleEngine->getAudioSelectionLengthInSamples(selectedSegment));
609 int segmentLength = jmax<int>(1, m_sampleEngine->getAudioSegmentLengthInSamples(selectedSegment));
610
611 m_activeGrains.push_back(newGrain); // We keep our own personal reference to the grains we are playing
612
613 // Populate all the properties of the grain ...
614
615 // Set the pan randomisation per grain - ranges from -1 to +1
616 newGrain->panRand = (m_rand.nextFloat() * 2.f - 1.f) * m_parameters[GranularParam::PanRand][m_sampleIndex];
617
618 // Set the width randomisation per grain - ranges from 0 to 1 when full up, to 1 when turned to 0
619 newGrain->widthRand = 1.f - m_rand.nextFloat() * m_parameters[GranularParam::WidthRand][m_sampleIndex];
620
621 // Calculate start and spray
622 // If the combination of start and spray take the spray range off the edge of the sample ...
623 // .. then the spray range is shifted forwards or backwards to keep it inside the sample while preserving
624 // the spray range size eg if spray is set to 1, start will have no effect whatsoever, as the spray range
625 // already covers the entire sample Conversely if spray is set to 0, start will be effective throughout its
626 // entire range
627
628 int startPositionInSamples =
630 int(m_parameters[GranularParam::Start][m_sampleIndex] * 0.01f * selectionLength);
631
632 int halfSpray = int(0.5f * m_parameters[GranularParam::StartRand][m_sampleIndex] * segmentLength);
633
634 Range<int> sprayRange(int(startPositionInSamples - halfSpray), int(startPositionInSamples + halfSpray));
635
636 Range<int> constraintRange(m_sampleEngine->getAudioSelectionStartPosition(selectedSegment),
639
640 Range<int> useableRange = constraintRange.constrainRange(sprayRange);
641 newGrain->playHead = (useableRange.getStart() + m_rand.nextFloat() * useableRange.getLength());
642
643 newGrain->playHeadStart = static_cast<int>(newGrain->playHead); // For display
644
645 int segmentSampleRate = m_sampleEngine->getAudioSegmentSampleRate(selectedSegment);
646
647 float gainNormalisationFactor =
650
651 float resampleRatio = static_cast<float>(segmentSampleRate) /
652 m_sampleEngine->getSampleRate(); // Calculate the ratio between the DAW sample rate
653 // and the source sample rate
654 newGrain->resampleRatio =
655 resampleRatio /
656 m_sampleEngine->getNativeNoteFrequency(); // Compensate by the frequency of the sampled note
657
658 // Calculate grain lifetime
659 float density = 1.0f - m_parameters[GranularParam::SizeRand][m_sampleIndex] * m_rand.nextFloat();
660 int userAdjustedGrainSize = int(m_parameters[GranularParam::Size][m_sampleIndex] * 0.01 * segmentLength);
661
662 int grainLength = int(density * userAdjustedGrainSize);
663 if (grainLength < 1)
664 grainLength = 1;
665 if (grainLength > selectionLength)
666 grainLength = selectionLength;
667
668 // For display
669 newGrain->length = grainLength;
670
671 newGrain->windowDelta =
672 1.f / float(grainLength); // Calculate the increment needed to go from 0->1 during lifetime of grain
673 newGrain->grainWindow = 0.f; // Reset the window value
674
675 // Calculate drift (pitch randomisation)
676 newGrain->pitchRand =
677 1.0f + m_parameters[GranularParam::DetuneRand][m_sampleIndex] * (m_rand.nextFloat() - 0.5f);
678
679 // Calculate jitter - this implementation is an amplitude variation factor of +-1 centered around 1
680 float gain =
681 1.0f + 2.f * m_parameters[GranularParam::LevelRand][m_sampleIndex] * (m_rand.nextFloat() - 0.5f);
682 newGrain->gainRand = gain * gain * gainNormalisationFactor;
683
684 if (m_isDrone)
685 {
686 newGrain->flavour = Grain::Flavour::background;
687 }
688 else
689 {
690 newGrain->flavour = Grain::Flavour::foreground;
691 }
692
693 newGrain->voiceIndex = m_voiceIndex;
694 }
695
696 generateGrainsGranular(sampleAccumulator);
697
698 killNANs(sampleAccumulator);
699
700 return sampleAccumulator;
701 }
702
704 {
705 // The main routine for Sampler mode
706
707 StereoSample sampleAccumulator;
708
709 // Calculate Pitch playBack
712 float playbackHzDetunedMod;
713 if (m_detuneModVal == 0)
714 {
715 playbackHzDetunedMod = m_playbackHz;
716 }
717 else
718 {
719 playbackHzDetunedMod =
720 (m_playbackHz * powf(2.0, m_detuneModVal / static_cast<float>(SEMITONES_PER_OCTAVE)));
721 }
722
723 if (detuneVal == 0)
724 {
725 m_playbackHzDetuned = playbackHzDetunedMod;
726 }
727 else
728 {
730 playbackHzDetunedMod * powf(2.0, detuneVal / static_cast<float>(SEMITONES_PER_OCTAVE));
731 }
732
733 // Tick the grain timer
735
736 /* It is time to launch a new sample if m_grainTimer has wrapped and we can obtain a granular slot
737 m_grainTimer is an instance of the class PhaseGenerator which can be used to keep track of
738 the progression in time though the samples that comprise a complete cycle of a waveform.
739 It is used here simply as a timer to regulate the rate of sample generation.
740 Every sample time the phase is incremented by a delta based on the RATE setting.
741 When the phase angle reaches 1, it wraps back to 0, and sets a flag which
742 can be read by the hasWrapped() method. This is the signal to launch a new grain
743 The sample engine has a finite number of slots available for playing grains, so allocateGrain()
744 can return with a null pointer if all slots are busy. In this case the launching of a new grain
745 is aborted. */
746
747 Grain* newGrain = nullptr;
748
749 if (m_grainTimer.hasWrapped()) // Time for new grain
750 {
751 // Only launch new grains for a drone when autoplay is enabled.
752 bool shouldLaunchNewGrain = !m_isDrone || m_autoplayEnabled;
753
754 if (shouldLaunchNewGrain)
755 {
756 m_sampleEngine->setGrainLimit(static_cast<int>(
758 [m_sampleIndex])); // TODO: this could be done further up the hierarchy
759
760 // If key is up, quench mode disables new grain generation
762 {
763 newGrain = m_sampleEngine->allocateGrain(); // Ask the sample engine for a free grain slot
765 }
766 }
767 }
768
769 if (newGrain)
770 {
771 // New flux value is generated every new grain time
772 // Slider names: Rate of grain generation - RATE, Randomisation of grain rate - FLUX
773 float rand = m_rand.nextFloat() - 0.5f; // rand varies between +- 0.5
774
775 // scaled by FLUX parameter, skewed for more action lower down
777
778 // Seperate scaling depending on the sign of rand
779 m_fluxFactor = 1.0f + rand * rand > 0.0f ? FLUX_SCALING_POSITIVE : FLUX_SCALING_NEGATIVE;
780 }
781
782 if (m_loopingEnable)
783 {
784 m_grainRate = 0.f;
786 }
787 else
788 {
789 // The grain launch timer is updated when GranularParam::Rate value changes or isNewGrainTime
791 {
794 }
795 }
796
797 // Time to launch a grain ?
798 if (newGrain) // If non-null, means a free grain slot was found and returned
799 {
800 int segmentIndex = m_sampleEngine->getNextSegmentIndex();
801
802 // Calculate the next segment index in advance. It can be set externally via setNextSegmentIndex, to allow
803 // for user cueing.
805 static_cast<bool>(m_parameters[GranularParam::Robin][m_sampleIndex]));
806
808
809 m_activeGrains.push_back(newGrain); // We keep our own personal reference to the grains we are playing
810
811 // Populate all the properties of the grain ...
812
813 // Set the pan randomisation per grain - ranges from -1 to +1
814 newGrain->panRand = (m_rand.nextFloat() * 2.f - 1.f) * m_parameters[GranularParam::PanRand][m_sampleIndex];
815
816 newGrain->widthRand = 1.f; // Sampler uses no width randomisation
817
818 // Calculate start
819 newGrain->segmentIndex = segmentIndex;
820
821 int segmentSampleRate = m_sampleEngine->getAudioSegmentSampleRate(segmentIndex);
822
823 // Calculate the ratio between the DAW sample rate and the source sample rate
824 const float resampleRatio = static_cast<float>(segmentSampleRate) / m_sampleEngine->getSampleRate();
825
826 m_rampSamples = static_cast<int>(m_samplerate * RAMP_LENGTH_SECONDS);
827 m_rampInc = 1.f / static_cast<float>(m_rampSamples) * resampleRatio;
828
829 newGrain->resampleRatio =
830 resampleRatio /
831 m_sampleEngine->getNativeNoteFrequency(); // Compensate by the frequency of the sampled note
832
833 float gainNormalisationFactor =
836 // Calculate jitter - this implementation is an amplitude variation factor of +-1 centered around 1
837 float gain =
838 1.0f + 2.f * m_parameters[GranularParam::LevelRand][m_sampleIndex] * (m_rand.nextFloat() - 0.5f);
839 newGrain->gainRand = gain * gain * gainNormalisationFactor;
840
842
843 newGrain->voiceIndex = m_voiceIndex;
844
845 setGrainData(newGrain);
846 }
847
848 generateGrainsSampler(sampleAccumulator);
849
850 killNANs(sampleAccumulator);
851
852 return sampleAccumulator;
853 }
854
856 {
858 playingGrain->playHead = static_cast<double>(playingGrain->playHeadStart);
859 playingGrain->length =
861 // Reset the window value
862 playingGrain->grainWindow = 0.f;
863 // Calculate the increment needed to go from 0->1 during lifetime of grain
864 playingGrain->windowDelta = 1.f / static_cast<float>(playingGrain->length);
865 // Calculate drift (pitch randomisation)
866 playingGrain->pitchRand =
867 1.0f + m_parameters[GranularParam::DetuneRand][m_sampleIndex] * (m_rand.nextFloat() - 0.5f);
868 playingGrain->ramp = 0.f;
869 }
870
872 {
874 float masterGain = gainMod * Decibels::decibelsToGain(m_parameters[GranularParam::Level][m_sampleIndex]);
876 int grainIndex = static_cast<int>(m_activeGrains.size()) - 1;
877
878 // This is the loop which generates the grains.
879 // Everything inside will be called per grain, so we have pre-calculated as much as possible before entry (& a
880 // bit more can be done in this respect)
881 while (grainIndex >= 0)
882 {
883 Grain* playingGrain = m_activeGrains[size_t(grainIndex)];
884 float increment = m_playbackHzDetuned * playingGrain->resampleRatio;
885 float randomisedPan = 0.5f * (1.f + jlimit<float>(-1.f, 1.f, pan + playingGrain->panRand));
886 playingGrain->envelope = 1.f; // Full amplitude most of the time, only set < 1 when applying shape
887
888 float rampVal = 1.f;
889 if (m_loopingEnable)
890 {
891 // Are we ramping down ?
892 if (playingGrain->playHead >=
893 static_cast<double>(playingGrain->playHeadStart + playingGrain->length - m_rampSamples))
894 {
895 playingGrain->ramp -= m_rampInc;
896 if (playingGrain->ramp < 0.f)
897 {
898 playingGrain->ramp = 0.f;
899 }
900 }
901 else // Ramping up
902 {
903 playingGrain->ramp += m_rampInc;
904 if (playingGrain->ramp > 1.f)
905 {
906 playingGrain->ramp = 1.f;
907 }
908 }
909
910 // Dont waste an expensive cosf if ramp is 1.0
911 if (playingGrain->ramp != 1.f)
912 {
913 rampVal = (cosf((1.f - playingGrain->ramp) * MathConstants<float>::pi) + 1.f) * 0.5f;
914 }
915 }
916 else
917 {
918 // Ensure we are always starting with silence
919 // (prevents clicking if user switching looping on and off)
920 playingGrain->ramp = 0.f;
921 }
922
923 // Accumulate audio from all the playing grains
924 StereoSample newSample = m_sampleEngine->getInterpolatedSample(playingGrain->playHead);
925 sampleAccumulator += newSample.panSine(randomisedPan) * masterGain * playingGrain->gainRand * rampVal;
926
927 // Has the grain come to the end of its life ?
928 if (playingGrain->playHead >= static_cast<double>(playingGrain->playHeadStart + playingGrain->length))
929 {
930 setLoopEnableFlag(); // Set here to enable looping to be switched on and off while playing
931 if (m_loopingEnable)
932 {
933 setGrainData(playingGrain); // Loop back to the start of the selection
934 }
935 else
936 {
937 // If so, release the grain to the sample engine's central pool of grains
938 m_sampleEngine->freeGrain(playingGrain);
939 // then remove its entry from our local list of playing grains
940 m_activeGrains.erase(m_activeGrains.begin() + grainIndex);
941 }
942 }
943 else // If the grain is still playing, we increment the playhead and grain window
944 {
945 float playheadIncrement = increment * playingGrain->pitchRand;
946 playingGrain->playHead += playheadIncrement; // Advance the playhead
947 playingGrain->grainWindow += playheadIncrement / playingGrain->length; // Keep the grain window in sync
948 }
949 playingGrain->gainMod = gainMod;
950 playingGrain->pitchMod = m_detuneModVal / static_cast<float>(SEMITONES_PER_OCTAVE);
951 grainIndex--;
952 }
953 }
954
956 {
958 float masterGain = gainMod * Decibels::decibelsToGain(m_parameters[GranularParam::Level][m_sampleIndex]);
961 float fadePoint = jmax(m_parameters[GranularParam::Shape][m_sampleIndex] * 0.5f, FADE_POINT_MIN);
962 int grainIndex = static_cast<int>(m_activeGrains.size()) - 1;
963
964 int minGrainsBeforeRampUp = 0;
966 {
967 minGrainsBeforeRampUp = 1; // In sampler the first loop when looping does not ramp up, this allows for a
968 // hard attack from the ADSR if so set
969 fadePoint = 0.2f; // Fadepoint hard wired presently for sampler mode - we may expose the "shape" slider at
970 // some point
971 width = 1.f; // Sampler uses max width
972 }
973
974 // This is the loop which generates the grains.
975 // Everything inside will be called per grain, so we have pre-calculated as much as possible before entry (& a
976 // bit more can be done in this respect)
977 while (grainIndex >= 0)
978 {
979 Grain* playingGrain = m_activeGrains[size_t(grainIndex)];
980 float increment = m_playbackHzDetuned * playingGrain->resampleRatio;
981 float randomisedPan = 0.5f * (1.f + jlimit<float>(-1.f, 1.f, pan + playingGrain->panRand));
982 playingGrain->envelope = 1.f; // Full amplitude most of the time, only set < 1 when ramping up or down.
983 // Expensive sinf() called only during ramp
984
985 if ((playingGrain->grainWindow < fadePoint) &&
986 (m_activeGrains.size() > minGrainsBeforeRampUp)) // Are we ramping up ? Dont ramp if this is the first
987 // loop to play (allow full attack)
988 {
989 // Sine law here provides for equal power crossfading when looping
990 playingGrain->envelope = sinf(jmap<float>(playingGrain->grainWindow, 0.f, fadePoint, 0.f, 1.f) *
991 MathConstants<float>::halfPi);
992 }
993 else if (playingGrain->grainWindow >= 1.f - fadePoint) // Are we ramping down ?
994 {
995 playingGrain->envelope = sinf(jmap<float>(playingGrain->grainWindow, 1.f - fadePoint, 1.f, 1.f, 0.f) *
996 MathConstants<float>::halfPi);
997 if ((m_activeGrains.size() < 2) && m_loopingEnable) // We acheive cross-dissolved looping by triggering
998 // a second grain wwhen we start ramping down
999 {
1000 m_grainTimer.triggerNewGrain(); // Trigger a new grain
1001 }
1002 }
1003
1004 // Accumulate audio from all the playing grains
1005 StereoSample newSample = m_sampleEngine->getInterpolatedSample(playingGrain->playHead);
1006 sampleAccumulator += newSample.panSine(randomisedPan).width(width * playingGrain->widthRand) * masterGain *
1007 playingGrain->gainRand * playingGrain->envelope;
1008
1009 if (playingGrain->grainWindow >= 1.f) // Has the grain come to the end of its life ?
1010 {
1012 playingGrain); // If so, release the grain to the sample engine's central pool of grains
1013 m_activeGrains.erase(m_activeGrains.begin() +
1014 grainIndex); // then remove its entry from our local list of playing grains
1015 }
1016 else // If the grain is still playing, we increment the playhead and grain window
1017 {
1018 float playheadIncrement = increment * playingGrain->pitchRand;
1019 playingGrain->playHead += playheadIncrement; // Advance the playhead
1020 playingGrain->grainWindow += playheadIncrement / playingGrain->length; // Keep the grain window in sync
1021 }
1022 playingGrain->gainMod = gainMod;
1023 playingGrain->pitchMod = m_detuneModVal / static_cast<float>(SEMITONES_PER_OCTAVE);
1024 grainIndex--;
1025 }
1026 }
1027
1029
1031
1032 inline int GranularOscillator::getNumActiveGrains() { return static_cast<int>(m_activeGrains.size()); }
1033} // namespace krotos
Definition OscillatorUtils.h:39
float resampleRatio
Definition OscillatorUtils.h:67
int length
Definition OscillatorUtils.h:61
float gainRand
Definition OscillatorUtils.h:69
float panRand
Definition OscillatorUtils.h:70
double playHead
Definition OscillatorUtils.h:65
float envelope
Definition OscillatorUtils.h:63
float windowDelta
Definition OscillatorUtils.h:74
float gainMod
Definition OscillatorUtils.h:76
int segmentIndex
Definition OscillatorUtils.h:66
float widthRand
Definition OscillatorUtils.h:71
float pitchMod
Definition OscillatorUtils.h:77
float pitchRand
Definition OscillatorUtils.h:68
float ramp
Definition OscillatorUtils.h:72
float grainSize
Definition OscillatorUtils.h:60
int voiceIndex
Definition OscillatorUtils.h:75
Flavour
Definition OscillatorUtils.h:49
@ foreground
Definition OscillatorUtils.h:52
@ vehicleStatic
Definition OscillatorUtils.h:51
@ vehicleMoving
Definition OscillatorUtils.h:50
@ background
Definition OscillatorUtils.h:53
int playHeadStart
Definition OscillatorUtils.h:58
float mix
Definition OscillatorUtils.h:57
float grainWindow
Definition OscillatorUtils.h:62
const float FADE_POINT_MIN
Definition GranularOscillator.h:239
float m_playbackHzDetuned
Definition GranularOscillator.h:205
volatile float m_directionGating
Definition GranularOscillator.h:222
float m_startIndexSearched
Definition GranularOscillator.h:211
bool m_loopingEnable
Definition GranularOscillator.h:230
void setTaggingType(bool isOn)
Definition GranularOscillator.cpp:116
StereoSample getNextStereoSampler()
Definition GranularOscillator.cpp:703
const float FLUX_SCALING_NEGATIVE
Definition GranularOscillator.h:247
const float AMPLITUDE_FIDDLE_FACTOR
Definition GranularOscillator.h:238
float m_grainRate
Definition GranularOscillator.h:209
float m_startIndex
Definition GranularOscillator.h:210
GranularPlaybackMode m_granularOscillatorType
Definition GranularOscillator.h:202
void startNote(int midiNote, float velocity, int voiceIndex)
Starts the oscillator playing, usually called by key down event.
Definition GranularOscillator.cpp:84
int m_voiceIndex
Definition GranularOscillator.h:220
std::vector< const float * > m_parameters
Definition GranularOscillator.h:216
float m_rampInc
Definition GranularOscillator.h:255
@ DetuneMod
Definition GranularOscillator.h:128
@ Start
Definition GranularOscillator.h:117
@ Smoothing
Definition GranularOscillator.h:151
@ SizeDY
Definition GranularOscillator.h:158
@ Quench
Definition GranularOscillator.h:136
@ DetuneRand
Definition GranularOscillator.h:129
@ SizeST
Definition GranularOscillator.h:154
@ Detune
Definition GranularOscillator.h:127
@ Autoplay
Definition GranularOscillator.h:140
@ DivDY
Definition GranularOscillator.h:159
@ RateRand
Definition GranularOscillator.h:122
@ PhaseSync
Definition GranularOscillator.h:147
@ VehicleLevel
Definition GranularOscillator.h:133
@ Size
Definition GranularOscillator.h:119
@ SizeRand
Definition GranularOscillator.h:120
@ NUM_PARAMS
Definition GranularOscillator.h:164
@ GrainLim
Definition GranularOscillator.h:142
@ MovementSensitivity
Definition GranularOscillator.h:152
@ SprayDY
Definition GranularOscillator.h:160
@ Robin
Definition GranularOscillator.h:137
@ SALoop
Definition GranularOscillator.h:138
@ PanRand
Definition GranularOscillator.h:126
@ SprayST
Definition GranularOscillator.h:156
@ DivST
Definition GranularOscillator.h:155
@ MixDY
Definition GranularOscillator.h:161
@ LevelMod
Definition GranularOscillator.h:134
@ MuteUp
Definition GranularOscillator.h:149
@ Width
Definition GranularOscillator.h:123
@ MixST
Definition GranularOscillator.h:157
@ Shape
Definition GranularOscillator.h:141
@ Pan
Definition GranularOscillator.h:125
@ StartRand
Definition GranularOscillator.h:118
@ GranularLevel
Definition GranularOscillator.h:132
@ MuteDown
Definition GranularOscillator.h:150
@ Rate
Definition GranularOscillator.h:121
@ WidthRand
Definition GranularOscillator.h:124
@ Normalise
Definition GranularOscillator.h:148
@ SamplerLevel
Definition GranularOscillator.h:131
@ Level
Definition GranularOscillator.h:130
@ LevelRand
Definition GranularOscillator.h:135
bool m_autoplayEnabled
Definition GranularOscillator.h:241
void setGrainData(Grain *playingGrain)
Definition GranularOscillator.cpp:855
bool m_taggingType
Definition GranularOscillator.h:228
std::vector< Grain * > m_activeGrains
Definition GranularOscillator.h:214
void clearNote()
Signals the oscillator to stop immediately, and return any grains to pool.
Definition GranularOscillator.cpp:106
SlewLimiter m_channelCrossfade
Definition GranularOscillator.h:223
double m_samplerate
Definition GranularOscillator.h:207
float m_crossfadeValue
Definition GranularOscillator.h:226
const float RAMP_LENGTH_SECONDS
Definition GranularOscillator.h:250
const float FLUX_SCALING_POSITIVE
Definition GranularOscillator.h:246
bool m_isDrone
Definition GranularOscillator.h:240
size_t m_sampleIndex
Definition GranularOscillator.h:232
void setLoopEnableFlag()
Definition GranularOscillator.cpp:120
const float getVelocity()
Returns the value of the velocity as a float in [0...1].
Definition GranularOscillator.cpp:104
SampleEngine * m_sampleEngine
Definition GranularOscillator.h:213
void setTrackingEnable(bool isOn)
Definition GranularOscillator.cpp:118
float m_playbackHz
Definition GranularOscillator.h:204
bool m_trackingEnable
Definition GranularOscillator.h:229
StereoSample getNextStereoGranularSamplePhaseLocked()
Definition GranularOscillator.cpp:306
StereoSample getNextStereoGranular()
Definition GranularOscillator.cpp:503
bool m_stopped
Definition GranularOscillator.h:217
void generateGrainsGranular(StereoSample &sampleAccumulator)
Definition GranularOscillator.cpp:955
void setGranularOscillatorType(GranularPlaybackMode newType)
Definition GranularOscillator.cpp:18
int m_length
Definition GranularOscillator.h:215
void setFrequency(float frequency) override
Sets the target frequency which you want the oscillator to output.
Definition GranularOscillator.cpp:76
float m_detuneModVal
Definition GranularOscillator.h:237
float m_rampSamples
Definition GranularOscillator.h:253
void setGranularParameter(size_t paramIndex, const float *newValue)
Updates the granular engine and returns the next audio value.
Definition GranularOscillator.cpp:24
void generateNewFluxFactor()
Definition GranularOscillator.cpp:159
float m_velocity
Definition GranularOscillator.h:233
void generateGrainsSampler(StereoSample &sampleAccumulator)
Definition GranularOscillator.cpp:871
GranularOscillator()
Definition GranularOscillator.cpp:16
bool m_allowAudiosegmentIncrement
Definition GranularOscillator.h:234
float cube(float val)
Definition GranularOscillator.cpp:82
bool m_keyDown
Definition GranularOscillator.h:218
const float FLUX_PARAM_SKEW
Definition GranularOscillator.h:245
void setSampleRate(double sampleRate) override
Sets the sample rate which the granular oscillator uses to calculate its output.
Definition GranularOscillator.cpp:36
Grain * launchGrain(float paramSize, float paramStart, float paramSpray, Grain::Flavour flavour, float gain=-1.f)
Definition GranularOscillator.cpp:196
AudioDescriptor m_closestDescriptor
Definition GranularOscillator.h:212
float m_fluxFactor
Definition GranularOscillator.h:208
void killNANs(StereoSample &sample)
Definition GranularOscillator.cpp:125
double calculatePlayheadPosition(double startPosition, float halfSpray, float referenceGrainsize)
Definition GranularOscillator.cpp:174
const float PARAMETER_TRUE_THRESHOLD
Definition GranularOscillator.h:242
double getSampleRate() override
Returns the sample rate which which you set with the setSampleRate method.
Definition GranularOscillator.cpp:1030
void flushGranular()
Definition GranularOscillator.cpp:52
SlewLimiter m_directionGatingSmoother
Definition GranularOscillator.h:224
PhaseGenerator m_grainTimer
Definition GranularOscillator.h:203
void setSampleData(SampleEngine *sampleEngine)
Sets the oscillator's internal reference to the SampleEngine structure it is to use.
Definition GranularOscillator.cpp:45
juce::Random m_rand
Definition GranularOscillator.h:201
void stopNote()
Signals the oscillator to begin key up behaviour, usually called by key up event The oscillator may c...
Definition GranularOscillator.cpp:102
float getFrequency() override
Gets the target frequency which you set with the setFrequency method.
Definition GranularOscillator.cpp:1028
void setGranularParameters(std::vector< const float * > &newParams)
Sets multiple internal parameter to new values.
Definition GranularOscillator.cpp:30
int getNumActiveGrains() override
Returns the number of grains currently active for this instance of the oscillator.
Definition GranularOscillator.cpp:1032
StereoSample getNextStereoSample(size_t sampleIndex) override
Updates the granular engine and returns the next audio value.
Definition GranularOscillator.cpp:264
bool analysisResultsAreValid()
Definition KrotosAudioBufferDSP.h:422
AudioDescriptor & audioIndexToDescriptor(int audioIndex)
Definition KrotosAudioBufferDSP.cpp:895
AudioDescriptor & audioFrequencyToDescriptor(float frequency)
Definition KrotosAudioBufferDSP.cpp:919
StereoSample getSampleFromGrainPhase(double grainPhase)
Definition KrotosAudioBufferDSP.cpp:949
std::vector< AudioDescriptor > & getGrainDescriptionByTime()
Definition KrotosAudioBufferDSP.h:452
AudioDescriptor & audioPercentToDescriptor(float audioPercent)
Definition KrotosAudioBufferDSP.cpp:974
float getSampleRate(void)
Definition KrotosAudioBuffer.cpp:506
StereoSample getInterpolatedSample(double index)
Get a stereo sample from the audio buffer.
Definition KrotosAudioBuffer.cpp:318
float getNativeNoteFrequency(void)
Definition KrotosAudioBuffer.cpp:512
void setLoopSize2(int val)
Definition OscillatorUtils.cpp:76
void synchronise(double &grainPhase)
Definition OscillatorUtils.h:98
void setSampleRate(float val)
Definition OscillatorUtils.cpp:105
bool hasLooped2()
Definition OscillatorUtils.cpp:103
bool hasLooped()
Definition OscillatorUtils.cpp:101
void triggerNewGrain()
Definition OscillatorUtils.cpp:92
void setFrequency(float val)
Definition OscillatorUtils.cpp:78
bool hasWrapped()
Definition OscillatorUtils.cpp:99
void nextPhase()
Definition OscillatorUtils.cpp:24
void setLoopSize(int val)
Definition OscillatorUtils.cpp:74
Definition SampleEngine.h:84
int getAudioSegmentLengthInSamples(int segmentIndex)
Returns the length of an audio segment.
Definition SampleEngine.cpp:269
void setGrainLimit(int newLimit)
Definition SampleEngine.cpp:64
float getAudioSegmentNormalisationFactor(int segmentIndex)
Definition SampleEngine.h:259
int getAudioSelectionStartPosition(int segmentIndex)
Returns the start of the selected section of an audio segment.
Definition SampleEngine.cpp:243
void freeGrain(Grain *grain)
Definition SampleEngine.cpp:66
Grain * allocateGrain()
Definition SampleEngine.cpp:31
void prepareNextSegmentIndex(bool sequential=true)
Calculate the next segment to be played back.
Definition SampleEngine.cpp:319
int getAudioSelectionLengthInSamples(int segmentIndex)
Returns the length of the selected section of an audio segment.
Definition SampleEngine.cpp:249
const int getNextSegmentIndex() const
Returns the index of the next segment to be played.
Definition SampleEngine.cpp:413
GranularPlaybackMode getGranularOscillatorTypeIndicator()
Definition SampleEngine.h:135
void setIndicator1(float newVal)
Definition SampleEngine.cpp:85
void setIndicatorRPM(float newVal)
Definition SampleEngine.cpp:91
int getAudioSegmentSampleRate(int segmentIndex)
Definition SampleEngine.h:246
void setIndicator2(float newVal)
Definition SampleEngine.cpp:88
float processSample(float sample)
Definition OscillatorUtils.cpp:196
void setMaxSlewRate(float val)
Definition OscillatorUtils.cpp:238
Definition KrotosAudioBuffer.h:16
float left
Definition KrotosAudioBuffer.h:107
float right
Definition KrotosAudioBuffer.h:107
StereoSample width(float width)
Definition KrotosAudioBuffer.h:98
StereoSample panSine(float pan)
Definition KrotosAudioBuffer.h:90
Definition AirAbsorptionFilter.cpp:2
static const int MIDI_NOTE_MASK
Definition OscillatorUtils.h:21
GranularPlaybackMode
Definition SampleEngine.h:46
static const int MIDI_NOTE_FLAG_DRONE
Definition OscillatorUtils.h:22
static const int SEMITONES_PER_OCTAVE
Definition OscillatorUtils.h:36
A class to contains any attributes we want to store for the described audio.
Definition KrotosAudioBufferDSP.h:177
int grainIndexByTime
Definition KrotosAudioBufferDSP.h:182
int audioIndex
Definition KrotosAudioBufferDSP.h:180
float frequency
Definition KrotosAudioBufferDSP.h:178
float grainSize
Definition KrotosAudioBufferDSP.h:181