-- | @SC3@ pitch model implementation. module Sound.SC3.Lang.Control.Pitch where import Data.Maybe {- base -} import Sound.SC3.Lang.Math -- * Pitched -- | 'Pitched' values, minimal definition is 'midinote'. -- -- > midinote (defaultPitch {degree = 5}) == 69 -- > freq (defaultPitch {degree = 5,detune = 10}) == 440 + 10 class Pitched p where midinote :: p -> Double freq :: p -> Double freq = midicps . midinote -- * Pitch -- | The supercollider language pitch model is organised as a tree -- with three separate layers, and is designed to allow separate -- processes to manipulate aspects of the model independently. -- -- The haskell variant implements 'Pitch' as a labeled data type, with -- a default value such that scale degree 5 is the A above middle C. -- -- > freq (defaultPitch {degree = 5}) == 440 -- -- The note is given as a degree, with a modal transposition, indexing -- a scale interpreted relative to an equally tempered octave divided -- into the indicated number of steps. -- -- The midinote is derived from the note by adding the indicated -- root, octave and gamut transpositions. -- -- The frequency is derived by a chromatic transposition of the -- midinote, with a harmonic multiplier. -- -- > let {p = defaultPitch -- > ;n = p {stepsPerOctave = 12 -- > ,scale = [0,2,4,5,7,9,11] -- > ,degree = 0 -- > ,mtranspose = 5} -- > ;m = n {root = 0 -- > ,octave = 5 -- > ,gtranspose = 0} -- > ;f = m {ctranspose = 0 -- > ,harmonic = 1}} -- > in (note n,midinote m,freq f) == (9,69,440) -- -- By editing the values of aspects of a pitch, processes can -- cooperate. Below one process controls the note by editing the -- modal transposition, a second edits the octave. -- -- > let {edit_mtranspose p d = p {mtranspose = mtranspose p + d} -- > ;edit_octave p o = p {octave = octave p + o} -- > ;p' = repeat defaultPitch -- > ;q = zipWith edit_mtranspose p' [0,2,4,3,5] -- > ;r = zipWith edit_octave q [0,-1,0,1,0] -- > ;f = map midinote} -- > in (f q,f r) == ([60,64,67,65,69],[60,52,67,77,69]) data Pitch = Pitch {mtranspose :: Double ,gtranspose :: Double ,ctranspose :: Double ,octave :: Double ,root :: Double ,scale :: [Double] ,degree :: Double ,stepsPerOctave :: Double ,detune :: Double ,harmonic :: Double ,freq' :: Maybe Double ,midinote' :: Maybe Double ,note' :: Maybe Double } deriving (Eq,Show) -- | A default 'Pitch' value of middle C given as degree @0@ of a C -- major scale. -- -- > let {p = defaultPitch -- > ;r = ([0,2,4,5,7,9,11],12,0,5,0)} -- > in (scale p,stepsPerOctave p,root p,octave p,degree p) == r defaultPitch :: Pitch defaultPitch = Pitch {mtranspose = 0 ,gtranspose = 0 ,ctranspose = 0 ,octave = 5 ,root = 0 ,degree = 0 ,scale = [0,2,4,5,7,9,11] ,stepsPerOctave = 12 ,detune = 0 ,harmonic = 1 ,freq' = Nothing ,midinote' = Nothing ,note' = Nothing } -- | Calculate /note/ field. -- -- > note (defaultPitch {degree = 5}) == 9 note :: Pitch -> Double note p = let f e = let d = degree e + mtranspose e in degreeToKey (scale e) (stepsPerOctave e) d in fromMaybe (f p) (note' p) instance Pitched Pitch where midinote p = let f e = let n = note e + gtranspose e + root e in (n / stepsPerOctave e + octave e) * 12 in fromMaybe (f p) (midinote' p) freq p = let f e = midicps (midinote e + ctranspose e) * harmonic e in fromMaybe (f p) (freq' p) + detune p -- * Optional -- | Tuple in 6-1-6 arrangement. type T616 a b c = (a,a,a,a,a,a,b,c,c,c,c,c,c) -- | 'Pitch' represented as tuple of optional values. type OptPitch = T616 (Maybe Double) (Maybe [Double]) (Maybe Double) -- | Transform 'OptPitch' to 'Pitch'. optPitch :: OptPitch -> Pitch optPitch (mt,gt,ct,o,r,d,s,s',d',h,f,m,n) = Pitch {mtranspose = fromMaybe 0 mt ,gtranspose = fromMaybe 0 gt ,ctranspose = fromMaybe 0 ct ,octave = fromMaybe 5 o ,root = fromMaybe 0 r ,degree = fromMaybe 0 d ,scale = fromMaybe [0,2,4,5,7,9,11] s ,stepsPerOctave = fromMaybe 12 s' ,detune = fromMaybe 0 d' ,harmonic = fromMaybe 1 h ,freq' = f ,midinote' = m ,note' = n}