hassound: generating musical instrument sounds in haskell

24
HasSound: Generating Musical Instrument Sounds in Haskell Paul Hudak Yale University Department of Computer Science Joint work with: Matt Zamec (Yale ‘00) Sarah Eisenstat (Brown ‘08) NEPLS, Brown University October 27, 2005

Upload: ona

Post on 14-Jan-2016

30 views

Category:

Documents


2 download

DESCRIPTION

HasSound: Generating Musical Instrument Sounds in Haskell. Paul Hudak Yale University Department of Computer Science Joint work with: Matt Zamec (Yale ‘00) Sarah Eisenstat (Brown ‘08). NEPLS, Brown University October 27, 2005. Haskore/HasSound Pictorial. Haskore. HasSound. - PowerPoint PPT Presentation

TRANSCRIPT

Page 1: HasSound: Generating Musical Instrument Sounds in Haskell

HasSound:Generating Musical Instrument Sounds

in Haskell

Paul HudakYale University

Department of Computer Science

Joint work with:

Matt Zamec (Yale ‘00)

Sarah Eisenstat (Brown ‘08)

NEPLS, Brown UniversityOctober 27, 2005

Page 2: HasSound: Generating Musical Instrument Sounds in Haskell

Haskore/HasSound PictorialHaskore

MIDI File Player(uses synthesizer on sound card)

HasSound

csound

Sound File Player(D/A conversion)

MIDI File(note level)

csoundscore file(note level)

wav/snd/mp3 file(signal sample level)

csoundorchestra file

(signal processingdescription)

small small small

large

Page 3: HasSound: Generating Musical Instrument Sounds in Haskell

Background• Haskore is a Haskell library for describing music at a

high level of abstraction.• MIDI is a low-level representation of music at the note

level.• csound is a low-level DSL both for describing music at

the note level, and for defining instruments that generate sounds in a signal-processing framework.

• The Haskore implementation translates high level descriptions into either MIDI or (the note-level version of) csound. (MIDI instruments are pre-defined.)

• What’s missing is a way to define instruments in Haskell.• HasSound fills this gap. The HasSound implementation

translates high-level sound descriptions into (the sound-generating version of) csound.

Page 4: HasSound: Generating Musical Instrument Sounds in Haskell

Haskore

• The primitive element in Haskore is a note (all higher-level concepts will be ignored).

• A note consists of a pitch, a duration, and one or more instrument-dependent parameters, or“p-fields” (which encode things like volume, vibrato, tremolo, reverb, etc).

• The instrument must know how to handle these:– In MIDI, only volume is allowed.– But in csound, the instrument is user-defined – so any

number of p-fields can be accommodated.

Page 5: HasSound: Generating Musical Instrument Sounds in Haskell

HasSound• A HasSound program describes an orchestra.• An orchestra consists of a header and a

collection of instruments.• Each instrument consists of:

1. An instrument number.2. A note extension.3. A signal expression describing the output of the

instrument in terms of pitch, duration, & p-fields.4. A list of global signals to which values (expressed as

signal expressions) are communicated.

• The key concept (3) above.

Page 6: HasSound: Generating Musical Instrument Sounds in Haskell

Example 1

Monophonic output of a 440 hertz using wavetable 1 (a sine wave) at amplitude 10,000, and sampled at the audio rate:

mono (oscil ar 10000 440 1)

Ok, so that’s not very interesting…

Page 7: HasSound: Generating Musical Instrument Sounds in Haskell

Score Files and Orchestra Files

f1 0 4096 10 1

i2 0 1 2000 880i2 2 1 4000 440i2 3 2 8000 220i2 4 1 16000 110i2 6 1 32000 150

e

<header>

Instr 1…endin

instr 2a1 oscil p4, p5, 1 out a1endin

Score file (.sco) Orchestra file (.orc)

P-fields 4 and 5 (p4 and p5)

Instrumentnumber (2)

start timeduration

p4 = volumep5 = frequency

Page 8: HasSound: Generating Musical Instrument Sounds in Haskell

A Bigger ExampleThis example chooses one of four waveforms (sine, sawtooth, square,

or pulse), and adds chorusing (detuned signals), vibrato (frequency modulation), and various kinds of envelopes.

• Let’s set up the p-fields as follows:– p3 = duration p6 = attack time p9 = vibrato depth– p4 = amplitude p7 = release time p10 = vibrato delay (0-1)– p5 = pitch p8 = vibrato rate p11 = waveform

selection

let irel = 0.01 -- vibrato releaseidel1 = p3 * p10 -- initial delayisus = p3 - idel1 - irel -- sustain timeiamp = ampdb p4 -- amplitudeinote = cpspch p5 -- frequencyk3 = linseg 0 idel1 p9 isus p9 irel 0 -- envelopek2 = oscil k3 p8 1 -- vibrato signalk1 = linen (iamp/3) p6 p3 p7 -- envelopea3 = oscil k1 (inote*0.999+k2) p11 -- chorusing signala2 = oscil k1 (inote*1.001+k2) p11 -- “ “a1 = oscil k1 (inote+k2) p11 -- main signal

in mono (a1+a2+a3) -- result

Page 9: HasSound: Generating Musical Instrument Sounds in Haskell

Executable Specification

oscil1

p8

p5

oscil oscil oscil

linen

cpspch

lineseg

+++

+

xx .9991.001

p11

k2

k1

let … inote = cpspch p5 k2 = oscil k3 p8 1 k1 = linen (iamp/3) p6 p3 p7 a3 = oscil k1 (inote*0.999+k2) p11 a2 = oscil k1 (inote*1.001+k2) p11 a1 = oscil k1 (inote+k2) p11in mono (a1+a2+a3)

a3a2

a1“A picture is worth athousand lines of code…”

inote

k3

Page 10: HasSound: Generating Musical Instrument Sounds in Haskell

csound Orchestra CodeThe previous example is equivalent

to the following csound code:

instr 6 irel = .01 idel1 = p3 * p10 isus = p3 - (idel1 + irel) iamp = ampdb(p4)inote = cpspch(p5) k3 linseg 0, idel1, p9, isus, p9, irel, 0 k2 oscil k3, p8, 1 k1 linen iamp/3, p6, p3, p7 a3 oscil k1, inote*.999+k2, p11 a2 oscil k1, inote*1.001+k2, p11 a1 oscil k1, inote+k2, p11 out a1+a2+a3

endin

This is not bad!

But there are some funny things going on:

• sometimes there is an “equal” sign (=), other times not

• “out” looks like a variable, not a function

• what happens when the order of the “statements” is changed?

Page 11: HasSound: Generating Musical Instrument Sounds in Haskell

Motivation and Goals

• HasSound allows Haskore user to define instru-ments without leaving “convenience” of Haskell.

• HasSound provides simple framework for algorithmic instrument synthesis.

• HasSound is:– as expressive as csound.– purely functional (thus the “value added” is the power

of functional programming).– compilable into csound (for efficiency).

(this is a big constraint!)

Page 12: HasSound: Generating Musical Instrument Sounds in Haskell

Problems

• There are several problems that arise in meeting these goals:– Global variables in csound.– Delay lines in csound.– Imperative glue in csound.– Recursive signals (not allowed in csound).– Csound is enormous (the manual is 1200

pages long!)

• I will talk about some ( ) of these…

Page 13: HasSound: Generating Musical Instrument Sounds in Haskell

Global Variables• Suppose we want reverb that lasts past the end of a note.• One way to do this is to use global variables to

“communicate” to another instrument that is “always on”:

garvb init 0 ; initialize global (in header)

instr 9 … ; instrument using reverbout a1 ; normal outputgarvb = garvb + a1 * rvbgain ; add reverb to global variable

endin

instr 99 ; global reverb instrumentasig reverb garvb, p4 ; compute reverbout asiggarvb = 0 ; then clear global!

endin

Page 14: HasSound: Generating Musical Instrument Sounds in Haskell

Globals in HasSound

• Global variables are replaced by global signals in HasSound. In addition to the normal signal output, an instrument can attach signals to global signal names:

data InstrBlock a = InstrBlockInstr -- instrument numberSigExp -- note extensiona -- normal output

-- (i.e. Mono se, Stereo se1 se2, Quad … )[(GlobalSigName, SigExp)] -- global signals

data GlobalSig = Global(SigExp -> SigExp -> SigExp) -- combining functionInt -- unique identifier

Page 15: HasSound: Generating Musical Instrument Sounds in Haskell

Example in HasSound

So the previous example can be written in HasSound like this:

let gsig = Global (+) 1in [ InstBlock 9 0 (mono a1) [ (gsig, a1*rvbgain) ] , InstBlock 99 0 (mono (reverb (read gsig) p4)) [ ] ]

Note: more than one “instance” of instrument 9 may be active at any given time.

Page 16: HasSound: Generating Musical Instrument Sounds in Haskell

Imperative Glue

• Global variables in csound are not modular – if you combine different instrument definitions, global variable names may clash.

• So in HasSound we introduce a monad of gobal signal names at the top level to ensure modularity. For example:

let a1 = oscI AR (tableNumber 1) 1000 440comp = do h <- mkSignal AR (+)

addInstr (InstrBlock 1 0 (Mono a1) [(h, a1)]) addInstr (InstrBlock 2 0 (Mono (readGlobal h)) [])

in saveIA (mkOrc (44100, 4410) comp)

Page 17: HasSound: Generating Musical Instrument Sounds in Haskell

Delay Lines

• A delay line delays a signal by a given duration. Useful for reverb, but also for certain percussive sounds.

• In csound, it is unnecessarily imperative:a1 delayr max ; sets max delay

…a2 deltapi atime1 ; taps delay linea3 deltapi atime2; another tap

… delayw asource ; input to delay line

• The effect is non-local, can’t have more than one delay line in same sequential fragment, and it is prone to errors.

Page 18: HasSound: Generating Musical Instrument Sounds in Haskell

Delay Lines in HasSound

• In HasSound we provide equivalent power, but purely functionally:

data DelayLine = DelayLine SigExp -- max delay time SigExp -- signal to be delayed

data SigExp = …| Tap DelayLine [SigExp] -- create multiple taps| Result DelayLine -- output of delay line

• Thus delay lines are “first-class values”.

Page 19: HasSound: Generating Musical Instrument Sounds in Haskell

Recursive Signals

• We would like to be able to write things such as:let x = delay (sig + 0.5 * x) 1.0in …

• This looks like a finite loop. But as a data structure, it’s an infinite tree…

• We could design our own DSL, or require the user to “flag” each recursive reference.

• In HasSound, we introduce implicit looping via a fixed point operator:

rec (\x -> delay (sig + 0.5 * x) 1.0)

Page 20: HasSound: Generating Musical Instrument Sounds in Haskell

The SigExp Data Type (sort of…)

data SigExp = Const Float| Pfield Int| Str String| Read GlobalSig| Tap Function DelayLine [SigExp]| Result DelayLine| Conditional Boolean SigExp SigExp| Infix Function SigExp SigExp| Paren Function SigExp| SigGen Function EvalRate OutCount [SigExp]| Rec (SigExp -> SigExp)| Var Integer -- not exported| Loop Integer SigExp -- not exported| Index OutCount SigExp

deriving (Show, Eq)

Page 21: HasSound: Generating Musical Instrument Sounds in Haskell

Translating Loops• Conceptually, this expression:

x = delay (sig + 0.5 * x) 1.0must be translated into this csound code:

ax init 0 -- initialize ax…ax1 delay (sig + 0.5 * ax) 1.0 -- create new

sigax = ax1 -- update old sig…

• This is done in two steps, starting from the “rec” form:– Generate a unique variable name, and inject into functional:

rec (\x -> delay (sig + 0.5 * x) 1.0) Loop n (delay (sig + 0.5 * Var n) 1.0) -- n unique

– For each Loop, generate init code and usage code as above.

Page 22: HasSound: Generating Musical Instrument Sounds in Haskell

Compiling Into csound

• All of the “functional” translations are straightforward.

• However, common subexpression elimination is critical.

• Delay lines are tedious but straightforward.• Global variables require special care in order to

ensure proper initialization and resetting.• Recursive signals also require careful

sequencing of code.

Page 23: HasSound: Generating Musical Instrument Sounds in Haskell

Csound is Enormous

• The csound manual is 1200 pages long.

• Chapter 15, “Orchestra Opcodes and Operators”, is 972 pages long!!

• We cannot hope to import everything, but it’s easy to add your favorite operation, as long as its functional…

Page 24: HasSound: Generating Musical Instrument Sounds in Haskell

Conclusions• Certain kinds of imperative ideas can be

redesigned, and others can be reengineered.• Embedding a DSL in Haskell works well, but has

some limitations (for example, recursion is not transparent).

• Future work:– Better (more type-safe, etc.) interface between

intruments and the score.– Import more of csound.– Graphical interface.