Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to play an audio file from Haskell code, cross-platform

I’m writing a Haskell command line application that runs on Linux, Windows and OS X. I now have to play audio files (.wav, .ogg and .mp3) from it. How would I go about implementing a function

playAudioFile :: FilePath -> IO ()

or even better

playAudio :: ByteString -> IO ()

that simply works on all system?

(I’m happy to invoke common command line tools and also don’t mind bundling them for the Windows distribution.)

like image 932
Joachim Breitner Avatar asked Apr 24 '15 08:04

Joachim Breitner


1 Answers

This is the code I came up with, using SDL-1.2:

module PlaySound (withSound, playSound) where

import Control.Monad
import System.IO
import System.Directory
import Data.Foldable
import Control.Exception
import qualified Data.ByteString.Lazy as B
import Foreign.ForeignPtr

import Graphics.UI.SDL as SDL
import Graphics.UI.SDL.Mixer as Mix

withSound :: IO a -> IO a
withSound = bracket_ init cleanup
  where
    init = do
        SDL.init [SDL.InitAudio]
        getError >>= traverse_ putStrLn
        ok <- Mix.tryOpenAudio Mix.defaultFrequency Mix.AudioS16LSB 2  4096
        unless ok $
            putStrLn "Failed to open SDL audio device"

    cleanup = do
        Mix.closeAudio
        SDL.quit

playSound :: B.ByteString -> IO ()
playSound content = do
        dir <- getTemporaryDirectory
        (tmp, h) <- openTempFile dir "sdl-input"
        B.hPutStr h content
        hClose h

        mus <- Mix.loadMUS tmp
        Mix.playMusic mus 1
        wait

        -- This would double-free the Music, as it is also freed via a
        -- finalizer
        --Mix.freeMusic mus
        finalizeForeignPtr mus
        removeFile tmp

wait :: IO ()
wait = do
    SDL.delay 50
    stillPlaying <- Mix.playingMusic
    when stillPlaying wait

The program in the end works fine, but

  • compiling the SDL bindings under Windows is tricky. I followed this nice explanation on how to do it
  • the SDL bindings for SDL-1.2 seem to be unmaintained and do not even compile with GHC-7.8 or newer. I didn’t notice at first, because my distribution (Debian) patches around such issues, but it means that my users cannot easily cabal install the dependencies any more.
  • there are bindings for SDL-2, but none for the SDL_mixer, which I need here (I believe).

So I’ll happily read better answers.

like image 64
Joachim Breitner Avatar answered Oct 14 '22 14:10

Joachim Breitner