11namespace tracktion {
inline namespace engine
15static constexpr int minimumSamplesToPlayWhenStopping = 8;
16static constexpr int maximumSimultaneousNotes = 32;
26 int sampleDelayFromBufferStart,
33 offset (-sampleDelayFromBufferStart),
35 openEnded (openEnded_)
40 const float volumeSliderPos = decibelsToVolumeFaderPosition (gainDb - (20.0f * (1.0f - velocity)));
41 getGainsFromVolumeFaderPositionAndPan (volumeSliderPos, pan, getDefaultPanLaw(), gains[0], gains[1]);
45 playbackRatio *= file.getSampleRate() / sampleRate;
46 samplesLeftToPlay = playbackRatio > 0 ? (1 + (
int) (lengthInSamples / playbackRatio)) : 0;
55 const int num =
std::min (-offset, numSamples);
61 auto numSamps =
std::min (numSamples, samplesLeftToPlay);
69 numUsed = resampler[i]
70 .processAdding (playbackRatio,
78 samplesLeftToPlay -= numSamps;
83 if (numSamples > numSamps && startFade > 0.0f)
85 startSamp += numSamps;
86 numSamps = numSamples - numSamps;
96 endFade =
std::max (0.0f, startFade - numSamps * 0.01f);
99 const int numSampsNeeded = 2 +
juce::roundToInt ((numSamps + 2) * playbackRatio);
105 scratch.
buffer.
copyFrom (i, 0, audioData, i, offset, numSampsNeeded);
112 if (numSampsNeeded > 2)
113 AudioFadeCurve::applyCrossfadeSection (scratch.
buffer, 0, numSampsNeeded - 2,
114 AudioFadeCurve::linear, startFade, endFade);
121 numUsed = resampler[i].processAdding (playbackRatio,
128 if (startFade <= 0.0f)
135 int offset, samplesLeftToPlay = 0;
137 double playbackRatio = 1.0;
139 float lastVals[4] = { 0, 0, 0, 0 };
140 float startFade = 1.0f;
141 bool openEnded, isFinished =
false;
153SamplerPlugin::~SamplerPlugin()
155 notifyListenersOfDeletion();
158const char* SamplerPlugin::xmlTypeName =
"sampler";
160void SamplerPlugin::valueTreeChanged()
163 Plugin::valueTreeChanged();
166void SamplerPlugin::handleAsyncUpdate()
172 for (
int i = 0; i < numSounds; ++i)
174 auto v = getSound (i);
176 if (v.hasType (IDs::SOUND))
178 auto s =
new SamplerSound (*
this,
179 v[IDs::source].toString(),
185 s->keyNote =
juce::jlimit (0, 127,
static_cast<int> (v[IDs::keyNote]));
186 s->minNote =
juce::jlimit (0, 127,
static_cast<int> (v[IDs::minNote]));
187 s->maxNote =
juce::jlimit (0, 127,
static_cast<int> (v[IDs::maxNote]));
188 s->pan =
juce::jlimit (-1.0f, 1.0f,
static_cast<float> (v[IDs::pan]));
189 s->openEnded = v[IDs::openEnded];
195 for (
auto newSound : newSounds)
197 for (
auto s : soundList)
199 if (s->source == newSound->source
200 && s->startTime == newSound->startTime
201 && s->length == newSound->length)
203 newSound->audioFile = s->audioFile;
204 newSound->fileStartSample = s->fileStartSample;
205 newSound->fileLengthSamples = s->fileLengthSamples;
206 newSound->audioData = s->audioData;
214 soundList.swapWith (newSounds);
239 if (highlightedNotes != keysDown)
241 for (
int i = playingNotes.size(); --i >= 0;)
242 if ((! keysDown [playingNotes.getUnchecked(i)->note])
243 && highlightedNotes [playingNotes.getUnchecked(i)->note]
244 && ! playingNotes.getUnchecked(i)->openEnded)
245 playingNotes.getUnchecked(i)->samplesLeftToPlay = minimumSamplesToPlayWhenStopping;
247 for (
int note = 128; --note >= 0;)
249 if (keysDown [note] && ! highlightedNotes [note])
251 for (
auto ss : soundList)
253 if (ss->minNote <= note
254 && ss->maxNote >= note
255 && ss->audioData.getNumSamples() > 0
256 && (! ss->audioFile.isNull())
257 && playingNotes.size() < maximumSimultaneousNotes)
259 playingNotes.add (
new SampledNote (note,
266 ss->fileLengthSamples,
275 highlightedNotes = keysDown;
279void SamplerPlugin::allNotesOff()
282 playingNotes.clear();
283 highlightedNotes.
clear();
290 SCOPED_REALTIME_CHECK
300 playingNotes.clear();
301 highlightedNotes.
clear();
308 const int note = m.getNoteNumber();
309 const int noteTimeSample =
juce::roundToInt (m.getTimeStamp() * sampleRate);
311 for (
auto playingNote : playingNotes)
313 if (playingNote->note == note && ! playingNote->openEnded)
315 playingNote->samplesLeftToPlay =
std::min (playingNote->samplesLeftToPlay,
316 std::max (minimumSamplesToPlayWhenStopping,
322 for (
auto ss : soundList)
324 if (ss->minNote <= note
325 && ss->maxNote >= note
326 && ss->audioData.getNumSamples() > 0
327 && playingNotes.size() < maximumSimultaneousNotes)
329 highlightedNotes.
setBit (note);
333 m.getVelocity() / 127.0f,
338 ss->fileLengthSamples,
345 else if (m.isNoteOff())
347 const int note = m.getNoteNumber();
348 const int noteTimeSample =
juce::roundToInt (m.getTimeStamp() * sampleRate);
350 for (
auto playingNote : playingNotes)
352 if (playingNote->note == note && ! playingNote->openEnded)
354 playingNote->samplesLeftToPlay =
std::min (playingNote->samplesLeftToPlay,
355 std::max (minimumSamplesToPlayWhenStopping,
362 else if (m.isAllNotesOff() || m.isAllSoundOff())
364 playingNotes.clear();
365 highlightedNotes.
clear();
370 for (
int i = playingNotes.size(); --i >= 0;)
372 auto sn = playingNotes.getUnchecked (i);
377 playingNotes.remove (i);
383int SamplerPlugin::getNumSounds()
const
386 [] (
int total,
auto v) { return total + (v.hasType (IDs::SOUND) ? 1 : 0); });
389juce::String SamplerPlugin::getSoundName (
int index)
const
391 return getSound (index)[IDs::name];
394void SamplerPlugin::setSoundName (
int index,
const juce::String& n)
396 getSound (index).
setProperty (IDs::name, n, getUndoManager());
406 for (
auto ss : soundList)
408 if (ss->minNote <= note && ss->maxNote >= note)
411 s <<
" + " << ss->name;
422AudioFile SamplerPlugin::getSoundFile (
int index)
const
426 if (
auto s = soundList[index])
432juce::String SamplerPlugin::getSoundMedia (
int index)
const
436 if (
auto s = soundList[index])
442int SamplerPlugin::getKeyNote (
int index)
const {
return getSound (index)[IDs::keyNote]; }
443int SamplerPlugin::getMinKey (
int index)
const {
return getSound (index)[IDs::minNote]; }
444int SamplerPlugin::getMaxKey (
int index)
const {
return getSound (index)[IDs::maxNote]; }
445float SamplerPlugin::getSoundGainDb (
int index)
const {
return getSound (index)[IDs::gainDb]; }
446float SamplerPlugin::getSoundPan (
int index)
const {
return getSound (index)[IDs::pan]; }
447double SamplerPlugin::getSoundStartTime (
int index)
const {
return getSound (index)[IDs::startTime]; }
448bool SamplerPlugin::isSoundOpenEnded (
int index)
const {
return getSound (index)[IDs::openEnded]; }
450double SamplerPlugin::getSoundLength (
int index)
const
452 const double l = getSound (index)[IDs::length];
458 if (
auto s = soundList[index])
466 double startTime,
double length,
float gainDb)
468 const int maxNumSamples = 64;
470 if (getNumSounds() >= maxNumSamples)
471 return TRANS(
"Can't load any more samples");
473 auto v = createValueTree (IDs::SOUND,
476 IDs::startTime, startTime,
479 IDs::minNote, 72 - 24,
480 IDs::maxNote, 72 + 24,
482 IDs::pan, (
double) 0);
484 state.
addChild (v, -1, getUndoManager());
488void SamplerPlugin::removeSound (
int index)
493 playingNotes.clear();
494 highlightedNotes.
clear();
497void SamplerPlugin::setSoundParams (
int index,
int keyNote,
int minNote,
int maxNote)
499 auto um = getUndoManager();
501 auto v = getSound (index);
502 v.setProperty (IDs::keyNote,
juce::jlimit (0, 127, keyNote), um);
507void SamplerPlugin::setSoundGains (
int index,
float gainDb,
float pan)
509 auto um = getUndoManager();
511 auto v = getSound (index);
512 v.setProperty (IDs::gainDb,
juce::jlimit (-48.0f, 48.0f, gainDb), um);
513 v.setProperty (IDs::pan,
juce::jlimit (-1.0f, 1.0f, pan), um);
516void SamplerPlugin::setSoundExcerpt (
int index,
double start,
double length)
518 auto um = getUndoManager();
520 auto v = getSound (index);
521 v.setProperty (IDs::startTime, start, um);
522 v.setProperty (IDs::length, length, um);
525void SamplerPlugin::setSoundOpenEnded (
int index,
bool b)
527 auto um = getUndoManager();
529 auto v = getSound (index);
530 v.setProperty (IDs::openEnded, b, um);
533void SamplerPlugin::setSoundMedia (
int index,
const juce::String& source)
535 auto v = getSound (index);
536 v.setProperty (IDs::source, source, getUndoManager());
545 if (v.hasType (IDs::SOUND))
546 if (index++ == soundIndex)
558 for (
int i = 0; i < getNumSounds(); ++i)
560 auto v = getSound (i);
562 Exportable::ReferencedItem
ref;
563 ref.itemID = ProjectItemID::fromProperty (v, IDs::source);
564 ref.firstTimeUsed = v[IDs::startTime];
565 ref.lengthUsed = v[IDs::length];
572void SamplerPlugin::reassignReferencedItem (
const ReferencedItem& item, ProjectItemID newID,
double newStartTime)
574 auto index = getReferencedItems().indexOf (item);
578 auto um = getUndoManager();
580 auto v = getSound (index);
581 v.setProperty (IDs::source, newID.toString(), um);
582 v.setProperty (IDs::startTime,
static_cast<double> (v[IDs::startTime]) - newStartTime, um);
594 for (
auto s : soundList)
598void SamplerPlugin::restorePluginStateFromValueTree (
const juce::ValueTree& v)
600 copyValueTree (state, v, getUndoManager());
607 const double startTime_,
608 const double length_,
613 gainDb (
juce::jlimit (-48.0f, 48.0f, gainDb_)),
614 startTime (startTime_),
616 audioFile (owner.edit.engine,
SourceFileReference::findFileFromString (owner.edit, source))
618 setExcerpt (startTime_, length_);
620 keyNote = audioFile.getInfo().loopInfo.getRootNote();
625 maxNote = keyNote + 24;
626 minNote = keyNote - 24;
629void SamplerPlugin::SamplerSound::setExcerpt (
double startTime_,
double length_)
633 if (! audioFile.isValid())
635 audioFile = AudioFile (owner.edit.
engine, SourceFileReference::findFileFromString (owner.edit, source));
638 if (! audioFile.isValid() && ProjectItemID (source).isValid())
639 DBG (
"Failed to find media: " << source);
643 if (audioFile.isValid())
645 const double minLength = 32.0 / audioFile.getSampleRate();
647 startTime =
juce::jlimit (0.0, audioFile.getLength() - minLength, startTime_);
650 length =
juce::jlimit (minLength, audioFile.getLength() - startTime, length_);
652 length = audioFile.getLength();
659 audioData.
setSize (audioFile.getNumChannels(), fileLengthSamples + 32);
665 int total = fileLengthSamples;
670 const int numThisTime =
std::min (8192, total);
671 reader->setReadPosition (fileStartSample + offset);
673 if (! reader->readSamples (numThisTime, audioData, audioDataChannelSet, offset, channelsToUse, 2000))
679 offset += numThisTime;
680 total -= numThisTime;
694 if (std::abs (*d) > 0.01f)
699 AudioFadeCurve::applyCrossfadeSection (audioData, 0, fadeLen, AudioFadeCurve::concave, 0.0f, 1.0f);
703 audioFile = AudioFile (owner.edit.
engine);
707void SamplerPlugin::SamplerSound::refreshFile()
709 audioFile = AudioFile (owner.edit.engine);
710 setExcerpt (startTime, length);
void add(const ElementType &newElement)
void triggerAsyncUpdate()
void setSize(int newNumChannels, int newNumSamples, bool keepExistingContent=false, bool clearExtraSpace=false, bool avoidReallocating=false)
Type * getWritePointer(int channelNumber) noexcept
int getNumChannels() const noexcept
int getNumSamples() const noexcept
void copyFrom(int destChannel, int destStartSample, const AudioBuffer &source, int sourceChannel, int sourceStartSample, int numSamples) noexcept
const Type * getReadPointer(int channelNumber) const noexcept
static AudioChannelSet JUCE_CALLTYPE stereo()
static AudioChannelSet JUCE_CALLTYPE canonicalChannelSet(int numChannels)
BigInteger & clear() noexcept
BigInteger & clearBit(int bitNumber) noexcept
BigInteger & setBit(int bitNumber)
static double getMidiNoteInHertz(int noteNumber, double frequencyOfA=440.0) noexcept
void clear(bool deleteObjects=true)
ObjectClass * add(ObjectClass *newObject)
bool isNotEmpty() const noexcept
Iterator begin() const noexcept
void removeChild(const ValueTree &child, UndoManager *undoManager)
int getNumChildren() const noexcept
ValueTree & setProperty(const Identifier &name, const var &newValue, UndoManager *undoManager)
void addChild(const ValueTree &child, int index, UndoManager *undoManager)
Iterator end() const noexcept
Reader::Ptr createReader(const AudioFile &)
Creates a Reader to read an AudioFile.
An audio scratch buffer that has pooled storage.
juce::AudioBuffer< float > & buffer
The buffer to use.
Engine & engine
A reference to the Engine.
AudioFileManager & getAudioFileManager() const
Returns the AudioFileManager instance.
void changed() override
method from Selectable, that's been overridden here to also tell the edit that it's changed.
void applyToBuffer(const PluginRenderContext &) override
Process the next block of data.
bool hasNameForMidiNoteNumber(int note, int midiChannel, juce::String &name) override
If it's a synth that names its notes, this can return the name it uses for this note 0-127.
void sourceMediaChanged() override
Called when ProjectItem sources are re-assigned so you can reload from the new source.
void deinitialise() override
Called after play stops to release resources.
This class wraps a string that is generally held in a 'source' property, and which is a reference to ...
#define TRANS(stringLiteral)
Type jlimit(Type lowerLimit, Type upperLimit, Type valueToConstrain) noexcept
int roundToInt(const FloatType value) noexcept
Interpolators::Lagrange LagrangeInterpolator
Passed into Plugins when they are being initialised, to give them useful contextual information that ...
The context passed to plugin render methods to provide it with buffers to fill.
int bufferNumSamples
The number of samples to write into the audio buffer.
MidiMessageArray * bufferForMidiMessages
A buffer of MIDI events to process.
juce::AudioBuffer< float > * destBuffer
The target audio buffer which needs to be filled.
int bufferStartSample
The index of the start point in the audio buffer from which data must be written.
#define CRASH_TRACER
This macro adds the current location to a stack which gets logged if a crash happens.