On Github jaycoskey / MusicProgrammingLanguages
Jay Coskey
jay<dot>coskey {at} gmail<dot>com
2016-05-04
reveal.js tips: Use n or <space> to advance to the next slide. Use p to return to the previous slide. Use <Esc> to toggle an overview of all slides. Use ? to see a list of all keyboard commands. Use s to see the speaker notes.
Music runs deep through the human psyche:
Traditional training vs. software
Any room for non-humans?
Examples: "Happy Birthday to You", "Greensleeves", "Yellow Submarine"
\version "2.18.2" \header { title = "Happy Birthday to You" } global = { \key c \major \time 3/4 } right = \relative c'' {\global g8 g8 a4 g4 c4 b2 } left = \relative c {\global r4 \chordmode{c,2} r4 <d f g>2} \score { \new PianoStaff \with {instrumentName = "Piano"} << \new Staff = "right" \with {midiInstrument = "acoustic grand"} \right \new Staff = "left" \with {midiInstrument = "acoustic grand"} {\clef bass \left} >> \layout { } \midi {\tempo 4=100} }
<score-partwise> <== Show the 1st part, then 2nd, etc. <work> <work-title>Happy Birthday to You</work-title> </work> ... <part-list>...<part-name>Piano</part-name>...</part-list> <part id="P1"> <measure number="1" width="273.03"> ... <note default-x="78.29" default-y="-30.00"> <== Just showing the first note here <pitch> <step>G</step> <octave>4</octave> </pitch> <duration>1</duration> <voice>1</voice> <type>eighth</type> <stem>up</stem> <staff>1</staff> <beam number="1">begin</beam> </note> ... </measure> ... </part> </score-partwise>
LilyPond is useful for printing beautiful scores.
\header { title = "Mary Had a Little Lamb" } song = \relative c' { \clef treble \key c \major \time 4/4 e4 d c d e e e2 d4 d d2 e4 e e2 e4 d c d e e e c d d e d c2 r2 } \score { \new Staff \song }
T:Mary had a little lamb M:C % meter L:1/4 % basic note length K:F % key AGFG|AAA2|GGG2|AAA2| AGFG|AAAA|GGAG|F4|]"^" / "_" sharp / flat (moves note up/down one semitone) "<apostrophe>" / "<comma>" increments / decrements octave for given note "|" (pipe) Bar line. (Lots of variations available.) "[", "]" Grouping of notes in chord A2, A3, A4, A5, A6, etc. Multiples of the default length of an 'A' note; A/2, A/3, A/4, A/5, A/6, etc. Fractions of the default length of an 'A' note;
Not listed in this section:
piano: o3 g8 a b > c d e f+ g | a b > c d e f+ g4 g8 f+ e d c < b a g | f+ e d c < b a g4 << g1/>g/>g/b/>d/g
% ghci ghci> import Euterpea ghci> play $ c 4 qn -- Play a quarter note, with sensible defaults ghci> import HSoM ghci> :load HSoM.Examples.MUIExamples2 ghci> import HSoM.Examples.MUIExamples2 ghci> bifurcate -- GUI app/test that lives in MUIExamples2
data Primitive = Note Dur Pitch | Rest DurType "Music" consists of Primitive events: glued together
data Music a = Prim (Primitive a) -- primitive value | Music a :+: Music a -- sequential composition | Music a :=: Music a -- parallel composition | Modify Control (Music a) -- modifierSo the different constructors of Music are Prim, (:+:), (:=:), and Modify. The function "play" uses defaults to perform conversions:
Music ==> to Music1 (non-parameterized) ==> [MEvent] ==> [MidiMessage]The final MidiMessage list is sent to a MIDI output.
import Euterpea c :: Octave -> Dur -> Music Pitch play $ c 4 qn mapM (\octave -> play $ c octave qn) [0 .. 8] -- Play C notes.Here is a slightly more complex: a basic version of Frère Jacques (from HSoM).
twice m = m :+: m fj1, fj2, fj3, fj4, fjNotes, fj :: Music Pitch fj1 = c 4 qn :+: d 4 qn :+: e 4 qn :+: c 4 qn fj2 = e 4 qn :+: f 4 qn :+: g 4 hn fj3 = g 4 en :+: a 4 en :+: g 4 en :+: f 4 en :+: e 4 qn :+: c 4 qn fj4 = c 4 qn :+: g 3 qn :+: c 4 hn fjNotes = twice fj1 :+: twice fj2 :+: twice fj3 :+: twice fj4 fj = Modify (Tempo 4) $ Modify (Instrument AcousticGrandPiano) fjNotes play :: Performable a => Music a -> IO () play fj
data Beat = Beat { bDur :: Dur, bHeard :: Int, bAcc :: Int } type Beats = [Beat] infixr 6 .+. infixr 7 .*. (.*.) n bs = concat $ replicate n bs (.+.) bs1 bs2 = bs1 ++ bs2 beats2m :: PercussionSound -> Beats -> Music Pitch beats2m percInstr [] = rest 0 beats2m percInstr (Beat { bDur=d, bHeard=h, bAcc=a } : beats) = m0 :+: (beats2m percInstr beats) where m = Modify (Instrument Percussion) $ perc percInstr d m0 = case (h,a) of (0, _) -> rest d -- Silent "beat" (1, 0) -> m -- Unaccented beat (1, 1) -> Modify (Phrase [Dyn (Accent 1.5)]) m instrB = AcousticBassDrum mkBeat d h a = [Beat { bDur = d, bHeard = h, bAcc = a }] qns = mkBeat qn 0 0; ens = mkBeat en 0 0; enb = mkBeat en 1 0; snb = mkBeat sn 1 0
amen1b = beats2m instrB $ 2.*.enb .+. qns .+. ens .+. 2.*.snb .+. qns
C |----------------|----------------|----------------|----------X-----| R |x-x-x-x-x-x-x-x-|x-x-x-x-x-x-x-x-|x-x-x-x-x-X-x-x-|x-x-x-x-x---x-x-| S |----o--o-o--o--o|----o--o-o--o--o|----o--o-o----o-|-o--o--o-o----o-| B |o-o-------oo----|o-o-------oo----|o-o-------o-----|--oo------o-----| ->|1 + 2 + 3 + 4 + |1 + 2 + 3 + 4 + |1 + 2 + 3 + 4 + |1 + 2 + 3 + 4 + |
playA :: (ToMusic1 a, Control.DeepSeq.NFData a) => PMap Note1 -> Context Note1 -> Music a -> IO()The Player determines how to interpret
data Context a = Context { cTime :: PTime, cPlayer :: Player a , cInst :: InstrumentName, cDur :: DurT , cPch :: AbsPitch, cVol :: Volume , cKey :: (PitchClass, Mode) } data Player a = MkPlayer { pName :: PlayerName -- ID for selection , playNote :: NoteFun a -- Interp. notes , interpPhrase :: PhraseFun a -- Interp. phrases -- , notatePlayer :: NotateFun -- Removed. }One of the uses of NFData's rnf (reduce to normal form?) is to force evaluation of lazy IO.
type Performance = [ Event ] data MEvent = MEvent { eTime, eInstr, ePitch, eDur, eVol, eParams :: [ Double ] } deriving (Show, Eq, Ord) perform :: PMap a -> Context a -> Music a -> Performance -- See p. 138 of HSoM type NoteFun a = Context a -> Dur -> a -> Performance type PhraseFun a = PMap a -> Context a -> [PhraseAttribute] -> Music a -> (Performance, DurT) type NotateFun a = () The PMap is a map from PlayerName (String) to Player (a means of rendering sound & score). type PMap a = PlayerName -> Player a There is a similar map for channels: type ChannelMap = [ (InstrumentName, Channel) ] type ChannelMapFun = InstrumentName -> ChannelMap -> (Channel, ChannelMap)
myPasHandler :: PhraseAttribute -> Performance -> Performance myPasHandler (Dyn (Crescendo x)) pf = boostVolsBy x pf where t0 = eTime (head performance) perfDur :: Dur perfDur = sum $ map eDur performance propTime :: PTime -> Rational propTime t = (t - t0) / perfDur propVolDelta :: PTime -> Rational propVolDelta t = x * (propTime t) newVol t v = round((1 + (propVolDelta t)) * (fromIntegral v)) boostMEventVol (e@MEvent {eTime = t, eVol = v}) = e { eVol = trace ("Vol=" ++ show (newVol t v)) (newVol t v) } boostVolsBy :: Rational -> [MEvent] -> [MEvent] boostVolsBy x pf = map boostMEventVol pf myPasHandler pa pf = defPasHandler pa pf
(note1 :=: (note2 :=: (note3 :=: note4)))Simplified internal representation can make later extraction of the real structure expensive.
(>>>) :: SF a b -> SF b c -> SF a c -- Left-to-right composition (<<<) :: SF b c -> SF a b -> SF a c -- Right-to-left composition z <- sigFunc -< (x, y) -- Signal function
{-# LANGUAGE Arrows #-} module Euterpea.Examples.SigFuns where import Euterpea import Control.Arrow ((>>>),(<<<),arr) s4 :: Clock c => SigFun c () Double s4 = proc () -> do f0 <- oscFixed 440 -< () f1 <- oscFixed 880 -< () f2 <- oscFixed 1320 -< () outA -< (f0 + 0.5*f1 + 0.33*f2) / 1.83 vibrato :: Clock c => Double -> Double -> SigFun c Double Double vibrato vfrq dep = proc afrq -> do vib <- osc tab1 0 -< vfrq aud <- osc tab2 0 -< afrq + vib * dep outA -< aud -- type a b = SigFun AudRate a b s5 :: AudSF () Double s5 = constA 1000 >>> vibrato 5 20
data Instrument Name = AcousticGrandPiano -- See p. 35 of HSoM | BrightAcousticPiano | ... | Custom String deriving (Show, Eq, Ord)This can be used as follows (see Chapter 19 of HSoM)
myMandolinName :: InstrumentName -- Or myFlugelhorn or myUkulele myMandolinName = Custom "My Mandolin" type Instr a = Dur -> AbsPitch -> Volume -> [Double] -> aThis is commonly used with the type variable a being "Instr (AudSF () Double)". The handling of the [Double] parameter above is determined by the instrument designer.
myMandolin :: Instr (AudSF () Double) -- Monaural: Could choose stereo instead. myMandolin dur ap vol [vfrq, dep] = proc () -> do vib <- osc tab1 () -< vfrq aud <- osc tab2 () -< apToHz ap + vib * dep outA -< aud
type InstrMap a = [(InstrumentName, Instr a)] myInstrMap :: InstrMap (AudSF () Double) myInstrMap = [(myMandolinName, myMandolin)]
renderSF :: (Performable a, AudioSample b, Clock c) => Music a -> InstrMap (SigFun p () b) -> (Double, SigFun p () b)
mandolinMelody = instrument myMandolinName $ ...music... (dur, sigFun) = renderSF mandolinMelody myInstrMap main = outFile "mandolinMelody.wav" dur sigFun
MTrait = MTInstrument IntrumentName | MTBars1 Int -- Begin | MTBars2 Int Int -- Begin, End mFilterBy :: [MTrait] -> Music a -> Music a
MAspect = MARhythm -- Presence of notes, but not freq/volume | MAFreq -- Accidentals, but no other annotations | MAVolume -- Dynamics only (accents/cresc/dim/fp, etc.) | MAFreqVolume -- Both frequency and volume mMatchCommon :: MAspect -> Music a -> Music a -> [Music a] mMatchDuplicate :: MAspect -> Music a -> [Music a] mDiff :: MAspect -> Bool -> Music a -> Music a -> Music a -- Bool=addBeats
MDistCost = | MDCAddition (Pitch -> Dur -> Float) | MDCDeletion (Pitch -> Dur -> Float) | MDCInstrument (InstrumentName -> InstrumentName -> Float) | MDCDuration (Dur -> Dur -> Float) | MDCPitch (Pitch -> Pitch -> Float) mMelodyDist :: MAspect -> [MDistanceCost] -> Music a -> Music a -> Float -- mDist: How to make this perceptually relevant?
type InstrumentState = Maybe InstrumentName filterByInstrument :: InstrumentName -> Music a -> Music a filterByInstrument instrName mus = filterImpl instrName Nothing mus where filterImpl :: InstrumentName -> InstrumentState -> Music a -> Music a filterImpl instrName instrState mus = case mus of Prim p -> if (instrState == Just instrName) then mus else (rest $ dur mus) mus1 :+: mus2 -> line1 $ map (filterImpl instrName instrState) [mus1, mus2] mus1 :=: mus2 -> chord1 $ map (filterImpl instrName instrState) [mus1, mus2] Modify (Tempo t) mus1 -> Modify (Tempo t) $ filterImpl instrName instrState mus1 Modify (Instrument newName) mus1 -> Modify (Instrument newName) $ filterImpl instrName (Just newName) mus1
type StandardNote = (PartT Part (ColorT (TextT (TremoloT (HarmonicT (SlideT (ArticulationT Articulation (DynamicT Dynamics [TieT Pitch])))))))) type Music = Score StandardNote
<CsoundSynthesizer> <CsOptions> </CsOptions> <CsInstruments> <== The "orchestra" section sr = 44100 <== Sample rate (Hz) for audio signals & vars ksmps = 128 <== Sample rate per control-block, e.g., mouse nchnls = 2 <== # of audio channels. Two for stereo. 0dbfs = 1 <== Max output in dBs before clipping instr 2 kFreq expon 100, 5, 1000 ; kFreq set to the output of expon(ential) aOut oscili 0.2, kFreq, 1 ; aOut set to the output of 'oscil' outvalue "freqsweep", kFreq outs aOut, aOut endin </CsInstruments> <CsScore> f 1 0 1024 10 1 ; this function table contains the sine information i 2 0 5 ; the instrument is called at t=0 & plays for 5 sec. e </CsScore> </CsoundSynthesizer>
instr 1 <== This takes 2 extra params: amp & freq. aSine poscil3 p4, p5, 1; <== p4 = 4th param; p5 = 5th param outs aSine, aSine <== One output for each channel endin
instr 1 iGreetingCount = 0 loop: <== Any label can be used here iGreetingCount = iGreetingCount + 1 prints "Hello world #%i\n", iGreetingCount if (iGreetingCount < 5) igoto loop endin
<<< "Hello, sine wave" >>> SinOsc s => dac; 0.6 => s.gain; 220 => s.freq; 5::second => now;In this script:
// Feedback setup adc => Gain g => dac; g => Gain feedback => DelayL delay => g; 0.75::second => delay.max => delay.delay; 0.5 => feedback.gain; // Note: gain < 1. 0.75 => delay.gain; while (true) 1::second => now;
// Karplus-Strong model of a plucked string Impulse imp => Delay plucked => dac; plucked => plucked; // Feedback 441.0::samp => plucked.delay; // Sample rate 0.98 => plucked.gain; // Round-trip gain < 1 1.0 => imp.next; // "Pluck" impulse 5.0::second => now; // The string resonates
15.squared; // Evaluates to 225. [45, 13, 10].sort; // Evaluates to [10, 13, 45] 5 pow: 8; // Same as 5.pow(8) 10.do({ "Hello world".postln }); // Writes to the "Post" pane. var factorial = { | n | var result = 1; n do: { | i | result = result * (i + 1) }; result; }; factorial.(4).postln; // Output: 24Caution: Binary operators are evaluated in left-to-right order, so 1 + 3 * 5 yields 20, not 16.
s.bootwhere the variable "s" is reserved to represent the server.
{ SinOsc.ar(440, phase: 0, mul: 0.5, add: 0) }.play; // Keyword argsor
{ SinOsc.ar(440, 0, 0.5, 0) }.play; // Positional argsor just
{ SinOsc.ar(440) }.play; // Omitting arguments with default valueswhere "ar" is the "audio rate" constructor of the SinOsc class. UGen is the superclass of all (250+) "unit generators" (basic signals). Some examples:
Pseq([0,1,2,3,4,5], 3, 2) <== [2,3,4, 2,3,4, 2,3,4]
Prand([1,2,3,4,5,6], 3) <== 3 random rolls of a d6
Same as Prand, but selection without replacement.
Pbind(*[dur: 0.2/2 freq: Pseq([220, 440, 880]) )
Pchain( Pbind( \degree, Pseq([1, 2, 3], inf) ) , (detune: [0, 4]) ).trace.play;
{ var freq, latchrate, index, ratio, env, speed = 9; speed = MouseX.kr(2, 20); latchrate = speed * 1.61803399; index = Latch.kr( LFSaw.kr(latchrate, mul: 4, add: 8), Impulse.kr(speed) ); freq = Latch.kr( LFSaw.kr(latchrate, mul: 36, add: 60), Impulse.kr(speed) ).round(1).midicps; ratio = 2.01; env = EnvGen.kr(Env.perc(0, 2/speed), gate: Impulse.kr(speed)); Out.ar(0, PMOsc.ar([freq, freq * 1.5], [freq * ratio, freq * 1.5 * ratio], index, mul: env * 0.5) )}.play
(definst metallia [freq 440 dur 1 volume 1.0] (-> (sin-osc freq (sin-osc 1)) (+ (sin-osc (* 1/2 freq) (sin-osc 1/3))) (clip2 (mul-add (saw 1/4) 0.2 0.7)) (* (env-gen (adsr 0.03 0.6 0.3) (line:kr 1 0 dur) :action FREE)) (* 1/4 volume))) (defn minor [chord] (update-in chord [:iii] (scale/from -1/2))) (def melody (->> (phrase [2/3 13/3 5/3 9/3 1/3 2/3 13/3 2/3 13/3] [ 1 2 1 0 0 1 2 3 0]) (where :pitch scale/raise) (where :part (is :melody))))
See Overtone examples for more sample code. Note the three distinct functions above:
proc {RestrictMelodicInterval Note1} Note2 = {Note1 getTimeAspectPredecessor($)} in 7 >=: {FD.distance {Note1 getPitch($)} {Note2 getPitch($)}} endAnd here is this rule being applied:
{MyScore forAll(RestrictMelodicInterval test:fun {$ X} {X isNote($)} andthen {X hasTimeAspectPredecessor($)} end)} }A typical Strasheela program can use hundreds of variables and constraints. Constrait order does matter. Usually it's Timing ⇒ Melodic ⇒ Harmonic. A novice user should gradually increase the number of constraints to determine impact.
Music & Music Theory