Skip to content

Commit

Permalink
add sample-accurate glide
Browse files Browse the repository at this point in the history
  • Loading branch information
madronalabs committed Nov 13, 2023
1 parent dd8d14f commit 29dce13
Show file tree
Hide file tree
Showing 3 changed files with 111 additions and 46 deletions.
84 changes: 74 additions & 10 deletions source/DSP/MLDSPGens.h
Original file line number Diff line number Diff line change
Expand Up @@ -392,34 +392,34 @@ class LinearGlide
float mDyPerVector{1.f / 32};
int mVectorsPerGlide{32};
int mVectorsRemaining{0};

public:
public:
void setGlideTimeInSamples(float t)
{
mVectorsPerGlide = static_cast<int>(t / kFloatsPerDSPVector);
if (mVectorsPerGlide < 1) mVectorsPerGlide = 1;
mDyPerVector = 1.0f / (mVectorsPerGlide + 0.f);
}

// set the current value to the given value immediately, without gliding
void setValue(float f)
{
mTargetValue = f;
mVectorsRemaining = 0;
}

DSPVector operator()(float f)
{
// set target value if different from current value.
// const float currentValue = mCurrVec[kFloatsPerDSPVector - 1];
if (f != mTargetValue)
{
mTargetValue = f;

// start counter
mVectorsRemaining = mVectorsPerGlide;
}

// process glide
if (mVectorsRemaining == 0)
{
Expand All @@ -433,13 +433,13 @@ class LinearGlide
// start glide: get change in output value per vector
float currentValue = mCurrVec[kFloatsPerDSPVector - 1];
float dydv = (mTargetValue - currentValue) * mDyPerVector;

// get constant step vector
mStepVec = DSPVector(dydv);

// setup current vector with first interpolation ramp.
mCurrVec = DSPVector(currentValue) + kUnityRampVec * DSPVector(dydv);

mVectorsRemaining--;
}
else
Expand All @@ -451,10 +451,74 @@ class LinearGlide
mCurrVec += mStepVec;
mVectorsRemaining--;
}

return mCurrVec;
}
};

class SampleAccurateLinearGlide
{
float mCurrValue{0.f};
float mStepValue{0.f};
float mTargetValue{0.f};
int mSamplesPerGlide{32};
float mDyPerSample{1.f/32};
int mSamplesRemaining{0};

public:
void setGlideTimeInSamples(float t)
{
mSamplesPerGlide = static_cast<int>(t);
if (mSamplesPerGlide < 1) mSamplesPerGlide = 1;
mDyPerSample = 1.0f / mSamplesPerGlide;
}

// set the current value to the given value immediately, without gliding
void setValue(float f)
{
mTargetValue = f;
mSamplesRemaining = 0;
}

float nextSample(float f)
{
// set target value if different from current value.
// const float currentValue = mCurrVec[kFloatsPerDSPVector - 1];
if (f != mTargetValue)
{
mTargetValue = f;

// start counter
mSamplesRemaining = mSamplesPerGlide;
}

// process glide
if (mSamplesRemaining == 0)
{
// end glide: write target value to output vector
mCurrValue = (mTargetValue);
mStepValue = (0.f);
mSamplesRemaining--;
}
else if (mSamplesRemaining == mSamplesPerGlide)
{
// start glide: get change in output value per sample
mStepValue = (mTargetValue - mCurrValue) * mDyPerSample;

mSamplesRemaining--;
}
else
{
// continue glide
// Note that repeated adding will create some error in target value.
// Because we return the target value explicity when we are done, this
// won't be a problem in reasonably short glides.
mCurrValue += mStepValue;
mSamplesRemaining--;
}

return mCurrValue;
}
};

} // namespace ml
59 changes: 30 additions & 29 deletions source/app/MLEventsToSignals.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,11 @@ namespace ml {
// EventsToSignals::Voice
//

void EventsToSignals::Voice::setSampleRate(float sr)

void EventsToSignals::Voice::setParams(float pitchGlideInSeconds, float sr)
{
_sampleRate = sr;

// separate glide time for note pitch
pitchGlide.setGlideTimeInSamples(sr*kPitchGlideTimeSeconds);
pitchGlide.setGlideTimeInSamples(sr*pitchGlideInSeconds);

pitchBendGlide.setGlideTimeInSamples(sr*kGlideTimeSeconds);
modGlide.setGlideTimeInSamples(sr*kGlideTimeSeconds);
Expand Down Expand Up @@ -62,7 +61,7 @@ float getAgeInSeconds(uint32_t age, float sr)
return fSeconds;
}

void EventsToSignals::Voice::writeNoteEvent(const Event& e, const Scale& scale)
void EventsToSignals::Voice::writeNoteEvent(const Event& e, const Scale& scale, float sampleRate)
{
auto time = e.time;

Expand All @@ -82,11 +81,9 @@ void EventsToSignals::Voice::writeNoteEvent(const Event& e, const Scale& scale)
for(size_t t = nextFrameToProcess; t < destTime; ++t)
{
outputs.row(kGate)[t] = currentVelocity;

// TODO sample accurate pitch glide
outputs.row(kPitch)[t] = currentPitch;
outputs.row(kPitch)[t] = pitchGlide.nextSample(currentPitch);
ageInSamples += ageStep;
outputs.row(kElapsedTime)[t] = getAgeInSeconds(ageInSamples, _sampleRate);
outputs.row(kElapsedTime)[t] = getAgeInSeconds(ageInSamples, sampleRate);
}

// set new values
Expand All @@ -113,16 +110,14 @@ void EventsToSignals::Voice::writeNoteEvent(const Event& e, const Scale& scale)
for(size_t t = nextFrameToProcess; t < destTime - 1; ++t)
{
outputs.row(kGate)[t] = currentVelocity;

// TODO sample accurate glide
outputs.row(kPitch)[t] = currentPitch;
outputs.row(kPitch)[t] = pitchGlide.nextSample(currentPitch);
ageInSamples += ageStep;
outputs.row(kElapsedTime)[t] = getAgeInSeconds(ageInSamples, _sampleRate);
outputs.row(kElapsedTime)[t] = getAgeInSeconds(ageInSamples, sampleRate);
}

// write retrigger frame
outputs.row(kGate)[destTime - 1] = 0;
outputs.row(kPitch)[destTime - 1] = currentPitch;
outputs.row(kPitch)[destTime - 1] = pitchGlide.nextSample(currentPitch);

// set new values
currentPitch = scale.noteToLogPitch(e.value1);
Expand All @@ -149,11 +144,9 @@ void EventsToSignals::Voice::writeNoteEvent(const Event& e, const Scale& scale)
for(size_t t = nextFrameToProcess; t < destTime; ++t)
{
outputs.row(kGate)[t] = currentVelocity;

// TODO sample accurate glide
outputs.row(kPitch)[t] = currentPitch;
outputs.row(kPitch)[t] = pitchGlide.nextSample(currentPitch);
ageInSamples += ageStep;
outputs.row(kElapsedTime)[t] = getAgeInSeconds(ageInSamples, _sampleRate);
outputs.row(kElapsedTime)[t] = getAgeInSeconds(ageInSamples, sampleRate);
}

// set new values
Expand All @@ -167,20 +160,19 @@ void EventsToSignals::Voice::writeNoteEvent(const Event& e, const Scale& scale)
}
}

void EventsToSignals::Voice::endProcess(float pitchBend)
void EventsToSignals::Voice::endProcess(float pitchBend, float sampleRate)
{
for(size_t t = nextFrameToProcess; t < kFloatsPerDSPVector; ++t)
{
// write velocity to end of buffer.
outputs.row(kGate)[t] = currentVelocity;

// write pitch to end of buffer.
// TODO sample accurate glide
outputs.row(kPitch)[t] = currentPitch;
outputs.row(kPitch)[t] = pitchGlide.nextSample(currentPitch);

// keep increasing age
ageInSamples += ageStep;
outputs.row(kElapsedTime)[t] = getAgeInSeconds(ageInSamples, _sampleRate);
outputs.row(kElapsedTime)[t] = getAgeInSeconds(ageInSamples, sampleRate);
}

// process glides, accurate to the DSP vector
Expand All @@ -207,7 +199,7 @@ EventsToSignals::EventsToSignals(int sr) : _eventQueue(kMaxEventsPerVector)

for(int i=0; i<kMaxVoices; ++i)
{
voices[i].setSampleRate(sr);
voices[i].setParams(_pitchGlideTimeInSeconds, sr);
voices[i].reset();
voices[i].outputs.row(kVoice) = DSPVector(i);
}
Expand Down Expand Up @@ -258,7 +250,7 @@ void EventsToSignals::process()
}
for(auto& v : voices)
{
v.endProcess(kPitchBendSemitones);
v.endProcess(kPitchBendSemitones, _sampleRate);
}
}

Expand Down Expand Up @@ -299,7 +291,7 @@ void EventsToSignals::processNoteOnEvent(const Event& e)

if(v >= 0)
{
voices[v].writeNoteEvent(e, _scale);
voices[v].writeNoteEvent(e, _scale, _sampleRate);
}
else
{
Expand All @@ -310,7 +302,7 @@ void EventsToSignals::processNoteOnEvent(const Event& e)
// are cut off. add more graceful stealing
Event f = e;
f.type = kNoteRetrig;
voices[v].writeNoteEvent(f, _scale);
voices[v].writeNoteEvent(f, _scale, _sampleRate);
}
}

Expand All @@ -326,7 +318,7 @@ void EventsToSignals::processNoteOffEvent(const Event& e)
{
Event eventToSend = e;
eventToSend.type = newEventType;
voice.writeNoteEvent(eventToSend, _scale);
voice.writeNoteEvent(eventToSend, _scale, _sampleRate);
}
}
}
Expand Down Expand Up @@ -374,7 +366,7 @@ void EventsToSignals::processControllerEvent(const Event& event)
{
Event eventToSend = event;
eventToSend.type = kNoteOff;
voice.writeNoteEvent(eventToSend, _scale);
voice.writeNoteEvent(eventToSend, _scale, _sampleRate);
}
}
}
Expand Down Expand Up @@ -413,7 +405,7 @@ void EventsToSignals::processSustainEvent(const Event& event)
{
Event newEvent;
newEvent.type = kNoteOff;
v.writeNoteEvent(newEvent, _scale);
v.writeNoteEvent(newEvent, _scale, _sampleRate);
}
}
}
Expand All @@ -424,6 +416,15 @@ void EventsToSignals::setPitchBendInSemitones(float f)
kPitchBendSemitones = f;
}

void EventsToSignals::setGlideTimeInSeconds(float f)
{
_pitchGlideTimeInSeconds = f;
for(int i=0; i<kMaxVoices; ++i)
{
voices[i].setParams(_pitchGlideTimeInSeconds, _sampleRate);
}
}

#pragma mark -

// return index of free voice or -1 for none.
Expand Down
14 changes: 7 additions & 7 deletions source/app/MLEventsToSignals.h
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ class EventsToSignals final
static constexpr int kMaxVoices{16};
static constexpr int kMaxEventsPerVector{128};

static constexpr float kPitchGlideTimeSeconds{0.f};
// glide time for all params except pitch
static constexpr float kGlideTimeSeconds{0.02f};

// Event: something that happens.
Expand Down Expand Up @@ -92,19 +92,19 @@ class EventsToSignals final
Voice() = default;
~Voice() = default;

void setSampleRate(float sr);
void setParams(float pitchGlideInSeconds, float sr);

void reset();

// send to start processing a new buffer.
void beginProcess();

// send a note on, off update or sustain event to the voice.
void writeNoteEvent(const Event& e, const Scale& Scale);
void writeNoteEvent(const Event& e, const Scale& Scale, float sr);

// write all current info to the end of the current buffer.
// add pitchBend to pitch.
void endProcess(float pitchBend);
void endProcess(float pitchBend, float sr);

int state{kOff};
size_t nextFrameToProcess{0};
Expand All @@ -122,7 +122,7 @@ class EventsToSignals final
uint32_t ageInSamples{0};
uint32_t ageStep{0};

LinearGlide pitchGlide;
SampleAccurateLinearGlide pitchGlide;
LinearGlide pitchBendGlide;
LinearGlide modGlide;
LinearGlide xGlide;
Expand All @@ -131,8 +131,6 @@ class EventsToSignals final

// output signals (velocity, pitch, voice... )
DSPVectorArray< kNumVoiceOutputRows > outputs;

float _sampleRate;
};

#pragma mark -
Expand All @@ -155,6 +153,7 @@ class EventsToSignals final
void process();

void setPitchBendInSemitones(float f);
void setGlideTimeInSeconds(float f);

// voices, containing signals for clients to read directly.
std::vector< Voice > voices;
Expand Down Expand Up @@ -187,6 +186,7 @@ class EventsToSignals final
bool _sustainPedalActive{false};
float _sampleRate;
float kPitchBendSemitones{7.f};
float _pitchGlideTimeInSeconds{0.f};
};


Expand Down

0 comments on commit 29dce13

Please sign in to comment.