Virtually all of the audio plugin development is done in low-level languages like C++ to keep the illusion of real-time as close to being true as possible. Recently though, ROLI released a new programming language for audio processing. It's called SOUL, a less convoluted way of designing audio processing software.
I took a look at it and in no time, created this simple detuned sinewave synth. Sure, it is pretty much based on the examples but it took only a couple of hours to implement. If I would have done this in C++, a week would not have been enough.
SOUL provides abstractions for inputs and outputs, lots of ready-made functions for waveforms, phases, and time-to-frequency domain conversions. It's pretty new though and examples, editor support, and proper documentation is still lacking. I plan to keep an eye on the development and will return with some more demanding audio processing later on.
You can try the example on the browser by copy-pasting it to the browser player.
/* | |
== SOUL example code == | |
== Author: Jules == | |
*/ | |
/// Title: A simple MPE sine-wave synthesiser example | |
/// | |
/// A simple sine-wave synthesiser featuring pitch-bend support, using a basic | |
/// envelope and voice-allocator. | |
// Modified from the example to add a detune LFO https://github.com/soul-lang/SOUL/tree/master/examples/patches/SineSynth | |
graph SineSynth [[ main ]] | |
{ | |
input smoothedGain.volume [[ name: "Volume", min: -40, max: 0, init: -6, step: 1 ]]; | |
input event soul::midi::Message midiIn; | |
input DetuneParamProcessor.detuneDepth [[ name: "Detune amount", min: 0, max: 100, init: 1, step: 1 ]]; | |
output stream float audioOut; | |
let | |
{ | |
voices = Voice[8]; | |
voiceAllocator = soul::voice_allocators::Basic(8); | |
smoothedGain = soul::gain::SmoothedGainParameter (0.5f); | |
gainProcessor = soul::gain::DynamicGain (float); | |
} | |
connection | |
{ | |
DetuneParamProcessor.detuneDepthOut -> SineOsc.detuneDepth; | |
midiIn -> soul::midi::MPEParser -> voiceAllocator; | |
// Plumb the voice allocator to the voices array | |
voiceAllocator.voiceEventOut -> voices.noteOn, | |
voices.noteOff, | |
voices.pitchBend; | |
DetuneParamProcessor.detuneDepthOut -> voices.detuneDepth; | |
// Sum the voices audio out to the output | |
voices -> gainProcessor.in; | |
smoothedGain -> gainProcessor.gain; | |
gainProcessor -> audioOut; | |
} | |
} | |
processor DetuneParamProcessor | |
{ | |
input event float detuneDepth; | |
output event float detuneDepthOut; | |
event detuneDepth (float newValue) | |
{ | |
detuneDepthScaled = newValue/10.0f; | |
onUpdate(); | |
} | |
float detuneDepthScaled = 0.0f; | |
void onUpdate() | |
{ | |
detuneDepthOut << detuneDepthScaled; | |
} | |
} | |
processor SineOsc | |
{ | |
input event | |
{ | |
soul::note_events::NoteOn noteOn; | |
soul::note_events::NoteOff noteOff; | |
soul::note_events::PitchBend pitchBend; | |
float detuneDepth; | |
} | |
input stream float detune; | |
output stream float audioOut; | |
event detuneDepth (float f) | |
{ | |
detuneAmountUsed = f; | |
} | |
event noteOn (soul::note_events::NoteOn e) | |
{ | |
notePitch = e.note; | |
bendSemitones = 0.0f; | |
calculatePhaseIncrement(); | |
} | |
event noteOff (soul::note_events::NoteOff e) {} | |
event pitchBend (soul::note_events::PitchBend e) | |
{ | |
bendSemitones = e.bendSemitones; | |
calculatePhaseIncrement(); | |
} | |
float notePitch, bendSemitones, phase, phaseIncrement; | |
float detuneAmountUsed = 1.0f; | |
void calculatePhaseIncrement() | |
{ | |
let noteFrequency = soul::noteNumberToFrequency (notePitch + bendSemitones); | |
phaseIncrement = float (noteFrequency * twoPi * processor.period); | |
} | |
void run() | |
{ | |
loop | |
{ | |
phase = addModulo2Pi (phase, phaseIncrement+(detune*detuneAmountUsed)); | |
audioOut << sin (phase); | |
advance(); | |
} | |
} | |
} | |
//============================================================================== | |
graph Voice | |
{ | |
input event | |
{ | |
soul::note_events::NoteOn noteOn; | |
soul::note_events::NoteOff noteOff; | |
soul::note_events::PitchBend pitchBend; | |
float detuneDepth; | |
} | |
output stream float audioOut; | |
namespace LFO = soul::oscillators::lfo; | |
let lfo = LFO::Processor (LFO::Shape::sine, LFO::Polarity::bipolar, 0.1f, 1.0f); | |
let | |
{ | |
amplitudeEnvelope = soul::envelope::FixedAttackReleaseEnvelope (0.2f, 0.02f, 0.1f); | |
attenuator = soul::gain::DynamicGain (float); | |
} | |
connection | |
{ | |
noteOn -> SineOsc.noteOn; | |
noteOff -> SineOsc.noteOff; | |
pitchBend -> SineOsc.pitchBend; | |
lfo.out -> SineOsc.detune; | |
noteOn, noteOff -> amplitudeEnvelope.noteIn; | |
SineOsc.audioOut -> attenuator.in; | |
amplitudeEnvelope.levelOut -> attenuator.gain; | |
attenuator -> audioOut; | |
detuneDepth -> SineOsc.detuneDepth; | |
} | |
} |
Comments
Post a Comment