summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndrew Cady <d@jerkface.net>2015-12-07 16:18:08 -0500
committerAndrew Cady <d@jerkface.net>2015-12-07 16:18:08 -0500
commit1bed0c53f8bdd6c3c5fb1346524ab133a45763dd (patch)
tree4e45c2a986dc119c752847e5c0853ba4b00f2e43
parentc15513cc1fc643dc088e430c0c41e923e29c928d (diff)
Store note velocities in pitch sets
(actually pitch maps, now)
-rw-r--r--AlsaSeq.hs30
-rw-r--r--midi-dump.hs42
2 files changed, 48 insertions, 24 deletions
diff --git a/AlsaSeq.hs b/AlsaSeq.hs
index 5b65967..0512c80 100644
--- a/AlsaSeq.hs
+++ b/AlsaSeq.hs
@@ -1,7 +1,7 @@
1{-# LANGUAGE NondecreasingIndentation #-} 1{-# LANGUAGE NondecreasingIndentation #-}
2module AlsaSeq (withAlsaInit, parseAlsaEvents, parseAlsaEvents', forwardNoteEvent, 2module AlsaSeq (withAlsaInit, parseAlsaEvents, parseAlsaEvents', parseAlsaEvents'', forwardNoteEvent,
3cmdlineAlsaConnect, printChordLn, showChord, pitchList, showPitch, 3cmdlineAlsaConnect, printChordLn, printChordLn', showChord, pitchList, showPitch,
4unPitch, unChannel, MidiHook, MidiPitchSet) where 4unPitch, unChannel, MidiHook, MidiPitchSet, MidiPitchMap) where
5import qualified Sound.ALSA.Exception as AlsaExc 5import qualified Sound.ALSA.Exception as AlsaExc
6import qualified Sound.ALSA.Sequencer.Address as Addr 6import qualified Sound.ALSA.Sequencer.Address as Addr
7import qualified Sound.ALSA.Sequencer as SndSeq 7import qualified Sound.ALSA.Sequencer as SndSeq
@@ -23,6 +23,7 @@ import Text.Printf
23import Control.Monad (when, forM_, forM) 23import Control.Monad (when, forM_, forM)
24 24
25import qualified Data.Set as Set 25import qualified Data.Set as Set
26import qualified Data.Map.Strict as Map
26import Data.List (group, sort) 27import Data.List (group, sort)
27import Haskore.Basic.Pitch 28import Haskore.Basic.Pitch
28import Foreign.C.Error (Errno(Errno)) 29import Foreign.C.Error (Errno(Errno))
@@ -32,6 +33,7 @@ unPitch = Event.unPitch
32unChannel = Event.unChannel 33unChannel = Event.unChannel
33 34
34printChordLn set = printWords $ pitchWords set 35printChordLn set = printWords $ pitchWords set
36printChordLn' = printWords . map (showPitch . Event.unPitch . snd) . Map.keys
35 37
36joinWords [] = "" 38joinWords [] = ""
37joinWords ls = foldr1 (\a b -> a ++ " " ++ b) ls 39joinWords ls = foldr1 (\a b -> a ++ " " ++ b) ls
@@ -41,7 +43,7 @@ printWords ls = putStrLn $ joinWords ls
41 43
42showChord ls = joinWords $ pitchWords ls 44showChord ls = joinWords $ pitchWords ls
43 45
44showPitch x = 46showPitch x =
45 let (octave, pitch) = Haskore.Basic.Pitch.fromInt $ fromIntegral x 47 let (octave, pitch) = Haskore.Basic.Pitch.fromInt $ fromIntegral x
46 in Haskore.Basic.Pitch.classFormat pitch (show octave) 48 in Haskore.Basic.Pitch.classFormat pitch (show octave)
47 49
@@ -145,6 +147,7 @@ inputPendingLoop h b = do
145 (Right result) -> return result 147 (Right result) -> return result
146 148
147type MidiPitchSet = Set.Set (Event.Channel, Event.Pitch) 149type MidiPitchSet = Set.Set (Event.Channel, Event.Pitch)
150type MidiPitchMap = Map.Map (Event.Channel, Event.Pitch) Event.Velocity
148parseAlsaEvents :: SndSeq.AllowInput mode => 151parseAlsaEvents :: SndSeq.AllowInput mode =>
149 SndSeq.T mode -> Set.Set (Event.Channel, Event.Pitch) -> (Event.T -> IO a) -> IO MidiPitchSet 152 SndSeq.T mode -> Set.Set (Event.Channel, Event.Pitch) -> (Event.T -> IO a) -> IO MidiPitchSet
150parseAlsaEvents h keysDown immediate = loop keysDown 153parseAlsaEvents h keysDown immediate = loop keysDown
@@ -185,6 +188,25 @@ parseAlsaEvents' h keysDown immediate = loop [] keysDown
185 loop (ev:events) (Set.delete (Event.noteChannel n, Event.noteNote n) keysDown) 188 loop (ev:events) (Set.delete (Event.noteChannel n, Event.noteNote n) keysDown)
186 _ -> loop (ev:events) keysDown 189 _ -> loop (ev:events) keysDown
187 190
191parseAlsaEvents'' h keysDown immediate = loop [] keysDown
192 where
193 loop events keysDown = do
194 pending <- inputPendingLoop h True
195 if (pending == 0) then
196 return (events, keysDown)
197 else do
198 ev <- Event.input h
199 immediate ev
200 case Event.body ev of
201 Event.NoteEv Event.NoteOn n ->
202 if (Event.unVelocity (Event.noteVelocity n) == 0) then
203 loop (ev:events) (Map.delete (Event.noteChannel n, Event.noteNote n) keysDown)
204 else
205 loop (ev:events) (Map.insert (Event.noteChannel n, Event.noteNote n) (Event.noteVelocity n) keysDown)
206 Event.NoteEv Event.NoteOff n ->
207 loop (ev:events) (Map.delete (Event.noteChannel n, Event.noteNote n) keysDown)
208 _ -> loop (ev:events) keysDown
209
188type MidiHook = Event.T -> IO () 210type MidiHook = Event.T -> IO ()
189 211
190forwardNoteEvent :: SndSeq.AllowOutput mode => SndSeq.T mode -> Queue.T -> Addr.T -> MidiHook 212forwardNoteEvent :: SndSeq.AllowOutput mode => SndSeq.T mode -> Queue.T -> Addr.T -> MidiHook
diff --git a/midi-dump.hs b/midi-dump.hs
index 52fe6b2..6c2c8b8 100644
--- a/midi-dump.hs
+++ b/midi-dump.hs
@@ -8,7 +8,7 @@ import AlsaSeq
8import Control.Monad.RWS.Strict 8import Control.Monad.RWS.Strict
9import Data.List 9import Data.List
10import Data.Maybe 10import Data.Maybe
11import qualified Data.Set as Set 11import qualified Data.Map.Strict as Map
12import qualified Sound.ALSA.Exception as AlsaExc 12import qualified Sound.ALSA.Exception as AlsaExc
13import qualified Sound.ALSA.Sequencer.Event as Event 13import qualified Sound.ALSA.Sequencer.Event as Event
14import System.Clock 14import System.Clock
@@ -43,7 +43,7 @@ data Triad = Major Event.Pitch | Minor Event.Pitch deriving (Show, Eq)
43 43
44data LoopState = LoopState { 44data LoopState = LoopState {
45 _wantExit :: Bool, 45 _wantExit :: Bool,
46 _keysDown :: MidiPitchSet, 46 _keysDown :: MidiPitchMap,
47 _triad :: Maybe Triad, 47 _triad :: Maybe Triad,
48 _scheduled :: Q.Queue Event.Data, 48 _scheduled :: Q.Queue Event.Data,
49 _recording :: Recording, 49 _recording :: Recording,
@@ -52,7 +52,7 @@ data LoopState = LoopState {
52} 52}
53 53
54initializeState :: TimeSpec -> LoopState 54initializeState :: TimeSpec -> LoopState
55initializeState now = LoopState False Set.empty Nothing createQueue (StartRecording now) (StartRecording now) now 55initializeState now = LoopState False Map.empty Nothing createQueue (StartRecording now) (StartRecording now) now
56 56
57data LoopEnv = LoopEnv { 57data LoopEnv = LoopEnv {
58 _saver :: Chan CompleteRecording, 58 _saver :: Chan CompleteRecording,
@@ -233,7 +233,7 @@ processMidi = do
233 h <- asks _h 233 h <- asks _h
234 oldKeys <- gets _keysDown 234 oldKeys <- gets _keysDown
235 forwardNOW <- getMidiSender 235 forwardNOW <- getMidiSender
236 (events, newKeys) <- liftIO $ parseAlsaEvents' h oldKeys forwardNOW 236 (events, newKeys) <- liftIO $ parseAlsaEvents'' h oldKeys forwardNOW
237 237
238 238
239 if oldKeys == newKeys 239 if oldKeys == newKeys
@@ -249,18 +249,18 @@ processMidi = do
249 , _lastTick = now 249 , _lastTick = now
250 } 250 }
251 251
252 whenFlag _printChordKeys $ liftIO $ printChordLn newKeys 252 whenFlag _printChordKeys $ liftIO $ printChordLn' newKeys
253 253
254 filterTriads newKeys 254 filterTriads newKeys
255 255
256 -- Whenever no keys are pressed, flush any buffered events to the database 256 -- Whenever no keys are pressed, flush any buffered events to the database
257 when (Set.null newKeys) $ do 257 when (Map.null newKeys) $ do
258 doSave <- asks _doSave 258 doSave <- asks _doSave
259 when doSave $ gets _recording >>= saveMidi >> return () 259 when doSave $ gets _recording >>= saveMidi >> return ()
260 modify $ \s -> s { _recording = StartRecording now } 260 modify $ \s -> s { _recording = StartRecording now }
261 261
262 -- When a key is pressed after 3+ seconds of silence, overwrite the replay buffer with the new keys 262 -- When a key is pressed after 3+ seconds of silence, overwrite the replay buffer with the new keys
263 when (Set.null oldKeys) $ do 263 when (Map.null oldKeys) $ do
264 replay <- gets _replay 264 replay <- gets _replay
265 when (latestEvent replay < (now - TimeSpec 3 0)) $ do 265 when (latestEvent replay < (now - TimeSpec 3 0)) $ do
266 modify $ \s -> s { _replay = StartRecording now } 266 modify $ \s -> s { _replay = StartRecording now }
@@ -268,13 +268,14 @@ processMidi = do
268 268
269 modify $ \s -> s { _replay = recordEvents (_replay s) newEvents } 269 modify $ \s -> s { _replay = recordEvents (_replay s) newEvents }
270 270
271filterTriads :: MidiPitchSet -> MidiController () 271filterTriads :: MidiPitchMap -> MidiController ()
272filterTriads newKeys = do 272filterTriads newKeys = do
273 let newTriad = detectTriad newKeys 273 let newTriad = detectTriad newKeys
274 vel = Event.Velocity 128
274 oldTriad <- gets _triad 275 oldTriad <- gets _triad
275 when (newTriad /= oldTriad) $ do 276 when (newTriad /= oldTriad) $ do
276 forM_ oldTriad (sendTriadEvents False) 277 forM_ oldTriad (sendTriadEvents Nothing)
277 forM_ newTriad (sendTriadEvents True) 278 forM_ newTriad (sendTriadEvents $ Just vel)
278 modify $ \s -> s { _triad = newTriad } 279 modify $ \s -> s { _triad = newTriad }
279 280
280triadBase :: Triad -> Event.Pitch 281triadBase :: Triad -> Event.Pitch
@@ -282,26 +283,27 @@ triadBase (Major n) = n
282triadBase (Minor n) = n 283triadBase (Minor n) = n
283 284
284-- TODO: set velocity based on average from triad (this requires storing that 285-- TODO: set velocity based on average from triad (this requires storing that
285-- information, changing the MidiPitchSet type for one more complex than a mere 286-- information, changing the MidiPitchMap type for one more complex than a mere
286-- Set) 287-- Set)
287sendTriadEvents :: Bool -> Triad -> MidiController () 288sendTriadEvents :: Maybe Event.Velocity -> Triad -> MidiController ()
288sendTriadEvents sendOn triad = do 289sendTriadEvents vel triad = do
289 forM_ notes (delayNoteEv (TimeSpec 0 0)) 290 forM_ notes (delayNoteEv (TimeSpec 0 0))
290 return () 291 return ()
291 292
292 where 293 where
293 onoff = bool Event.NoteOff Event.NoteOn sendOn
294 base = Event.unPitch $ triadBase triad 294 base = Event.unPitch $ triadBase triad
295 notes = (Event.NoteEv onoff . mkNote) <$> fill base 295 notes = fromVel vel <$> fill base
296 fill n = [ x + y | x <- [n, n + 7], y <- [12, -12]] 296 fill n = [ x + y | x <- [n, n + 7], y <- [12, -12]]
297 fromVel (Just v) pitch = Event.NoteEv Event.NoteOn $ Event.simpleNote (Event.Channel 0) (Event.Pitch pitch) v
298 fromVel Nothing pitch = Event.NoteEv Event.NoteOff $ Event.simpleNote (Event.Channel 0) (Event.Pitch pitch) (Event.Velocity 0)
297 299
298detectTriad :: MidiPitchSet -> Maybe Triad 300detectTriad :: MidiPitchMap -> Maybe Triad
299detectTriad pitches = listToMaybe $ concatMap f pitches 301detectTriad pitches = listToMaybe $ concatMap f (Map.keys pitches)
300 where 302 where
301 f pitch 303 f pitch
302 | not $ Set.member (addPitch 7 pitch) pitches = [] 304 | not $ Map.member (addPitch 7 pitch) pitches = []
303 | Set.member (addPitch 4 pitch) pitches = [Major $ snd pitch] -- TODO: do not just drop the channel!! 305 | Map.member (addPitch 4 pitch) pitches = [Major $ snd pitch] -- TODO: do not just drop the channel!!
304 | Set.member (addPitch 3 pitch) pitches = [Minor $ snd pitch] 306 | Map.member (addPitch 3 pitch) pitches = [Minor $ snd pitch]
305 | otherwise = [] 307 | otherwise = []
306 addPitch n (c, Event.Pitch p) = (c, Event.Pitch $ p+n) 308 addPitch n (c, Event.Pitch p) = (c, Event.Pitch $ p+n)
307 309