hommage-0.0.5: Sound/Hommage/Signal.hs
-- | Dieses Module stellt Datentypen und Funktionen zum Umgang mit Mono- und
-- Stereodaten zur Verfügung. Speziell im Falle des Öffnens einer WAV-Datei
-- ist erst zur Laufzeit bekannt, ob es sich um eine Mono- oder Stereodatei
-- handelt, hier wird der Typ 'Signal' benötigt.
module Sound.Hommage.Signal
(
-- * Mono and Stereo Values
Mono
, Stereo (..)
, leftStereo
, rightStereo
, stereoToMono
, monoToStereo
, balance
-- * Signal datatype
, Signal (..)
, readWavSignal
, openWavSignal
, writeWavMono
, writeWavStereo
, writeWavSignal
, writeTracks
, signalToMono
, signalToStereo
, eitherSignal
, liftSignal
, mergeSignal
, mergeSignals
, multSignal
, infiniteSignal
)
where
import Sound.Hommage.WavFile
import Sound.Hommage.Misc
import System.IO.Unsafe
import Data.List
-------------------------------------------------------------------------------
-- | A mono value.
type Mono = Double
-------------------------------------------------------------------------------
-- | A stereo value with left and right part. It is an instance of class Num
-- and Fractional.
data Stereo = !Double :><: !Double
deriving (Eq, Show)
-- | Access to the left part of a 'Stereo' value.
leftStereo :: Stereo -> Mono
leftStereo (x :><: _) = x
-- | Access to the right part of a 'Stereo' value.
rightStereo :: Stereo -> Mono
rightStereo (_ :><: x) = x
instance Num Stereo where
(l1:><:r1) + (l2:><:r2) = (l1+l2) :><: (r1+r2)
(l1:><:r1) - (l2:><:r2) = (l1-l2) :><: (r1-r2)
(l1:><:r1) * (l2:><:r2) = (l1*l2) :><: (r1*r2)
negate (l:><:r) = negate l :><: negate r
abs (l:><:r) = abs l :><: abs r
signum (l:><:r) = signum l :><: signum r
fromInteger i = fromInteger i :><: fromInteger i
instance Fractional Stereo where
(l1:><:r1) / (l2:><:r2) = (l1/l2) :><: (r1/r2)
fromRational x = fromRational x :><: fromRational x
-------------------------------------------------------------------------------
-- | Converts a Stereo value to a Mono value (Double)
stereoToMono :: Stereo -> Mono
stereoToMono (x:><:y) = (x+y) / 2.0
-- | Converts a Mono value (Double) to a Stereo value
monoToStereo :: Mono -> Stereo
monoToStereo x = x :><: x
--panorama :: Double -> Mono -> Stereo
--panorama p m | p == 0.0 = m :><: m
-- | p > 0.0 = m :><: ((1.0 - p) * m)
-- | p < 0.0 = m :><: ((1.0 + p) * m)
-- | The range of the Double value must be between -1 and 1. If it is below 0
-- the left channel is turned down, if it is greater than 0 the right channel
-- is turned down. NOTE: This function should be replaced by a better one.
balance :: Double -> Stereo -> Stereo
balance p (l :><: r) | p == 0.0 = l :><: r
| p > 0.0 = l :><: ((1.0 - p) * r)
| p < 0.0 = ((1.0 + p) * l) :><: r
-------------------------------------------------------------------------------
-- | A Signal is either a list of 'Mono' values or a list of 'Stereo' values.
data Signal = Mono [Mono]
| Stereo [Stereo]
eitherSignal :: ([Mono] -> a) -> ([Stereo] -> a) -> Signal -> a
eitherSignal f g (Mono xs) = f xs
eitherSignal f g (Stereo xs) = g xs
-- | Applies the function to the input signal. If it is a stereo signal, the
-- function is applied to both channels seperately.
liftSignal :: ([Mono] -> [Mono]) -> Signal -> Signal
liftSignal f (Mono xs) = Mono $ f xs
liftSignal f (Stereo xs) = Stereo $ zipWith (:><:)
(f $ map leftStereo xs) (f $ map rightStereo xs)
-- mapSignal, liftSignal,
-------------------------------------------------------------------------------
-- | Reads a 'Signal' from a WAV-File.
readWavSignal :: FilePath -> IO Signal
readWavSignal fp = readWavFile fp >>=
return . either
(Mono . map wavInt16ToDouble)
(Stereo . map (\(l,r) -> (wavInt16ToDouble l :><: wavInt16ToDouble r)))
-- | Opens a 'Signal' from a WAV-File.
openWavSignal :: FilePath -> Signal
openWavSignal fp = unsafePerformIO $ readWavSignal fp
-- | Writes a list of mono values to a WAV-File.
writeWavMono :: FilePath -> [Mono] -> IO ()
writeWavMono fp = writeWavFileMono fp . map wavDoubleToInt16
-- | Writes a list of stereo values to a WAV-File.
writeWavStereo :: FilePath -> [Stereo] -> IO ()
writeWavStereo fp = writeWavFileStereo fp . map
(\(l :><: r) -> (wavDoubleToInt16 l, wavDoubleToInt16 r))
-- | Writes a 'Signal' to a WAV-File.
writeWavSignal :: FilePath -> Signal -> IO ()
writeWavSignal fp (Mono xs) = writeWavMono fp xs
writeWavSignal fp (Stereo xs) = writeWavStereo fp xs
writeTracks :: FilePath -> [Signal] -> IO ()
writeTracks fp s = writeWavFiles fp "" strs
where
strs = map foo s
foo (Mono xs) = Left $ map wavDoubleToInt16 xs
foo (Stereo xs) = Right $ map (\(l:><:r)->(wavDoubleToInt16 l, wavDoubleToInt16 r)) xs
-------------------------------------------------------------------------------
-- | Transfroms a signal to a list of mono values.
signalToMono :: Signal -> [Mono]
signalToMono (Mono xs) = xs
signalToMono (Stereo xs) = map stereoToMono xs
-- | Transfroms a signal to a list of stereo values.
signalToStereo :: Signal -> [Stereo]
signalToStereo (Mono xs) = map monoToStereo xs
signalToStereo (Stereo xs) = xs
-------------------------------------------------------------------------------
-- | The sum of a set of signals. If all signals are mono, the result will be mono.
-- Otherwise it will be stereo.
mergeSignals :: [Signal] -> Signal
mergeSignals ss = fun
where
loop (Mono x : xs) = let (l,r) = loop xs in (x:l,r)
loop (Stereo x : xs) = let (l,r) = loop xs in (l,x:r)
loop [] = ([],[])
fun = case loop ss of
([],[]) -> Mono []
(ls,[]) -> Mono $ map sum $ transpose ls
([],rs) -> Stereo $ map sum $ transpose rs
(ls,rs) -> Stereo $ map sum $ transpose (map (monoToStereo . sum) (transpose ls) : rs)
-- | The sum of two signals
mergeSignal :: Signal -> Signal -> Signal
mergeSignal (Stereo xs) (Stereo ys) = Stereo $ merge (+) xs ys
mergeSignal (Stereo xs) (Mono ys) = Stereo $ merge (+) xs (map monoToStereo ys)
mergeSignal (Mono xs) (Stereo ys) = Stereo $ merge (+) (map monoToStereo xs) ys
mergeSignal (Mono xs) (Mono ys) = Mono $ merge (+) xs ys
-- | The Double value is added to the input signal (offset). The resulting signal will be infinie
-- in any case.
infiniteSignal :: Double -> Signal -> Signal
infiniteSignal d (Mono a) = Mono (map (+d) a ++ repeat d)
infiniteSignal d (Stereo a) = Stereo (map (+(d:><:d)) a ++ repeat (d:><:d))
-- | Multiplies two signals.
multSignal :: Signal -> Signal -> Signal
multSignal (Mono a) (Mono b) = Mono $ zipWith (*) a b
multSignal (Mono a) (Stereo b) = Stereo $ zipWith (\x y -> monoToStereo x * y) a b
multSignal (Stereo a) (Stereo b) = Stereo $ zipWith (*) a b
multSignal (Stereo a) (Mono b) = Stereo $ zipWith (\x y -> x * monoToStereo y) a b