diff options
author | Andrew Cady <d@jerkface.net> | 2015-12-05 11:45:51 -0500 |
---|---|---|
committer | Andrew Cady <d@jerkface.net> | 2015-12-05 11:47:55 -0500 |
commit | 9207096755c1db24b81115fe02ca6e97e21e48bf (patch) | |
tree | cb40e55cfd9ee8ddb3cbf54ead7c26c9e5b83e2c | |
parent | 7477265c3c91341bcc96181fd876de0231bd667f (diff) |
Implement replay functionality
(Replay last MIDI input, divided by 3 second periods of silence.)
This has a bug where if there is too much input to replay, the program
exits. (Oddly, there is no exit failure code.)
I think this is because the kernel ALSA buffer is full. The solution is
to implement in-application queueing.
-rw-r--r-- | midi-dump.hs | 53 |
1 files changed, 37 insertions, 16 deletions
diff --git a/midi-dump.hs b/midi-dump.hs index 07281f5..07dfb1c 100644 --- a/midi-dump.hs +++ b/midi-dump.hs | |||
@@ -14,7 +14,6 @@ import qualified Sound.ALSA.Sequencer.Event as Event | |||
14 | import System.Clock | 14 | import System.Clock |
15 | 15 | ||
16 | import Control.Applicative | 16 | import Control.Applicative |
17 | import Data.Int | ||
18 | import Database.SQLite.Simple | 17 | import Database.SQLite.Simple |
19 | import Database.SQLite.Simple.FromRow () | 18 | import Database.SQLite.Simple.FromRow () |
20 | 19 | ||
@@ -145,33 +144,51 @@ _playNote noteOn note = | |||
145 | playNoteEv $ Event.NoteEv onoff note | 144 | playNoteEv $ Event.NoteEv onoff note |
146 | where onoff = if noteOn then Event.NoteOn else Event.NoteOff | 145 | where onoff = if noteOn then Event.NoteOn else Event.NoteOff |
147 | 146 | ||
148 | delayEvent :: Event.T -> Integer -> Event.T | 147 | delayEvent :: Event.T -> TimeSpec -> Event.T |
149 | delayEvent evt nanosecs = evt {Event.time = Time.consRel $ Time.Real (RealTime.fromInteger nanosecs)} | 148 | delayEvent evt ts = evt {Event.time = Time.consRel $ Time.Real (RealTime.fromInteger nanosecs)} |
149 | where nanosecs = timeSpecAsNanoSecs ts | ||
150 | 150 | ||
151 | playNoteEv :: Event.Data -> RWST LoopEnv () LoopState IO () | 151 | playNoteEv :: Event.Data -> RWST LoopEnv () LoopState IO () |
152 | playNoteEv nevdata = do | 152 | playNoteEv = delayNoteEv (TimeSpec 0 0) |
153 | |||
154 | delayNoteEv :: TimeSpec -> Event.Data -> RWST LoopEnv () LoopState IO () | ||
155 | delayNoteEv delay nevdata = do | ||
153 | ms <- getMidiSender | 156 | ms <- getMidiSender |
154 | publicAddr <- asks _publicAddr | 157 | publicAddr <- asks _publicAddr |
155 | liftIO $ ms $ Event.simple publicAddr nevdata `delayEvent` (2 * 10^(9::Int)) | 158 | liftIO $ ms $ Event.simple publicAddr nevdata `delayEvent` delay |
156 | 159 | ||
157 | _whenFlag :: forall (m :: * -> *) s. MonadState s m => (s -> Bool) -> m () -> m () | 160 | _whenFlag :: forall (m :: * -> *) s. MonadState s m => (s -> Bool) -> m () -> m () |
158 | _whenFlag flag f = gets flag >>= flip when f | 161 | _whenFlag flag f = gets flag >>= flip when f |
159 | 162 | ||
160 | processCommand :: forall (m :: * -> *). (MonadIO m, MonadState LoopState m) => String -> m () | 163 | mkNote :: Word8 -> Event.Note |
164 | mkNote pitch = Event.simpleNote (Event.Channel 0) (Event.Pitch pitch) (Event.Velocity 128) | ||
165 | |||
166 | processCommand :: String -> RWST LoopEnv () LoopState IO () | ||
161 | processCommand "exit" = modify $ \s -> s { _wantExit = True } | 167 | processCommand "exit" = modify $ \s -> s { _wantExit = True } |
162 | processCommand "" = return () | 168 | -- processCommand "" = return () |
169 | processCommand "" = gets _replay >>= playRecording | ||
163 | processCommand "C" = do | 170 | processCommand "C" = do |
164 | let mkNote pitch = Event.simpleNote (Event.Channel 0) (Event.Pitch pitch) (Event.Velocity 128) | ||
165 | let notes = (Event.NoteEv Event.NoteOn . mkNote) <$> [60, 64, 67] | 171 | let notes = (Event.NoteEv Event.NoteOn . mkNote) <$> [60, 64, 67] |
166 | modify $ \s -> s { _playNOW = notes } | 172 | modify $ \s -> s { _playNOW = notes } |
167 | processCommand "C'" = do | 173 | processCommand "C'" = do |
174 | let notes = (Event.NoteEv Event.NoteOn . mkNote) <$> [60, 64, 67] | ||
175 | forM_ notes (delayNoteEv (TimeSpec 2 0)) | ||
176 | {- | ||
177 | processCommand "C'" = do | ||
168 | -- changing the duration seems to do nothing | 178 | -- changing the duration seems to do nothing |
169 | let mkNote pitch = Event.simpleNote (Event.Channel 0) (Event.Pitch pitch) (Event.Velocity 128) | 179 | let mkNote pitch = Event.simpleNote (Event.Channel 0) (Event.Pitch pitch) (Event.Velocity 128) |
170 | setDuration d note = note { Event.noteDuration = Event.Duration d } | 180 | setDuration d note = note { Event.noteDuration = Event.Duration d } |
171 | let notes = (Event.NoteEv Event.NoteOn . setDuration 4290000000 . mkNote) <$> [60, 64, 67] | 181 | let notes = (Event.NoteEv Event.NoteOn . setDuration 4290000000 . mkNote) <$> [60, 64, 67] |
172 | modify $ \s -> s { _playNOW = notes } | 182 | modify $ \s -> s { _playNOW = notes } |
183 | -} | ||
173 | processCommand str = liftIO $ putStrLn $ "Unknown command: " ++ str | 184 | processCommand str = liftIO $ putStrLn $ "Unknown command: " ++ str |
174 | 185 | ||
186 | playRecording :: Recording -> RWST LoopEnv () LoopState IO () | ||
187 | playRecording (RecordingInProgress _ _ evts@(_:_)) = | ||
188 | mapM_ (uncurry delayNoteEv) (zip (subtract (head delays) <$> delays) events) | ||
189 | where (delays, events) = unzip $ fmap Event.body <$> reverse evts | ||
190 | playRecording _ = return () | ||
191 | |||
175 | getMidiSender :: RWST LoopEnv () LoopState IO MidiHook | 192 | getMidiSender :: RWST LoopEnv () LoopState IO MidiHook |
176 | getMidiSender = do | 193 | getMidiSender = do |
177 | h <- asks _h | 194 | h <- asks _h |
@@ -196,21 +213,25 @@ processMidi = do | |||
196 | liftIO $ printChordLn newKeys | 213 | liftIO $ printChordLn newKeys |
197 | modify $ \s -> s { keysDown = newKeys, _recording = recordEvents (_recording s) newEvents, lastTick = now } | 214 | modify $ \s -> s { keysDown = newKeys, _recording = recordEvents (_recording s) newEvents, lastTick = now } |
198 | 215 | ||
199 | {- | 216 | when (Set.null newKeys) $ do |
217 | |||
218 | doSave <- asks _doSave | ||
219 | when doSave $ gets _recording >>= saveMidi >> return () | ||
220 | modify $ \s -> s { _recording = StartRecording now } | ||
200 | 221 | ||
201 | when (Set.null oldKeys) $ do | 222 | when (Set.null oldKeys) $ do |
202 | 223 | ||
203 | replay <- gets _replay | 224 | replay <- gets _replay |
204 | 225 | when (latestEvent replay < (now - TimeSpec 3 0)) $ do | |
205 | when (lastEventTime replay < now - 10*10^9) $ do | ||
206 | modify $ \s -> s { _replay = StartRecording now } | 226 | modify $ \s -> s { _replay = StartRecording now } |
207 | -} | 227 | return () |
208 | 228 | ||
209 | when (Set.null newKeys) $ do | 229 | modify $ \s -> s { _replay = recordEvents (_replay s) newEvents } |
210 | 230 | ||
211 | doSave <- asks _doSave | 231 | latestEvent :: Recording -> TimeSpec |
212 | when doSave $ gets _recording >>= saveMidi >> return () | 232 | latestEvent (StartRecording x) = x |
213 | modify $ \s -> s { _recording = StartRecording now } | 233 | latestEvent (RecordingInProgress _ x []) = x |
234 | latestEvent (RecordingInProgress _ _ ((x,_):_)) = x | ||
214 | 235 | ||
215 | maybeReadLine :: RWST LoopEnv () LoopState IO (Maybe String) | 236 | maybeReadLine :: RWST LoopEnv () LoopState IO (Maybe String) |
216 | maybeReadLine = asks _lineReader >>= liftIO . tryTakeMVar | 237 | maybeReadLine = asks _lineReader >>= liftIO . tryTakeMVar |