module Sound.Audacity.Project.Track.Wave where
import qualified Sound.Audacity.Project.Track.Wave.Summary as Summary
import qualified Sound.Audacity.XML.Attribute as Attr
import qualified Sound.Audacity.XML as XML
import qualified Text.HTML.Tagchup.Tag as Tag
import qualified Text.XML.Basic.Name.MixedCase as Name
import qualified Data.ByteString.Char8 as BS
import Text.Printf (printf)
import qualified System.IO as IO
import System.Directory (createDirectoryIfMissing)
import System.FilePath ((</>))
import qualified Data.StorableVector.Lazy as SVL
import qualified Data.NonEmpty as NonEmpty
import qualified Data.List as List
import Data.NonEmpty ((!:))
import Data.Tuple.HT (mapSnd)
import qualified Control.Monad.Trans.Reader as MR
import Control.Monad.IO.Class (MonadIO, liftIO)
import Control.Monad (liftM)
import Prelude hiding (sequence_)
data T =
Cons {
name_ :: String,
channel_ :: Channel,
linked_, mute_, solo_, minimized_ :: Bool,
height_ :: Int,
rate_ :: Int,
gain_, pan_ :: Double,
clips_ :: [Clip]
}
deriving Show
deflt :: T
deflt =
Cons {
name_ = "",
channel_ = Mono,
linked_ = False, mute_ = False, solo_ = False,
minimized_ = False,
height_ = 150,
rate_ = 44100,
gain_ = 1.0,
pan_ = 0.0,
clips_ = []
}
data Channel = Left | Right | Mono
deriving (Eq, Ord, Enum, Show)
data Clip =
Clip {
offset_ :: Double,
sequence_ :: Sequence
}
deriving Show
data Sequence =
Sequence {
maxSamples_ :: Int,
sampleFormat_ :: SampleFormat,
numSamples_ :: Int,
blocks_ :: [Block]
}
deriving Show
data SampleFormat = Int16Sample | Int24Sample | FloatSample
deriving (Eq, Ord, Enum, Bounded, Show)
intFromSampleFormat :: SampleFormat -> Int
intFromSampleFormat fmt =
case fmt of
Int16Sample -> 0x00020001
Int24Sample -> 0x00040001
FloatSample -> 0x0004000F
data Block =
Block {
blockStart_, blockLength_ :: Int,
blockFile_ :: BlockFile
}
deriving Show
data BlockFile =
PCMAliasBlockFile {
summaryFile_, aliasFile_ :: FilePath,
aliasStart_ :: Int,
aliasChannel_ :: Int,
limits_ :: Summary.Limits
}
deriving Show
toXML :: T -> [[Tag.T Name.T String]]
toXML x =
XML.tag "wavetrack" x
(Attr.string "name" name_ :
Attr.enum "channel" channel_ :
Attr.bool "linked" linked_ :
Attr.bool "mute" mute_ :
Attr.bool "solo" solo_ :
Attr.int "height" height_ :
Attr.bool "minimized" minimized_ :
Attr.int "rate" rate_ :
Attr.double "gain" gain_ :
Attr.double "pan" pan_ :
[])
(concatMap clipToXML (clips_ x))
clipToXML :: Clip -> [[Tag.T Name.T String]]
clipToXML x =
XML.tag "waveclip" x
(Attr.string "offset" (printf "%.8f" . offset_) :
[])
(sequenceToXML (sequence_ x))
sequenceToXML :: Sequence -> [[Tag.T Name.T String]]
sequenceToXML x =
XML.tag "sequence" x
(Attr.int "maxsamples" maxSamples_ :
Attr.int "numsamples" numSamples_ :
Attr.int "sampleformat" (intFromSampleFormat . sampleFormat_) :
[])
(concatMap blockToXML (blocks_ x))
blockToXML :: Block -> [[Tag.T Name.T String]]
blockToXML x =
XML.tag "waveblock" x
(Attr.int "start" blockStart_ :
[])
$
XML.tag "pcmaliasblockfile" x
(Attr.string "summaryfile" (summaryFile_ . blockFile_) :
Attr.string "aliasfile" (aliasFile_ . blockFile_) :
Attr.int "aliasstart" (aliasStart_ . blockFile_) :
Attr.int "aliaslen" blockLength_ :
Attr.int "aliaschannel" (aliasChannel_ . blockFile_) :
Attr.float "min" (Summary.min_ . limits_ . blockFile_) :
Attr.float "max" (Summary.max_ . limits_ . blockFile_) :
Attr.float "rms" (Summary.rms_ . limits_ . blockFile_) :
[])
[]
pcmAliasSequence ::
(Monad m) =>
SampleFormat -> Int -> Int -> FilePath -> Int -> Summary.Monad m Sequence
pcmAliasSequence fmt blockSize totalSize path channel =
liftM
(\bs ->
Sequence {
maxSamples_ = blockSize,
numSamples_ = totalSize,
sampleFormat_ = fmt,
blocks_ = bs
}) $
mapM
(\start -> do
(Summary.State n) <- Summary.reserve
return $
Block {
blockStart_ = start,
blockLength_ = min totalSize (start+blockSize) - start,
blockFile_ =
PCMAliasBlockFile {
summaryFile_ = printf "e%05x.auf" n,
aliasFile_ = path,
aliasStart_ = start,
aliasChannel_ = channel,
limits_ = Summary.defltLimits
}
}) $
takeWhile (<totalSize) $
iterate (blockSize+) 0
pcmAliasSequenceFromStorableVector ::
(MonadIO m) =>
Int -> FilePath -> Int -> SVL.Vector Float -> Summary.Monad m Sequence
pcmAliasSequenceFromStorableVector blockSize aliasFile channel =
liftM (sequenceFromBlocksSize blockSize) .
mapM (uncurry $ storeSummary aliasFile channel) .
Summary.attachStarts .
Summary.sequenceFromStorableVector blockSize
pcmAliasSequenceFromSummary ::
(MonadIO m) =>
FilePath -> Int -> [Summary.T] -> Summary.Monad m Sequence
pcmAliasSequenceFromSummary aliasFile channel =
liftM sequenceFromBlocks .
mapM (uncurry $ storeSummary aliasFile channel) .
Summary.attachStarts
data BlockOrder =
Serial
| Interleaved
deriving (Eq, Ord, Show, Enum, Bounded)
pcmAliasSequencesFromStorableVectorChannels ::
(MonadIO m) =>
BlockOrder ->
Int -> FilePath -> [SVL.Vector Float] -> Summary.Monad m [Sequence]
pcmAliasSequencesFromStorableVectorChannels order blockSize aliasFile =
liftM (map (sequenceFromBlocksSize blockSize)) .
blocksFromChannelSummaries order aliasFile .
map (Summary.sequenceFromStorableVector blockSize)
pcmAliasSequencesFromChannelSummaries ::
(MonadIO m) =>
BlockOrder -> FilePath -> [[Summary.T]] -> Summary.Monad m [Sequence]
pcmAliasSequencesFromChannelSummaries order aliasFile =
liftM (map sequenceFromBlocks) .
blocksFromChannelSummaries order aliasFile
blocksFromChannelSummaries ::
(MonadIO m) =>
BlockOrder -> FilePath -> [[Summary.T]] -> Summary.Monad m [[Block]]
blocksFromChannelSummaries order aliasFile =
let applyOrder f =
case order of
Serial -> f
Interleaved -> liftM List.transpose . f . List.transpose
in applyOrder
(mapM
(mapM
(\(channel, startBlock) ->
uncurry (storeSummary aliasFile channel) startBlock))) .
zipWith (\channel -> map ((,) channel)) (iterate (1+) 0) .
map Summary.attachStarts
sequenceFromBlocks :: [Block] -> Sequence
sequenceFromBlocks bs =
let lens = map blockLength_ bs
in Sequence {
maxSamples_ = NonEmpty.maximum $ 1024 !: lens,
numSamples_ = sum lens,
sampleFormat_ = FloatSample,
blocks_ = bs
}
sequenceFromBlocksSize :: Int -> [Block] -> Sequence
sequenceFromBlocksSize blockSize bs =
Sequence {
maxSamples_ = blockSize,
numSamples_ = sum $ map blockLength_ bs,
sampleFormat_ = FloatSample,
blocks_ = bs
}
storeSummary ::
MonadIO m =>
FilePath -> Int -> Int -> Summary.T -> Summary.Monad m Block
storeSummary aliasFile channel start
(Summary.Cons {
Summary.length_ = len, Summary.limits_ = limits,
Summary.content_ = cont}) = do
(Summary.State n) <- Summary.reserve
summaryDir <- MR.ask
let fileName = printf "e%07x.auf" n
let dirName =
case mapSnd (take 2) $ splitAt 3 fileName of
(e, d) -> summaryDir </> e </> 'd':d
liftIO $ do
createDirectoryIfMissing True dirName
IO.withBinaryFile (dirName </> fileName) IO.WriteMode $ \h -> do
BS.hPut h $ BS.pack "AudacityBlockFile112"
SVL.hPut h cont
return $
Block {
blockStart_ = start,
blockLength_ = len,
blockFile_ =
PCMAliasBlockFile {
summaryFile_ = fileName,
aliasFile_ = aliasFile,
aliasStart_ = start,
aliasChannel_ = channel,
limits_ = limits
}
}