packages feed

alsa-midi-0.3: examples/SimpleSynth.hs

module Main where

import qualified Sound.JACK as Jack
import qualified Sound.ALSA.Sequencer as Alsa
import Sound.ALSA.Sequencer (MIDIEvent)

import Foreign.C.Types (CFloat)

import Data.IORef (IORef, newIORef, writeIORef, readIORef, modifyIORef)
import Control.Concurrent


main :: IO ()
main = do
    n <- newIORef Nothing
    (_client, incoming) <- Alsa.receiveMIDIEvents "SimpleSynth" "inport"
    forkIO $ writeNotes n incoming
    performNotes n

writeNotes ::
     IORef (Maybe MIDIEvent)
  -> [MIDIEvent]
  -> IO ()
writeNotes ref =
    mapM_ (\ a -> writeIORef ref $ toMaybe (Alsa.isNoteOn a) a)

toMaybe :: Bool -> a -> Maybe a
toMaybe c x = if c then Just x else Nothing

performNotes ::
  IORef (Maybe MIDIEvent) -> IO ()
performNotes ref = do
    putStrLn "performNotes..."
    phase <- newIORef 0
    Jack.mainStereo (const (synth ref phase))

synth ::
  IORef (Maybe MIDIEvent)
  -> IORef CFloat
  -> IO (CFloat, CFloat)
synth ref phase =
    readIORef ref >>=
    liftM split
       (maybe
          (return 0)
          (\n -> do
            p <- readIORef phase
            modifyIORef phase (incPhase (midi2Hz (Alsa.note n)) 44100)
            return $ sin (p * 2 * pi)))

split :: t -> (t, t)
split a = (a, a)

incPhase :: (RealFrac a) => a -> a -> a -> a
incPhase frq samplerate phase =
   snd (properFraction (phase + frq / samplerate) `asTypeOf`
            (undefined::Int,phase))

midi2Hz :: Int -> CFloat
midi2Hz mn = midiBaseFrq * semitoneFactor ^ mn

semitoneFactor :: CFloat
semitoneFactor = 2 ** (1 / 12)

midiBaseFrq :: CFloat
midiBaseFrq = 440 / semitoneFactor ^ (69::Int)