RSound: An Adequate Sound Engine for Racket
A note about volume: be careful not to damage your hearing, please. To take a simple example, the sine-wave function generates a sine wave with amplitude 1.0. That translates into the loudest possible sine wave that can be represented. So please set your volume low, and be careful with the headphones. Maybe there should be a parameter that controls the clipping volume. Hmm.
1 Sound Control
These procedures start and stop playing sounds and loops.
2 Sound I/O
These procedures read and write rsounds from/to disk.
The RSound library reads and writes WAV files only; this means fewer FFI dependencies (the reading & writing is done in Racket), and works on all platforms.
procedure
path : path-string?
It currently has lots of restrictions (it insists on 16-bit PCM encoding, for instance), but deals with a number of common bizarre conventions that certain WAV files have (PAD chunks, extra blank bytes at the end of the fmt chunk, etc.), and tries to fail relatively gracefully on files it can’t handle.
Reading in a large sound can result in a very large value (~10 Megabytes per minute); for larger sounds, consider reading in only a part of the file, using rs-read/clip.
procedure
(rs-read/clip path start finish) → rsound?
path : path-string? start : nonnegative-integer? finish : nonnegative-integer?
It currently has lots of restrictions (it insists on 16-bit PCM encoding, for instance), but deals with a number of common bizarre conventions that certain WAV files have (PAD chunks, extra blank bytes at the end of the fmt chunk, etc.), and tries to fail relatively gracefully on files it can’t handle.
procedure
(rs-read-frames path) → nonnegative-integer?
path : path-string?
The file must be encoded as a WAV file readable with rsound-read.
procedure
(rs-read-sample-rate path) → number?
path : path-string?
The file must be encoded as a WAV file readable with rs-read.
procedure
rsound : rsound? path : path-string?
3 Rsound Manipulation
These procedures allow the creation, analysis, and manipulation of rsounds.
struct
(struct rsound (data sample-rate) #:extra-constructor-name make-rsound) data : s16vector? sample-rate : nonnegative-number?
procedure
(rsound-equal? sound1 sound2) → boolean?
sound1 : rsound? sound2 : rsound?
This procedure is necessary because s16vectors don’t natively support equal?.
procedure
(rs-ith/left rsound frame) → nonnegative-integer?
rsound : rsound? frame : nonnegative-integer?
procedure
(rs-ith/right rsound frame) → nonnegative-integer?
rsound : rsound? frame : nonnegative-integer?
procedure
rsound : rsound? start : nonnegative-integer? finish : nonnegative-integer?
procedure
(rs-append* rsounds) → rsound?
rsounds : (listof rsound?)
procedure
(rs-overlay rsound-1 rsound-2) → rsound?
rsound-1 : rsound? rsound-2 : rsound?
procedure
(rs-overlay* rsounds) → rsound?
rsounds : (listof rsound?)
So, suppose we have two rsounds: one called ’a’, of length 20000, and one called ’b’, of length 10000. Evaluating
(rs-overlay* (list (list a 5000) (list b 0) (list b 11000)))
... would produce a sound of 21000 frames, where each instance of ’b’ overlaps with the central instance of ’a’.
4 Signals
A signal is a function mapping a frame number to a real number in the range -1.0 to 1.0. There are several built-in functions that produce signals.
procedure
frequency : nonnegative-number? sample-rate : nonnegative-number?
procedure
(sawtooth-wave frequency sample-rate) → signal?
frequency : nonnegative-number? sample-rate : nonnegative-number?
procedure
(square-wave frequency sample-rate) → signal?
frequency : nonnegative-number? sample-rate : nonnegative-number?
In order to listen to them, you can transform them into rsounds, or play them directly:
procedure
(mono-signal->rsound frames signal) → rsound?
frames : nonnegative-integer? signal : signal?
Here’s an example of using it:
(define samplerate 44100) (define sr/inv (/ 1 samplerate)) (define (sig1 t) (* 0.1 (sin (* t 560 twopi sr/inv)))) (define r (mono-signal->rsound (* samplerate 4) sig1)) (play r)
Alternatively, we could use sine-wave to achieve the same result:
(define samplerate (default-sample-rate)) (define r (mono-signal->rsound (* samplerate 4) (scale 0.1 (sine-wave 560 samplerate)))) (play r)
procedure
(signals->rsound frames left-fun right-fun) → rsound?
frames : nonnegative-integer? left-fun : signal? right-fun : signal?
procedure
(signal-play signal sample-rate?) → void?
signal : signal? sample-rate? : positive-real?
procedure
proc : procedure? args : (listof any/c)
So, for instance, if we defined the function flatline as
(define (flatline t l) l)
... then (signal flatline 0.4) would produce the same result as (dc-signal 0.4).
There are also a number of functions that combine existing signals, called "signal combinators":
We can turn an rsound back into a signal, using rsound->signal:
procedure
(rsound->signal/left rsound) → signal?
rsound : rsound?
procedure
(rsound->signal/right rsound) → signal?
rsound : rsound?
procedure
(thresh/signal threshold signal) → signal?
threshold : real-number? signal : signal?
procedure
(clip&volume volume signal) → signal?
volume : real-number? signal : signal?
Where should these go?
procedure
(thresh threshold input) → real-number?
threshold : real-number? input : real-number?
Finally, here’s a predicate. This could be a full-on contract, but I’m afraid of the overhead.
5 Visualizing Rsounds
(require (planet clements/rsound:3:=3/draw)) |
procedure
(rs-draw rsound #:title title [ #:width width #:height height]) → void? rsound : rsound? title : string? width : nonnegative-integer? = 800 height : nonnegative-integer? = 200
procedure
(rsound-fft-draw rsound #:zoom-freq zoom-freq #:title title [ #:width width #:height height]) → void? rsound : rsound? zoom-freq : nonnegative-real? title : string? width : nonnegative-integer? = 800 height : nonnegative-integer? = 200
procedure
(vector-pair-draw/magnitude left right #:title title [ #:width width #:height height]) → void? left : (vectorof complex?) right : (vectorof complex?) title : string? width : nonnegative-integer? = 800 height : nonnegative-integer? = 200
procedure
(vector-draw/real/imag vec #:title title [ #:width width #:height height]) → void? vec : (vectorof complex?) title : string? width : nonnegative-integer? = 800 height : nonnegative-integer? = 200
6 RSound Utilities
procedure
(make-harm3tone frequency volume? frames sample-rate) → rsound? frequency : nonnegative-number? volume? : nonnegative-number? frames : nonnegative-integer? sample-rate : nonnegative-number?
procedure
pitch : nonnegative-number? volume : nonnegative-number? duration : nonnegative-exact-integer?
procedure
(rsound-fft/left rsound) → (vectorof complex?)
rsound : rsound?
procedure
(rsound-fft/right rsound) → (vectorof complex?)
rsound : rsound?
procedure
(midi-note-num->pitch note-num) → number?
note-num : nonnegative-integer?
procedure
(fir-filter delay-lines) → procedure?
delay-lines : (listof (list/c nonnegative-exact-integer? real-number?))
So, for instance,
...would produce a filter that added the current frame to 4/10 of the input frame 13 frames ago and 1/10 of the input frame 4 frames ago.
procedure
(iir-filter delay-lines) → procedure?
delay-lines : (listof (list/c nonnegative-exact-integer? real-number?))
So, for instance,
...would produce a filter that added the current frame to 4/10 of the output frame 13 frames ago and 1/10 of the output frame 4 frames ago.
7 Frequency Response
procedure
(response-plot poly dbrel min-freq max-freq) → void?
poly : procedure? dbrel : real? min-freq : real? max-freq : real
procedure
(poles&zeros->fun poles zeros) → procedure?
poles : (listof real?) zeros : (listof real?)
(response-plot (poles&zeros->fun '(0.5 0.5+0.5i 0.5-0.5i) '(0+1i 0-1i)) 40 0 22050)
8 Filtering
(define (control f) (+ 0.5 (* 0.2 (sin (* f 7.123792865282977e-05))))) (define (sawtooth f) (/ (modulo f 220) 220)) (play (mono-signal->rsound 88200 (lpf/dynamic control sawtooth)))
9 Single-cycle sounds
procedure
(synth-note family spec midi-note-number duration) → rsound family : string? spec : number-or-path? midi-note-number : natural? duration : natural?
(synth-note "vgame" 49 60 22010)
procedure
(synth-note/raw family spec midi-note-number duration) → rsound family : string? spec : number-or-path? midi-note-number : natural? duration : natural?
10 Stream-based Playing
procedure
(current-time/s) → natural?
11 Reporting Bugs
For Heaven’s sake, report lots of bugs!