Create Spectrograms of SuperCollider Code using Sox

This first installment of my algorithms project has one primary goal, to automate as much as possible the creation of spectrogram files for use online and in teaching materials. For this and for all future posts I will upload the source files (only my original code and none of the artifacts of running said code) to my github here: https://github.com/scottericpetersen/algorithms.

For those who aren’t familiar with spectrograms, they are visual representations of the frequencies present in a sound or piece of music. The process whereby the sound is analyzed and converted to visual representation is called a DFT or FFT (depending on the algorithm used.) The FFT, Fast Fourier Transform, converts time-domain signals to the frequency domain. J.C. Risset famously used analog spectrometers in the mid-to-late 60s to analyze and synthesize instruments such as drums, trumpets and flutes. He wrote subroutines for the MUSIC V language to make working with the sounds easier and released all of the code along with sound examples in the form of a 120 page book with LP((This document (mentioned here: https://liberalarts.utexas.edu/france-ut/_files/pdf/resources/risset_2.pdf) can be found “around” the internet. It was apparently (initially?) “published” out of Murray Hill NJ (Bell Labs) in 1969, but with no formal catalog number (ISBN, etc) for the publication and accompanying recording (which itself can be found on youtube.com). They are variously listed around the ‘net with publishing dates of 1969, 1971 and 1979. However, the sound recordings (along with the document as part of the CD booklet) can be found on Wergo: WER 2033-2 Wergo, 282 033-2 Wergo, the Historical CD of Digital Sound Synthesis.)). Perhaps more on FFT in a future post. For now it’s only important to know that spectrograms are useful for visualizing and conceptualizing what is actually happening with a sound so we can understand it better.

One note: these tutorials are neither basic nor are they “how-to” posts for learning SuperCollider (or any other programming language I mention.) To get anything from these posts you will need to have at least basic working knowledge of SuperCollider and sound synthesis techniques. If you are looking for a good introduction, I highly recommend Bruno Ruviaro’s A Gentle Introduction to SuperCollider.

Spectrograms

The program I will use to create the spectrogram itself is Sox. Sox is a fantastic application to play, analyze, convert and otherwise process digital sound files. Conversion and basic editing (especially performed by batch processing) is critical if you are going to be doing a lot of sound file generation. Batch editing to normalize, remove silence at the beginning and end of files as well as adding fades is common as doing these manually can be laborious. The CLI tools available on *nix systems are also available through the new Linux-on-windows products now being offered by Micro$oft.

You can install Sox on any (deb-based) Linux system with the command:

$ sudo apt install sox

On a Mac, you should use brew, available here. Then:

$ brew install sox

These are the sox options I’ll mostly use for this site.

$ sox insoundfile.wav -n spectrogram -x 2048 -z 90 -m -l -r -s -w hamming -o outsoundfile.png

The complete options for the ‘spectrogram’ function can be found here.

The most practically useful of the options concerning visual appearance are the -x, -z, and -m options. These are the width (in pixels) of the output file, the dynamic range (read: contrast) and the -m option specifies monochrome because that’s my thing. Of course, to create a spectrogram we must have an audio file (or stream.)

Preparing SuperCollider Code

Usually, SuperCollider code is run interactively from the IDE by selecting chunks of code and evaluating them to produce various results. For batch processing and flexibility it is sometimes advantageous to not have to open the IDE at all. Regardless, there are a couple ways to record the output of your SC3 code, but what follows is designed for either a one-click solution from the IDE or by running from the CLI. ((An aside: if you find yourself struggling to find those SuperCollider files you are looking for, you might have luck with $ grep –include=\*.{scd} -rnw ‘/path/to/somewhere/’ -e “pattern”))

Here we have options:

1. Run and record SuperCollider code from within the IDE
2. Run the code from the command line using sclang (requires special formatting of your SC3 code)

The following code is formatted in a one-click configuration that doesn’t require the user to evaluate multiple expressions. Simply select-all and evaluate. A couple of important things here:

1) Wrapping everything in a .waitForBoot function returns a Routine. This is advantageous for a number of reasons. First, you can have timed .wait statements to fire off code at successive intervals. Second, it allows the use of the .sync statement which allows all asynchronous statements to complete before allowing the code to continue. This is absolutely critical because calling .record, for example, in the below code without the s.sync statement results in an error. This is also critical when registering SynthDefs and calling Synths. If there isn’t sufficient time for the SynthDef file to be written, calling the Synth will result in an error.

2) This format can be used to run code from the command line using sclang precluding user-interaction requirements from the IDE. This is good when 1) you want to run code from a headless machine (Raspberry Pi), 2) you want code to be executed by another process, say Cron, and/or 3) when you want to execute a lot of code, potentially in multiple files and you want to run batch processes rather than one-offs.

One critical component of the code is the last line 0.exit; which causes sclang to exit once the Routine has completed. If this statement is missing, the sclang executable will remain open in the CLI or virtual terminal.

s.waitForBoot {

var runtime = 4.0; // the total length of the recording: required to stop recording and free sclang
var title = "algos"; // alternate title for testing CLI/sclang
var time = "time".unixCmd.asString; // a unique (ish) number to keep overwrites from happening.

r = Recorder.new(s);
r.recHeaderFormat = "WAV";
r.recSampleFormat = "int24";
r.prepareForRecord("".resolveRelative ++ title ++ time ++ ".wav", 1);

s.sync;

r.record(duration: runtime);

Pbind(\instrument, \default, \degree, Pseq([4,2,0], 1), \dur, 1, \amp, 1).play(); // example: descending major triad

runtime.wait; // wait until the code has executed

"end".postln;
0.exit; // frees sclang: only use if running code from the CLI using sclang

}

I have saved the above code in a file named ‘sep-dot-com-algorithms-post-1-sc3-code.scd’. The above can be run as follows:

$ sclang sep-dot-com-algorithms-post-1-sc3-code.scd

The last of the CLI ouput shows:

Preparing recording on 'localhost'
Usage: time [-apvV] [-f format] [-o file] [--append] [--verbose]
[--portability] [--format=format] [--output=file] [--version]
[--quiet] [--help] command [arg...]
Recording channels [ 0 ] ...
path: './algos16167.wav'
Recording Stopped: (algos16167.wav)
end
server 'localhost' disconnected shared memory interface
'/quit' message sent to server 'localhost'.
cleaning up OSC

This created a sound file named algos16167.wav which sounds like this:

I can then run my sox line to create a spectrogram:

$ sox algos16167.wav -n spectrogram -x 1024 -z 90 -m -l -r -s -w hamming -o algos16167.png

This creates a spectrogram file named algos16167.png that looks like this:

If I run it with the following, it creates a color version. Not to my taste, but…

$ sox algos16167.wav -n spectrogram -x 1024 -z 90 -l -r -s -w hamming -o algos16167_color.png

And here it is:

Putting them Together

Without going as far as writing a bash script to automate the entire fabric of spacetime, we could put the above all in one file and run the sox spectrogram generation from sclang. I’ve changed a few things, not completely optimized or fool-proof, but a little more streamlined. I’ve also removed the -r and -m options to show you what the spectrogram looks like with the xy labels sox uses. Of particular interest here is the use of .escapeChar($ ) with the paths when using .unixCmd. You do not need this when generating and using system paths in SC3, but you do need to add the escape character when using paths generated in SC3 in the CLI. This method simply prepends the space in the path with the escape character \.

/*
The following code will generate a sound file then use the sox command-line tool to create a spectrogram of that sound file.

#sox, #oneclick, #runtime, #s.sync, #spectrogram
(c) scott petersen 2020
*/

s.waitForBoot {

var runtime = 3.5;
var title = "algos" ++ "time".unixCmd.asString;
var path = "".resolveRelative ++ title ++ ".wav";

r = Recorder.new(s);
r.recHeaderFormat = "WAV";
r.recSampleFormat = "int24";
r.prepareForRecord(path, 1);

s.sync;

r.record(duration: runtime);

Pbind(\instrument, \default, \degree, Pseq([4,2,0], 1), \dur, 1, \amp, 1).play();

runtime.wait;

"Creating Spectrogram:".postln;

("sox" + path.escapeChar($ ) + "-n spectrogram -x 1024 -z 90 -l -s -w hamming -o" + path.escapeChar($ ) ++ ".png").unixCmd { |res, pid| [\done, res, pid].postln };

0.exit; // frees sclang: only use if running code from the CLI using sclang

}

Output:

Preparing recording on 'localhost'
Recording channels [ 0 ] ... 
path: '/home/nodenoise/Google Sync/External Sync/sep-dot-com-post-files/algos17712.wav'
Creating Spectrogram:
Recording Stopped: (algos17712.wav)
[ done, 0, 18261 ]

Spectrogram:

And there you have it. Some nice, reusable tools to make uploading posts with sounds and spectrograms to your (my) WordPress blog a little less onerous.

Stay tuned for the next algo post where I will talk about semi-random multi-modulator LFOs (or SRMMLFOs as I like to call them for short…) for controlling various synthesis parameters. Riveting stuff!