diff options
author | Andrew Cady <d@jerkface.net> | 2015-12-13 12:23:17 -0500 |
---|---|---|
committer | Andrew Cady <d@jerkface.net> | 2015-12-13 12:23:17 -0500 |
commit | 24d588281bb8d0d4c3967b425266d28490edd830 (patch) | |
tree | 7f26a550d0cd668735433b507e19846a41f10f4b | |
parent | d372f8ba1f6852fce5a5ac8eec4a9371828617c5 (diff) |
initial support for reprogammable triads
-rw-r--r-- | midi-dump.hs | 63 |
1 files changed, 55 insertions, 8 deletions
diff --git a/midi-dump.hs b/midi-dump.hs index 369ee9c..7b7bf8b 100644 --- a/midi-dump.hs +++ b/midi-dump.hs | |||
@@ -3,12 +3,14 @@ | |||
3 | {-# LANGUAGE OverloadedStrings #-} | 3 | {-# LANGUAGE OverloadedStrings #-} |
4 | {-# LANGUAGE ScopedTypeVariables #-} | 4 | {-# LANGUAGE ScopedTypeVariables #-} |
5 | {-# LANGUAGE KindSignatures #-} | 5 | {-# LANGUAGE KindSignatures #-} |
6 | {-# LANGUAGE LambdaCase #-} | ||
6 | 7 | ||
7 | import AlsaSeq | 8 | import AlsaSeq |
8 | import Control.Monad.RWS.Strict | 9 | import Control.Monad.RWS.Strict |
9 | import Data.List | 10 | import Data.List |
10 | import Data.Maybe | 11 | import Data.Maybe |
11 | import qualified Data.Map.Strict as Map | 12 | import qualified Data.Map.Strict as Map |
13 | import Data.Map.Strict (Map) | ||
12 | import qualified Sound.ALSA.Exception as AlsaExc | 14 | import qualified Sound.ALSA.Exception as AlsaExc |
13 | import qualified Sound.ALSA.Sequencer.Event as Event | 15 | import qualified Sound.ALSA.Sequencer.Event as Event |
14 | import System.Clock | 16 | import System.Clock |
@@ -53,13 +55,30 @@ main = main' `AlsaExc.catch` handler | |||
53 | handler e = when verbose $ putStrLn $ "alsa_exception: " ++ AlsaExc.show e | 55 | handler e = when verbose $ putStrLn $ "alsa_exception: " ++ AlsaExc.show e |
54 | 56 | ||
55 | data TriadType = Major | Minor deriving (Show, Eq, Ord) | 57 | data TriadType = Major | Minor deriving (Show, Eq, Ord) |
56 | data Triad = Triad TriadType Event.Pitch Event.Velocity deriving (Show, Eq, Ord) | 58 | data Triad = |
59 | Triad | ||
60 | { _triadType :: TriadType | ||
61 | , triadPitch :: Event.Pitch | ||
62 | , _triadVelocity :: Event.Velocity | ||
63 | } | ||
64 | deriving (Show, Eq, Ord) | ||
65 | toPitchClass :: Event.Pitch -> PitchClass | ||
66 | toPitchClass = Event.unPitch >>> (`mod` 12) >>> fromIntegral | ||
67 | tonic :: Triad -> PitchClass | ||
68 | tonic (Triad _ p _) = toPitchClass p | ||
69 | |||
70 | data TriadRecorder = TriadNotRecording | AwaitingTriad | AwaitingRelease Event.Pitch TriadType | Recording Event.Pitch TriadType | ||
71 | |||
72 | type PitchClass = Int | ||
73 | type ScaleDegree = Int | ||
57 | 74 | ||
58 | data LoopState = LoopState { | 75 | data LoopState = LoopState { |
59 | _wantExit :: Bool, | 76 | _wantExit :: Bool, |
60 | _waitThreads :: [IO (Thread.Result ())], | 77 | _waitThreads :: [IO (Thread.Result ())], |
61 | _keysDown :: MidiPitchMap, | 78 | _keysDown :: MidiPitchMap, |
62 | _triad :: Set Triad, | 79 | _triad :: Set Triad, |
80 | _triadMap :: Map (PitchClass, TriadType) (Set ScaleDegree), | ||
81 | _triadRecording :: TriadRecorder, | ||
63 | _scheduled :: Q.Queue Event.Data, | 82 | _scheduled :: Q.Queue Event.Data, |
64 | _metronome :: Maybe Metronome, | 83 | _metronome :: Maybe Metronome, |
65 | _recording :: Recording, | 84 | _recording :: Recording, |
@@ -74,7 +93,7 @@ data Metronome = Metronome { | |||
74 | } | 93 | } |
75 | 94 | ||
76 | initializeState :: TimeSpec -> LoopState | 95 | initializeState :: TimeSpec -> LoopState |
77 | initializeState now = LoopState False [] Map.empty Set.empty createQueue Nothing (StartRecording now) (StartRecording now) now | 96 | initializeState now = LoopState False [] Map.empty Set.empty Map.empty TriadNotRecording createQueue Nothing (StartRecording now) (StartRecording now) now |
78 | 97 | ||
79 | data LoopEnv = LoopEnv { | 98 | data LoopEnv = LoopEnv { |
80 | _saver :: Chan CompleteRecording, | 99 | _saver :: Chan CompleteRecording, |
@@ -292,6 +311,13 @@ processCommand "dump" = sqlSelectEVERYTHING >>= saveRecording "dumped.mid" . mco | |||
292 | processCommand "C" = do | 311 | processCommand "C" = do |
293 | let notes = (Event.NoteEv Event.NoteOn . mkNote) <$> [60, 64, 67] | 312 | let notes = (Event.NoteEv Event.NoteOn . mkNote) <$> [60, 64, 67] |
294 | forM_ notes (delayNoteEv (TimeSpec 0 0)) | 313 | forM_ notes (delayNoteEv (TimeSpec 0 0)) |
314 | processCommand "M-t" = gets _triadRecording >>= \case | ||
315 | TriadNotRecording -> do | ||
316 | liftIO $ putStrLn "Recording triad" | ||
317 | modify $ \s -> s { _triadRecording = AwaitingTriad } | ||
318 | _ -> do | ||
319 | liftIO $ putStrLn "Cancelled ecording triad" | ||
320 | modify $ \s -> s { _triadRecording = TriadNotRecording } | ||
295 | processCommand "M-m" = do | 321 | processCommand "M-m" = do |
296 | now <- getAbsTime | 322 | now <- getAbsTime |
297 | m <- gets _metronome | 323 | m <- gets _metronome |
@@ -402,7 +428,27 @@ processMidi = do | |||
402 | 428 | ||
403 | whenFlag _printChordKeys $ liftIO $ printChordLn' newKeys | 429 | whenFlag _printChordKeys $ liftIO $ printChordLn' newKeys |
404 | 430 | ||
405 | filterTriads newKeys | 431 | triadRecording <- gets _triadRecording |
432 | case triadRecording of | ||
433 | TriadNotRecording -> filterTriads newKeys | ||
434 | AwaitingTriad -> do | ||
435 | let detected = snd <$> listToMaybe (detectTriads newKeys) | ||
436 | forM_ detected $ \t@(Triad _ p _) -> | ||
437 | (modify $ \s -> s { _triadRecording = AwaitingRelease p (_triadType t) }) | ||
438 | AwaitingRelease pitch ttype -> do | ||
439 | let pc = toPitchClass pitch | ||
440 | when (Map.null newKeys) $ | ||
441 | modify $ \s -> s { _triadRecording = Recording pitch ttype, _triadMap = Map.delete (pc, ttype) (_triadMap s) } | ||
442 | Recording pitch ttype -> do | ||
443 | let f :: Map (Event.Channel, Event.Pitch) Event.Velocity -> Set ScaleDegree | ||
444 | f = Set.fromList . map (subtract (fromIntegral $ Event.unPitch pitch) . fromIntegral . Event.unPitch . snd) . Map.keys | ||
445 | pc = toPitchClass pitch | ||
446 | let detected = triadPitch . snd <$> detectTriads newKeys | ||
447 | detected :: [Event.Pitch] | ||
448 | if pitch `elem` detected then | ||
449 | modify $ \s -> s { _triadRecording = TriadNotRecording } | ||
450 | else | ||
451 | modify $ \s -> s { _triadMap = Map.insertWith Set.union (pc, ttype) (f newKeys) (_triadMap s) } | ||
406 | 452 | ||
407 | -- Whenever no keys are pressed, flush any buffered events to the database | 453 | -- Whenever no keys are pressed, flush any buffered events to the database |
408 | when (Map.null newKeys) $ do | 454 | when (Map.null newKeys) $ do |
@@ -432,13 +478,14 @@ triadOff :: Triad -> Triad | |||
432 | triadOff (Triad t p _) = Triad t p (Event.Velocity 0) | 478 | triadOff (Triad t p _) = Triad t p (Event.Velocity 0) |
433 | 479 | ||
434 | sendTriadEvents :: Triad -> MidiController () | 480 | sendTriadEvents :: Triad -> MidiController () |
435 | sendTriadEvents (Triad _ (Event.Pitch base) vel) = do | 481 | sendTriadEvents t@(Triad ttype (Event.Pitch base) vel) = do |
436 | forM_ notes (delayNoteEv (TimeSpec 0 0)) | 482 | mappedNotes <- Map.lookup (tonic t, ttype) <$> gets _triadMap |
437 | return () | 483 | when (isJust mappedNotes) $ notesOn $ notes (map fromIntegral . Set.toList <$> mappedNotes) |
438 | 484 | ||
439 | where | 485 | where |
440 | notes = fromVel vel <$> fill base | 486 | notesOn n = forM_ n (delayNoteEv (TimeSpec 0 0)) |
441 | fill n = [ x + y | x <- [n, n + 7], y <- [12, -12]] | 487 | notes :: Maybe [Word8] -> [Event.Data] |
488 | notes mappedNotes = fromVel vel <$> map (base +) (fromMaybe [12, -12, 7+12, 7-12] mappedNotes) | ||
442 | fromVel (Event.Velocity 0) pitch = Event.NoteEv Event.NoteOff $ Event.simpleNote (Event.Channel 0) (Event.Pitch pitch) (Event.Velocity 0) | 489 | fromVel (Event.Velocity 0) pitch = Event.NoteEv Event.NoteOff $ Event.simpleNote (Event.Channel 0) (Event.Pitch pitch) (Event.Velocity 0) |
443 | fromVel v pitch = Event.NoteEv Event.NoteOn $ Event.simpleNote (Event.Channel 0) (Event.Pitch pitch) v | 490 | fromVel v pitch = Event.NoteEv Event.NoteOn $ Event.simpleNote (Event.Channel 0) (Event.Pitch pitch) v |
444 | 491 | ||