1 -- Basic Semantics for a Reactive Music Cellular Automaton.
2 -- Inspired by the reacTogon.
3 -- Written by Henrik Nilsson, 2016-05-03
4 -- Based on an earlier version.
6 -- This gives the semantics of a single Reactogon layer. The output is
7 -- a high-level representation of notes for each beat. This is to be
8 -- translated to low-level MIDI message by a subsequent translator
9 -- responsible for merging notes from different layers, ensuring that
10 -- a note off message corresponding to each note on message is always
11 -- emitted after the appropriate time, rendering any embellismnets
12 -- such as slides (while not generating too much MIDI data), etc.
14 module Reactogon.Semantics where
17 import Data.Maybe (catMaybes)
18 import Data.List (nub, intersperse)
22 ------------------------------------------------------------------------------
23 -- Basic Type Synonyms
24 ------------------------------------------------------------------------------
26 -- Unipolar control value; [0, 1]
29 -- Unipolar control values are usually between 0 and 127.
30 toUCtrl :: Int -> UCtrl
31 toUCtrl x = fromIntegral x / 127
33 fromUCtrl :: UCtrl -> Int
34 fromUCtrl x = floor $ x * 127
36 -- Bipolar control values are usually between -127 and 127.
37 toBCtrl :: Int -> BCtrl
40 fromBCtrl :: BCtrl -> Int
43 -- Bipolar control value; [-1, 1]
47 ------------------------------------------------------------------------------
49 ------------------------------------------------------------------------------
51 -- The assumption is that the automaton is clocked by a beat clock and
52 -- thus advances one step per beat. For an automaton working in real time,
53 -- the beat clock would be defined externally, synchronized with other
54 -- layers and possibly external MIDI, and account for tempo, any swing, etc.
58 -- The tempo is the number of beats per minute.
63 -- A beat in itself is not important.
66 -- Beats per Bar: number of beats per bar in the time signature of a layer.
68 type BeatsPerBar = Int
70 -- The beat number in the time signature of the layer. The first beat is 1.
73 nextBeatNo :: BeatsPerBar -> BeatNo -> BeatNo
74 nextBeatNo bpb bn = bn `mod` bpb + 1
78 -- Not needed for individual layers (at present)
85 ------------------------------------------------------------------------------
87 ------------------------------------------------------------------------------
89 -- This semantics mainly works with a high-level represemntation of notes.
90 -- But it is convenient to express some of the high-level aspects directly
91 -- in the corresponding MIDI terms to facilitate the translation.
93 -- MIDI note number; [0,127]
97 -- Assume MIDI convetion: 60 = "Middle C" = C4
102 -- MIDI velocity; [0,127]
106 -- MIDI Program Change: Program Number; [0,127]
110 -- MIDI Control Change: Control Number and Control Value; [0,127]
114 -- MIDICVRnd gives the option to pick a control value at random.
115 -- (Handled through subsequent translation to low-level MIDI events.)
116 data MIDICVRnd = MIDICV MIDICV | MIDICVRnd deriving (Eq, Show)
119 ------------------------------------------------------------------------------
121 ------------------------------------------------------------------------------
125 -- We chose to represent pitch by MIDI note number
126 newtype Pitch = Pitch MIDINN deriving Eq
128 pitchToMNN :: Pitch -> MIDINN
129 pitchToMNN (Pitch nn) = nn
131 instance Show Pitch where
132 show (Pitch nn) = names !! note ++ show oct
136 oct = nn' `div` 12 + middleCOct
137 names = ["C", "C#", "D", "D#", "E", "F",
138 "F#", "G", "G#", "A", "A#", "B"]
140 -- Relative pitch in semi tones. Used for e.g. transposition.
146 -- Each layer has a setting that indicate how strongly the notes
147 -- should normally be played as a percentage of full strength. (In
148 -- the real application, this setting can be set to a fixed value or
149 -- set to be derived from the last input note, "as played").
150 -- Individual notes can tehn be accented (played more strongly),
151 -- either unconditionally or as a function of the beat count.
153 type Strength = UCtrl
155 -- This could of course be generalised, e.g. a list of beat numbers to
156 -- accentuate. But this is simple and accounts for the most common patterns.
157 data Articulation = NoAccent
167 -- Articulated strength
168 articStrength :: Strength -> BeatNo -> Articulation -> Strength
169 articStrength st bn art
170 | accentedBeat = st * accentStrength
175 (_, NoAccent) -> False
178 (1, Accent13) -> True
179 (3, Accent13) -> True
180 (1, Accent14) -> True
181 (4, Accent14) -> True
182 (1, Accent24) -> True
183 (4, Accent24) -> True
189 -- Duration in terms of a whole note at the *system* tempo. (Each
190 -- layer is clocked at a layer beat that is a fraction/multiple of the
191 -- system tempo). Note that notes are played a little shorter than
192 -- their nominal duration. This is taken care of by the translation
193 -- into low-level MIDI events. (One might consider adding indications
194 -- of staccato or tenuto.)
195 type Duration = Rational
200 -- Notes can be ornamented. Traditionnally, ornamenting refers to modifications
201 -- of the pitch, such as a trill or a grace note. Here we use the term in
202 -- a generalised sense.
203 -- * A MIDI program change (to be sent before the note).
204 -- * A MIDI Continuous Controler Change (to be sent before the note).
206 -- One might also consider adding trills, grace notes, MIDI after touch ...
208 data Ornaments = Ornaments {
209 ornPC :: Maybe MIDIPN,
210 ornCC :: [(MIDICN, MIDICVRnd)],
211 ornSlide :: SlideType
214 data SlideType = NoSlide | SlideUp | SlideDn deriving (Eq, Show)
219 -- Attributes needed to generate a note.
220 -- * The pitch of a note is given by the position on the board
221 -- * The strength is given by the layer strength, beat no., and articulation
222 -- * Duratio and Ornamentatio are stored
223 data NoteAttr = NoteAttr {
224 naArt :: Articulation,
230 -- High level note representation emitted from a layer
239 ------------------------------------------------------------------------------
241 ------------------------------------------------------------------------------
243 -- Numbering; row number inside tile, column number below:
254 -- Angle measured in multiples of 60 degrees.
257 data Dir = N | NE | SE | S | SW | NW deriving (Enum, Eq, Show)
260 turn :: Dir -> Angle -> Dir
261 turn d a = toEnum ((fromEnum d + a) `mod` 6)
264 type Pos = (Int, Int)
266 -- Position of neighbour in given direction
267 neighbor :: Dir -> Pos -> Pos
268 neighbor N (x,y) = (x, y + 1)
269 neighbor NE (x,y) = (x + 1, y + 1 - x `mod` 2)
270 neighbor SE (x,y) = (x + 1, y - x `mod` 2)
271 neighbor S (x,y) = (x, y - 1)
272 neighbor SW (x,y) = (x - 1, y - x `mod` 2)
273 neighbor NW (x,y) = (x - 1, y + 1 - x `mod` 2)
276 -- Position and transposition to pitch:
277 -- * Harmonic Table" layout: N = +7; NE = +4; SE = -3
278 -- * (0,0) assumed to be "Middle C"
279 posToPitch :: Pos -> RelPitch -> Pitch
280 posToPitch (x,y) tr =
281 Pitch (y * 7 + x `div` 2 - 3 * (x `mod` 2) + middleC + tr)
285 -- Maybe this could be refined: some of the actions might be useful
286 -- both in note playing and silent versions: e.g. changing direction without
287 -- playing a note; playing a note without changing direction.
289 data Action = Inert -- No action, play heads just move through.
290 | Absorb -- Remove play head silently.
291 | Stop NoteAttr -- Play note then remove play head.
292 | ChDir NoteAttr Dir -- Play note then change direction.
293 | Split NoteAttr -- Play note then split head into five new.
298 -- A cell stores an action and a repetition number.
299 -- 0: the cell is completely bypassed, as if it wasn't there.
300 -- 1: the action is carried out once (default)
301 -- n > 1: any note output of the action is repeated (n-1) times before the
302 -- action is carried out.
304 type Cell = (Action, Int)
307 -- Make a cell with a default repeat count of 1.
308 mkCell :: Action -> Cell
309 mkCell a = mkCellRpt a 1
312 -- Make a cell with a non-default repeition number.
313 mkCellRpt :: Action -> Int -> Cell
314 mkCellRpt a n | n >= 0 = (a, n)
315 | otherwise = error "The repetition number of a cell must be \
319 -- Board extent: south-west corner and north-east corner.
320 -- This covers most of the MIDI range: A#-1 (10) to G7 (103).
326 -- Test if a position is on the board as defined by swc and nec.
327 -- The assumption is that odd columns contain one more cell, as per the
328 -- picture above. Of course, one could opt for a "zig-zag" layout
329 -- with each column having the same number of cells which would be slightly
331 onBoard :: Pos -> Bool
332 onBoard (x,y) = xMin <= x && x <= xMax
340 (xMax, yMax) = case nec of
341 (x, y) | even x -> (x, y + 1)
342 | otherwise -> (x, y)
345 type Board = Array Pos Cell
348 -- Build a board from a list specifying the non-empty cells.
349 makeBoard :: [(Pos, Cell)] -> Board
352 ([(p, if onBoard p then mkCell Inert else mkCell Absorb)
353 | p <- range (swc, nec')]
354 ++ [(p,c) | (p, c) <- pcs, onBoard p])
356 -- This is to ensure (neighbor NW nec) is included on the board,
357 -- regardless of whether the column of nec is even or odd.
358 -- Otherwise, due to the "jagged" upper edge, the top row would
359 -- be missing, but every other cell of that *is* on the board.
360 -- The "superfluous" cells are set to Absorb above.
361 nec' = neighbor N nec
365 lookupCell :: Board -> Pos -> Cell
366 lookupCell b p = if onBoard p then (b ! p) else (Absorb, 1)
369 ------------------------------------------------------------------------------
371 ------------------------------------------------------------------------------
373 -- A play head is characterised by:
374 -- * Current position
375 -- * Number of beats before moving
376 -- * Direction of travel
377 -- If an action involves playing a note, this is repeated once for
378 -- each beat the play head is staying, with the rest of the action
379 -- carried out at the last beat.
390 ------------------------------------------------------------------------------
392 ------------------------------------------------------------------------------
394 -- Advance the state of a single play head.
396 -- The result is a list of heads to be actioned at the *next* beat
397 -- later) and possibly a note to be played at *this* beat.
399 advanceHead :: Board -> BeatNo -> RelPitch -> Strength -> PlayHead
400 -> ([PlayHead], Maybe Note)
401 advanceHead bd bn tr st ph = ahAux (moveHead bd ph)
403 ahAux ph@PlayHead {phPos = p, phBTM = btm, phDir = d} =
404 case fst (lookupCell bd p) of
405 Inert -> ([ph], Nothing)
406 Absorb -> ([], Nothing) -- No point waiting until BTM=0
407 Stop na -> (newPHs [], Just (mkNote p bn tr st na))
408 ChDir na d' -> (newPHs [ph {phDir = d'}],
409 Just (mkNote p bn tr st na))
410 Split na -> (newPHs [ PlayHead {
418 Just (mkNote p bn tr st na))
420 newPHs phs = if btm > 0 then [ph] else phs
423 -- Moves a play head if the BTM counter has reached 0, otherwise decrement BTM.
424 -- Any encountered cells where the repeat count is < 1 are skipped.
425 moveHead :: Board -> PlayHead -> PlayHead
426 moveHead bd (ph@PlayHead {phPos = p, phBTM = btm, phDir = d})
429 btm' = snd (lookupCell bd p')
431 moveHead bd (ph {phPos = p', phBTM = btm'})
432 | otherwise = ph {phBTM = btm - 1}
435 mkNote :: Pos -> BeatNo -> RelPitch -> Strength -> NoteAttr -> Note
436 mkNote p bn tr st na =
438 notePch = posToPitch p tr,
439 noteStr = articStrength st bn (naArt na),
445 -- Advance a list of heads, collecting all resulting heads and notes.
446 -- Any duplicate play heads are eliminated (or their number may uselessly
447 -- grow very quickly), and a cap (50, arbitrary, but should be plenty,
448 -- expecially given the board size) on the number of simultaneous playheads
449 -- per layer is imposed.
450 advanceHeads :: Board -> BeatNo -> RelPitch -> Strength -> [PlayHead]
451 -> ([PlayHead], [Note])
452 advanceHeads bd bn tr st phs =
454 (phss, mns) = unzip (map (advanceHead bd bn tr st) phs)
456 (take 50 (nub (concat phss)), catMaybes mns)
459 -- Given an initial list of play heads, run a board until there are no
460 -- more heads (or "forever", if that does not happen). The result is
461 -- a list of all notes played for each pulse.
463 -- Note: The original reactogon has special start counters. An "internal"
464 -- board as defined here along with a list of inital read heads could
465 -- be derived from an "external" board representation more closely aligned
466 -- with the GUI represenatation.
468 -- In the real implementation:
469 -- * A layer beat clock would be derived from the system beat (as a
470 -- fraction/multiple, adding any swing) and each clock event be tagged
471 -- with the beat number.
472 -- * The board would not necessarily be a constant input. (One might
473 -- consider allowing editing a layer while the machine is running)
474 -- * The time signature and thus the beats per par would not necessarily
475 -- be a constant input (one might consider allowing changing it while
476 -- the machine is running, but perhaps not very useful).
477 -- * The transposition would be dynamic, the sum of a per layer
478 -- transposition that can be set through the user interface and the
479 -- difference between the MIDI note number of the last external
480 -- note received for the layer and middle C (say).
481 -- * The strength would be dynamic, configurable as either the strength
482 -- set through the user interface or the strength of the last external
483 -- note received for the layer (derived from its MIDI velocity).
485 runRMCA :: Board -> BeatsPerBar -> RelPitch -> Strength -> [PlayHead]
487 runRMCA _ _ _ _ [] = []
488 runRMCA bd bpb tr st phs = runAux 1 phs
490 runAux bn phs = ns : runAux (nextBeatNo bpb bn) phs'
492 (phs', ns) = advanceHeads bd bn tr st phs
495 -- Print played notes in a time-stamped (bar, beat), easy-to-read format.
497 ppNotes :: BeatsPerBar -> [[Note]] -> IO ()
498 ppNotes bpb nss = ppnAux (zip [(br,bn) | br <- [1..], bn <- [1..bpb]] nss)
500 ppnAux [] = return ()
501 ppnAux ((_, []) : tnss) = ppnAux tnss
502 ppnAux ((t, ns) : tnss) = do
503 putStrLn ((leftJustify 10 (show t)) ++ ": "
504 ++ concat (intersperse ", " (map show ns)))
508 leftJustify :: Int -> String -> String
509 leftJustify w s = take (w - length s) (repeat ' ') ++ s
512 ------------------------------------------------------------------------------
514 ------------------------------------------------------------------------------
516 -- testBoard = makeBoard [((0,0), mkCell (ChDir na1 N)),
517 -- ((0,1), mkCell (ChDir na1 SE)),
518 -- ((1,1), mkCell (Split na1)),
519 -- ((1,-1), mkCell (Split na1)),
520 -- ((-1,0), mkCell (ChDir na2 NE))]
522 testBoard = makeBoard [((0,0), mkCell (ChDir na1 N)),
523 ((0,2), mkCellRpt (ChDir na2 SE) 3),
524 ((2,1), mkCell (ChDir na1 SW)),
525 ((1,1), mkCellRpt (ChDir na1 N) 0) {- Skipped! -}]
530 naOrn = Ornaments Nothing [] NoSlide
536 naOrn = Ornaments Nothing [(10, MIDICVRnd)] SlideUp
542 main = ppNotes bpb (take 50 (runRMCA testBoard
546 [PlayHead (0,0) 1 N]))