-- | 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,
ramTab, mincer, temposcal,
Phsr(..), lphase, relPhsr, sndPhsr, phsrBounce, phsrOnce,
ram, ram1,
-- ** Simple audio reading functions (Stereo)
Fidelity, TempoSig, PitchSig,
readRam, loopRam, readSeg, loopSeg, readRel, loopRel,
-- ** Simple audio reading functions (Mono)
readRam1, loopRam1, readSeg1, loopSeg1, readRel1, loopRel1,
-- ** Scaling audio files
scaleDrum, scaleHarm, scaleDrum1, scaleHarm1, scaleWav1, scaleWav,
-- * Writing sound files
SampleFormat(..),
writeSigs, writeWav, writeAiff, writeWav1, writeAiff1,
dumpWav, dumpWav1,
-- * Utility
lengthSnd, segments,
-- * Signal manipulation
takeSnd, delaySnd, afterSnd, lineSnd, loopLineSnd, segmentSnd, repeatSnd, toMono
) where
import Data.List(isSuffixOf)
import Data.Default
import Data.Boolean
import Temporal.Media
import Csound.Dynamic hiding (int, Sco)
import Csound.Typed
import Csound.Typed.Opcode hiding (tempo, pitch, metro, tab)
import Csound.Tab(mp3s, mp3Left, wavs, wavLeft, WavChn(..), Mp3Chn(..))
import Csound.Control.Instr(withDur)
import Csound.Control.Evt(metro, loadbang)
import Csound.Air.Spec
--------------------------------------------------------------------------
-- Signal manipulation
-- | Takes only given amount (in seconds) from the signal (the rest is silence).
takeSnd :: Sigs a => Sig -> a -> a
takeSnd dt asig = sched (const $ return asig) $ withDur dt $ loadbang
-- | Delays signals by the given amount (in seconds).
delaySnd :: Sigs a => Sig -> a -> a
delaySnd dt = segmentSnd dt infiniteDur
-- | Delays a signal by the first argument and takes only second argument amount
-- of signal (everything is measured in seconds).
segmentSnd ::Sigs a => Sig -> Sig -> a -> a
segmentSnd dt durS asig = sched (const $ return asig) $ fmap (del dt) $ withDur durS $ loadbang
-- | Repeats the signal with the given period.
repeatSnd :: Sigs a => Sig -> 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) => Sig -> b -> b -> b
afterSnd dt a b = takeSnd dt a + delaySnd dt b
-- | Creates a sequence of signals. Each segment lasts for
-- fixed amount of time given in the first argument.
lineSnd :: (Num a, Sigs a) => Sig -> [a] -> a
lineSnd dt xs = foldr1 go xs
where
go a b = afterSnd dt a b
-- | Creates a sequence of signals and loops over the sequence.
-- Each segment lasts for fixed amount of time given in the first argument.
loopLineSnd :: (Num a, Sigs a) => Sig -> [a] -> a
loopLineSnd dt xs = repeatSnd (dt * (sig $ int $ length xs)) $ lineSnd dt xs
--------------------------------------------------------------------------
-- 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 :: Sig -> Evt (Sco Unit)
segments dt = withDur dt $ metro (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)
-- | Reads stereo signal from the sound-file (wav or mp3 or aiff)
-- and loops it with the given period (in seconds).
loopSndBy :: Sig -> 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 (sig $ 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) `withSig` 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) `withSig` speed
-- | Reads a segment from wav file.
readSegWav :: D -> D -> Sig -> String -> (Sig, Sig)
readSegWav start end speed fileName = takeSnd (sig $ end - start) $ (diskin2 (text fileName) `withSig` 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) `withSig` 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) `withSig` speed
-- Mono
-- | The mono variant of the function @readSnd@.
readSnd1 :: String -> Sig
readSnd1 fileName
| isMp3 fileName = toMono $ readSnd fileName
| otherwise = diskin2 (text fileName)
-- | The mono variant of the function @loopSndBy@.
loopSndBy1 :: Sig -> String -> Sig
loopSndBy1 dt fileName = repeatSnd dt $ readSnd1 fileName
-- | The mono variant of the function @loopSnd@.
loopSnd1 :: String -> Sig
loopSnd1 fileName = loopSndBy1 (sig $ lengthSnd fileName) fileName
-- | The mono variant of the function @readWav@.
readWav1 :: Sig -> String -> Sig
readWav1 speed fileName = diskin2 (text fileName) `withSig` speed
-- | The mono variant of the function @loopWav@.
loopWav1 :: Sig -> String -> Sig
loopWav1 speed fileName = flip withDs [0, 1] $ diskin2 (text fileName) `withSig` speed
-- | Reads a segment from wav file.
readSegWav1 :: D -> D -> Sig -> String -> Sig
readSegWav1 start end speed fileName = takeSnd (sig $ end - start) $ diskin2 (text fileName) `withSig` 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 3 minutes for 44100 sample rate (sr), 2.9 minutes for 48000 sr, 1.4 minutes for 96000 sr.
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 def
| otherwise = wavs file 0 def
-- | 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 44100 sample rate (sr), 5.9 minutes for 48000 sr, 2.8 minutes for 96000 sr.
ramSnd1 :: LoopMode -> Sig -> String -> Sig
ramSnd1 loopMode speed file
| isMp3 file = (\(aleft, aright) -> 0.5 * (aleft + aright)) $ loscil3 1 speed (mp3s file 0 def) `withDs` [1, int $ fromEnum loopMode]
| otherwise = loscil3 1 speed (wavs file 0 WavLeft) `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]
-- | Dumps signals to file and sends the audio through. Useful to monitor the signals.
dumpWav :: String -> (Sig, Sig) -> SE (Sig, Sig)
dumpWav file asig = writeWav file asig >> return asig
-- | Dumps mono signal to file and sends the audio through. Useful to monitor the signals.
dumpWav1 :: String -> Sig -> SE Sig
dumpWav1 file asig = writeWav file (asig, asig) >> return asig
-- | 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)
-------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------
-- mincer
-- | Mincer. We can playback a table and scale by tempo and pitch.
--
-- > mincer fidelity table pointer pitch
--
-- fidelity is the parameter that specifies the size of the window (for FFT transform).
-- The size equals to formula (fidelity + 11) ^ 2. If you don't know what to choose
-- choose 0 for pitched sounds and -2 for drums. The table contains the sample to playback.
-- The pointer loops over the table. The pitch specifies a scaling factor for pitch.
-- So we can raise tone an octave up by setting the pitch to 2.
ramTab :: Fidelity -> Tab -> Sig -> Sig -> Sig
ramTab winSizePowerOfTwo tab aptr pitch = mincer aptr 1 pitch tab 1 `withD` (2 ** (winSizePowerOfTwo + 11))
-- > let x n = mincer2 (Phsr "/home/anton/fox.wav" 0 (stepSeq [0.2, 1, 0.1, 0.5] 0.5) (lpshold [1, 0.8, -1, 0.2] 0.25)) n
-- > dac $ mul 3 $ at (lp18 0.7 800 0.1) $ cfd (slide 0.5 $ usqr 0.2) (x 1) (sum [x $ 6/5, x $ 2])
-- | Creates a pointer signal for reading audio from the table in loops.
--
-- > lphase length start end speed
--
-- Arguments are:
--
-- * length of the table in seconds
--
-- * start and end points of the reading interval
--
-- * playback speed
lphase :: D -> Sig -> Sig -> Sig -> Sig
lphase irefdur kloopstart kloopend kspeed = atimpt * sig irefdur
where
kfqrel = kspeed / (kloopend - kloopstart)
andxrel = phasor kfqrel
atimpt = andxrel * (kloopend-kloopstart) + kloopstart
----------------------------------------------------------------------
-- | Looping phasor. It creates a looping pointer to the file.
-- It's used in the function ram.
--
-- Ther arguments are: file name, start and end of the looping segment (in seconds),
-- and the playback speed.
data Phsr = Phsr
{ phsrFile :: String
, phsrStart :: Sig
, phsrEnd :: Sig
, phsrSpeed :: Sig
}
-- | Forces phasor to play only once.
phsrOnce :: Phsr -> Phsr
phsrOnce a = a { phsrSpeed = phsrSpeed a * linseg [1, dt, 1, 0.01, 0] }
where dt = ir $ abs $ (phsrEnd a - phsrStart a) / phsrSpeed a
-- | Reads the file forth and back.
phsrBounce :: Phsr -> Phsr
phsrBounce a = a { phsrSpeed = phsrSpeed a * sqr (1 / dt) }
where dt = abs $ (phsrEnd a - phsrStart a) / phsrSpeed a
-- | Creates a phasor if segments are relative to the total length.
-- It can be useful for drum loops. If we don't know the complete length
-- but we know that loop contains four distinct parts.
relPhsr :: String -> Sig -> Sig -> Sig -> Phsr
relPhsr file start end speed = Phsr
{ phsrFile = file
, phsrStart = start * sig len
, phsrEnd = end * sig len
, phsrSpeed = speed }
where
len = (filelen $ text file) / srFactor
srFactor = getSampleRate / filesr (text file)
-- | Creates a phasor for reading the whole audio file in loops
-- with given speed.
sndPhsr :: String -> Sig -> Phsr
sndPhsr file speed = relPhsr file 0 1 speed
ram1 :: Fidelity -> Phsr -> Sig -> Sig
ram1 = ramChn True 1
-- | Reads audio files in loops. The file is loaded in RAM.
-- The size of the file is limited. It should be not more than 6 minutes
-- for sample rate of 44100. 5.9 minutes for 48000.
--
-- What makes this function so cool is
-- that we can scale the sound by tempo
-- without affecting pitch, and we can scale the sound by pitch
-- without affecting the tempo. Let's study the arguments.
--
-- > ram fidelity phasor pitch
--
-- fidelity corresponds to the size of the FFT-window.
-- The function performs the FFT transform and it has to know the size.
-- It's not the value for the size it's an integer value
-- that proportional to the size. The higher the value the higher the size
-- the lower the value the lower the size. The default value is 0.
-- Zero is best for most of the cases. For drums we can lower it to (-2).
--
-- The phasor is a quadruple of values
--
-- > (Phsr fileName startTime endTime playbackSpeed)
--
-- we can read the file from startTime to endTime (in seconds)
-- and we can set the speed for playback. If speed is negative
-- file is played in reverse. The playback is looped.
-- So to scale the tempo or play in reverse we can change the playbackSpeed.
--
-- The last argument is pitch factor. We can rise by octave with factor 2.
-- It's good place to use the function semitone. It produces factors for a number in semitones.
--
-- Note that all parameters (except window size) are signals.
-- It makes this function very flexible. We can change the speed of playback
-- and start and end of the reading segment as we wish.
--
-- > ram 0 (Phsr "file.wav" 0 1 1.2) 1
--
-- PS: here is the formula for window size: 2 ** (fidelity + 11)
ram :: Fidelity -> Phsr -> Sig -> Sig2
ram winSize phsr pitch = (ramChn False 1 winSize phsr pitch, ramChn False 2 winSize phsr pitch)
ramChn :: Bool -> Int -> Fidelity -> Phsr -> Sig -> Sig
ramChn isMono n winSize (Phsr file start end speed) pitch =
ifB (abs speed `lessThan` 0.001) 0 $
ramTab winSize (mkTab isMono n file ) (lphase (filelen $ text file) start end (speed * srFactor)) (pitch * srFactor)
where srFactor = sig $ (filesr $ text file) / getSampleRate
mkTab :: Bool -> Int -> String -> Tab
mkTab isMono chn file
| mp3 && isMono = mp3s file 0 Mp3Mono
| mp3 && isStereo = mp3s file 0 (if chn == 1 then Mp3Left else Mp3Right)
| otherwise = wavs file 0 (if chn == 1 then WavLeft else WavRight)
where
mp3 = isMp3 file
isStereo = not isMono
----------------------------------------
-- std funs
-- | Fidelity corresponds to the size of the FFT-window that is used by functions of RAM-family.
-- The function performs the FFT transform and it has to know the size.
-- It's not the value for the size it's an integer value
-- that proportional to the size. The higher the value the higher the size
-- the lower the value the lower the size. The default value is 0.
-- Zero is best for most of the cases. For drums we can lower it to (-2).
--
-- PS: here is the formula for window size: 2 ** (fidelity + 11).
-- So the fidelity is actually the degree for power of two.
-- The FFT-algorithm requires the window size to be a power of two.
--
-- The lower fidelity is the less power is consumed by the function.
type Fidelity = D
-- | Scaling factor for tempo. The 1 is inherent tempo.
type TempoSig = Sig
-- | Scaling factor for pitch. The 1 is inherent pitch.
type PitchSig = Sig
-- | Reads file once and scales it by tempo and pitch.
readRam :: Fidelity -> TempoSig-> PitchSig -> String -> Sig2
readRam winSize tempo pitch file = ram winSize (phsrOnce $ sndPhsr file tempo) pitch
-- | Loop over file and scales it by tempo and pitch (it's based on mincer opcode).
loopRam :: Fidelity -> TempoSig-> PitchSig -> String -> Sig2
loopRam winSize tempo pitch file = ram winSize (sndPhsr file tempo) pitch
-- | Reads a segment from file once and scales it by tempo and pitch.
-- Segment is defined in seconds.
readSeg :: Fidelity -> (Sig, Sig) -> TempoSig-> PitchSig -> String -> Sig2
readSeg winSize (kmin, kmax) tempo pitch file = ram winSize (phsrOnce $ Phsr file kmin kmax tempo) pitch
-- | Loops over a segment of file and scales it by tempo and pitch.
-- Segment is defined in seconds.
loopSeg :: Fidelity -> (Sig, Sig) -> TempoSig-> PitchSig -> String -> Sig2
loopSeg winSize (kmin, kmax) tempo pitch file = ram winSize (Phsr file kmin kmax tempo) pitch
-- | Reads a relative segment from file once and scales it by tempo and pitch.
-- Segment is defined in seconds. The end ponits for the segment are relative to the
-- total length of the file.
readRel :: Fidelity -> (Sig, Sig) -> TempoSig-> PitchSig -> String -> Sig2
readRel winSize (kmin, kmax) tempo pitch file = ram winSize (phsrOnce $ relPhsr file kmin kmax tempo) pitch
-- | Loops over a relative segment of file and scales it by tempo and pitch.
-- Segment is defined in seconds. The end ponits for the segment are relative to the
-- total length of the file.
loopRel :: Fidelity -> (Sig, Sig) -> TempoSig-> PitchSig -> String -> Sig2
loopRel winSize (kmin, kmax) tempo pitch file = ram winSize (relPhsr file kmin kmax tempo) pitch
-- | The mono version of readRam.
readRam1 :: Fidelity -> TempoSig-> PitchSig -> String -> Sig
readRam1 winSize tempo pitch file = ram1 winSize (phsrOnce $ sndPhsr file tempo) pitch
-- | The mono version of loopRam.
loopRam1 :: Fidelity -> TempoSig-> PitchSig -> String -> Sig
loopRam1 winSize tempo pitch file = ram1 winSize (sndPhsr file tempo) pitch
-- | The mono version of readSeg.
readSeg1 :: Fidelity -> (Sig, Sig) -> TempoSig-> PitchSig -> String -> Sig
readSeg1 winSize (kmin, kmax) tempo pitch file = ram1 winSize (phsrOnce $ Phsr file kmin kmax tempo) pitch
-- | The mono version of loopSeg.
loopSeg1 :: Fidelity -> (Sig, Sig) -> TempoSig-> PitchSig -> String -> Sig
loopSeg1 winSize (kmin, kmax) tempo pitch file = ram1 winSize (Phsr file kmin kmax tempo) pitch
-- | The mono version of readRel.
readRel1 :: Fidelity -> (Sig, Sig) -> TempoSig-> PitchSig -> String -> Sig
readRel1 winSize (kmin, kmax) tempo pitch file = ram1 winSize (phsrOnce $ relPhsr file kmin kmax tempo) pitch
-- | The mono version of loopRel.
loopRel1 :: Fidelity -> (Sig, Sig) -> TempoSig-> PitchSig -> String -> Sig
loopRel1 winSize (kmin, kmax) tempo pitch file = ram1 winSize (relPhsr file kmin kmax tempo) pitch
------------------------------------
-- scaling tempo/pitch based on temposcale
-- | ScaleWav function with fidelity set for drum-loops.
scaleDrum :: TempoSig -> PitchSig -> String -> Sig2
scaleDrum = scaleWav (-2)
-- | ScaleWav function with fidelity set for hormonical-loops.
scaleHarm :: TempoSig -> PitchSig -> String -> Sig2
scaleHarm = scaleWav 0
-- | ScaleWav1 function with fidelity set for drum-loops.
scaleDrum1 :: TempoSig -> PitchSig -> String -> Sig
scaleDrum1 = scaleWav1 (-2)
-- | ScaleWav1 function with fidelity set for hormonical-loops.
scaleHarm1 :: TempoSig -> PitchSig -> String -> Sig
scaleHarm1 = scaleWav1 0
-- | Scaling mono audio files (accepts both midi and wav). It's based on temposcal Csound opcode.
scaleWav1 :: Fidelity -> TempoSig -> PitchSig -> String -> Sig
scaleWav1 winSizePowerOfTwo tempo pitch filename = go $ if mp3 then mp3Left filename else wavLeft filename
where
go = simpleTempoScale winSizePowerOfTwo tempo pitch
mp3 = isMp3 filename
-- | Scaling stereo audio files (accepts both midi and wav). It's based on temposcal Csound opcode.
scaleWav :: Fidelity -> TempoSig -> PitchSig -> String -> Sig2
scaleWav winSizePowerOfTwo tempo pitch filename = (go $ mkTab False 0 filename, go $ mkTab False 1 filename)
where go = simpleTempoScale winSizePowerOfTwo tempo pitch
simpleTempoScale :: D -> Sig -> Sig -> Tab -> Sig
simpleTempoScale winSizePowerOfTwo tempo pitch t = temposcal tempo 1 pitch t 1 `withD` (2 ** (winSizePowerOfTwo + 11))