Overtone is an open source audio environment which uses Clojure and SuperCollider (an environment and programming language for real time audio synthesis and algorithmic composition).
Overtone makes it very easy to define instruments based on sound samples from freesound.
We will walk through getting samples for a flute and then using them to define the flute instrument in Overtone.
The Flute
Getting the samples
Firstly we need to manually grab all the IDs from freesound. Sounds are often grouped into packs making this a little less painful.
A little Ruby script I use to produce a map of freesound ids to notes (which are usually in the filename of the sample):
123456789101112131415161718192021
#!/usr/bin/env ruby -wrequire'rubygems'require'json'raiseException.new("You must pass in the pack id!")unlessARGV[0]pack_id=ARGV[0]note_extractor=λ{|filename|filename.gsub(/Transverse-Flute |\.wav|Tenuto|NonVibrato|-/,"").gsub(/ NonVibrato/,"")}data=JSON.parse(`curl --silent "http://www.freesound.org/api/packs/#{pack_id}/sounds/?api_key=47efd585321048819a2328721507ee23"`)number_of_pages=data["num_pages"]||1number_of_pages.timesdo|page|data=JSON.parse(`curl --silent "http://www.freesound.org/api/packs/#{pack_id}/sounds/?api_key=47efd585321048819a2328721507ee23&p=#{page+1}"`)sound_ids=data["sounds"].map{|sound|sound["id"]}note_names=data["sounds"].map{|sound|note_extractor.call(sound["original_filename"])}sound_ids.each_with_indexdo|sound_id,index|puts"#{sound_id} :#{note_names[index]}"endend
Adding these to our Clojure code we have a Freesound ID to note mapping:
12345678
(def FREESOUND-VIBRATO-FLUTE-SAMPLES"Freesound ids for all samples in the Vibrato Transverse Flute pack"{154274:C7154273:B6154272:A#6154271:A6154270:G#6154269:G6154268:F#6154267:F6154266:E6154265:D#6154264:D6154263:C#6154262:C6154261:B5154260:A#5154259:A5154258:G#5154257:G5154256:F#5154255:F5154254:E5154253:D#5154252:D5154251:C#5154250:C5154249:B4154248:A#4154247:A4154246:G#4154245:G4154244:F#4154243:E4154242:F4154241:D#4154240:D4154239:C#4154238:C4})
These samples are cached in the asset store (usually ~/.overtone/assets) so we don’t have to keep downloading them. We define a cache key for our downloaded samples.
1234
(defn- registered-samples"Fetch flute samples from the asset store if they have been manually registered"[](registered-assets::TransverseFluteTenutoVibrato))
Load all the samples into Overtone. This will automatically download the samples if they are not already present.
Now we can generate the buffer id to midi note map.
123456789
(defn- note-index"Returns a map of midi-note values [0-127] to buffer ids."[buffers](reduce (fn [index buf](let [id(-> buf:id)note(buffer->midi-notebuf)](assoc index noteid))){}buffers))
And we use this to set the buffers:
12345678910
;Silent buffer used to ensure all 127 buffer spaces are filled (defonce ^:privatesilent-buffer(buffer0))(defonce index-buffer(let [tab(note-indexflute-samples)buf(buffer128)](buffer-fill!buf(:idsilent-buffer))(doseq [[idxval]tab](buffer-set!bufidxval))buf))
Defining the Instrument
Now we have all our samples we define a instrument in Overtone using definst:
This allows us to specify a (huge) number of options on how our samples are played.
There is quite a lot going on here. Lets focus on the configuration that effects how our sound samples are played.
Defining the Envelope Generator (env-gen).
An envelope generator (mapping to function env-gen) makes an audio signal that smoothly rises and falls. We are taking the recording of the real flute instrument as a digitized waveform, and then playing back its recordings at different speeds to produce different tones.
The most common form of envelope generator and the one we are using here is ADSR:
attack (A), decay (D), sustain (S) and release (R)
There are a number of other config settings in our example:
123456789101112
[note60; Default pitchlevel1; Volumerate1; Playback rate loop?0; The note should loop after the first playattack0; The way a sound is initiated. Fast attack (gunshot) vs Slow attack (closing a door slowly)decay1; The time for notes to decay after the initial strikesustain1; Once a sound has reached its peak, the length of time that the sound will sustain.release0.1; The time for notes to decay after the key is releasedcurve-4; Curve factorgate1; note occurs when gate goes from nonpositive to positive. Note off occurs when it goes from positive to nonpositive]
Look to the SuperCollider documentation on UGENs if you want to see all possible configuration options. Overtone is really a wrapper around the extremely powerful SuperCollider.
Grand finale
Finally we can start to play music in overtone using our flute:
(ns overtone.samples.flute-vibrato(:use[overtone.core])(:require[clojure.string:asstr]))(defn- registered-samples"Fetch flute samples from the asset store if they have been manually registered"[](registered-assets::TransverseFluteTenutoVibrato))(def FREESOUND-VIBRATO-FLUTE-SAMPLES"Freesound ids for all samples in the Vibrato Transverse Flute pack"{154274:C7154273:B6154272:A#6154271:A6154270:G#6154269:G6154268:F#6154267:F6154266:E6154265:D#6154264:D6154263:C#6154262:C6154261:B5154260:A#5154259:A5154258:G#5154257:G5154256:F#5154255:F5154254:E5154253:D#5154252:D5154251:C#5154250:C5154249:B4154248:A#4154247:A4154246:G#4154245:G4154244:F#4154243:E4154242:F4154241:D#4154240:D4154239:C#4154238:C4})(def FLUTE-SAMPLE-IDS(keys FREESOUND-VIBRATO-FLUTE-SAMPLES))(def flute-samples(doall (map freesound-sampleFLUTE-SAMPLE-IDS)))(defn- buffer->midi-note[buf](-> buf:freesound-idFREESOUND-VIBRATO-FLUTE-SAMPLESname note))(defn- note-index"Returns a map of midi-note values [0-127] to buffer ids."[buffers](reduce (fn [index buf](let [id(-> buf:id)note(buffer->midi-notebuf)](assoc index noteid))){}buffers));; Silent buffer used to fill in the gaps.(defonce ^:privatesilent-buffer(buffer0))(defonce index-buffer(let [tab(note-indexflute-samples)buf(buffer128)](buffer-fill!buf(:idsilent-buffer))(doseq [[idxval]tab](buffer-set!bufidxval))buf))