summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndrew Cady <d@jerkface.net>2015-12-07 10:49:19 -0500
committerAndrew Cady <d@jerkface.net>2015-12-07 10:49:19 -0500
commitc15513cc1fc643dc088e430c0c41e923e29c928d (patch)
tree82cb4c69e5e4bd1c1f236784e14600f297fa0484
parent7ed747a7db1fcfd5f57524b7ffca45a527951d9f (diff)
Add basic support for "filling" triads.
I.e., any triads played will have additional notes played at the root & fifth of the octave above and below the triad. Eventually I want to program the triads using the keyboard itself, so that the chords can be "filled" arbitrarily. This also shows the need to represent the pressed key set differently than as a Set of (channel, pitch) pairs: the velocity needs to be saved, so that the "fill" notes can use it (probably use the average of the triad). Furthermore the whole infrastructure needs to be designed around the concept of input channels mapping to output channels. Filters (such as the triad filter) should be applied to channels -- right now, the assumption of a single channel has been baked in in several places, but this will eventually interfere with things like looping. (Playing back the input needs to be able to play back the filters that were in place on the input. Although, note: we also want to record output and primarily play that back.)
-rw-r--r--midi-dump.hs54
1 files changed, 48 insertions, 6 deletions
diff --git a/midi-dump.hs b/midi-dump.hs
index 534671f..52fe6b2 100644
--- a/midi-dump.hs
+++ b/midi-dump.hs
@@ -39,17 +39,20 @@ main = main' `AlsaExc.catch` handler
39 where 39 where
40 handler e = when verbose $ putStrLn $ "alsa_exception: " ++ AlsaExc.show e 40 handler e = when verbose $ putStrLn $ "alsa_exception: " ++ AlsaExc.show e
41 41
42data Triad = Major Event.Pitch | Minor Event.Pitch deriving (Show, Eq)
43
42data LoopState = LoopState { 44data LoopState = LoopState {
43 _wantExit :: Bool, 45 _wantExit :: Bool,
44 keysDown :: MidiPitchSet, 46 _keysDown :: MidiPitchSet,
47 _triad :: Maybe Triad,
45 _scheduled :: Q.Queue Event.Data, 48 _scheduled :: Q.Queue Event.Data,
46 _recording :: Recording, 49 _recording :: Recording,
47 _replay :: Recording, 50 _replay :: Recording,
48 _lastTick :: TimeSpec 51 _lastTick :: TimeSpec
49} 52}
50 53
51initializeState :: TimeSpec -> LoopState 54initializeState :: TimeSpec -> LoopState
52initializeState now = LoopState False Set.empty createQueue (StartRecording now) (StartRecording now) now 55initializeState now = LoopState False Set.empty Nothing createQueue (StartRecording now) (StartRecording now) now
53 56
54data LoopEnv = LoopEnv { 57data LoopEnv = LoopEnv {
55 _saver :: Chan CompleteRecording, 58 _saver :: Chan CompleteRecording,
@@ -125,7 +128,6 @@ main' :: IO ()
125main' = withAlsaInit $ \h public private q publicAddr privateAddr -> do 128main' = withAlsaInit $ \h public private q publicAddr privateAddr -> do
126 cmdlineAlsaConnect h public 129 cmdlineAlsaConnect h public
127 130
128 putStrLn "Rock on!"
129 startTime <- getTime Monotonic 131 startTime <- getTime Monotonic
130 startTimeReal <- getTime Realtime 132 startTimeReal <- getTime Realtime
131 133
@@ -138,6 +140,7 @@ main' = withAlsaInit $ \h public private q publicAddr privateAddr -> do
138 140
139 let env = LoopEnv saver sqlite startTime startTimeReal h public private q publicAddr privateAddr doSave False lineReader 141 let env = LoopEnv saver sqlite startTime startTimeReal h public private q publicAddr privateAddr doSave False lineReader
140 142
143 putStrLn "Rock on!"
141 (_, ()) <- execRWST mainLoop env $ initializeState startTimeReal 144 (_, ()) <- execRWST mainLoop env $ initializeState startTimeReal
142 return () 145 return ()
143 146
@@ -228,7 +231,7 @@ getMidiSender = do
228processMidi :: MidiController () 231processMidi :: MidiController ()
229processMidi = do 232processMidi = do
230 h <- asks _h 233 h <- asks _h
231 oldKeys <- gets keysDown 234 oldKeys <- gets _keysDown
232 forwardNOW <- getMidiSender 235 forwardNOW <- getMidiSender
233 (events, newKeys) <- liftIO $ parseAlsaEvents' h oldKeys forwardNOW 236 (events, newKeys) <- liftIO $ parseAlsaEvents' h oldKeys forwardNOW
234 237
@@ -241,13 +244,15 @@ processMidi = do
241 244
242 modify $ \s -> 245 modify $ \s ->
243 s 246 s
244 { keysDown = newKeys 247 { _keysDown = newKeys
245 , _recording = recordEvents (_recording s) newEvents 248 , _recording = recordEvents (_recording s) newEvents
246 , _lastTick = now 249 , _lastTick = now
247 } 250 }
248 251
249 whenFlag _printChordKeys $ liftIO $ printChordLn newKeys 252 whenFlag _printChordKeys $ liftIO $ printChordLn newKeys
250 253
254 filterTriads newKeys
255
251 -- Whenever no keys are pressed, flush any buffered events to the database 256 -- Whenever no keys are pressed, flush any buffered events to the database
252 when (Set.null newKeys) $ do 257 when (Set.null newKeys) $ do
253 doSave <- asks _doSave 258 doSave <- asks _doSave
@@ -263,6 +268,43 @@ processMidi = do
263 268
264 modify $ \s -> s { _replay = recordEvents (_replay s) newEvents } 269 modify $ \s -> s { _replay = recordEvents (_replay s) newEvents }
265 270
271filterTriads :: MidiPitchSet -> MidiController ()
272filterTriads newKeys = do
273 let newTriad = detectTriad newKeys
274 oldTriad <- gets _triad
275 when (newTriad /= oldTriad) $ do
276 forM_ oldTriad (sendTriadEvents False)
277 forM_ newTriad (sendTriadEvents True)
278 modify $ \s -> s { _triad = newTriad }
279
280triadBase :: Triad -> Event.Pitch
281triadBase (Major n) = n
282triadBase (Minor n) = n
283
284-- TODO: set velocity based on average from triad (this requires storing that
285-- information, changing the MidiPitchSet type for one more complex than a mere
286-- Set)
287sendTriadEvents :: Bool -> Triad -> MidiController ()
288sendTriadEvents sendOn triad = do
289 forM_ notes (delayNoteEv (TimeSpec 0 0))
290 return ()
291
292 where
293 onoff = bool Event.NoteOff Event.NoteOn sendOn
294 base = Event.unPitch $ triadBase triad
295 notes = (Event.NoteEv onoff . mkNote) <$> fill base
296 fill n = [ x + y | x <- [n, n + 7], y <- [12, -12]]
297
298detectTriad :: MidiPitchSet -> Maybe Triad
299detectTriad pitches = listToMaybe $ concatMap f pitches
300 where
301 f pitch
302 | not $ Set.member (addPitch 7 pitch) pitches = []
303 | Set.member (addPitch 4 pitch) pitches = [Major $ snd pitch] -- TODO: do not just drop the channel!!
304 | Set.member (addPitch 3 pitch) pitches = [Minor $ snd pitch]
305 | otherwise = []
306 addPitch n (c, Event.Pitch p) = (c, Event.Pitch $ p+n)
307
266latestEvent :: Recording -> TimeSpec 308latestEvent :: Recording -> TimeSpec
267latestEvent (StartRecording x) = x 309latestEvent (StartRecording x) = x
268latestEvent (RecordingInProgress _ x []) = x 310latestEvent (RecordingInProgress _ x []) = x