thought i would post this small command-line tool for spectral analysis. it’s quite basic and low level, but is a kind of thing that i often find useful in computer music practice, and it’s nice to have a portable, self-contained, scriptable and efficient implementation.
the tool accepts a soundfile, performs a short-term fourier transform, and emits the peaks of the power spectrum for each analysis frame, alongside some statistical measures.
usage, examples
(from readme):
dowser <infile> <outfile>
output file is a supercollider script, defining a list of dictionaries.
each dictionary contains data for a single spectral analysis frame.
per-frame measurements includes:
-
peaks
: list of events with(hz, mag)
keys, listing spectral peaks for the given frame -
papr
: peak-to-average-power ratio, a measure of “tonal-ness” -
flatness
: AKA weiner entropy, geometric mean / arithmetic mean. another measure of “tonalness” -
centroid
: spectral centroid, a measure of “brightness”
example
starting with this short loop of electric viola:
[ http://catfact.net/dowser/loop2.mp3 ]
dowser
produces this fairly large (286KB) supercollider script describing the analysis data:
[ http://catfact.net/dowser/dowser-output-loop2.scd.txt ]
which can then be processed in whatever way you find useful. the following is not very well-considered as a musical algorithm - it is sort of a naive and basic resynthesis of the spectral peaks as sine waves. but it gives a good idea of the nature of this data. (and its limitations! e.g. the relative noisiness of low frequencies)
script:
supercollider script
// load the data
(
post("reading data... ");
~data = this.executeFile(PathName(Document.current.path).pathOnly++"dowser-output-loop2.scd");
postln("done.");
AppClock.sched(0, {
~data.collect({arg frame; frame.papr}).histo.plot;
~data.collect({arg frame; frame.flatness}).histo.plot;
~data.collect({arg frame; frame.peaks.collect({ arg peak; peak[\hz].cpsmidi})}).flatten.histo.plot;
nil
});
)
// play some tones
(
s = Server.default;
s.boot;
s.waitForBoot {
r = Routine {
~frame_stretch = 4;
/// magic numbers here:
// 2**13 = fft size
// 2 = overlap factor
// 48k = original samplerate of analyzed file
~frame_period = (2**12) / 48000.0;
~frame_period = ~frame_period * ~frame_stretch;
~frame_period.postln;
b = Bus.audio(s, 2);
SynthDef.new(\sine_1shot, {
arg out=0, amp=0, hz=110, pan=0, atk=1, sus=0, rel=2;
var snd, env;
env = EnvGen.ar(Env.linen(atk, sus, rel), doneAction:2);
snd = SinOsc.ar(hz) * amp * env;
Out.ar(out, Pan2.ar(snd, pan));
}).send(s);
~out_limit = {
Out.ar(0, Limiter.ar(In.ar(b, 2), 0.9, 0.2).clip(-1, 1))
}.play(s);
s.sync;
~papr_min = 20;
~flat_max = 20;
~mag_min = 1;
~hz_max = 3000;
~max_peaks_per_frame = 3;
~data.do({
arg frame;
var flat, papr;
flat = frame[\flatness];
papr = frame[\papr];
if ((flat < ~flat_max) && (papr > ~papr_min), {
frame[\peaks].do({
arg peak, i;
var hz, mag, sineIdx, amp;
//peak.postln;
hz = peak[\hz];
mag = peak[\mag];
if (i < ~max_peaks_per_frame, {
if ((mag > ~mag_min) && (hz < ~hz_max), {
var db;
amp = (mag / 64);
db = amp.ampdb;
postln([hz, db]);
Synth.new(\sine_1shot, [
\out, b,
\hz, hz,
\amp, amp,
\pan, i.linlin(0, ~max_peaks_per_frame, 0, 1).rand2,
\atk, ~frame_period * 4,
\dur, ~frame_period * db.linlin(-60, 0, 4, 16),
\rel, ~frame_period * db.linlin(-60, 0, 4, 32)
], s, \addToHead);
});
});
});
});
~frame_period.wait;
});
}.play;
}
)
(note that this must be run in two sections.)
resulting audio:
[ http://catfact.net/dowser/loop2-resynth.mp3 ]
[warning: woops, i “mastered” the original loop rather quietly so the resynthesis is quite loud by comparison]
where to get it
github : https://github.com/catfact/dowser
i have not created any binary release packages and am not particularly planning to, because frankly it has become painful to do this for macOS and windows. if there is interest i could make source packages for e.g. homebrew
and chocolatey
.
roadmap
the readme mentions a few more wish-haves. but the most pressing would be:
- support for other output formats. (.csv and .npy come to mind.)
- expose more tuning parameters (especially frequency range.)
happy to accept contributions. i made the app using JUCE and cmake, not because either are necessary (it is a simple project,) but to facilitate cross-platform development (have tested on macOS, windows 10 and ubuntu,) and the potential addition of higher-level features.
and, please use this topic for questions, ideas etc!