diff options
author | Andrew Cady <d@jerkface.net> | 2015-12-10 17:18:30 -0500 |
---|---|---|
committer | Andrew Cady <d@jerkface.net> | 2015-12-10 17:18:30 -0500 |
commit | 66f65478a22fcc3ff024f0c1456ece372aae554b (patch) | |
tree | b461ea2b145012b9e62609e84e8d08e12121a674 | |
parent | cd9e7854db78041b7453a3d1bcfa45a95fe53604 (diff) |
Implement saving of .mid files
(Currently accessible only under testing command "save".)
-rw-r--r-- | axis-of-eval.cabal | 2 | ||||
-rw-r--r-- | midi-dump.hs | 59 |
2 files changed, 55 insertions, 6 deletions
diff --git a/axis-of-eval.cabal b/axis-of-eval.cabal index dac8e24..d1aee15 100644 --- a/axis-of-eval.cabal +++ b/axis-of-eval.cabal | |||
@@ -37,7 +37,7 @@ executable midi-dump | |||
37 | build-depends: | 37 | build-depends: |
38 | base, time, containers, haskore, alsa-seq, alsa-core, mtl, clock, | 38 | base, time, containers, haskore, alsa-seq, alsa-core, mtl, clock, |
39 | sqlite-simple, bytestring, base-prelude, midi-alsa, midi, psqueues, | 39 | sqlite-simple, bytestring, base-prelude, midi-alsa, midi, psqueues, |
40 | transformers, semigroups, HCodecs | 40 | transformers, semigroups, HCodecs, threads |
41 | main-is: midi-dump.hs | 41 | main-is: midi-dump.hs |
42 | other-modules: AlsaSeq, Midi, RealTimeQueue | 42 | other-modules: AlsaSeq, Midi, RealTimeQueue |
43 | ghc-options: -threaded -W -Wall -O2 | 43 | ghc-options: -threaded -W -Wall -O2 |
diff --git a/midi-dump.hs b/midi-dump.hs index d2b6c15..eacaa03 100644 --- a/midi-dump.hs +++ b/midi-dump.hs | |||
@@ -31,6 +31,9 @@ import qualified Sound.ALSA.Sequencer.RealTime as RealTime | |||
31 | import Midi | 31 | import Midi |
32 | import RealTimeQueue as Q hiding (null) | 32 | import RealTimeQueue as Q hiding (null) |
33 | import qualified Codec.Midi | 33 | import qualified Codec.Midi |
34 | import Codec.Midi (Midi(..), FileType(..), TimeDiv(..)) | ||
35 | |||
36 | import qualified Control.Concurrent.Thread as Thread | ||
34 | 37 | ||
35 | verbose :: Bool | 38 | verbose :: Bool |
36 | verbose = False | 39 | verbose = False |
@@ -45,6 +48,7 @@ data Triad = Triad TriadType Event.Pitch Event.Velocity deriving (Show, Eq) | |||
45 | 48 | ||
46 | data LoopState = LoopState { | 49 | data LoopState = LoopState { |
47 | _wantExit :: Bool, | 50 | _wantExit :: Bool, |
51 | _waitThreads :: [IO (Thread.Result ())], | ||
48 | _keysDown :: MidiPitchMap, | 52 | _keysDown :: MidiPitchMap, |
49 | _triad :: Maybe Triad, | 53 | _triad :: Maybe Triad, |
50 | _scheduled :: Q.Queue Event.Data, | 54 | _scheduled :: Q.Queue Event.Data, |
@@ -54,7 +58,7 @@ data LoopState = LoopState { | |||
54 | } | 58 | } |
55 | 59 | ||
56 | initializeState :: TimeSpec -> LoopState | 60 | initializeState :: TimeSpec -> LoopState |
57 | initializeState now = LoopState False Map.empty Nothing createQueue (StartRecording now) (StartRecording now) now | 61 | initializeState now = LoopState False [] Map.empty Nothing createQueue (StartRecording now) (StartRecording now) now |
58 | 62 | ||
59 | data LoopEnv = LoopEnv { | 63 | data LoopEnv = LoopEnv { |
60 | _saver :: Chan CompleteRecording, | 64 | _saver :: Chan CompleteRecording, |
@@ -109,8 +113,27 @@ sqlInsert = fromString . concat $ | |||
109 | , "VALUES (?,?,?,?, ?,?,?,?, ?)" | 113 | , "VALUES (?,?,?,?, ?,?,?,?, ?)" |
110 | ] | 114 | ] |
111 | 115 | ||
112 | sqlSelectEVERYTHING :: MidiController [CompleteRecording] | 116 | sqlSelectRECENT :: MidiController [CompleteRecording] |
113 | sqlSelectEVERYTHING = do | 117 | sqlSelectRECENT = do |
118 | conn <- asks _sqlite | ||
119 | fmap reverse $ liftIO $ query_ conn $ fromString . concat $ | ||
120 | [ "SELECT " | ||
121 | , "start_sec," | ||
122 | , "start_nsec," | ||
123 | , "end_sec," | ||
124 | , "end_nsec," | ||
125 | , "first_sec," | ||
126 | , "first_nsec," | ||
127 | , "last_sec," | ||
128 | , "last_nsec," | ||
129 | , "midi" | ||
130 | , " FROM axis_input", | ||
131 | " ORDER BY start_sec DESC, start_nsec DESC ", | ||
132 | " LIMIT 10" | ||
133 | ] | ||
134 | |||
135 | _sqlSelectEVERYTHING :: MidiController [CompleteRecording] | ||
136 | _sqlSelectEVERYTHING = do | ||
114 | conn <- asks _sqlite | 137 | conn <- asks _sqlite |
115 | liftIO $ query_ conn $ fromString . concat $ | 138 | liftIO $ query_ conn $ fromString . concat $ |
116 | [ "SELECT " | 139 | [ "SELECT " |
@@ -151,7 +174,12 @@ mainLoop = do | |||
151 | maybeReadLine >>= maybe processMidi processCommand | 174 | maybeReadLine >>= maybe processMidi processCommand |
152 | wantExit <- gets _wantExit | 175 | wantExit <- gets _wantExit |
153 | playScheduled | 176 | playScheduled |
154 | unless wantExit mainLoop | 177 | if wantExit |
178 | then waitThreads | ||
179 | else mainLoop | ||
180 | |||
181 | waitThreads :: MidiController () | ||
182 | waitThreads = gets _waitThreads >>= mapM_ liftIO | ||
155 | 183 | ||
156 | playScheduled :: MidiController () | 184 | playScheduled :: MidiController () |
157 | playScheduled = queueAction $ runScheduledIO (TimeSpec 1 0) $ uncurry alsaDelayNoteEv | 185 | playScheduled = queueAction $ runScheduledIO (TimeSpec 1 0) $ uncurry alsaDelayNoteEv |
@@ -195,7 +223,8 @@ processCommand :: String -> MidiController () | |||
195 | processCommand "exit" = modify $ \s -> s { _wantExit = True } | 223 | processCommand "exit" = modify $ \s -> s { _wantExit = True } |
196 | -- processCommand "" = return () | 224 | -- processCommand "" = return () |
197 | processCommand "" = gets _replay >>= playRecording | 225 | processCommand "" = gets _replay >>= playRecording |
198 | processCommand "dump" = sqlSelectEVERYTHING >>= playRecording . mconcat | 226 | processCommand "dump" = sqlSelectRECENT >>= playRecording . mconcat |
227 | processCommand "save" = sqlSelectRECENT >>= saveRecording "saved.mid" . mconcat | ||
199 | processCommand "C" = do | 228 | processCommand "C" = do |
200 | let notes = (Event.NoteEv Event.NoteOn . mkNote) <$> [60, 64, 67] | 229 | let notes = (Event.NoteEv Event.NoteOn . mkNote) <$> [60, 64, 67] |
201 | forM_ notes (delayNoteEv (TimeSpec 0 0)) | 230 | forM_ notes (delayNoteEv (TimeSpec 0 0)) |
@@ -218,6 +247,9 @@ type MidiController = MidiControllerT IO | |||
218 | playRecording :: Playable p => p -> MidiController () | 247 | playRecording :: Playable p => p -> MidiController () |
219 | playRecording = playEvents . playableEvents | 248 | playRecording = playEvents . playableEvents |
220 | 249 | ||
250 | saveRecording :: Playable p => FilePath -> p -> MidiController () | ||
251 | saveRecording file = saveEvents file . playableEvents | ||
252 | |||
221 | fixedOutputChannel :: Maybe Codec.Midi.Channel | 253 | fixedOutputChannel :: Maybe Codec.Midi.Channel |
222 | fixedOutputChannel = Just 0 | 254 | fixedOutputChannel = Just 0 |
223 | 255 | ||
@@ -225,6 +257,23 @@ setOutputChannel :: Codec.Midi.Message -> Codec.Midi.Message | |||
225 | setOutputChannel = case fixedOutputChannel of Just n -> setChannel n | 257 | setOutputChannel = case fixedOutputChannel of Just n -> setChannel n |
226 | Nothing -> id | 258 | Nothing -> id |
227 | 259 | ||
260 | saveEvents :: FilePath -> [RecordedEvent] -> MidiController () | ||
261 | saveEvents file evts@(_:_) = do | ||
262 | (_, wait) <- liftIO $ Thread.forkIO $ Codec.Midi.exportFile file midi | ||
263 | modify $ \s -> s { _waitThreads = wait:_waitThreads s } | ||
264 | where | ||
265 | midi = Midi MultiTrack (TicksPerBeat $ fromIntegral ticksPerBeat) [track ++ [(0, Codec.Midi.TrackEnd)]] | ||
266 | track = zip (toDeltas (conv . subtract (head delays) <$> delays)) events | ||
267 | (delays, events) = unzip $ reverse $ (fmap.fmap) setOutputChannel evts -- TODO: do not use fixed channel | ||
268 | conv :: TimeSpec -> Int | ||
269 | conv = fromIntegral . (`div` (10^(9::Int) `div` ticksPerSecond)) . timeSpecAsNanoSecs | ||
270 | ticksPerSecond = ticksPerBeat * beatsPerSecond | ||
271 | beatsPerSecond = 120 `div` 60 | ||
272 | ticksPerBeat :: Integer | ||
273 | -- ticksPerBeat = 2^(15::Int) - 1 | ||
274 | ticksPerBeat = 2400 | ||
275 | saveEvents _ _ = return () | ||
276 | |||
228 | playEvents :: [RecordedEvent] -> RWST LoopEnv () LoopState IO () | 277 | playEvents :: [RecordedEvent] -> RWST LoopEnv () LoopState IO () |
229 | playEvents evts@(_:_) = | 278 | playEvents evts@(_:_) = |
230 | mapM_ (uncurry delayNoteEv) (zip (subtract (head delays) <$> delays) events) | 279 | mapM_ (uncurry delayNoteEv) (zip (subtract (head delays) <$> delays) events) |