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] -> a
This 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: 24
Caution: 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 args
or
{ SinOsc.ar(440, 0, 0.5, 0) }.play; // Positional args
or just
{ SinOsc.ar(440) }.play; // Omitting arguments with default values
where "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($)}}
end
And 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