Created
November 12, 2019 20:25
-
-
Save Xenakios/7f4372758e17b98e3a0b239522d9f64a to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
== 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