diff options
author | Andrew Cady <d@jerkface.net> | 2015-12-04 08:04:27 -0500 |
---|---|---|
committer | Andrew Cady <d@jerkface.net> | 2015-12-04 08:04:27 -0500 |
commit | f15f60496ca7a726934cd0983d8b114c3fbb85b0 (patch) | |
tree | 06951aa00dcdba2c4931e1f12f0a1d393433e254 | |
parent | 507d7eea79bd52dcf3e5ae75a112532b7d89a5e8 (diff) |
change data structures for midi recordings
-rw-r--r-- | midi-dump.hs | 70 |
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 | ||
36 | data EVENT = MidiEvent TimeSpec Event.T | Silence TimeSpec | 36 | type RecordedEvents = [(TimeSpec, Event.T)] |
37 | deriving Show | ||
38 | 37 | ||
39 | isSilence (Silence _) = True | 38 | data Recording = Recording { |
40 | isSilence _ = False | 39 | _recordingStart :: TimeSpec, -- from initial silence |
40 | _recordingEvents :: RecordedEvents | ||
41 | } | ||
41 | 42 | ||
42 | getTS (MidiEvent ts _) = ts | 43 | recordEvents :: Recording -> RecordedEvents -> Recording |
43 | getTS (Silence ts) = ts | 44 | recordEvents (Recording s orig) new = Recording s (new ++ orig) |
44 | 45 | ||
45 | data LoopState = LoopState { | 46 | data 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 | ||
52 | emptyLoopState = LoopState False Set.empty [] | 53 | initializeState now = LoopState False Set.empty (emptyRecording now) now |
53 | 54 | ||
54 | data LoopEnv = LoopEnv { | 55 | data 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 | ||
99 | processCommand "exit" = modify $ \s -> s { _wantExit = True } | 100 | processCommand "exit" = modify $ \s -> s { _wantExit = True } |
101 | processCommand "" = return () | ||
100 | processCommand str = liftIO $ putStrLn $ "Unknown command: " ++ str | 102 | processCommand str = liftIO $ putStrLn $ "Unknown command: " ++ str |
101 | 103 | ||
102 | processMidi = do | 104 | processMidi = 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 | |||
129 | emptyRecording now = Recording now [] | ||
135 | 130 | ||
136 | maybeReadLine = asks _lineReader >>= liftIO . tryTakeMVar | 131 | maybeReadLine = asks _lineReader >>= liftIO . tryTakeMVar |
137 | startLineReader = do | 132 | startLineReader = 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 | 137 | data Chunk = Chunk Int64 Int64 Int64 {- duration of silence in nanoseconds -} BS.ByteString | |
143 | data Chunk = Chunk Int64 Int64 BS.ByteString | ||
144 | instance FromRow Chunk where | 138 | instance FromRow Chunk where |
145 | fromRow = Chunk <$> field <*> field <*> field | 139 | fromRow = Chunk <$> field <*> field <*> field <*> field |
146 | instance ToRow Chunk where | 140 | instance 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 | ||
149 | saveMidi chunk = do | 143 | saveMidi 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 | ||
154 | startSaver sqlite = do | 148 | startSaver 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 | ||
166 | getMidiDesc :: EVENT -> Maybe String | 162 | getMidiDesc :: Event.T -> Maybe String |
167 | getMidiDesc (MidiEvent _ (Event.Cons _ _ _ _ _ _ (Event.NoteEv Event.NoteOn ev))) = return $ ("on:" ++) $ showPitch $ unPitch $ Event.noteNote ev | 163 | getMidiDesc ((Event.Cons _ _ _ _ _ _ (Event.NoteEv Event.NoteOn ev))) = return $ ("on:" ++) $ showPitch $ unPitch $ Event.noteNote ev |
168 | getMidiDesc (MidiEvent _ (Event.Cons _ _ _ _ _ _ (Event.NoteEv Event.NoteOff ev))) = return $ ("off:" ++) $ showPitch $ unPitch $ Event.noteNote ev | 164 | getMidiDesc ((Event.Cons _ _ _ _ _ _ (Event.NoteEv Event.NoteOff ev))) = return $ ("off:" ++) $ showPitch $ unPitch $ Event.noteNote ev |
169 | getMidiDesc _ = Nothing | 165 | getMidiDesc _ = Nothing |
170 | 166 | ||
171 | tsDeltas :: [TimeSpec] -> [Integer] | 167 | tsDeltas :: [TimeSpec] -> [Integer] |