-- | Sound file playback
module Csound.Air.Wav(
-- * Stereo
readSnd, loopSnd, loopSndBy,
readWav, loopWav, readSegWav,
tempoLoopWav, tempoReadWav,
-- * Mono
readSnd1, loopSnd1, loopSndBy1,
readWav1, loopWav1, readSegWav1,
tempoLoopWav1, tempoReadWav1,
-- * Read sound with RAM
--
-- Loads the sample in the table and plays it back from RAM. The sample should be short. The size of the table is limited.
-- It's up to 6 minutes for 44100 sample rate, 5 minutes for 48000 and 2.8 minutes for 96000.
LoopMode(..), ramSnd, ramSnd1,
-- * Writing sound files
SampleFormat(..),
writeSigs, writeWav, writeAiff, writeWav1, writeAiff1,
-- * Utility
lengthSnd, segments,
-- * Signal manipulation
takeSnd, delaySnd, afterSnd, segmentSnd, repeatSnd, toMono
) where
import Data.List(isSuffixOf)
import Csound.Typed
import Csound.Typed.Opcode
import Csound.Tab(mp3s, wavs)
import Csound.Control.Instr(withDur, sched)
import Csound.SigSpace(mapSig)
import Csound.Types(Sig2)
import Csound.Control.Evt(metroE, eventList)
import Csound.Air.Spec
--------------------------------------------------------------------------
-- Signal manipulation
-- | Takes only given amount (in seconds) from the signal (the rest is silence).
takeSnd :: Sigs a => D -> a -> a
takeSnd dt asig = trigs (const $ return asig) $ eventList [(0, dt, unit)]
-- | Delays signals by the given amount (in seconds).
delaySnd :: Sigs a => D -> a -> a
delaySnd dt asig = trigs (const $ return asig) $ eventList [(dt, -1, unit)]
-- | Delays a signal by the first argument and takes only second argument amount
-- of signal (everything is measured in seconds).
segmentSnd ::Sigs a => D -> D -> a -> a
segmentSnd del dur asig = trigs (const $ return asig) $ eventList [(del, dur, unit)]
-- | Repeats the signal with the given period.
repeatSnd :: Sigs a => D -> a -> a
repeatSnd dt asig = sched (const $ return asig) $ segments dt
-- | Plays the first signal for some time (in seconds) and then switches to the next one.
--
-- > afterSnd dur sig1 sig2
afterSnd :: (Num b, Sigs b) => D -> b -> b -> b
afterSnd dt a b = takeSnd dt a + delaySnd dt b
--------------------------------------------------------------------------
-- sound files playback
isMp3 :: String -> Bool
isMp3 name = ".mp3" `isSuffixOf` name
-- | Converts stereosignal to mono with function mean.
toMono :: (Sig, Sig) -> Sig
toMono (a, b) = 0.5 * a + 0.5 * b
-- | Length in seconds of the sound file.
lengthSnd :: String -> D
lengthSnd fileName
| isMp3 fileName = mp3len $ text fileName
| otherwise = filelen $ text fileName
-- | Produces repeating segments with the given time in seconds.
segments :: D -> Evt (D, Unit)
segments dt = withDur dt $ metroE (sig $ recip dt)
-- Stereo
-- | Reads stereo signal from the sound-file (wav or mp3 or aiff).
readSnd :: String -> (Sig, Sig)
readSnd fileName
| isMp3 fileName = mp3in (text fileName)
| otherwise = diskin2 (text fileName) 1
-- | Reads stereo signal from the sound-file (wav or mp3 or aiff)
-- and loops it with the given period (in seconds).
loopSndBy :: D -> String -> (Sig, Sig)
loopSndBy dt fileName = repeatSnd dt $ readSnd fileName
-- | Reads stereo signal from the sound-file (wav or mp3 or aiff)
-- and loops it with the file length.
loopSnd :: String -> (Sig, Sig)
loopSnd fileName = loopSndBy (lengthSnd fileName) fileName
-- | Reads the wav file with the given speed (if speed is 1 it's a norma playback).
-- We can use negative speed to read file in reverse.
readWav :: Sig -> String -> (Sig, Sig)
readWav speed fileName = diskin2 (text fileName) speed
-- | Reads th wav file and loops over it.
loopWav :: Sig -> String -> (Sig, Sig)
loopWav speed fileName = flip withDs [0, 1] $ ar2 $ diskin2 (text fileName) speed
-- | Reads a segment from wav file.
readSegWav :: D -> D -> Sig -> String -> (Sig, Sig)
readSegWav start end speed fileName = takeSnd (end - start) $ diskin2 (text fileName) speed `withDs` [start, 1]
-- | Reads the wav file with the given speed (if speed is 1 it's a norma playback).
-- We can use negative speed to read file in reverse. Scales the tempo with first argument.
tempoReadWav :: Sig -> String -> (Sig, Sig)
tempoReadWav speed fileName = mapSig (scaleSpec (1 / abs speed)) $ diskin2 (text fileName) speed
-- | Reads th wav file and loops over it. Scales the tempo with first argument.
tempoLoopWav :: Sig -> String -> (Sig, Sig)
tempoLoopWav speed fileName = mapSig (scaleSpec (1 / abs speed)) $ flip withDs [0, 1] $ ar2 $ diskin2 (text fileName) speed
-- Mono
-- | The mono variant of the function @readSnd@.
readSnd1 :: String -> Sig
readSnd1 fileName
| isMp3 fileName = toMono $ readSnd fileName
| otherwise = diskin2 (text fileName) 1
-- | The mono variant of the function @loopSndBy@.
loopSndBy1 :: D -> String -> Sig
loopSndBy1 dt fileName = repeatSnd dt $ readSnd1 fileName
-- | The mono variant of the function @loopSnd@.
loopSnd1 :: String -> Sig
loopSnd1 fileName = loopSndBy1 (lengthSnd fileName) fileName
-- | The mono variant of the function @readWav@.
readWav1 :: Sig -> String -> Sig
readWav1 speed fileName = diskin2 (text fileName) speed
-- | The mono variant of the function @loopWav@.
loopWav1 :: Sig -> String -> Sig
loopWav1 speed fileName = flip withDs [0, 1] $ diskin2 (text fileName) speed
-- | Reads a segment from wav file.
readSegWav1 :: D -> D -> Sig -> String -> Sig
readSegWav1 start end speed fileName = takeSnd (end - start) $ diskin2 (text fileName) speed `withDs` [start, 1]
-- | Reads the mono wav file with the given speed (if speed is 1 it's a norma playback).
-- We can use negative speed to read file in reverse. Scales the tempo with first argument.
tempoReadWav1 :: Sig -> String -> Sig
tempoReadWav1 speed fileName = scaleSpec (1 / abs speed) $ readWav1 speed fileName
-- | Reads th mono wav file and loops over it. Scales the tempo with first argument.
tempoLoopWav1 :: Sig -> String -> Sig
tempoLoopWav1 speed fileName = scaleSpec (1 / abs speed) $ loopWav1 speed fileName
--------------------------------------------------------------------------
-- With RAM
data LoopMode = Once | Loop | Bounce
deriving (Show, Eq, Enum)
-- | Loads the sample in the table. The sample should be short. The size of the table is limited.
-- It's up to 6 minutes for
ramSnd :: LoopMode -> Sig -> String -> Sig2
ramSnd loopMode speed file = loscil3 1 speed t `withDs` [1, int $ fromEnum loopMode]
where t
| isMp3 file = mp3s file 0
| otherwise = wavs file 0 0
ramSnd1 :: LoopMode -> Sig -> String -> Sig
ramSnd1 loopMode speed file
| isMp3 file = (\(aleft, aright) -> 0.5 * (aleft + aright)) $ loscil3 1 speed (mp3s file 0) `withDs` [1, int $ fromEnum loopMode]
| otherwise = loscil3 1 speed (wavs file 0 1) `withDs` [1, int $ fromEnum loopMode]
--------------------------------------------------------------------------
-- writing sound files
-- | The sample format.
data SampleFormat
= NoHeaderFloat32 -- ^ 32-bit floating point samples without header
| NoHeaderInt16 -- ^ 16-bit integers without header
| HeaderInt16 -- ^ 16-bit integers with a header. The header type depends on the render (-o) format
| UlawSamples -- ^ u-law samples with a header
| Int16 -- ^ 16-bit integers with a header
| Int32 -- ^ 32-bit integers with a header
| Float32 -- ^ 32-bit floats with a header
| Uint8 -- ^ 8-bit unsigned integers with a header
| Int24 -- ^ 24-bit integers with a header
| Float64 -- ^ 64-bit floats with a header
deriving (Eq, Ord, Enum)
-- | Writes a sound signal to the file with the given format.
-- It supports only four formats: Wav, Aiff, Raw and Ircam.
writeSigs :: FormatType -> SampleFormat -> String -> [Sig] -> SE ()
writeSigs fmt sample file = fout (text file) formatToInt
where
formatToInt = int $ formatTypeToInt fmt * 10 + fromEnum sample
formatTypeToInt :: FormatType -> Int
formatTypeToInt x = case x of
Wav -> 1
Aiff -> 2
Raw -> 3
Ircam -> 4
_ -> error $ "Format " ++ (show x) ++ " is not supported in the writeSnd."
-- | Writes wav files.
writeWav :: String -> (Sig, Sig) -> SE ()
writeWav file = writeSigs Wav Int16 file . \(a, b) -> [a, b]
-- | Writes aiff files.
writeAiff :: String -> (Sig, Sig) -> SE ()
writeAiff file = writeSigs Aiff Int16 file . \(a, b) -> [a, b]
-- | Writes mono signals to wav files.
writeWav1 :: String -> Sig -> SE ()
writeWav1 file = writeWav file . \x -> (x, x)
-- | Writes mono signals to aiff files.
writeAiff1 :: String -> Sig -> SE ()
writeAiff1 file = writeAiff file . \x -> (x, x)