packages feed

YampaSynth-0.0.1: src/Player/Gtk.hs

module Player.Gtk (Player.Gtk.play) where

import Paths_YampaSynth (getDataFileName)

import Player.OpenAL

import Codec.Midi
import Data.Audio

import FRP.Yampa

import Data.Int
import qualified Data.Map as Map
import Data.IORef
import Control.Concurrent
import Foreign.Storable
import Foreign.Marshal.Alloc

import Graphics.UI.Gtk hiding (Event)
import Graphics.UI.Gtk.Glade

import Sound.OpenAL

play :: Int -> Int -> SF (Event [Message]) (Sample, Event ()) -> IO ()
play sampleRate' sampleNumber' synth = do
  (device,context,pSource,pBuffer) <- initOpenAL
  mVarMessage <- newEmptyMVar
  mVarReplyGui <- newEmptyMVar
  mVarKeyboardActive <- newEmptyMVar
  refKeyboardStatus <- newIORef $ (Map.empty :: Map.Map String ()) 
  
  unsafeInitGUIForThreadedRTS --initGUI
  
  guiDescFile <- getDataFileName "gui/YampaSynth.glade"
  Just xml <- xmlNew guiDescFile

  window   <- xmlGetWidget xml castToWindow "window1"
  onDestroy window $ do
    deInitOpenAL device context pSource pBuffer
    mainQuit
  let
    onKeyPressWindow e = do
      b <- isEmptyMVar mVarKeyboardActive
      if b 
        then return ()
        else do
          let k = eventKeyName e
          m <- readIORef refKeyboardStatus
          if Map.member k m
            then return ()
            else do
              writeIORef refKeyboardStatus (Map.insert k () m)
              putMVar mVarMessage (NoteOn  {channel = 0, key = str2key k, velocity = 85})
      return True
    onKeyReleaseWindow e = do
      b <- isEmptyMVar mVarKeyboardActive
      if b
        then return ()
        else do
          let k = eventKeyName e
          modifyIORef refKeyboardStatus (\m -> Map.delete k m)
          putMVar mVarMessage (NoteOff  {channel = 0, key = str2key k, velocity = 85}) 
      return True
  onKeyPress window onKeyPressWindow
  onKeyRelease window onKeyReleaseWindow
  
  combobox <- xmlGetWidget xml castToComboBox "combobox1"
  comboBoxSetActive combobox 0
  
  onChanged combobox $ do
    b <- isEmptyMVar mVarKeyboardActive
    if b
      then return ()
      else do
        mi <- comboBoxGetActive combobox
        case mi of
          Nothing -> return ()
          Just i  -> putMVar mVarMessage (ProgramChange { channel = 0,  preset = fromIntegral i })
        return ()

  playButton <- xmlGetWidget xml castToButton "button1"
  stopButton <- xmlGetWidget xml castToButton "button2"
  widgetSetSensitivity stopButton False

  onClicked playButton $ do
    forkIO $ runSynth sampleRate' sampleNumber' pSource pBuffer
                      mVarMessage mVarReplyGui synth
    widgetSetSensitivity playButton False
    widgetSetSensitivity stopButton True
    mi <- comboBoxGetActive combobox
    case mi of
      Nothing -> return ()
      Just i  -> putMVar mVarMessage (ProgramChange { channel = 0,  preset = fromIntegral i })
    putMVar mVarKeyboardActive ()

  onClicked stopButton $ do
    putMVar mVarMessage TrackEnd
    takeMVar mVarReplyGui
    widgetSetSensitivity stopButton False
    widgetSetSensitivity playButton True
    takeMVar mVarKeyboardActive

  widgetShowAll window
  mainGUI

runSynth :: Int -> Int -> Source -> Buffer -> MVar Message -> MVar ()
  ->  SF (Event [Message]) (Sample, Event ()) -> IO ()
runSynth sampleRate' sampleNumber' pSource pBuffer mVarMessage mVarReplyGui synth = do
  
  mVarMaybeChunk <- newEmptyMVar
  mVarReplyPlayer <- newEmptyMVar

  forkIO $ process sampleRate' pSource pBuffer mVarMaybeChunk mVarReplyPlayer

  ir <- newIORef (0 :: Int)
  let sampleSize = sizeOf (undefined :: Int16)
      chunkSize' = sampleNumber' * sampleSize
  chunkData' <- mallocBytes chunkSize'
  
  let chunk = Chunk chunkData' chunkSize' 
      sense _ = do
        let dt = 1.0 / fromIntegral sampleRate'
        maybeMsg <- tryTakeMVar mVarMessage
        case maybeMsg of
          Nothing -> return (dt, Just noEvent) 
          Just msg -> return (dt, Just (Event [msg])) 
      actuate _ (s,e) = if (isEvent e)
        then return True
        else do
          i <- readIORef ir
          pokeElemOff (chunkData chunk) i (fromSample s)
          if i == (sampleNumber' - 1)
            then do
              putMVar mVarMaybeChunk (Just chunk)
              takeMVar mVarReplyPlayer
              writeIORef ir 0
              return False
            else do
              writeIORef ir (i + 1)
              return False
  
  reactimate (return noEvent) sense actuate synth
  
  i <- readIORef ir
  putMVar mVarMaybeChunk (Just $ chunk {chunkSize = i * sampleSize})
  takeMVar mVarReplyPlayer
  putMVar mVarMaybeChunk Nothing
  takeMVar mVarReplyPlayer

  free (chunkData chunk)
  putMVar mVarReplyGui ()
  
  
str2key :: String -> Key
str2key s = case s of
  "p" -> 64
  "0" -> 63
  "o" -> 62
  "9" -> 61
  "i" -> 60
  "u" -> 59
  "7" -> 58
  "y" -> 57
  "6" -> 56
  "t" -> 55
  "5" -> 54
  "r" -> 53
  "e" -> 52
  "3" -> 51
  "w" -> 50
  "2" -> 49
  "q" -> 48
  
  "m" -> 59 - 12
  "j" -> 58 - 12
  "n" -> 57 - 12
  "h" -> 56 - 12
  "b" -> 55 - 12
  "g" -> 54 - 12
  "v" -> 53 - 12
  "c" -> 52 - 12
  "d" -> 51 - 12
  "x" -> 50 - 12
  "s" -> 49 - 12
  "z" -> 48 - 12
 
  _   -> 0