"This is my assistant, the awful DYNNE," said Dr. Dischord. "You must forgive his appearance, for he really doesn't have any."
From The Phantom Tollbooth, by Norton Juster
A library for working with audio data. Supports manipulating, playing, visualizing, and saving sounds. Sounds can be read in .wav and .mp3 formats, and saved in .wav format.
Available on Clojars. Put
this in your
The basic concept in dynne is the sound. A sound is the combination
of a duration, a number of channels, and a deferred way to get the
amplitudes of all the channels. There is a protocol,
dynne.sampled-sound/SampledSound that gives these capabilities
chunks returns a Clojure seq of chunks. A chunk is a seq
of Java primitive double arrays, one per channel. All arrays in the
chunk will be the same length. Amplitudes are represented as
double-precision floating point numbers, which will be clipped to -1.0
and 1.0 (inclusive) when a sound is saved (via save ).
Constructors for sounds are given for WAV and MP3 files ( read-sound ), and for arbitrary functions ( fn-sound ).
Processor functions accept one or more sounds and return a new sound. Combination is by functional composition, so combining two sounds does not result in the underlying sound being sampled - computation of the combined operation is deferred until the combined sound is sampled.
(require '[dynne.sampled-sound :refer :all]) ;; Or, to use the API based on core.async ;; (require '[dynne.async-sound :refer :all]) ;; Create a simple one-second, 440 Hz sine wave sound (def s (sinusoid 1.0 440)) ;; See what it looks like (visualize s) ;; See what the first 0.01 seconds look like (visualize (trim s 0 0.01)) ;; Play it. Maybe turn down your volume a bit first. :) (play s) ;; Define a new sound that fades `s` in over 0.5 seconds (def s2 (fade-in s 0.5)) ;; Visualize that (visualize s2) ;; Get the double array holding the raw amplitude data of the first ;; channel of the first chunk. Note that we have to pass a sample ;; rate. File-based sounds will be converted to this rate. (ffirst (chunks s2 16000)) ;; => #<double [D@53b7f3b2> ;; Build up a more complicated sound (def l (-> (sinusoid 3.0 440) (fade-in 1.5))) (def r (-> (square-wave 3.0 880) (fade-out 1.5))) (def s3 (-> (->stereo l r) (pan 0.3))) ;; And play it (play s3) ;; And save it as a 44.1 KHz WAV (save s3 "sample.wav" 44100) ;; Load it back in (def s4 (read-sound "sample.wav")) ;; read-sound also works with MP3 files, but we can only save to WAV ;; Make a sound of our own design: two seconds of stereo white noise (def s5 (fn-sound 2.0 2 (fn ^double [^long c ^double t] (- (rand 2.0) 1.0)))) ;; Play it too (play (gain s5 0.1))
A moderate performance improvement in
Introduces a new namespace,
dynne.async-sound, which has sounds
generate chunks in separate threads, with handoff between threads via
core.async. This should
result in performance improvements for larger audio streams on systems
with multiple cores.
The new namespace is API-compatible with the
the only change required should be to switch to requiring the new
Major overhaul. Deprecate
dynne.sound namespace in favor of
dynne.sampled-sound, which uses a more efficient, chunked
representation of sounds leveraging Java native arrays for way higher
Why did you build this?
I produce the Relevance podcast. We talk about Clojure a lot, so I thought I would see if I could automate the podcast production using Clojure. You can see the in-progress product of that effort here. Along the way, a general-purpose audio processing library started to fall out of it. So I busted it out into its own thing, which is now dynne.
Why didn't you just use Overtone?
Mainly because I started out just playing around with the idea and at some point crossed into being completely obsessed with writing my own thing. I never even looked at whether Overtone can do what I need, because I knew if I did, and it could, I would stop working on the problem, and I was having a lot of fun.
In short: if Overtone does anything like this, you should use it. It is likely to be much, much better than dynne.
Thanks to Stuart Sierra for helping me with some of the weirder problems I hit, especially around compiler errors.
Thanks to Zach Tellman for his primitive-math library, which was helpful in finding sub-optimal usages of numeric operations.
Thanks to Rich Hickey for his suggestion to rewrite dynne in terms of sequences of double arrays, which led to a massive increase in performance.
Thanks to Prismatic for their hiphip library, which was really helpful in writing the most out of the operations at the core of dynne.
Thanks to tommyettinger for his fork of hiphip that works around an issue with AOT.
Copyright © 2013 Craig Andera
Distributed under the Eclipse Public License, the same as Clojure.