packages feed

cue-sheet-2.0.2: Text/CueSheet/Render.hs

{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}

-- |
-- Module      :  Text.CueSheet.Render
-- Copyright   :  © 2016–present Mark Karpov
-- License     :  BSD 3 clause
--
-- Maintainer  :  Mark Karpov <markkarpov92@gmail.com>
-- Stability   :  experimental
-- Portability :  portable
--
-- The module contains a CUE sheet render. You probably want to import
-- "Text.CueSheet" instead.
module Text.CueSheet.Render
  ( renderCueSheet,
  )
where

import Control.Monad
import Control.Monad.State
import Control.Monad.Writer.Lazy
import qualified Data.ByteString as B
import qualified Data.ByteString.Builder as BB
import qualified Data.ByteString.Lazy as BL
import Data.Char (chr, isSpace)
import qualified Data.List.NonEmpty as NE
import Data.Maybe (catMaybes)
import Data.Text (Text)
import qualified Data.Text as T
import qualified Data.Text.Encoding as T
import Numeric.Natural
import Text.CueSheet.Types
import Text.Printf (printf)

-- | Render a CUE sheet as a lazy 'BL.ByteString'. All 'Text' values in the
-- 'CueSheet' will be UTF-8 encoded.
renderCueSheet ::
  -- | Use CRLF sequence as “end of line” separator
  Bool ->
  -- | The 'CueSheet' to render
  CueSheet ->
  -- | The result
  BL.ByteString
renderCueSheet csrf CueSheet {..} =
  BB.toLazyByteString . execWriter . flip evalStateT (max cueFirstTrackNumber 1) $ do
    let eol = tell (if csrf then "\r\n" else "\n")
        tellText x =
          let raw = T.encodeUtf8 x
           in tell . BB.byteString $
                if B.any (isSpace . chr . fromIntegral) raw || B.null raw
                  then "\"" <> raw <> "\""
                  else raw
    forM_ cueCatalog $ \x -> do
      tell "CATALOG "
      tellText (unMcn x)
      eol
    forM_ cueCdTextFile $ \x -> do
      tell "CDTEXTFILE "
      tellText (T.pack x)
      eol
    forM_ cuePerformer $ \x -> do
      tell "PERFORMER "
      tellText (unCueText x)
      eol
    forM_ cueTitle $ \x -> do
      tell "TITLE "
      tellText (unCueText x)
      eol
    forM_ cueSongwriter $ \x -> do
      tell "SONGWRITER "
      tellText (unCueText x)
      eol
    forM_ cueFiles $ \CueFile {..} -> do
      tell "FILE "
      tellText (T.pack cueFileName)
      tell " "
      tellText (renderFileType cueFileType)
      eol
      forM_ cueFileTracks $ \CueTrack {..} -> do
        currentTrack <- get
        modify succ
        tell "  TRACK "
        tellText (formatNat currentTrack)
        tell " "
        tellText (renderTrackType cueTrackType)
        eol
        let flags =
              catMaybes
                [ if cueTrackDigitalCopyPermitted then Just "DCP" else Nothing,
                  if cueTrackFourChannelAudio then Just "4CH" else Nothing,
                  if cueTrackPreemphasisEnabled then Just "PRE" else Nothing,
                  if cueTrackSerialCopyManagement then Just "SCMS" else Nothing
                ]
        unless (null flags) $ do
          tell "    FLAGS "
          (tell . BB.byteString) (B.intercalate " " flags)
          eol
        forM_ cueTrackIsrc $ \x -> do
          tell "    ISRC "
          tellText (unIsrc x)
          eol
        forM_ cueTrackTitle $ \x -> do
          tell "    TITLE "
          tellText (unCueText x)
          eol
        forM_ cueTrackPerformer $ \x -> do
          tell "    PERFORMER "
          tellText (unCueText x)
          eol
        forM_ cueTrackSongwriter $ \x -> do
          tell "    SONGWRITER "
          tellText (unCueText x)
          eol
        forM_ cueTrackPregap $ \x -> do
          tell "    PREGAP "
          tellText (showMmSsFf x)
          eol
        forM_ cueTrackPregapIndex $ \x -> do
          tell "    INDEX 00 "
          tellText (showMmSsFf x)
          eol
        forM_ (NE.zip (NE.fromList [1 ..]) cueTrackIndices) $ \(n, index) -> do
          tell "    INDEX "
          tellText (formatNat n)
          tell " "
          tellText (showMmSsFf index)
          eol
        forM_ cueTrackPostgap $ \x -> do
          tell "    POSTGAP "
          tellText (showMmSsFf x)
          eol

----------------------------------------------------------------------------
-- Helpers

-- | Format a 'Natural' padding it with zeros to 2 digits.
formatNat :: Natural -> Text
formatNat = T.pack . printf "%02d"

-- | Render a 'CueFileType' as per the CUE specs.
renderFileType :: CueFileType -> Text
renderFileType Binary = "BINARY"
renderFileType Motorola = "MOTOROLA"
renderFileType Aiff = "AIFF"
renderFileType Wave = "WAVE"
renderFileType MP3 = "MP3"

-- | Render a 'CueTrackType' as per the CUE specs.
renderTrackType :: CueTrackType -> Text
renderTrackType CueTrackAudio = "AUDIO"
renderTrackType CueTrackCdg = "CDG"
renderTrackType CueTrackMode1_2048 = "MODE1/2048"
renderTrackType CueTrackMode1_2352 = "MODE1/2352"
renderTrackType CueTrackMode2_2336 = "MODE2/2336"
renderTrackType CueTrackMode2_2352 = "MODE2/2352"
renderTrackType CueTrackCdi2336 = "CDI/2336"
renderTrackType CueTrackCdi2352 = "CDI/2352"