Skip to content

Commit

Permalink
fixed the stereo-only assumption for MIDI note audio smoothing (only …
Browse files Browse the repository at this point in the history
…affects a couple of unfinished instruments)
  • Loading branch information
sophiapoirier committed Aug 1, 2020
1 parent 5f7521b commit 904ede9
Show file tree
Hide file tree
Showing 3 changed files with 69 additions and 42 deletions.
66 changes: 43 additions & 23 deletions dfx-library/dfxmidi.cpp
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/*---------------------------------------------------------------
Destroy FX Library is a collection of foundation code
for creating audio processing plug-ins.
Copyright (C) 2001-2019 Sophia Poirier
Copyright (C) 2001-2020 Sophia Poirier
This file is part of the Destroy FX Library (version 1.0).
Expand Down Expand Up @@ -49,9 +49,12 @@ void DfxMidi::reset()
{
note.mVelocity = 0;
note.mEnvelope.setInactive();
note.mLastOutValue = 0.0f;
note.mSmoothSamples = 0;
note.clearTail();
}
for (auto& noteAudio : mNoteAudioTable)
{
std::fill(noteAudio.mLastOutValue.begin(), noteAudio.mLastOutValue.end(), 0.0f);
noteAudio.mSmoothSamples = 0;
std::for_each(noteAudio.mTails.begin(), noteAudio.mTails.end(), [](auto& tail){ tail.fill(0.0f); });
}
mSustainQueue.fill(false);

Expand All @@ -75,6 +78,16 @@ void DfxMidi::setSampleRate(double inSampleRate)
}
}

//------------------------------------------------------------------------
void DfxMidi::setChannelCount(unsigned long inChannelCount)
{
for (auto& noteAudio : mNoteAudioTable)
{
noteAudio.mLastOutValue.assign(inChannelCount, 0.0f);
noteAudio.mTails.assign(inChannelCount, {});
}
}

//------------------------------------------------------------------------
void DfxMidi::setEnvParameters(double inAttackDur, double inDecayDur, double inSustainLevel, double inReleaseDur)
{
Expand Down Expand Up @@ -300,7 +313,7 @@ void DfxMidi::heedEvents(long inEventNum, double inPitchBendRange, bool inLegato
// if the note is still sounding and in release, then smooth the end of that last note
if (!(mNoteTable[currentNote].mEnvelope.isResumedAttackMode()) && (mNoteTable[currentNote].mEnvelope.getState() == DfxEnvelope::State::Release))
{
mNoteTable[currentNote].mSmoothSamples = kStolenNoteFadeDur;
mNoteAudioTable[currentNote].mSmoothSamples = kStolenNoteFadeDur;
}
}
break;
Expand Down Expand Up @@ -417,36 +430,43 @@ float DfxMidi::processEnvelope(int inMidiNote)
//-------------------------------------------------------------------------
// this function writes the audio output for smoothing the tips of cut-off notes
// by sloping down from the last sample outputted by the note
void DfxMidi::processSmoothingOutputSample(float* outAudio, long inNumSamples, int inMidiNote)
void DfxMidi::processSmoothingOutputSample(float* const* outAudio, unsigned long inNumFrames, int inMidiNote)
{
for (long sampleIndex = 0; sampleIndex < inNumSamples; sampleIndex++)
auto& noteAudio = mNoteAudioTable[inMidiNote];
auto& smoothSamples = noteAudio.mSmoothSamples;
auto const entrySmoothSamples = smoothSamples;
for (size_t channelIndex = 0; channelIndex < noteAudio.mLastOutValue.size(); channelIndex++)
{
// add the latest sample to the output collection, scaled by the note envelope and user gain
float outputFadeScalar = static_cast<float>(mNoteTable[inMidiNote].mSmoothSamples * kStolenNoteFadeStep);
outputFadeScalar = outputFadeScalar * outputFadeScalar * outputFadeScalar;
outAudio[sampleIndex] += mNoteTable[inMidiNote].mLastOutValue * outputFadeScalar;
// decrement the smoothing counter
(mNoteTable[inMidiNote].mSmoothSamples)--;
// exit this function if we've done all of the smoothing necessary
if (mNoteTable[inMidiNote].mSmoothSamples <= 0)
smoothSamples = entrySmoothSamples;
auto const lastOutValue = noteAudio.mLastOutValue[channelIndex];
for (unsigned long sampleIndex = 0; (sampleIndex < inNumFrames) && (smoothSamples > 0); sampleIndex++)
{
return;
// add the latest sample to the output collection, scaled by the note envelope and user gain
auto outputFadeScalar = static_cast<float>(smoothSamples * kStolenNoteFadeStep);
outputFadeScalar = outputFadeScalar * outputFadeScalar * outputFadeScalar;
outAudio[channelIndex][sampleIndex] += lastOutValue * outputFadeScalar;
smoothSamples--;
}
}
}

//-------------------------------------------------------------------------
// this function writes the audio output for smoothing the tips of cut-off notes
// by fading out the samples stored in the tail buffers
void DfxMidi::processSmoothingOutputBuffer(float* outAudio, long inNumSamples, int inMidiNote, int inMidiChannel)
void DfxMidi::processSmoothingOutputBuffer(float* const* outAudio, unsigned long inNumFrames, int inMidiNote)
{
auto& smoothsamples = mNoteTable[inMidiNote].mSmoothSamples;
auto const& tail = (inMidiChannel == 1) ? mNoteTable[inMidiNote].mTail1 : mNoteTable[inMidiNote].mTail2;

for (long sampleIndex = 0; (sampleIndex < inNumSamples) && (smoothsamples > 0); sampleIndex++, smoothsamples--)
auto& noteAudio = mNoteAudioTable[inMidiNote];
auto& smoothSamples = noteAudio.mSmoothSamples;
auto const entrySmoothSamples = smoothSamples;
for (size_t channelIndex = 0; channelIndex < noteAudio.mTails.size(); channelIndex++)
{
outAudio[sampleIndex] += tail[kStolenNoteFadeDur - smoothsamples] *
static_cast<float>(smoothsamples) * kStolenNoteFadeStep;
smoothSamples = entrySmoothSamples;
auto const& tail = noteAudio.mTails[channelIndex];
for (unsigned long sampleIndex = 0; (sampleIndex < inNumFrames) && (smoothSamples > 0); sampleIndex++, smoothSamples--)
{
outAudio[channelIndex][sampleIndex] += tail[kStolenNoteFadeDur - smoothSamples] *
static_cast<float>(smoothSamples) * kStolenNoteFadeStep;
}
}
}

Expand Down
41 changes: 22 additions & 19 deletions dfx-library/dfxmidi.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ Sophia's Destroy FX MIDI stuff


#include <array>
#include <vector>

#include "dfxenvelope.h"

Expand All @@ -43,7 +44,7 @@ class DfxMidi
static constexpr int kPitchBendMidpointValue = 0x2000;
static constexpr int kPitchBendMaxValue = 0x3FFF;
static constexpr double kPitchBendSemitonesMax = 36.0;
static constexpr long kStolenNoteFadeDur = 48;
static constexpr unsigned long kStolenNoteFadeDur = 48;

// these are the MIDI event status types
enum
Expand Down Expand Up @@ -147,30 +148,31 @@ class DfxMidi
// this holds information for each MIDI note
struct MusicNote
{
MusicNote()
{
clearTail();
}

void clearTail() // zero out a note's tail buffers
{
mTail1.fill(0.0f);
mTail2.fill(0.0f);
}

int mVelocity = 0; // note velocity (7-bit MIDI value)
float mNoteAmp = 0.0f; // the gain for the note, scaled with velocity, curve, and influence
DfxEnvelope mEnvelope;
float mLastOutValue = 0.0f; // capture the most recent output value for smoothing, if necessary XXX mono assumption
long mSmoothSamples = 0; // counter for quickly fading cut-off notes, for smoothity
std::array<float, kStolenNoteFadeDur> mTail1 {}; // a little buffer of output samples for smoothing a cut-off note (left channel)
std::array<float, kStolenNoteFadeDur> mTail2 {}; // (right channel) XXX wow this stereo assumption is such a bad idea
};

struct MusicNoteAudio
{
MusicNoteAudio() = default;
~MusicNoteAudio() = default;
// deleting copy assignment and construction to prevent accidental dynamic allocation in realtime context
MusicNoteAudio(MusicNoteAudio const&) = delete;
MusicNoteAudio& operator=(MusicNoteAudio const&) = delete;
MusicNoteAudio(MusicNoteAudio&&) = default;
MusicNoteAudio& operator=(MusicNoteAudio&&) = default;

std::vector<float> mLastOutValue; // capture the most recent output value of each audio channel for smoothing, if necessary
unsigned long mSmoothSamples = 0; // counter for quickly fading cut-off notes, for smoothity
std::vector<std::array<float, kStolenNoteFadeDur>> mTails; // per-channel little buffer of output samples for smoothing a cut-off note
};

DfxMidi();

void reset(); // resets the variables
void setSampleRate(double inSampleRate);
void setChannelCount(unsigned long inChannelCount);
void setEnvParameters(double inAttackDur, double inDecayDur, double inSustainLevel, double inReleaseDur);
void setEnvCurveType(DfxEnvelope::CurveType inCurveType);
void setResumedAttackMode(bool inNewMode);
Expand Down Expand Up @@ -252,17 +254,17 @@ class DfxMidi

// this writes the audio output for smoothing the tips of cut-off notes
// by sloping down from the last sample outputted by the note
void processSmoothingOutputSample(float* outAudio, long inNumSamples, int inMidiNote);
void processSmoothingOutputSample(float* const* outAudio, unsigned long inNumFrames, int inMidiNote);

// this writes the audio output for smoothing the tips of cut-off notes
// by fading out the samples stored in the tail buffers
void processSmoothingOutputBuffer(float* outAudio, long inNumSamples, int inMidiNote, int inMidiChannel);
void processSmoothingOutputBuffer(float* const* outAudio, unsigned long inNumFrames, int inMidiNote);


private:
static constexpr size_t kEventQueueSize = 12000;
static constexpr float kStolenNoteFadeStep = 1.0f / static_cast<float>(kStolenNoteFadeDur);
static constexpr long kLegatoFadeDur = 39;
static constexpr unsigned long kLegatoFadeDur = 39;
static constexpr float kLegatoFadeStep = 1.0f / static_cast<float>(kLegatoFadeDur);

void fillFrequencyTable();
Expand All @@ -272,6 +274,7 @@ class DfxMidi
void turnOffNote(int inMidiNote, bool inLegato);

std::array<MusicNote, kNumNotes> mNoteTable {}; // a table with important data about each note
std::array<MusicNoteAudio, kNumNotes> mNoteAudioTable {};
std::array<double, kNumNotes> mNoteFrequencyTable {}; // a table of the frequency corresponding to each MIDI note

std::array<int, kNumNotes> mNoteQueue {}; // a chronologically ordered queue of all active notes
Expand Down
4 changes: 4 additions & 0 deletions dfx-library/dfxplugin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1115,6 +1115,10 @@ void DfxPlugin::updatesamplerate()
// called when the number of audio channels has changed
void DfxPlugin::updatenumchannels()
{
#if TARGET_PLUGIN_USES_MIDI
mMidiState.setChannelCount(getnumoutputs());
#endif

#ifdef TARGET_API_AUDIOUNIT
// the number of inputs or outputs may have changed
mNumInputs = getnuminputs();
Expand Down

0 comments on commit 904ede9

Please sign in to comment.