A Modular Audio Synthesizer Engine implemented in Common Lisp.
cl-synthesizer is an easy to use library which follows the approach of a classical hardware modular synthesizer, where modules are put into a rack and patched together with cables.
The source code of cl-synthesizer can be found here.
cd ~/quicklisp/local-projects
git clone https://github.com/Frechmatz/cl-wave-file-writer.git
git clone https://github.com/Frechmatz/cl-java-sound-client.git
git clone https://github.com/Frechmatz/cl-synthesizer.git
(ql:quickload "cl-synthesizer")
(defpackage :cl-synthesizer-patches-saw
(:documentation "Saw")
(:use :cl))
(in-package :cl-synthesizer-patches-saw)
(defun example ()
(let ((rack (cl-synthesizer:make-rack
:environment (cl-synthesizer:make-environment))))
(cl-synthesizer:add-module
rack
"VCO"
#'cl-synthesizer-modules-vco:make-module
:base-frequency 440.0 :v-peak 5.0)
(cl-synthesizer-monitor:add-monitor
rack
#'cl-synthesizer-monitor-wave-file-agent:make-backend
'(("VCO" :output-socket :saw))
:filename "docs/saw.wav"
:v-peak 5.0)
rack))
(defun run-example ()
(cl-synthesizer:play-rack (example) :duration-seconds 3.0))
;; (run-example)
(defpackage :cl-synthesizer-patches-frequency-modulated-saw
(:use :cl)
(:documentation "Frequency modulated Saw"))
(in-package :cl-synthesizer-patches-frequency-modulated-saw)
(defun example ()
(let ((rack (cl-synthesizer:make-rack
:environment (cl-synthesizer:make-environment))))
(cl-synthesizer:add-module
rack
"LFO"
#'cl-synthesizer-modules-vco:make-module
:base-frequency 1.0
:v-peak 5.0)
(cl-synthesizer:add-module
rack
"VCO"
#'cl-synthesizer-modules-vco:make-module
:base-frequency 440
:v-peak 5.0
:cv-lin-hz-v 20.0)
(cl-synthesizer:add-patch rack "LFO" :sine "VCO" :cv-lin)
(cl-synthesizer-monitor:add-monitor
rack
#'cl-synthesizer-monitor-wave-file-agent:make-backend
'(("VCO" :output-socket :saw))
:filename "docs/frequency-modulated-saw.wav"
:v-peak 5.0)
rack))
(defun run-example ()
(cl-synthesizer:play-rack (example) :duration-seconds 3.0))
;;(run-example)
(defpackage :cl-synthesizer-patches-two-frequency-modulated-saws
(:use :cl)
(:documentation "Two frequency modulated Saws"))
(in-package :cl-synthesizer-patches-two-frequency-modulated-saws)
(defun make-modulated-saw (name environment &key lfo-frequency vco-frequency)
(declare (ignore name))
(let ((rack (cl-synthesizer:make-rack :environment environment)))
(cl-synthesizer:add-module
rack
"LFO"
#'cl-synthesizer-modules-vco:make-module
:base-frequency lfo-frequency :v-peak 5.0)
(cl-synthesizer:add-module
rack
"VCO"
#'cl-synthesizer-modules-vco:make-module
:base-frequency vco-frequency :v-peak 5.0 :cv-lin-hz-v 20.0)
(cl-synthesizer:add-patch rack "LFO" :sine "VCO" :cv-lin)
(cl-synthesizer:add-rack-output rack :saw "VCO" :saw)
rack))
(defun example ()
(let ((rack (cl-synthesizer:make-rack
:environment (cl-synthesizer:make-environment))))
(cl-synthesizer:add-module
rack "SAW-1" #'make-modulated-saw :lfo-frequency 1.0 :vco-frequency 440.0)
(cl-synthesizer:add-module
rack "SAW-2" #'make-modulated-saw :lfo-frequency 2.0 :vco-frequency 442.0)
(cl-synthesizer-monitor:add-monitor
rack
#'cl-synthesizer-monitor-wave-file-agent:make-backend
'(("SAW-1" :output-socket :saw)
("SAW-2" :output-socket :saw))
:filename "docs/two-frequency-modulated-saws.wav"
:v-peak 5.0)
rack))
(defun run-example ()
(cl-synthesizer:play-rack (example) :duration-seconds 3.0))
;;(run-example)
An environment defines properties such as the sample rate and the home directory of the synthesizer.
Modules are creating the sounds of the synthesizer. Modules do have a name, inputs, outputs and a shutdown function. The inputs/outputs are represented by keywords and are so called sockets. The shutdown function can be used to release resources that have been allocated by the module.
Beside the input/output sockets a module can also expose "state sockets". State sockets represent internal states of the module. These sockets are not accessible when connecting modules with each other. Their purpose is to support debugging/analyzation of a module, for example by writing certain states to a CSV file.
A module is represented by a property list. This list provides functions such as to get the input sockets, to get the output sockets, to get the state sockets, to set input values, to retrieve output values, to update the module, to shutdown the module and so on.
A module must provide a factory/instantiation function. The typical name of this function is "make-module". When a module is added to the synthesizer then not the readily instantiated module is passed, but its factory function. This function is called by the synthesizer. The synthesizer passes the module name, the environment and any arbitrary initialization parameters to it.
For each input/output socket that a module exposes, it must provide a corresponding setter/getter function. When processing an update, the synthesizer sets the inputs of the module via successive calls to the input setters. An input setter must not change the current outputs of the module. When all inputs have been set, the synthesizer calls the update function of the module, which has no parameters. The update function sets the module-outputs according to the previously set inputs.
(defpackage :cl-synthesizer-example-1-adder-2
(:use :cl)
(:export :make-module))
(in-package :cl-synthesizer-example-1-adder-2)
(defun make-module (name environment)
"Adder module with 2 inputs"
(declare (ignore name environment))
(let ((input-1 nil) (input-2 nil) (cur-output nil))
(let ((inputs
(list
:input-1 (list
:set (lambda (value) (setf input-1 value))
:get (lambda() input-1))
:input-2 (list
:set (lambda (value) (setf input-2 value))
:get (lambda() input-2))))
(outputs
(list
:sum (list :get (lambda() cur-output)))))
(list
:inputs (lambda() inputs)
:outputs (lambda() outputs)
:update (lambda () (setf cur-output (+ input-1 input-2)))))))
(defpackage :cl-synthesizer-example-1-adder-4
(:use :cl)
(:export :make-module))
(in-package :cl-synthesizer-example-1-adder-4)
(defun make-module (name environment)
"Adder module with 4 inputs"
(declare (ignore name))
;; create a rack
(let ((rack (cl-synthesizer:make-rack :environment environment)))
;; add modules
(cl-synthesizer:add-module rack "ADDER-1" #'cl-synthesizer-example-1-adder-2:make-module)
(cl-synthesizer:add-module rack "ADDER-2" #'cl-synthesizer-example-1-adder-2:make-module)
(cl-synthesizer:add-module rack "MAIN-ADDER" #'cl-synthesizer-example-1-adder-2:make-module)
;; patch modules
(cl-synthesizer:add-patch rack "ADDER-1" :sum "MAIN-ADDER" :input-1)
(cl-synthesizer:add-patch rack "ADDER-2" :sum "MAIN-ADDER" :input-2)
;; define rack inputs
(cl-synthesizer:add-rack-input rack :input-1 "ADDER-1" :input-1)
(cl-synthesizer:add-rack-input rack :input-2 "ADDER-1" :input-2)
(cl-synthesizer:add-rack-input rack :input-3 "ADDER-2" :input-1)
(cl-synthesizer:add-rack-input rack :input-4 "ADDER-2" :input-2)
;; define rack output
(cl-synthesizer:add-rack-output rack :sum "MAIN-ADDER" :sum)
;; return the rack
rack))
Racks are holding modules and their connections with each other. The connections are so called "Patches".
(defpackage :cl-synthesizer-example-1
(:use :cl))
(in-package :cl-synthesizer-example-1)
(defun make-example-rack ()
(let ((rack (cl-synthesizer:make-rack :environment (cl-synthesizer:make-environment))))
(cl-synthesizer:add-module rack "ADDER" #'cl-synthesizer-example-1-adder-4:make-module)
;; add rack inputs
(cl-synthesizer:add-rack-input rack :input-1 "ADDER" :input-1)
(cl-synthesizer:add-rack-input rack :input-2 "ADDER" :input-2)
(cl-synthesizer:add-rack-input rack :input-3 "ADDER" :input-3)
(cl-synthesizer:add-rack-input rack :input-4 "ADDER" :input-4)
;; add rack output
(cl-synthesizer:add-rack-output rack :sum "ADDER" :sum)
rack))
(defun run-example ()
(let ((rack (make-example-rack)))
(let ((rack-inputs (funcall (getf rack :inputs)))
(rack-outputs (funcall (getf rack :outputs))))
;; set inputs
(let ((input-setter (getf (getf rack-inputs :input-1) :set)))
(funcall input-setter 10))
(let ((input-setter (getf (getf rack-inputs :input-2) :set)))
(funcall input-setter 20))
(let ((input-setter (getf (getf rack-inputs :input-3) :set)))
(funcall input-setter 30))
(let ((input-setter (getf (getf rack-inputs :input-4) :set)))
(funcall input-setter 40))
;; update
(cl-synthesizer:update rack)
;; print output of rack
(let ((sum (funcall (getf (getf rack-outputs :sum) :get))))
(format t "~%Sum:~a~%" sum))
;; shutdown
(cl-synthesizer:shutdown rack))))
;; (run-example)
Creates an environment. An enviroment is a property list with the following keys:
Optional home-directory setting of cl-synthesizer. Overrides the home-directory argument passed to make-environment.
Returns the name of a module.
Returns the rack to which a module belongs.
Calls the update function of a module.
Calls the (optional) shutdown function of a module.
Returns t if the given module represents a rack.
Creates a rack.
The function has the following parameters:
Returns the environment of the rack.
Adds a module to a rack.
The function has the following parameters:
Get a module by its name or path.
The function has the following parameters:
Returns the modules of a rack.
Adds a patch to the rack. A patch is an unidirectional connection between an output socket of a source module and an input socket of a destination module.
The function has the following parameters:
The rack signals an assembly-error in cases such as:
Returns a list of the patches of a rack. Each patch is represented by a property list with the following keys:
Exposes an input socket of a module as an input socket of the rack.
The function has the following parameters:
Exposes an output socket of a module as an output socket of the rack.
The function has the following parameters:
Adds a hook to a rack.
The function has the following parameters:
A utility function that "plays" the rack by consecutively calling its update function for a given number of "ticks".
The function has the following parameters:
Creates a module which exposes runtime information of its rack.
The function has the following parameters:
The module has no inputs.
The module has the following outputs:
Example
(defpackage :cl-synthesizer-modules-system-example-1
(:use :cl))
(in-package :cl-synthesizer-modules-system-example-1)
(defun example ()
"System example"
(let ((rack (cl-synthesizer:make-rack
:environment (cl-synthesizer:make-environment))))
(cl-synthesizer:add-module
rack "SYSTEM"
#'cl-synthesizer-modules-system:make-module)
(cl-synthesizer-monitor:add-monitor
rack
#'cl-synthesizer-monitor-csv-file-agent:make-backend
'(("SYSTEM" :output-socket :ticks :name "ticks")
("SYSTEM" :output-socket :milliseconds :name "milliseconds")
("SYSTEM" :output-socket :seconds :name "seconds")
("SYSTEM" :output-socket :sample-rate :name "sample-rate"))
:filename "cl-synthesizer-examples/system-example-1.csv"
:add-header t
:column-separator ",")
rack))
(defun run-example ()
(let ((rack (example)))
(cl-synthesizer:play-rack rack :duration-seconds 2)))
;; (run-example)
Creates a Voltage Controlled Oscillator module with 1V/Octave and linear frequency modulation inputs. The oscillator has through-zero support.
The function has the following parameters:
The module has the following inputs:
The module has the following outputs (depending on the :wave-forms parameter):
The module exposes the following states via the state function:
Example
(defpackage :cl-synthesizer-modules-vco-example-1
(:use :cl))
(in-package :cl-synthesizer-modules-vco-example-1)
(defun example ()
"Write all wave forms to a Wave and a CSV file"
(let ((rack (cl-synthesizer:make-rack :environment (cl-synthesizer:make-environment))))
(cl-synthesizer:add-module
rack
"VCO"
#'cl-synthesizer-modules-vco:make-module
:base-frequency 10.0 :v-peak 5.0)
(cl-synthesizer-monitor:add-monitor
rack
#'cl-synthesizer-monitor-wave-file-agent:make-backend
'(("VCO" :output-socket :sine)
("VCO" :output-socket :triangle)
("VCO" :output-socket :saw)
("VCO" :output-socket :square))
:filename "cl-synthesizer-examples/vco-example-1.wav"
:v-peak 5.0)
(cl-synthesizer-monitor:add-monitor
rack
#'cl-synthesizer-monitor-csv-file-agent:make-backend
'(("VCO" :output-socket :sine :name "Sine")
("VCO" :output-socket :triangle :name "Triangle")
("VCO" :output-socket :saw :name "Saw")
("VCO" :output-socket :square :name "Square"))
:filename "cl-synthesizer-examples/vco-example-1.csv"
:add-header t
:column-separator ",")
rack))
(defun run-example ()
(let ((rack (example)))
(cl-synthesizer:play-rack rack :duration-seconds 1)))
;; (run-example)
Creates a Voltage Controlled Amplifier/Attenuator module. The VCA multiplies an incoming signal with a factor of 0..1.
The function has the following parameters:
The module has the following inputs:
The module has the following outputs:
Example
(defpackage :cl-synthesizer-modules-vca-example-1
(:use :cl))
(in-package :cl-synthesizer-modules-vca-example-1)
(defun example ()
"Amplification of a 10kHz sine wave with a bipolar triangular signal."
(let ((rack (cl-synthesizer:make-rack :environment (cl-synthesizer:make-environment))))
;; Set up oscillator modulating the amplification
(cl-synthesizer:add-module
rack "LFO-CV"
#'cl-synthesizer-modules-vco:make-module
:base-frequency 0.5
:v-peak 5.0)
;; set up oscillator providing the audio signal
(cl-synthesizer:add-module
rack "VCO-AUDIO"
#'cl-synthesizer-modules-vco:make-module
:base-frequency 5.0
:v-peak 5.0)
;; Set up VCA
(cl-synthesizer:add-module
rack "VCA"
#'cl-synthesizer-modules-vca:make-module
:cv-max 5.0
:exponential t)
;; Add patches
(cl-synthesizer:add-patch rack "VCO-AUDIO" :sine "VCA" :input)
(cl-synthesizer:add-patch rack "LFO-CV" :triangle "VCA" :cv)
;; Write VCA inputs/outputs to a CSV file
(cl-synthesizer-monitor:add-monitor
rack
#'cl-synthesizer-monitor-csv-file-agent:make-backend
'(("VCA" :input-socket :cv :name "CV")
("VCA" :input-socket :input :name "Input")
("VCA" :output-socket :output :name "Output"))
:filename "cl-synthesizer-examples/vca-example-1.csv")
rack))
(defun run-example ()
(let ((rack (example)))
(cl-synthesizer:play-rack rack :duration-seconds 5.0)))
;; (run-example)
Creates an envelope generator module with the phases Attack, Decay, Sustain and Release.
The function has the following parameters:
The module has the following inputs:
The module has the following outputs:
Example
(defpackage :cl-synthesizer-modules-adsr-example-1
(:use :cl))
(in-package :cl-synthesizer-modules-adsr-example-1)
(defun make-voice (name environment &key exponential)
(declare (ignore name))
(let ((rack (cl-synthesizer:make-rack :environment environment)))
(cl-synthesizer:add-module
rack "ADSR"
#'cl-synthesizer-modules-adsr:make-module
:attack-time-ms 500 :attack-target-output 5.0
:decay-time-ms 250 :decay-target-output -3.0
:release-time-ms 1000
:exponential exponential)
(cl-synthesizer:add-rack-input rack :gate "ADSR" :gate)
(cl-synthesizer:add-rack-output rack :adsr-out "ADSR" :cv)
rack))
(defun example ()
"ADSR example. Linear vs. Exponential"
(let ((rack (cl-synthesizer:make-rack
:environment (cl-synthesizer:make-environment))))
(cl-synthesizer:add-module
rack "MIDI-SEQUENCER"
#'cl-synthesizer-modules-midi-sequencer:make-module :events
(list
(list :timestamp-milli-seconds 0
:midi-events (list
(cl-synthesizer-midi-event:make-note-on-event :channel 1 :note-number 69 :velocity 100)))
(list :timestamp-milli-seconds 1500
:midi-events (list
(cl-synthesizer-midi-event:make-note-off-event :channel 1 :note-number 69 :velocity 100)))))
(cl-synthesizer:add-module
rack "MIDI-IFC"
#'cl-synthesizer-modules-midi-polyphonic-interface:make-module :voice-count 1)
(cl-synthesizer:add-patch rack "MIDI-SEQUENCER" :midi-events "MIDI-IFC" :midi-events)
(cl-synthesizer:add-module
rack "GATE-MULTIPLE"
#'cl-synthesizer-modules-multiple:make-module
:output-count 2)
(cl-synthesizer:add-patch rack "MIDI-IFC" :gate-1 "GATE-MULTIPLE" :input)
(cl-synthesizer:add-module
rack "LINEAR" #'make-voice :exponential nil)
(cl-synthesizer:add-patch rack "GATE-MULTIPLE" :output-1 "LINEAR" :gate)
(cl-synthesizer:add-module
rack "EXPONENTIAL" #'make-voice :exponential t)
(cl-synthesizer:add-patch rack "GATE-MULTIPLE" :output-2 "EXPONENTIAL" :gate)
(cl-synthesizer-monitor:add-monitor
rack
#'cl-synthesizer-monitor-csv-file-agent:make-backend
'(("GATE-MULTIPLE" :input-socket :input :name "Gate")
("LINEAR" :output-socket :adsr-out :name "ADSR Linear")
("EXPONENTIAL" :output-socket :adsr-out :name "ADSR Exponential"))
:filename "cl-synthesizer-examples/adsr-example-1.csv")
rack))
(defun run-example ()
(let ((rack (example)))
(cl-synthesizer:play-rack rack :duration-seconds 3)))
;; (run-example)
Creates a Multiple module. A multiple passes the value of exactly one input socket to as many output sockets as defined by output-count.
The function has the following parameters:
The module has the following inputs:
The module has the following outputs:
Example
(defpackage :cl-synthesizer-modules-multiple-example-1
(:use :cl))
(in-package :cl-synthesizer-modules-multiple-example-1)
(defun example ()
"Multiple example"
(let ((rack (cl-synthesizer:make-rack
:environment (cl-synthesizer:make-environment))))
(cl-synthesizer:add-module
rack "LFO"
#'cl-synthesizer-modules-vco:make-module
:base-frequency 1.0 :v-peak 1.0)
(cl-synthesizer:add-module rack "MULTIPLE"
#'cl-synthesizer-modules-multiple:make-module :output-count 5)
(cl-synthesizer:add-patch rack "LFO" :sine "MULTIPLE" :input)
(cl-synthesizer:add-rack-output rack :line-out-1 "MULTIPLE" :output-1)
(cl-synthesizer:add-rack-output rack :line-out-2 "MULTIPLE" :output-2)
rack))
(defun run-example ()
(let ((rack (example)))
(cl-synthesizer:play-rack rack :duration-seconds 10)))
;; (run-example)
Creates a module with a fixed output value.
The function has the following parameters:
Example
(defpackage :cl-synthesizer-modules-fixed-output-example-1
(:use :cl))
(in-package :cl-synthesizer-modules-fixed-output-example-1)
(defun example ()
"Fixed-Output example"
(let ((rack (cl-synthesizer:make-rack
:environment (cl-synthesizer:make-environment))))
(cl-synthesizer:add-module
rack "FIXED-OUTPUT"
#'cl-synthesizer-modules-fixed-output:make-module
:value 3.0
:output-socket :fixed)
(cl-synthesizer:add-rack-output rack :line-out "FIXED-OUTPUT" :fixed)
rack))
(defun run-example ()
(cl-synthesizer:play-rack (example) :duration-seconds 1))
;; (run-example)
Creates a simple voltage adder module.
The function has the following parameters:
The module has the following inputs:
The module has the following outputs:
Creates a mixer module. The mixer provides an attenuator for each input and a main attenuator for the mixer output. All attenuators have linear amplification characteristic.
The function has the following parameters:
The module has the following inputs:
The module has the following outputs:
Example
(defpackage :cl-synthesizer-modules-mixer-example-1
(:use :cl))
(in-package :cl-synthesizer-modules-mixer-example-1)
(defun example ()
"Mixer example."
(let ((rack (cl-synthesizer:make-rack
:environment (cl-synthesizer:make-environment))))
;;
;; add modules...
;;
(cl-synthesizer:add-module
rack "MIXER" #'cl-synthesizer-modules-mixer:make-module
:channel-count 2
:channel-cv-max 5.0
:channel-cv-gain 5.0
:main-cv-max 5.0
:main-cv-gain 2.5)
(cl-synthesizer:add-patch rack "VOICE-1" :audio "MIXER" :channel-1)
(cl-synthesizer:add-patch rack "VOICE-2" :audio "MIXER" :channel-2)
(cl-synthesizer:add-rack-output rack :line-out "MIXER" :output)
rack))
Creates a Voltage to Trigger Converter module. The module fires a one clock cycle long pulse when input voltage >= trigger-threshold and then waits that the input voltage descends below trigger-threshold before the next pulse can be triggered. The module can for example be used to generate a trigger out of a gate signal.
The function has the following parameters:
The module has the following inputs:
The module has the following outputs:
Example
(defpackage :cl-synthesizer-modules-trigger-example-1
(:use :cl))
(in-package :cl-synthesizer-modules-trigger-example-1)
(defun example ()
"Emit trigger signal based on sine input"
(let ((rack (cl-synthesizer:make-rack :environment (cl-synthesizer:make-environment))))
(cl-synthesizer:add-module
rack
"VCO"
#'cl-synthesizer-modules-vco:make-module
:base-frequency 5.0 :v-peak 5.0)
(cl-synthesizer:add-module
rack
"TRIGGER"
#'cl-synthesizer-modules-trigger:make-module
:trigger-threshold 4.9 :pulse-voltage 3.0)
(cl-synthesizer:add-patch rack "VCO" :sine "TRIGGER" :input)
(cl-synthesizer-monitor:add-monitor
rack
#'cl-synthesizer-monitor-wave-file-agent:make-backend
'(("TRIGGER" :input-socket :input)
("TRIGGER" :output-socket :output))
:filename "cl-synthesizer-examples/trigger-example-1.wav"
:v-peak 5.0)
rack))
(defun run-example ()
(let ((rack (example)))
(cl-synthesizer:play-rack rack :duration-seconds 2)))
;; (run-example)
Creates a module whose output climbs from a given input value to a given output value in a given time. Main purpose of this module is to create envelope generators by chaining multiple ramp and sustain modules.
The function has the following parameters:
The module has the following inputs:
The module has the following outputs:
Example
(defpackage :cl-synthesizer-modules-ramp-example-1
(:use :cl))
(in-package :cl-synthesizer-modules-ramp-example-1)
(defun make-voice (name environment &key (exponential nil))
(declare (ignore name))
(let ((rack
(cl-synthesizer:make-rack :environment environment)))
(cl-synthesizer:add-module
rack "ATTACK"
#'cl-synthesizer-modules-ramp:make-module
:time-ms 700 :target-output 5.0 :gate-state nil :exponential exponential)
(cl-synthesizer:add-module
rack "DECAY"
#'cl-synthesizer-modules-ramp:make-module
:time-ms 500 :target-output -2.5 :exponential exponential)
(cl-synthesizer:add-rack-input rack :trigger "ATTACK" :trigger)
(cl-synthesizer:add-patch rack "ATTACK" :busy "DECAY" :pass-through)
(cl-synthesizer:add-patch rack "ATTACK" :output "DECAY" :input)
(cl-synthesizer:add-patch rack "ATTACK" :done "DECAY" :trigger)
(cl-synthesizer:add-rack-output rack :output "DECAY" :output)
rack))
(defun example ()
"Linear and Exponential Ramp"
(let ((rack (cl-synthesizer:make-rack
:environment (cl-synthesizer:make-environment))))
(cl-synthesizer:add-module
rack "VCO"
#'cl-synthesizer-modules-vco:make-module
:base-frequency 0.5 :v-peak 5.0)
(cl-synthesizer:add-module
rack "TRIGGER"
#'cl-synthesizer-modules-trigger:make-module
:trigger-threshold 4.9 :pulse-voltage 5.0)
(cl-synthesizer:add-patch rack "VCO" :square "TRIGGER" :input)
(cl-synthesizer:add-module
rack "TRIGGER-MULTIPLE"
#'cl-synthesizer-modules-multiple:make-module
:output-count 2)
(cl-synthesizer:add-patch rack "TRIGGER" :output "TRIGGER-MULTIPLE" :input)
(cl-synthesizer:add-module
rack "LINEAR" #'make-voice :exponential nil)
(cl-synthesizer:add-patch rack "TRIGGER-MULTIPLE" :output-1 "LINEAR" :trigger)
(cl-synthesizer:add-module
rack "EXPONENTIAL" #'make-voice :exponential t)
(cl-synthesizer:add-patch rack "TRIGGER-MULTIPLE" :output-2 "EXPONENTIAL" :trigger)
(cl-synthesizer-monitor:add-monitor
rack
#'cl-synthesizer-monitor-csv-file-agent:make-backend
'(("LINEAR" :output-socket :output :name "Lin Out")
("EXPONENTIAL" :output-socket :output :name "Exp Out"))
:filename "cl-synthesizer-examples/ramp-example-1.csv")
rack))
(defun run-example ()
(let ((rack (example))) (cl-synthesizer:play-rack rack :duration-seconds 5)))
;; (run-example)
Creates a module which holds a given input as long as its gate input is "on". Main purpose of this module is to create envelope generators by chaining multiple ramp and sustain modules.
The function has the following parameters:
The module has the following inputs:
The module has the following outputs:
Example
(defpackage :cl-synthesizer-modules-sustain-example-1
(:use :cl))
(in-package :cl-synthesizer-modules-sustain-example-1)
(defun example ()
"Sustain example"
(let ((rack (cl-synthesizer:make-rack
:environment (cl-synthesizer:make-environment))))
;; Use MIDI sequencer for generation of Gate signals
(cl-synthesizer:add-module
rack "MIDI-SEQUENCER"
#'cl-synthesizer-modules-midi-sequencer:make-module :events
(list
(list :timestamp-milli-seconds 300
:midi-events (list
(cl-synthesizer-midi-event:make-note-on-event
:channel 1
:note-number 69
:velocity 100)))
(list :timestamp-milli-seconds 700
:midi-events (list
(cl-synthesizer-midi-event:make-note-off-event
:channel 1
:note-number 69
:velocity 100)))
(list :timestamp-milli-seconds 1800
:midi-events (list
(cl-synthesizer-midi-event:make-note-on-event
:channel 1
:note-number 69
:velocity 100)))
(list :timestamp-milli-seconds 2100
:midi-events (list
(cl-synthesizer-midi-event:make-note-off-event
:channel 1
:note-number 69
:velocity 100)))))
(cl-synthesizer:add-module
rack "MIDI-IFC"
#'cl-synthesizer-modules-midi-polyphonic-interface:make-module :voice-count 1)
(cl-synthesizer:add-module
rack "GATE-MULTIPLE"
#'cl-synthesizer-modules-multiple:make-module :output-count 2)
(cl-synthesizer:add-module
rack "TRIGGER"
#'cl-synthesizer-modules-trigger:make-module
:trigger-threshold 4.9 :pulse-voltage 5.0)
(cl-synthesizer:add-module
rack "VCO"
#'cl-synthesizer-modules-vco:make-module
:base-frequency 0.5 :v-peak 5.0)
(cl-synthesizer:add-module
rack "SUSTAIN"
#'cl-synthesizer-modules-sustain:make-module)
(cl-synthesizer:add-patch rack "MIDI-SEQUENCER" :midi-events "MIDI-IFC" :midi-events)
(cl-synthesizer:add-patch rack "MIDI-IFC" :gate-1 "GATE-MULTIPLE" :input)
(cl-synthesizer:add-patch rack "GATE-MULTIPLE" :output-1 "TRIGGER" :input)
(cl-synthesizer:add-patch rack "GATE-MULTIPLE" :output-2 "SUSTAIN" :gate)
(cl-synthesizer:add-patch rack "TRIGGER" :output "SUSTAIN" :trigger)
(cl-synthesizer:add-patch rack "VCO" :sine "SUSTAIN" :input)
(cl-synthesizer-monitor:add-monitor
rack
#'cl-synthesizer-monitor-csv-file-agent:make-backend
'(("MIDI-IFC" :output-socket :gate-1 :name "Gate")
("SUSTAIN" :input-socket :trigger :name "Sustain Trigger In")
("SUSTAIN" :input-socket :input :name "Sustain In")
("SUSTAIN" :output-socket :output :name "Sustain Out")
("SUSTAIN" :output-socket :done :name "Sustain Done Out"))
:filename "cl-synthesizer-examples/sustain-example-1.csv")
rack))
(defun run-example ()
(let ((rack (example))) (cl-synthesizer:play-rack rack :duration-seconds 3)))
;; (run-example)
Creates a Wave File Writer module. Writes files in "Waveform Audio File" ("WAV") format.
The function has the following parameters:
The module has the following inputs:
Creates a CSV File Writer module.
The function has the following parameters:
The module has the following inputs:
Due to performance/consing considerations all columns are written using the Lisp-Writer. If a value contains the column separator it will not be quoted. The file is opened on the first call of the update function and closed by the shutdown handler.
The module has no outputs.Monitors are hook implementations that register themselves at a rack. They are called after a rack has processed an update. They collect data like output socket values and pass them to a so called "Monitor Backend". A monitor backend can for example be a CSV-File-Writer. A monitor backend is a module. It is instantiated by a so called "Monitor Agent". This agent sits between the monitor and its backend. The agent instantiates the backend and defines the mapping of the values collected by the monitor to input sockets of the backend.
Registers a monitor at a rack.
The function has the following parameters:
Creates a monitor backend which writes to a Wave file.
The function has the following parameters:
The function returns a values object consisting of
Example
(defpackage :cl-synthesizer-monitor-wave-file-example-1
(:use :cl))
(in-package :cl-synthesizer-monitor-wave-file-example-1)
(defun example ()
"Wave-File-Agent example"
(let ((rack (cl-synthesizer:make-rack :environment (cl-synthesizer:make-environment))))
(cl-synthesizer:add-module
rack "VCO" #'cl-synthesizer-modules-vco:make-module
:base-frequency 5.0 :v-peak 5.0)
(cl-synthesizer-monitor:add-monitor
rack
#'cl-synthesizer-monitor-wave-file-agent:make-backend
'(("VCO" :output-socket :sine))
:filename "cl-synthesizer-examples/monitor-wave-file-example-1.wav"
:v-peak 5.0)
rack))
(defun run-example ()
(let ((rack (example)))
(cl-synthesizer:play-rack rack :duration-seconds 2)))
;; (run-example)
Creates a monitor backend which writes to a CSV file.
The function has the following parameters:
The function returns a values object consisting of
Example
(defpackage :cl-synthesizer-monitor-csv-file-example-1
(:use :cl))
(in-package :cl-synthesizer-monitor-csv-file-example-1)
(defun example ()
"CSV-Agent example"
(let ((rack (cl-synthesizer:make-rack :environment (cl-synthesizer:make-environment))))
(cl-synthesizer:add-module
rack "VCO" #'cl-synthesizer-modules-vco:make-module :base-frequency 5.0 :v-peak 5.0)
(cl-synthesizer-monitor:add-monitor
rack
#'cl-synthesizer-monitor-csv-file-agent:make-backend
'(("VCO" :output-socket :sine :name "Sine"))
:filename "cl-synthesizer-examples/monitor-csv-file-example-1.csv"
:add-header t
:column-separator ",")
rack))
(defun run-example ()
(let ((rack (example)))
(cl-synthesizer:play-rack rack :duration-seconds 2)))
;; (run-example)
Creates a monitor backend which writes to a memory buffer.
The function has the following parameters:
The function returns a values object consisting of
Example
(defpackage :cl-synthesizer-monitor-buffer-example-1
(:use :cl))
(in-package :cl-synthesizer-monitor-buffer-example-1)
(defun example ()
"Buffer-Agent example"
(let ((rack (cl-synthesizer:make-rack :environment (cl-synthesizer:make-environment))))
(cl-synthesizer:add-module
rack "VCO" #'cl-synthesizer-modules-vco:make-module :base-frequency 5.0 :v-peak 5.0)
(cl-synthesizer-monitor:add-monitor
rack
#'cl-synthesizer-monitor-buffer-agent:make-backend
'(("VCO" :output-socket :sine)
("VCO" :output-socket :saw))
:buffer (make-array 2))
rack))
(defun run-example ()
(let ((rack (example)))
(cl-synthesizer:play-rack rack :duration-seconds 2)))
;; (run-example)
Creates a MIDI control change event.
cl-synthesizer-midi-event:make-note-on-event (&key channel note-number velocity)Creates a MIDI Note-On event.
cl-synthesizer-midi-event:make-note-off-event (&key channel note-number velocity)Creates a MIDI Note-Off event.
cl-synthesizer-midi-event:control-change-eventp (event)Returns t if the given MIDI event is a Control-Change event.
cl-synthesizer-midi-event:note-on-eventp (event)Returns t if the given MIDI event is a Note-On event.
cl-synthesizer-midi-event:note-off-eventp (event)Returns t if the given MIDI event is a Note-Off event.
cl-synthesizer-midi-event:get-channel (event)Returns the MIDI channel number to which the event belongs.
cl-synthesizer-midi-event:get-controller-number (event)Returns the controller number of a Control-Change MIDI event.
cl-synthesizer-midi-event:get-control-value (event)Returns the control value of a Control-Change MIDI event.
cl-synthesizer-midi-event:get-note-number (event)Returns the note number of Note-On/Off MIDI event.
cl-synthesizer-midi-event:get-velocity (event)Returns the velocity of a Note-On/Off MIDI event.
Creates a polyphonic MIDI interface module. The module dispatches MIDI-Note events to so called voices where each voice is represented by a pitch-control voltage, a velocity-control voltage and a gate signal.
The function has the following parameters:
The module has the following inputs:
The module has the following outputs:
Creates a monophonic MIDI interface module. The module dispatches MIDI-Note events to a single voice. If the voice is already assigned to a note, then the incoming note is pushed on top of the current note.
The function has the following parameters:
The module has the following inputs:
The module has the following outputs:
Creates a MIDI CC Event interface module. CC events are interpreted as relative changes to the current output value.
The function has the following parameters:
The module has the following inputs:
The module has the following outputs:
Example
(defpackage :cl-synthesizer-modules-midi-relative-cc-interface-example-1
(:use :cl))
(in-package :cl-synthesizer-modules-midi-relative-cc-interface-example-1)
(defun example ()
"MIDI Relative CC-Interface Example"
(let ((rack (cl-synthesizer:make-rack
:environment (cl-synthesizer:make-environment))))
(cl-synthesizer:add-module
rack "MIDI-CC-IFC" #'cl-synthesizer-modules-midi-relative-cc-interface:make-module
:mappings '((:controller-number 112 :control-value 61 :offset -1.0)
(:controller-number 112 :control-value 67 :offset 1.0))
:initial-output 2.5
:channel nil)
(cl-synthesizer:add-module
rack "MIDI-SEQUENCER"
#'cl-synthesizer-modules-midi-sequencer:make-module :events
(list
(list :timestamp-milli-seconds 100
:midi-events (list
(cl-synthesizer-midi-event:make-control-change-event :channel 1 :controller-number 112 :control-value 61)))
(list :timestamp-milli-seconds 200
:midi-events (list
(cl-synthesizer-midi-event:make-control-change-event :channel 1 :controller-number 112 :control-value 61)))
(list :timestamp-milli-seconds 300
:midi-events (list
(cl-synthesizer-midi-event:make-control-change-event :channel 1 :controller-number 112 :control-value 61)))
(list :timestamp-milli-seconds 400
:midi-events (list
(cl-synthesizer-midi-event:make-control-change-event :channel 1 :controller-number 112 :control-value 61)))
(list :timestamp-milli-seconds 500
:midi-events (list
(cl-synthesizer-midi-event:make-control-change-event :channel 1 :controller-number 112 :control-value 67)))
(list :timestamp-milli-seconds 600
:midi-events (list
(cl-synthesizer-midi-event:make-control-change-event :channel 1 :controller-number 112 :control-value 67)))
(list :timestamp-milli-seconds 700
:midi-events (list
(cl-synthesizer-midi-event:make-control-change-event :channel 1 :controller-number 112 :control-value 67)))
(list :timestamp-milli-seconds 800
:midi-events (list
(cl-synthesizer-midi-event:make-control-change-event :channel 1 :controller-number 112 :control-value 67)))))
(cl-synthesizer:add-patch rack "MIDI-SEQUENCER" :midi-events "MIDI-CC-IFC" :midi-events)
(cl-synthesizer-monitor:add-monitor
rack
#'cl-synthesizer-monitor-csv-file-agent:make-backend
'(("MIDI-CC-IFC" :output-socket :output :name "CC-Out"))
:filename "cl-synthesizer-examples/midi-relative-cc-interface-example-1.csv")
rack))
(defun run-example ()
(cl-synthesizer::play-rack (example) :duration-seconds 0.9))
;; (run-example)
Creates a Midi-Sequencer module.
The function has the following parameters:
Example
(defpackage :cl-synthesizer-modules-midi-sequencer-example-1
(:use :cl))
(in-package :cl-synthesizer-modules-midi-sequencer-example-1)
(defun example ()
"Midi-Sequencer example"
(let ((rack (cl-synthesizer:make-rack
:environment (cl-synthesizer:make-environment))))
;; Add sequencer
(cl-synthesizer:add-module
rack
"MIDI-SEQUENCER"
#'cl-synthesizer-modules-midi-sequencer:make-module :events
(list
(list :timestamp-milli-seconds 0
:midi-events (list
(cl-synthesizer-midi-event:make-note-on-event
:channel 1
:note-number 69
:velocity 100)))
(list :timestamp-milli-seconds 1000
:midi-events (list
(cl-synthesizer-midi-event:make-note-off-event
:channel 1
:note-number 69
:velocity 100)))
(list :timestamp-milli-seconds 2000
:midi-events (list
(cl-synthesizer-midi-event:make-note-on-event
:channel 1
:note-number 75
:velocity 100)))
(list :timestamp-milli-seconds 2500
:midi-events (list
(cl-synthesizer-midi-event:make-note-off-event
:channel 1
:note-number 75
:velocity 100)))))
;; Add MIDI Interface and connect it with the MIDI Sequencer
(cl-synthesizer:add-module
rack
"MIDI-IFC"
#'cl-synthesizer-modules-midi-polyphonic-interface:make-module :voice-count 1)
(cl-synthesizer:add-patch rack "MIDI-SEQUENCER" :midi-events "MIDI-IFC" :midi-events)
;; Add VCO
(cl-synthesizer:add-module
rack "VCO" #'cl-synthesizer-modules-vco:make-module
:base-frequency (cl-synthesizer-midi:get-note-number-frequency 0)
:v-peak 5.0)
;; Add ADSR
(cl-synthesizer:add-module
rack "ADSR"
#'cl-synthesizer-modules-adsr:make-module
:attack-time-ms 100 :attack-target-output 5.0
:decay-time-ms 50 :decay-target-output 3.0
:release-time-ms 100)
;; Add VCA
(cl-synthesizer:add-module rack "VCA" #'cl-synthesizer-modules-vca:make-module :cv-max 5.0 :exponential nil)
;; Connect VCA with ADSR and VCO
(cl-synthesizer:add-patch rack "ADSR" :cv "VCA" :cv)
(cl-synthesizer:add-patch rack "VCO" :triangle "VCA" :input)
;; Connect Midi interface with ADSR and VCO
(cl-synthesizer:add-patch rack "MIDI-IFC" :cv-1 "VCO" :cv-exp)
(cl-synthesizer:add-patch rack "MIDI-IFC" :gate-1 "ADSR" :gate)
;; Write VCA output to a wave file
(cl-synthesizer-monitor:add-monitor
rack
#'cl-synthesizer-monitor-wave-file-agent:make-backend
'(("VCA" :output-socket :output))
:filename "cl-synthesizer-examples/midi-sequencer-example-1.wav"
:v-peak 5.0)
rack))
(defun run-example ()
(let ((rack (example)))
(cl-synthesizer::play-rack rack :duration-seconds 5)))
;; (run-example)
Returns the frequency of a given note number. Note number 69 results in a frequency of 440Hz. This function implements a mapping according to midinote2freq
Note number | Frequency |
---|---|
0 | 8.175798 |
1 | 8.661958 |
2 | 9.177024 |
3 | 9.722718 |
4 | 10.300861 |
5 | 10.913383 |
6 | 11.5623255 |
7 | 12.249857 |
8 | 12.9782715 |
9 | 55/4 |
10 | 14.567618 |
11 | 15.433853 |
12 | 16.351597 |
13 | 17.323915 |
14 | 18.354048 |
15 | 19.445436 |
16 | 20.601723 |
17 | 21.826765 |
18 | 23.124651 |
19 | 24.499714 |
20 | 25.956543 |
21 | 55/2 |
22 | 29.135237 |
23 | 30.867706 |
24 | 32.703194 |
25 | 34.64783 |
26 | 36.708096 |
27 | 38.890873 |
28 | 41.203445 |
29 | 43.65353 |
30 | 46.249302 |
31 | 48.999428 |
32 | 51.913086 |
33 | 55 |
34 | 58.270473 |
35 | 61.735413 |
36 | 65.40639 |
37 | 69.29566 |
38 | 73.41619 |
39 | 77.781746 |
40 | 82.40689 |
41 | 87.30706 |
42 | 92.498604 |
43 | 97.998856 |
44 | 103.82617 |
45 | 110 |
46 | 116.54095 |
47 | 123.470825 |
48 | 130.81277 |
49 | 138.59132 |
50 | 146.83238 |
51 | 155.56349 |
52 | 164.81378 |
53 | 174.61412 |
54 | 184.99721 |
55 | 195.99771 |
56 | 207.65234 |
57 | 220 |
58 | 233.0819 |
59 | 246.94165 |
60 | 261.62555 |
61 | 277.18265 |
62 | 293.66476 |
63 | 311.12698 |
64 | 329.62756 |
65 | 349.22824 |
66 | 369.99442 |
67 | 391.99542 |
68 | 415.3047 |
69 | 440 |
70 | 466.1638 |
71 | 493.8833 |
72 | 523.2511 |
73 | 554.3653 |
74 | 587.3295 |
75 | 622.25397 |
76 | 659.2551 |
77 | 698.4565 |
78 | 739.98883 |
79 | 783.99084 |
80 | 830.6094 |
81 | 880 |
82 | 932.3276 |
83 | 987.7666 |
84 | 1046.5022 |
85 | 1108.7306 |
86 | 1174.659 |
87 | 1244.5079 |
88 | 1318.5103 |
89 | 1396.913 |
90 | 1479.9777 |
91 | 1567.9817 |
92 | 1661.2188 |
93 | 1760 |
94 | 1864.6552 |
95 | 1975.5332 |
96 | 2093.0044 |
97 | 2217.4612 |
98 | 2349.318 |
99 | 2489.0159 |
100 | 2637.0205 |
101 | 2793.826 |
102 | 2959.9553 |
103 | 3135.9634 |
104 | 3322.4375 |
105 | 3520 |
106 | 3729.3103 |
107 | 3951.0664 |
108 | 4186.009 |
109 | 4434.9224 |
110 | 4698.636 |
111 | 4978.0317 |
112 | 5274.041 |
113 | 5587.652 |
114 | 5919.9106 |
115 | 6271.927 |
116 | 6644.875 |
117 | 7040 |
118 | 7458.6206 |
119 | 7902.133 |
120 | 8372.018 |
121 | 8869.845 |
122 | 9397.272 |
123 | 9956.063 |
124 | 10548.082 |
125 | 11175.304 |
126 | 11839.821 |
127 | 12543.854 |
This condition is signalled in cases where the assembly of a rack fails, because for example a module name is not unique, a module is malformed, a patch is added for a non-existing module, a patch is added to an already patched socket and so on.
(asdf:test-system :cl-synthesizer)
~/cl-synthesizer-examples/
(asdf:load-system :cl-synthesizer/examples)
(cl-synthesizer-examples::run-examples)
~/cl-synthesizer-profiler/
(asdf:load-system :cl-synthesizer/profiling)
(cl-synthesizer-profiling::run-all)
(asdf:load-system :cl-synthesizer/doc)
(cl-synthesizer-make-doc::make-doc)
Envelope generation has been inspired by dhemery