Skip to content

Instantly share code, notes, and snippets.

@Xenakios
Created November 12, 2019 20:25
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Xenakios/7f4372758e17b98e3a0b239522d9f64a to your computer and use it in GitHub Desktop.
Save Xenakios/7f4372758e17b98e3a0b239522d9f64a to your computer and use it in GitHub Desktop.
/*
== SOUL example code ==
"Minimum-Viable-Piano"
You probably won't want to use this patch to replace your Bosendorfer,
but it shows how to play some re-pitched external sample data, and
it'll do a pretty good job of recreating some early 1990s rave piano.
In memory of Philip Meehan, who recorded these piano samples for use in
the early Tracktion demo songs, back in around 2001.
*/
graph Reverb
{
input stream float audioIn;
output stream float<2> audioOut;
input event
{
float roomSize [[ name: "Room Size", max: 100, init: 80, text: "tiny|small|medium|large|hall" ]];
float damping [[ min: 0, max: 100, init: 50, name: "Damping Factor", unit: "%", step: 1 ]];
float wetLevel [[ min: 0, max: 100, init: 33, name: "Wet Level", unit: "%", step: 1 ]];
float dryLevel [[ min: 0, max: 100, init: 40, name: "Dry Level", unit: "%", step: 1 ]];
float width [[ min: 0, max: 100, init: 100, name: "Width", unit: "%", step: 1 ]];
}
let
{
dryGainParameterRamp = ParameterRamp (20.0f);
wetGain1ParameterRamp = ParameterRamp (20.0f);
wetGain2ParameterRamp = ParameterRamp (20.0f);
dampingParameterRamp = ParameterRamp (20.0f);
feedbackParameterRamp = ParameterRamp (20.0f);
reverbChannelLeft = ReverbChannel (0);
reverbChannelRight = ReverbChannel (23);
}
connection
{
// Parameter events to the parameter handler
roomSize -> ReverbParameterProcessorParam.roomSize;
damping -> ReverbParameterProcessorParam.damping;
wetLevel -> ReverbParameterProcessorParam.wetLevel;
dryLevel -> ReverbParameterProcessorParam.dryLevel;
width -> ReverbParameterProcessorParam.width;
// Parameter outputs to smoothing processors
ReverbParameterProcessorParam.dryGainOut -> dryGainParameterRamp.updateParameter;
ReverbParameterProcessorParam.wetGain1Out -> wetGain1ParameterRamp.updateParameter;
ReverbParameterProcessorParam.wetGain2Out -> wetGain2ParameterRamp.updateParameter;
ReverbParameterProcessorParam.dampingOut -> dampingParameterRamp.updateParameter;
ReverbParameterProcessorParam.feedbackOut -> feedbackParameterRamp.updateParameter;
// Sum the audio
audioIn -> Mixer.audioInDry;
// Left channel
audioIn -> reverbChannelLeft.audioIn;
dampingParameterRamp.parameterOut -> reverbChannelLeft.damping;
feedbackParameterRamp.parameterOut -> reverbChannelLeft.feedback;
reverbChannelLeft.audioOut -> Mixer.audioInLeftWet;
// Right channel
audioIn -> reverbChannelRight.audioIn;
dampingParameterRamp.parameterOut -> reverbChannelRight.damping;
feedbackParameterRamp.parameterOut -> reverbChannelRight.feedback;
reverbChannelRight.audioOut -> Mixer.audioInRightWet;
// Mix parameters to the mixer
dryGainParameterRamp.parameterOut -> Mixer.dryIn;
wetGain1ParameterRamp.parameterOut -> Mixer.wet1In;
wetGain2ParameterRamp.parameterOut -> Mixer.wet2In;
// Write the mixed values to the output
Mixer.audioOut -> audioOut;
}
}
//==============================================================================
processor AllpassFilter (int bufferSize)
{
output stream float audioOut;
input stream float audioIn;
float[bufferSize] buffer;
void run()
{
wrap<bufferSize> bufferIndex = 0;
loop
{
let in = audioIn;
let bufferedValue = buffer[bufferIndex];
buffer[bufferIndex] = in + (bufferedValue * 0.5f);
bufferIndex++;
audioOut << bufferedValue - in;
advance();
}
}
}
//==============================================================================
processor CombFilter (int bufferSize)
{
output stream float audioOut;
input stream float audioIn, dampingIn, feedbackLevelIn;
float[bufferSize] buffer;
void run()
{
wrap<bufferSize> bufferIndex = 0;
let gain = 0.015f;
float last = 0.0f;
loop
{
let out = buffer[bufferIndex];
audioOut << out;
last = (out * (1.0f - dampingIn)) + (last * dampingIn);
buffer[bufferIndex] = (gain * audioIn) + (last * feedbackLevelIn);
bufferIndex++;
advance();
}
}
}
//==============================================================================
processor Mixer
{
input stream float audioInDry;
input stream float dryIn, wet1In, wet2In,
audioInLeftWet, audioInRightWet;
output stream float<2> audioOut;
void run()
{
loop
{
let wet1 = wet1In;
let wet2 = wet2In;
let dry = dryIn;
let left = (audioInLeftWet * wet1) + (audioInRightWet * wet2);
let right = (audioInRightWet * wet1) + (audioInLeftWet * wet2);
audioOut << float<2> (left, right) + audioInDry * dry;
advance();
}
}
}
//==============================================================================
// Converts an input value into a stream (limited to the given slewRate)
processor ParameterRamp (float slewRate)
{
input event float updateParameter;
output stream float parameterOut;
event updateParameter (float newTarget)
{
targetValue = newTarget;
let diff = targetValue - currentValue;
let rampSeconds = abs (diff) / slewRate;
rampSamples = int (processor.frequency * rampSeconds);
rampIncrement = diff / float (rampSamples);
}
float targetValue;
float currentValue;
float rampIncrement;
int rampSamples;
void run()
{
loop
{
if (rampSamples > 0)
{
currentValue += rampIncrement;
rampSamples--;
}
parameterOut << currentValue;
advance();
}
}
}
//==============================================================================
// Correctly applies parameter changes to the streams of input to the algorithm
processor ReverbParameterProcessorParam
{
input event
{
float roomSize;
float damping;
float wetLevel;
float dryLevel;
float width;
}
output event
{
float dryGainOut;
float wetGain1Out;
float wetGain2Out;
float dampingOut;
float feedbackOut;
}
event roomSize (float f) { roomSize_ = f / 100.0f; onUpdate(); }
event damping (float f) { damping_ = f / 100.0f; onUpdate(); }
event wetLevel (float f) { wetLevel_ = f / 100.0f; onUpdate(); }
event dryLevel (float f) { dryLevel_ = f / 100.0f; onUpdate(); }
event width (float f) { width_ = f / 100.0f; onUpdate(); }
float roomSize_ = 0.5f;
float damping_ = 0.5f;
float wetLevel_ = 0.33f;
float dryLevel_ = 0.4f;
float width_ = 1.0f;
void onUpdate()
{
// Various tuning factors for the reverb
let wetScaleFactor = 3.0f;
let dryScaleFactor = 2.0f;
let roomScaleFactor = 0.28f;
let roomOffset = 0.7f;
let dampScaleFactor = 0.4f;
// Write updated values
dryGainOut << dryLevel_ * dryScaleFactor;
wetGain1Out << (0.5f * wetLevel_ * wetScaleFactor * (1.0f + width_));
wetGain2Out << (0.5f * wetLevel_ * wetScaleFactor * (1.0f - width_));
dampingOut << damping_ * dampScaleFactor;
feedbackOut << roomSize_ * roomScaleFactor + roomOffset;
}
void run()
{
loop
{
advance();
}
}
}
//==============================================================================
// Mono freeverb implementation
graph ReverbChannel (int offset)
{
input stream float audioIn, damping, feedback;
output stream float audioOut;
let
{
allpass_1 = AllpassFilter(225 + offset);
allpass_2 = AllpassFilter(341 + offset);
allpass_3 = AllpassFilter(441 + offset);
allpass_4 = AllpassFilter(556 + offset);
comb_1 = CombFilter(1116 + offset);
comb_2 = CombFilter(1188 + offset);
comb_3 = CombFilter(1277 + offset);
comb_4 = CombFilter(1356 + offset);
comb_5 = CombFilter(1422 + offset);
comb_6 = CombFilter(1491 + offset);
comb_7 = CombFilter(1557 + offset);
comb_8 = CombFilter(1617 + offset);
}
connection
{
damping -> comb_1.dampingIn,
comb_2.dampingIn,
comb_3.dampingIn,
comb_4.dampingIn,
comb_5.dampingIn,
comb_6.dampingIn,
comb_7.dampingIn,
comb_8.dampingIn;
feedback -> comb_1.feedbackLevelIn,
comb_2.feedbackLevelIn,
comb_3.feedbackLevelIn,
comb_4.feedbackLevelIn,
comb_5.feedbackLevelIn,
comb_6.feedbackLevelIn,
comb_7.feedbackLevelIn,
comb_8.feedbackLevelIn;
audioIn -> comb_1.audioIn,
comb_2.audioIn,
comb_3.audioIn,
comb_4.audioIn,
comb_5.audioIn,
comb_6.audioIn,
comb_7.audioIn,
comb_8.audioIn;
comb_1.audioOut,
comb_2.audioOut,
comb_3.audioOut,
comb_4.audioOut,
comb_5.audioOut,
comb_6.audioOut,
comb_7.audioOut,
comb_8.audioOut -> allpass_1.audioIn;
allpass_1.audioOut -> allpass_2.audioIn;
allpass_2.audioOut -> allpass_3.audioIn;
allpass_3.audioOut -> allpass_4.audioIn;
allpass_4.audioOut -> audioOut;
}
}
graph Piano [[ main ]]
{
input event float volume [[ min: -40, max: 0, init: -6, unit: "dB", step: 1, label: "Volume" ]];
input event midi::Message midiIn;
output stream float<2> audioOut;
let
{
midiParser = midi::MPEParser;
voices = Voice[8];
voiceAllocator = soul::VoiceAllocators::Basic(8);
gainParameter = GainParameterRamp (1.0f);
volumeProcessor = Gain;
reverb = Reverb;
}
connection
{
midiIn -> midiParser.parseMIDI;
volume -> gainParameter.gainDb;
midiParser.eventOut -> voiceAllocator.eventIn;
// Plumb the voice allocator to the voices array
voiceAllocator.voiceEventOut -> voices.eventIn;
// Sum the voices audio out to the output
voices.audioOut -> volumeProcessor.audioIn;
gainParameter.parameterOut -> volumeProcessor.gain;
// volumeProcessor.audioOut -> audioOut;
volumeProcessor.audioOut -> reverb.audioIn;
reverb.audioOut -> audioOut;
}
}
//==============================================================================
processor PianoSamplePlayer
{
input event (soul::NoteEvents::NoteOn,
soul::NoteEvents::NoteOff) eventIn;
output stream float audioOut;
external soul::AudioSamples::Mono piano_C5,
piano_G5,
piano_C6,
piano_G6;
struct Sample
{
soul::AudioSamples::Mono audioData;
int rootNote;
}
Sample[4] samples;
float[] sourceFrames;
float64 playbackPosition, positionIncrement;
event eventIn (soul::NoteEvents::NoteOn e)
{
let sample = findBestSampleForNote (int (e.note));
sourceFrames = sample.audioData.frames;
positionIncrement = soul::getSpeedRatioForPitchedSample (sample.audioData.sampleRate,
float (sample.rootNote),
processor.frequency, e.note);
playbackPosition = 0;
}
event eventIn (soul::NoteEvents::NoteOff e) {}
void initialiseSamples()
{
samples = ((piano_C5, 72),
(piano_G5, 79),
(piano_C6, 84),
(piano_G6, 91));
}
Sample findBestSampleForNote (int targetNote)
{
int bestDistance = 128;
int bestIndex = 0;
for (int i = 0; i < samples.size; ++i)
{
let distance = abs (samples.at(i).rootNote - targetNote);
if (distance < bestDistance)
{
bestDistance = distance;
bestIndex = i;
}
}
return samples.at (bestIndex);
}
void run()
{
initialiseSamples();
loop
{
if (positionIncrement > 0)
{
audioOut << sourceFrames.readLinearInterpolated (playbackPosition);
playbackPosition += positionIncrement;
if (playbackPosition >= sourceFrames.size)
positionIncrement = 0; // stop at the end of the sample
}
advance();
}
}
}
//==============================================================================
processor Envelope (float targetLevel, float releaseSeconds)
{
input event (soul::NoteEvents::NoteOn,
soul::NoteEvents::NoteOff) eventIn;
output stream float audioOut;
event eventIn (soul::NoteEvents::NoteOn e) { active = true; gain = e.velocity; }
event eventIn (soul::NoteEvents::NoteOff e) { active = false; }
bool active = false;
float gain;
void run()
{
let releaseMultiplier = float (pow (0.0001, processor.period / releaseSeconds));
loop
{
// Waiting for note-on
while (! active)
advance();
// Sustaining
while (active)
{
audioOut << gain;
advance();
}
// Releasing
for (float level = gain; ! active && level > 0.00001f; level *= releaseMultiplier)
{
audioOut << level;
advance();
}
}
}
}
//==============================================================================
processor Gain
{
input stream float audioIn, gain;
output stream float audioOut;
void run()
{
loop
{
audioOut << audioIn * gain;
advance();
}
}
}
//==============================================================================
graph Voice
{
input event (soul::NoteEvents::NoteOn,
soul::NoteEvents::NoteOff) eventIn;
output stream float audioOut;
let
{
samplePlayer = PianoSamplePlayer;
envelope = Envelope (0.2f, 0.3f);
}
connection
{
eventIn -> samplePlayer.eventIn,
envelope.eventIn;
samplePlayer.audioOut -> Gain.audioIn;
envelope.audioOut -> Gain.gain;
Gain.audioOut -> audioOut;
}
}
//==============================================================================
// Converts an input event (as a gain in db) to a ramped stream
processor GainParameterRamp (float slewRate)
{
input event float gainDb;
output stream float parameterOut;
event gainDb (float targetDb)
{
targetValue = soul::dBtoGain (targetDb);
let diff = targetValue - currentValue;
let rampSeconds = abs (diff) / slewRate;
rampSamples = int (processor.frequency * rampSeconds);
rampIncrement = diff / float (rampSamples);
}
float targetValue;
float currentValue;
float rampIncrement;
int rampSamples;
void run()
{
loop
{
if (rampSamples > 0)
{
currentValue += rampIncrement;
--rampSamples;
}
parameterOut << currentValue;
advance();
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment