[Haskell-cafe] example of PortMidi use

erik flister erik.flister at gmail.com
Mon Oct 26 12:12:44 EDT 2009


hey mike-
i think the trouble with your program is that it sends all the events
as fast as possible and then terminates -- it is insensitive to their
timestamps.  probably portmidi's buffer receives them all, starts the
first one, and then is destroyed before ever emitting the
corresponding off message, leaving the first note hung.

i've just been learning haskell and have been using portmidi as a
vehicle.  here's a demo i've been working on, it seems to work well.
it uses Data.Heap (http://hackage.haskell.org/package/heap) as a
priority queue to manage the timestamp scheduling and allow you to
schedule events nonmonotonically (portmidi expects you to handle
this).  it also shows how to use the random generator i noticed you
asked about.

http://code.google.com/p/h1ccup/source/browse/trunk/theory/haskell/src/PMTest.hs

i would really appreciate any style/design critiques that anyone could
offer!  some specific q's:

1) the 'addNote' function has a local predicate 'match', which winds
up being called on every member in the queue twice -- but memoizing it
would be hard because it is used by the heap's higher order functions
('filter' and 'partition').  how should i handle this?  can i
expect/rely on any auto-memoizing?

2) because a noteOff event in midi turns off a note no matter how many
previous noteOns occurred, 'addNote' needs to be careful to not
schedule a noteOff during a previously scheduled note, and remove any
previously scheduled noteOff that would prematurely cut off the new
note.  the alg is a little tricky and it would be nice to "prove" its
correctness -- and there are enough degrees of freedom that QuickCheck
would not explore the relevant corner cases.  how would one approach
this from a curry-howard perspective?

3) i would like the most natural possible musical EDSL, but currently
have to write things like 'Dotted $ Triplet $ NoteDur Eighth' rather
than 'Dotted Triplet Eighth'.  it would be worse if i added a layer of
symbols to prevent arbitrary nesting and ordering of 'Dotted' and
'Triplet'.  can this be addressed?

4) threadDelay seems to have a resolution of 10ms.  isn't that awfully
high?  (i'm on OSX 10.5.8, ghc 6.10.3).  could this possibly because i
forkIO the producer, but not the consumer (only the consumer
threadDelays)?

5) ski on #haskell wrote 'asksTo' for me (see
http://tunes.org/~nef//logs/haskell/09.08.06) -- but it is not as
polymorphic as we'd like -- it only works for ReaderT's instead of all
MonadReader's.  'asksTo' is for accessing more than one MVar
simultaneously inside a ReaderT, one needs to avoid nesting liftIO's
(see http://hpaste.org/fastcgi/hpaste.fcgi/view?id=7915).  why not
allow nested liftIO's, and if there is a good reason, is there a more
general solution than asksTo?

6) i would (neurotically) like to work under 'default ()', but this
causes zillions of problems that i can't figure out.  is there a good
strategy for getting all your numbers to work polymorphically?

7) i want to write as paradigmatically as possible, so style advice is
very helpful!  i've tried to follow the guidelines from the wiki.  i
didn't see any opportunities to define a new monad, but maybe there
are some?  the 'addNote' and 'drain' functions, which do the queueing
and dequeuing, seem uglier than necessary -- any way to decompose them
better?

-e

On Wed, 14 Oct 2009, Michael Mossey wrote:

> Can someone give me an example of Sound.PortMidi use? I'm having trouble.
> This program has bugs---makes sound only intermittently, and seems to have
> set up some kind of loop that is sending midi messages continuously even
> after terminating the program:


import Sound.PortMidi
import Foreign.C

msgs = [ (0::CULong,PMMsg 0x9c 0x40 0x40)
        , (500,      PMMsg 0x8c 0x40 0x40)
        , (1000,     PMMsg 0x9c 0x41 0x40)
        , (1500,     PMMsg 0x8c 0x41 0x40) ]

main = do
   let deviceId = 12
   initialize >>= print
   getDeviceInfo deviceId >>= print
   startTime <- time
   let evts = map (\(t,msg) -> PMEvent msg (t+startTime)) msgs
   result <- openOutput deviceId 10
   case result of
     Right err   -> putStrLn ("After open: " ++ show err)
     Left stream ->
         do result <- writeEvents stream evts
            putStrLn ("After write: " ++ show result)
            close stream
            return ()
   terminate >>= print


More information about the Haskell-Cafe mailing list