summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndrew Cady <d@jerkface.net>2015-12-04 08:04:27 -0500
committerAndrew Cady <d@jerkface.net>2015-12-04 08:04:27 -0500
commitf15f60496ca7a726934cd0983d8b114c3fbb85b0 (patch)
tree06951aa00dcdba2c4931e1f12f0a1d393433e254
parent507d7eea79bd52dcf3e5ae75a112532b7d89a5e8 (diff)
change data structures for midi recordings
-rw-r--r--midi-dump.hs70
1 files changed, 33 insertions, 37 deletions
diff --git a/midi-dump.hs b/midi-dump.hs
index c77ef00..6227c31 100644
--- a/midi-dump.hs
+++ b/midi-dump.hs
@@ -33,26 +33,27 @@ main = main' `AlsaExc.catch` handler
33 where 33 where
34 handler e = when verbose $ putStrLn $ "alsa_exception: " ++ AlsaExc.show e 34 handler e = when verbose $ putStrLn $ "alsa_exception: " ++ AlsaExc.show e
35 35
36data EVENT = MidiEvent TimeSpec Event.T | Silence TimeSpec 36type RecordedEvents = [(TimeSpec, Event.T)]
37 deriving Show
38 37
39isSilence (Silence _) = True 38data Recording = Recording {
40isSilence _ = False 39 _recordingStart :: TimeSpec, -- from initial silence
40 _recordingEvents :: RecordedEvents
41}
41 42
42getTS (MidiEvent ts _) = ts 43recordEvents :: Recording -> RecordedEvents -> Recording
43getTS (Silence ts) = ts 44recordEvents (Recording s orig) new = Recording s (new ++ orig)
44 45
45data LoopState = LoopState { 46data LoopState = LoopState {
46 _wantExit :: Bool, 47 _wantExit :: Bool,
47 keysDown :: MidiPitchSet, 48 keysDown :: MidiPitchSet,
48 inputHistory :: [EVENT], 49 _recording :: Recording,
49 lastTick :: TimeSpec 50 lastTick :: TimeSpec
50} 51}
51 52
52emptyLoopState = LoopState False Set.empty [] 53initializeState now = LoopState False Set.empty (emptyRecording now) now
53 54
54data LoopEnv = LoopEnv { 55data LoopEnv = LoopEnv {
55 _saver :: Chan (Int64, Int64, [EVENT]), 56 _saver :: Chan Recording,
56 _sqlite :: Connection, 57 _sqlite :: Connection,
57 _startTime :: TimeSpec, 58 _startTime :: TimeSpec,
58 _startTimeReal :: TimeSpec, 59 _startTimeReal :: TimeSpec,
@@ -80,15 +81,15 @@ main' = withAlsaInit $ \h public private q publicAddr privateAddr -> do
80 startTimeReal <- getTime Realtime 81 startTimeReal <- getTime Realtime
81 82
82 sqlite <- open "test.db" 83 sqlite <- open "test.db"
83 execute_ sqlite "CREATE TABLE IF NOT EXISTS chunks (id INTEGER PRIMARY KEY, sec INTEGER, nsec INTEGER, midi BLOB)" 84 execute_ sqlite "CREATE TABLE IF NOT EXISTS axis_input (id INTEGER PRIMARY KEY, sec INTEGER, nsec INTEGER, silence INTEGER, midi BLOB)"
84 saver <- startSaver sqlite 85 saver <- startSaver sqlite
85 lineReader <- startLineReader 86 lineReader <- startLineReader
86 87
87 doSave <- isJust <$> lookupEnv "SAVE_MIDI" 88 doSave <- isNothing <$> lookupEnv "NO_SAVE_MIDI"
88 89
89 let env = LoopEnv saver sqlite startTime startTimeReal h public private q publicAddr privateAddr doSave lineReader 90 let env = LoopEnv saver sqlite startTime startTimeReal h public private q publicAddr privateAddr doSave lineReader
90 91
91 (_, ()) <- execRWST loop env (emptyLoopState startTime) 92 (_, ()) <- execRWST loop env $ initializeState startTimeReal
92 return () 93 return ()
93 where 94 where
94 loop = do 95 loop = do
@@ -97,6 +98,7 @@ main' = withAlsaInit $ \h public private q publicAddr privateAddr -> do
97 unless wantExit loop 98 unless wantExit loop
98 99
99processCommand "exit" = modify $ \s -> s { _wantExit = True } 100processCommand "exit" = modify $ \s -> s { _wantExit = True }
101processCommand "" = return ()
100processCommand str = liftIO $ putStrLn $ "Unknown command: " ++ str 102processCommand str = liftIO $ putStrLn $ "Unknown command: " ++ str
101 103
102processMidi = do 104processMidi = do
@@ -113,25 +115,18 @@ processMidi = do
113 liftIO $ threadDelay 15000 -- 15ms. Seems like a lot, but it sounds OK. Cuts CPU down to 2%. 115 liftIO $ threadDelay 15000 -- 15ms. Seems like a lot, but it sounds OK. Cuts CPU down to 2%.
114 else do 116 else do
115 now <- getAbsTime 117 now <- getAbsTime
116 let newEvents = map (MidiEvent now) events 118 let newEvents = map ((,) now) events
117 119
118 liftIO $ printChordLn newKeys 120 liftIO $ printChordLn newKeys
119 modify $ \s -> s { keysDown = newKeys, inputHistory = newEvents ++ inputHistory s, lastTick = now } 121 modify $ \s -> s { keysDown = newKeys, _recording = recordEvents (_recording s) newEvents, lastTick = now }
120 122
121 when (Set.null newKeys) $ do 123 when (Set.null newKeys) $ do
122 124
123 when False $ do
124 chunk <- gets $ takeWhile (not . isSilence) . inputHistory
125 saveMidi chunk
126
127 hist <- gets $ filter (not . isSilence) . inputHistory
128 liftIO $ print $ map (`div` 1000000) $ tsDeltas $ map getTS $ reverse hist
129 liftIO $ print $ mapMaybe getMidiDesc $ reverse hist
130 modify $ \s -> s { inputHistory = Silence now:inputHistory s }
131
132 doSave <- asks _doSave 125 doSave <- asks _doSave
133 when doSave $ gets inputHistory >>= saveMidi >> return () 126 when doSave $ gets _recording >>= saveMidi >> return ()
134 modify $ \s -> s { inputHistory = [] } 127 modify $ \s -> s { _recording = emptyRecording now }
128
129emptyRecording now = Recording now []
135 130
136maybeReadLine = asks _lineReader >>= liftIO . tryTakeMVar 131maybeReadLine = asks _lineReader >>= liftIO . tryTakeMVar
137startLineReader = do 132startLineReader = do
@@ -139,17 +134,16 @@ startLineReader = do
139 thread <- liftIO $ forkIO (forever $ getLine >>= putMVar mv) 134 thread <- liftIO $ forkIO (forever $ getLine >>= putMVar mv)
140 return mv 135 return mv
141 136
142 137data Chunk = Chunk Int64 Int64 Int64 {- duration of silence in nanoseconds -} BS.ByteString
143data Chunk = Chunk Int64 Int64 BS.ByteString
144instance FromRow Chunk where 138instance FromRow Chunk where
145 fromRow = Chunk <$> field <*> field <*> field 139 fromRow = Chunk <$> field <*> field <*> field <*> field
146instance ToRow Chunk where 140instance ToRow Chunk where
147 toRow (Chunk s ns b) = toRow (s, ns, b) 141 toRow (Chunk s ns b pre) = toRow (s, ns, b, pre)
148 142
149saveMidi chunk = do 143saveMidi recording = do
150 saver <- asks _saver 144 saver <- asks _saver
151 (TimeSpec s ns) <- gets lastTick 145 (TimeSpec s ns) <- gets lastTick
152 liftIO $ writeChan saver (s, ns, chunk) 146 liftIO $ writeChan saver recording
153 147
154startSaver sqlite = do 148startSaver sqlite = do
155 chan <- liftIO newChan 149 chan <- liftIO newChan
@@ -157,15 +151,17 @@ startSaver sqlite = do
157 return chan 151 return chan
158 where 152 where
159 saver chan = forever $ do 153 saver chan = forever $ do
160 (s, ns, chunk) <- readChan chan 154 (Recording silenceStart events) <- readChan chan
161 let bytes = pack $ show chunk 155 let bytes = pack $ show events
162 liftIO $ execute sqlite "INSERT INTO chunks (sec, nsec, midi) VALUES (?,?,?)" (Chunk s ns bytes) 156 ts@(TimeSpec s ns) = fst $ head events
157 silence = fromIntegral $ timeSpecAsNanoSecs $ ts - silenceStart
158 liftIO $ execute sqlite "INSERT INTO axis_input (sec, nsec, silence, midi) VALUES (?,?,?,?)" (Chunk s ns silence bytes)
163 return () 159 return ()
164 160
165 161
166getMidiDesc :: EVENT -> Maybe String 162getMidiDesc :: Event.T -> Maybe String
167getMidiDesc (MidiEvent _ (Event.Cons _ _ _ _ _ _ (Event.NoteEv Event.NoteOn ev))) = return $ ("on:" ++) $ showPitch $ unPitch $ Event.noteNote ev 163getMidiDesc ((Event.Cons _ _ _ _ _ _ (Event.NoteEv Event.NoteOn ev))) = return $ ("on:" ++) $ showPitch $ unPitch $ Event.noteNote ev
168getMidiDesc (MidiEvent _ (Event.Cons _ _ _ _ _ _ (Event.NoteEv Event.NoteOff ev))) = return $ ("off:" ++) $ showPitch $ unPitch $ Event.noteNote ev 164getMidiDesc ((Event.Cons _ _ _ _ _ _ (Event.NoteEv Event.NoteOff ev))) = return $ ("off:" ++) $ showPitch $ unPitch $ Event.noteNote ev
169getMidiDesc _ = Nothing 165getMidiDesc _ = Nothing
170 166
171tsDeltas :: [TimeSpec] -> [Integer] 167tsDeltas :: [TimeSpec] -> [Integer]