4
\$\begingroup\$

This is my first Haskell program, a command-line sdl-based(so, hopefully, cross-platform), parameter-less sound recorder. Tutorials, articles or chapters that concern mistakes in my code are just as welcome as concrete code corrections.

{-# OPTIONS_GHC -Wall #-}


-- Dependant on cabal packages: sdl2, wave.


module Main where


import qualified Control.Concurrent as C
--import qualified Control.Monad as M
import qualified Data.Vector.Storable.Mutable as V
import qualified Data.Set as S
import Foreign.ForeignPtr as P

import qualified SDL
import qualified SDL.Audio as A

import qualified Codec.Audio.Wave as W

import qualified System.IO as IO


micSpec :: IO.Handle -> A.OpenDeviceSpec
micSpec h = A.OpenDeviceSpec {A.openDeviceFreq = A.Mandate 48000
                             ,A.openDeviceFormat = A.Mandate A.Signed16BitNativeAudio
                             ,A.openDeviceChannels = A.Mandate A.Mono
                             ,A.openDeviceSamples = 4096
                             ,A.openDeviceCallback = \_ (V.MVector size ptr) -> P.withForeignPtr ptr (\p -> IO.hPutBuf h p size)
                             ,A.openDeviceUsage = A.ForCapture
                             ,A.openDeviceName = Nothing}


waveSpec :: W.Wave
waveSpec = W.Wave {W.waveFileFormat = W.WaveVanilla
                  , W.waveSampleRate = 48000
                  , W.waveSampleFormat = W.SampleFormatPcmInt 16
                  , W.waveChannelMask = S.singleton W.SpeakerFrontCenter
                  , W.waveDataOffset = 0
                  , W.waveDataSize = 0
                  , W.waveSamplesTotal = 0
                  , W.waveOtherChunks = []}


record :: IO.Handle -> IO ()
record h = do
  SDL.initialize [SDL.InitAudio]
  (dev, _) <- A.openAudioDevice $ micSpec h
  A.setAudioDevicePlaybackState dev A.Play
--  _ <- M.forever (C.threadDelay maxBound)
  _ <- C.threadDelay 10000000
  return ()


main :: IO ()
main =  W.writeWaveFile "mic.rec" waveSpec record

$ hlint Main.hs
No hints

Some of the concerns:

  • code duplication - micSpec and waveSpec should be somehow automatically connected, e.g. setting different frame rates should be impossible
  • what is an idiomatic approach for imports for such and application?
  • A.openDeviceCallback feels logically separate from the constants of the file format; with c function pointer thinking, there would be a factory micSpec :: (AudioFormat t -> IOVector t -> IO ()) -> A.OpenDeviceSpec; this looks like nonsense in Haskell
  • record() feels superfluous
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.