packages feed

jack-0.7: examples/Synthesizer/Render.hs

module Synthesizer.Render where

import qualified Sound.MIDI.Message.Channel.Voice as VoiceMsg
import Sound.MIDI.Message.Channel.Voice (Pitch, )

import qualified Data.StorableVector.ST.Strict as SVST
import qualified Data.StorableVector as SV

import Foreign.Storable (Storable, )
import Control.Monad.ST.Strict as ST

import qualified Control.Monad.Trans.State.Strict as MS

import qualified Data.Map as Map

import Control.Monad (liftM, )

import Debug.Trace (trace, )


type Time = Integer
type Size = Int
type SampleRate = Int


check :: Monad m => Bool -> String -> m () -> m ()
check b msg act =
   if not b
     then trace msg $ return ()
     else act

unsafeAddChunkToBuffer :: (Storable a, Num a) =>
   SVST.Vector s a -> Int -> SV.Vector a -> ST s ()
unsafeAddChunkToBuffer v start xs =
   let go i j =
          if j >= SV.length xs
            then return ()
            else
              SVST.unsafeModify v i (SV.index xs j +) >>
              go (i + 1) (j + 1)
   in  check (start>=0)
                ("start negative: " ++ show (start, SV.length xs)) $
       check (start <= SVST.length v)
                ("start too late: " ++ show (start, SV.length xs)) $
       check (start+SV.length xs <= SVST.length v)
                ("end too late: " ++ show (start, SV.length xs)) $
       go start 0

arrange ::
   (Storable a, Num a) =>
   Size ->
   [(Int, SV.Vector a)] ->
   SV.Vector a
arrange size evs =
   SVST.runSTVector (do
      v <- SVST.new (fromIntegral size) 0
      mapM_ (uncurry $ unsafeAddChunkToBuffer v) evs
      return v)


data OscillatorState a = OscillatorState a a Int

type State a = Map.Map Pitch (OscillatorState a)


initialState :: State a
initialState = Map.empty


stopTone ::
   Int ->
   (Maybe (Int, OscillatorState a),
    [(Int, Int, OscillatorState a)]) ->
   [(Int, Int, OscillatorState a)]
stopTone stopTime (mplaying, finished) =
   case mplaying of
      Just (startTime, osci) ->
         (startTime, stopTime-startTime, osci) : finished
      Nothing -> finished

renderTone ::
   (Storable a, Floating a) =>
   Int -> OscillatorState a ->
   (SV.Vector a, OscillatorState a)
renderTone dur state@(OscillatorState amp freq phase) =
   if dur<0
     then
       trace ("renderTone: negative duration " ++ show dur) $
       (SV.empty, state)
     else
       let gain = 0.9999
       in  (SV.zipWith (\y k -> y * sin (2*pi*fromIntegral k * freq))
               (SV.iterateN dur (gain*) amp)
               (SV.iterateN dur (1+) phase),
            OscillatorState (amp*gain^dur) freq (phase+dur))

processEvents ::
   (Storable a, Floating a, Monad m) =>
   Size ->
   SampleRate ->
   [(Time, VoiceMsg.T)] ->
   MS.StateT (State a) m [(Int, SV.Vector a)]
processEvents size rate input = do
   oscis0 <- MS.get
   let pendingOscis =
          fmap
             (\(mplaying, finished) ->
                let mplayingNew =
                       fmap
                          (\(start,s0) ->
                             case renderTone (fromIntegral size - start) s0 of
                                (chunk, s1) -> ((start,chunk), s1))
                          mplaying
                in  (fmap snd mplayingNew,
                     maybe id (\p -> (fst p :)) mplayingNew $
                     map
                        (\(start, dur, s) -> (start, fst $ renderTone dur s))
                        finished)) $
          foldl
             (\oscis (time,ev) ->
                case VoiceMsg.explicitNoteOff ev of
                   VoiceMsg.NoteOn pitch velocity ->
                      Map.insertWith
                         (\(newOsci, []) s ->
                            {-
                            A key may be pressed that was already pressed.
                            This should not happen, but we must be prepared for it.
                            Thus we call stopTone.
                            -}
                            (newOsci, stopTone time s))
                         pitch
                         (Just (time,
                             OscillatorState
                                (0.2 * 2 ** VoiceMsg.realFromVelocity velocity)
                                (VoiceMsg.frequencyFromPitch pitch /
                                 fromIntegral rate)
                                0),
                          [])
                         oscis
                   VoiceMsg.NoteOff pitch _velocity ->
                      Map.adjust
                         (\s ->
                            {-
                            A key may be released that was not pressed.
                            This should not happen, but we must be prepared for it.
                            Thus stopTone also handles that case.
                            -}
                            (Nothing, stopTone time s))
                         pitch
                         oscis
                   _ -> oscis)
             (fmap (\s -> (Just (0, s), [])) oscis0)
             (map (\(time,ev) -> (fromInteger time, ev)) input)
   MS.put (Map.mapMaybe fst pendingOscis)
   return (concatMap snd $ Map.elems pendingOscis)

run ::
   (Storable a, Floating a, Monad m) =>
   Size ->
   SampleRate ->
   [(Time, VoiceMsg.T)] ->
   MS.StateT (State a) m (SV.Vector a)
run size rate input =
   liftM (arrange size) $ processEvents size rate input