hscdio-0.1.0.0: src/Foreign/Libcdio/Sector.hs
{-# LANGUAGE ForeignFunctionInterface #-}
{-|
Description: Values related to on-disc data layout: track boundaries, sector sizes, address formats, etc.
Copyright: (c) 2018-2021 Sam May
License: GPL-3.0-or-later
Maintainer: ag@eitilt.life
Stability: stable
Portability: non-portable (requires libcdio)
The bulk of this module deals with the sizes of various parts of disc sectors,
and so a brief description of each is likely helpful; for more information, see
the libcdio user guide, or any of the other various documentation that people
have written. Depending on the accuracy required, multiple methods of encoding
data onto discs were developed enabling different degrees of error detection
and recovery. For ease of implementation, these check regions are spaced
evenly throughout the data, effectively dividing it into blocks of 'sectorSize'
bytes. The inner structure of each block is thus:
= Sector layout
== Audio
As audio CDs are intended to be listened to by human ears, often in noisy
environments, byte-perfect fidelity was deemed unnecessary; data fills the
entire sector without leaving room for checksums or synchronization points.
This structure is more technically described as "CD-DA" (Digital Audio).
@
|- 'sectorSize' -|
@
== Data (Mode 1)
The original structure described in the /Yellow Book/ standard. Mode 1, Form 1
is notably the layout used for CD-ROMs. It would be most correct to describe
these as "Mode 1" and "Mode 2" with no reference to form, but the libcdio
documentation has adopted the practice below as a parallel to the XA names.
=== Mode 1, Form 1
@
|------------------------------------ 'sectorSize' -------------------------------------|
'syncSize' + 'headerSize' + __'dataSize'__ + 'errorDetectionSize' + 'padSize' + 'errorCorrectionSize'
|------------------------------- 'syncedSize' -------------------------------|
|-------------------- 'tailSize' --------------------|
@
=== Mode 1, Form 2
@
|------------ 'sectorSize' -----------|
'syncSize' + 'headerSize' + __'dataSizeRaw'__
|------ 'syncedSize' ------|
@
== XA (Mode 2)
Developed as a later extension to the Mode 1, Form 2 (technically just "Mode
2") standard above to allow different data to be interleaved.
=== Mode 2, Form 1
@
|--------------------------------------- 'sectorSize' ----------------------------------------|
'syncSize' + 'headerSize' + 'subheaderSize' + __'dataSize'__ + 'errorDetectionSize' + 'errorCorrectionSize'
|---------- 'syncHeaderSizeXa' ---------| |-------------- 'tailSizeXa' --------------|
|------ 'headerSizeXa' ------|
|---------------------------------- 'syncedSize' ----------------------------------|
|--------------------------- 'dataSizeRaw' ---------------------------|
@
=== Mode 2, Form 2
@
|--------------------------- 'sectorSize' --------------------------|
'syncSize' + 'headerSize' + 'subheaderSize' + __'dataSizeRawXa'__ + 'padSizeXa'
|---------- 'syncHeaderSizeXa' ---------|
|------ 'headerSizeXa' ------|
|--------------------- 'syncedSize' ---------------------|
|-------------- 'dataSizeRaw' --------------|
|----- 'taggedDataSizeRaw' -----|
@
= @sector.h@
== Defines
* @CDIO_CD_CHUNK_SIZE@ -> 'chunkSize'
* @CDIO_CD_ECC_SIZE@ -> 'errorCorrectionSize'
* @CDIO_CD_EDC_SIZE@ -> 'errorDetectionSize'
* @CDIO_CD_FRAMESIZE@ -> 'dataSize'
* @CDIO_CD_FRAMESIZE_RAW@ -> 'sectorSize'
* @CDIO_CD_FRAMESIZE_RAW0@ -> 'dataSizeRaw'
* @CDIO_CD_FRAMESIZE_RAW1@ -> 'syncedSize'
* @CDIO_CD_FRAMESIZE_RAWER@ -> 'sectorSizeMax'
* @CDIO_CD_FRAMESIZE_SUB@ -> 'framesizeSub'
* @CDIO_CD_FRAMES_PER_MIN@ -> 'framesPerMin'
* @CDIO_CD_FRAMES_PER_SEC@ -> 'framesPerSec'
* @CDIO_CD_HEADER_SIZE@ -> 'headerSize'
* @CDIO_CD_M1F1_ZERO_SIZE@ -> 'padSize'
* @CDIO_CD_MAX_LSN@ (removed; identical to @'maxBound' :: 'Lsn'@)
* @CDIO_CD_MAX_SESSIONS@ -> 'maxSessions'
* @CDIO_CD_MINS@ -> 'cdMins'
* @CDIO_CD_MIN_LSN@ (removed; identical to @'minBound' :: 'Lsn'@)
* @CDIO_CD_MIN_SESSION_NO@ -> 'minSessionNo'
* @CDIO_CD_NUM_OF_CHUNKS@ -> 'chunksPerSector'
* @CDIO_CD_SECS_PER_MIN@ -> 'secsPerMin'
* @CDIO_CD_SUBHEADER_SIZE@ -> 'subheaderSize'
* @CDIO_CD_SYNC_SIZE@ -> 'syncSize'
* @CDIO_CD_XA_HEADER@ -> 'headerSizeXa'
* @CDIO_CD_XA_SYNC_HEADER@ -> 'syncHeaderSizeXa'
* @CDIO_CD_XA_TAIL@ -> 'tailSizeXa'
* @CDIO_POSTGAP_SECTORS@ -> 'postgapSectors'
* @CDIO_PREGAP_SECTORS@ -> 'pregapSectors'
* @M2F2_SECTOR_SIZE@ -> 'dataSizeRawXa'
* @M2RAW_SECTOR_SIZE@ (removed; identical to 'dataSizeRaw')
* @M2SUB_SECTOR_SIZE@ -> 'taggedDataSizeRaw'
* @msf_t_SIZEOF@ (removed; structural constant not required for Haskell)
== Types
* @cdio_cd_minutes_sectors@ (removed; most values were simple calculations on @CDIO_CD_FRAMES_PER_MIN@, which can be done manually)
- @CDIO_CD_MAX_SECTORS@ -> 'maxSectors'
* @cdio_subchannel@ -> 'SubchannelData'
- @CDIO_SUBCHANNEL_SUBQ_DATA@ -> 'QChannelData'
* @flag_t@ -> 'Flags' and 'Flag'
- @SCMS@ -> 'SerialCopyManagement'
== Symbols
* @CDIO_SECTOR_SYNC_HEADER@ -> 'sectorSyncHeader'
* @cdio_lba_to_lsn@ -> 'lbaToLsn'
* @cdio_lba_to_msf@ -> 'lbaToMsf'
* @cdio_lba_to_msf_str@ -> 'lbaToMsfStr'
* @cdio_lsn_to_lba@ -> 'lsnToLba'
* @cdio_lsn_to_msf@ -> 'lsnToMsf'
* @cdio_msf_to_lba@ -> 'msfToLba'
* @cdio_msf_to_lsn@ -> 'msfToLsn'
* @cdio_msf_to_str@ -> 'msfToStr'
= "Sound.Libcdio.Read.Data"
* 'Flag' (currently removed; unused in the translated portion of the interface)
* 'Flags' (currently removed; unused in the translated portion of the interface)
* 'Lba' (removed; only one address format is needed, and 'Lsn' is more widespread)
* 'Msf' (removed; only one address format is needed, and timestamps can be confusing for non-audio data)
* 'SubchannelData' (currently removed; unused in the translated portion of the interface)
* 'cdMins' -> 'Sound.Libcdio.Read.Data.maxCdMinutes'
* 'chunkSize' (removed; unnecessary low-level detail)
* 'chunksPerSector' (removed; unnecessary low-level detail)
* 'framesizeSub' (removed; unnecessary low-level detail)
* 'lbaToLsn' (removed; only 'Lsn' is used in @Sound@)
* 'lbaToMsf' (removed; only 'Lsn' is used in @Sound@)
* 'lbaToMsfStr' -> 'Sound.Libcdio.Read.Data.audioTimestamp'
* 'lsnToLba' (removed; only 'Lsn' is used in @Sound@)
* 'lsnToMsf' (removed; only 'Lsn' is used in @Sound@)
* 'maxSectors' -> 'Sound.Libcdio.Read.Data.maxSectors'
* 'maxSessions' (removed; explicit session handling not yet implemented by libcdio)
* 'minSessionNo' (removed; explicit session handling not yet implemented by libcdio)
* 'msfToLba' (removed; only 'Lsn' is used in @Sound@)
* 'msfToLsn' (removed; only 'Lsn' is used in @Sound@)
* 'msfToStr' (removed; only 'Lsn' is used in @Sound@)
* 'postgapSectors' -> 'Sound.Libcdio.Read.Data.defaultPostgapSectors'
* 'pregapSectors' -> 'Sound.Libcdio.Read.Data.defaultPregapSectors'
* 'secsPerMin' (removed; real-world constant)
* 'sectorSyncHeader' (removed; unnecessary low-level detail)
* 'framesPerMin' (removed; simple arithmatic of 'framesPerSec'
All size constants (e.g. 'sectorSize') likewise removed as unnecessary.
-}
module Foreign.Libcdio.Sector
( -- * Types
SubchannelData ( .. )
, Flags
, Flag ( .. )
, Lba
, Lsn
, Msf ( .. )
-- * Conversions
, lbaToLsn
, lsnToLba
, lbaToMsf
, msfToLba
, lsnToMsf
, msfToLsn
, lbaToMsfStr
, msfToStr
-- * Basic counts
, chunkSize
, minSessionNo, maxSessions
, pregapSectors, postgapSectors
, maxSectors
, framesizeSub
-- ** Units of time
, chunksPerSector
, framesPerSec
, secsPerMin
, framesPerMin
, cdMins
-- * Sector blocks
, sectorSize
, syncedSize
, sectorSizeMax
-- ** Headers
, sectorSyncHeader
, syncSize
, headerSize
, subheaderSize
, headerSizeXa
, syncHeaderSizeXa
-- ** Data
, dataSize
, dataSizeRaw
, dataSizeRawXa
, taggedDataSizeRaw
-- ** Error correction
, errorDetectionSize
, errorCorrectionSize
, padSize
, padSizeXa
, tailSize
, tailSizeXa
) where
import qualified Data.Array.BitArray as A
import qualified Data.ByteString as BS
import qualified Foreign.C.String as C
import qualified Foreign.C.Types as C
import qualified Foreign.Ptr as C
import qualified Foreign.Marshal.Alloc as M
import qualified Foreign.Marshal.Utils as M
import qualified Foreign.Storable as S
import qualified System.IO.Unsafe as IO.Unsafe
import Foreign.Libcdio.Marshal
import Foreign.Libcdio.Types.Enums
import Foreign.Libcdio.Types.Internal
import Foreign.Libcdio.Types.Offsets
import Foreign.Libcdio.Util
-- | The collection of metadata describing a particular track.
type Flags = A.BitArray Flag
-- | The number of sectors spanned by a track pre-gap by default.
pregapSectors :: Word
pregapSectors = fromIntegral pregapSectors'
foreign import ccall safe "cdio/compat/sector.h pregap_sectors"
pregapSectors' :: C.CUInt
-- | The number of sectors spanned by a track post-gap by default.
postgapSectors :: Word
postgapSectors = fromIntegral postgapSectors'
foreign import ccall safe "cdio/compat/sector.h postgap_sectors"
postgapSectors' :: C.CUInt
-- | The typical maximum length of a disc, though it's not a strict limit.
cdMins :: Word
cdMins = fromIntegral cdMins'
foreign import ccall safe "cdio/compat/sector.h cd_mins"
cdMins' :: C.CUInt
-- | The number of seconds in a minute.
secsPerMin :: Word
secsPerMin = fromIntegral secsPerMin'
foreign import ccall safe "cdio/compat/sector.h secs_per_min"
secsPerMin' :: C.CUInt
-- | The number of disc sectors comprising a second of audio data.
framesPerSec :: Word
framesPerSec = fromIntegral framesPerSec'
foreign import ccall safe "cdio/compat/sector.h frames_per_sec"
framesPerSec' :: C.CUInt
-- | The number of bytes in a disc sector's sync header.
syncSize :: Word
syncSize = fromIntegral syncSize'
foreign import ccall safe "cdio/compat/sector.h sync_size"
syncSize' :: C.CUInt
-- | The size of the smallest meaningful segment of data, in bytes.
chunkSize :: Word
chunkSize = fromIntegral chunkSize'
foreign import ccall safe "cdio/compat/sector.h chunk_size"
chunkSize' :: C.CUInt
-- | The number of data chunks comprising a disc sector.
chunksPerSector :: Word
chunksPerSector = fromIntegral chunksPerSector'
foreign import ccall safe "cdio/compat/sector.h chunks_per_frame"
chunksPerSector' :: C.CUInt
-- | The size of a segment of data in the subchannel, in bytes.
framesizeSub :: Word
framesizeSub = fromIntegral framesizeSub'
foreign import ccall safe "cdio/compat/sector.h framesize_sub"
framesizeSub' :: C.CUInt
-- | The size of the address of a data sector, in bytes.
headerSize :: Word
headerSize = fromIntegral headerSize'
foreign import ccall safe "cdio/compat/sector.h header_size"
headerSize' :: C.CUInt
-- | The size of the subheader of an XA sector, in bytes.
subheaderSize :: Word
subheaderSize = fromIntegral subheaderSize'
foreign import ccall safe "cdio/compat/sector.h subheader_size"
subheaderSize' :: C.CUInt
-- | The size of the EDC error correction segment, in bytes.
errorDetectionSize :: Word
errorDetectionSize = fromIntegral errorDetectionSize'
foreign import ccall safe "cdio/compat/sector.h edc_size"
errorDetectionSize' :: C.CUInt
-- | The amount of padding between the EDC and ECC segments in a Mode 1 sector,
-- in bytes.
padSize :: Word
padSize = fromIntegral padSize'
foreign import ccall safe "cdio/compat/sector.h mode1_pad_size"
padSize' :: C.CUInt
-- | The size of the ECC error correction segment, in bytes.
errorCorrectionSize :: Word
errorCorrectionSize = fromIntegral errorCorrectionSize'
foreign import ccall safe "cdio/compat/sector.h ecc_size"
errorCorrectionSize' :: C.CUInt
-- | The amount of data which may be contained in a disc sector /with/ error
-- correction, in bytes.
dataSize :: Word
dataSize = fromIntegral dataSize'
foreign import ccall safe "cdio/compat/sector.h framesize"
dataSize' :: C.CUInt
-- | The size of an entire disc sector, in bytes.
sectorSize :: Word
sectorSize = fromIntegral sectorSize'
foreign import ccall safe "cdio/compat/sector.h framesize_raw"
sectorSize' :: C.CUInt
-- | The maximum number of bytes that may be returned from a single call.
sectorSizeMax :: Word
sectorSizeMax = fromIntegral sectorSizeMax'
foreign import ccall safe "cdio/compat/sector.h framesize_rawer"
sectorSizeMax' :: C.CUInt
-- | The size of a disc sector in bytes, ignoring the sync header.
syncedSize :: Word
syncedSize = fromIntegral syncedSize'
foreign import ccall safe "cdio/compat/sector.h framesize_raw1"
syncedSize' :: C.CUInt
-- | The amount of data which may be contained in a disc sector /without/ error
-- correction, in bytes.
dataSizeRaw :: Word
dataSizeRaw = fromIntegral dataSizeRaw'
foreign import ccall safe "cdio/compat/sector.h framesize_raw0"
dataSizeRaw' :: C.CUInt
-- | The total size of the meaningful XA sector headers, in bytes.
headerSizeXa :: Word
headerSizeXa = fromIntegral headerSizeXa'
foreign import ccall safe "cdio/compat/sector.h header_size_xa"
headerSizeXa' :: C.CUInt
-- | The total size of all data sector error correction segments, in bytes.
tailSize :: Word
tailSize = errorDetectionSize + padSize + errorCorrectionSize
-- | The total size of all XA sector error correction segments, in bytes.
tailSizeXa :: Word
tailSizeXa = fromIntegral tailSizeXa'
foreign import ccall safe "cdio/compat/sector.h tail_size_xa"
tailSizeXa' :: C.CUInt
-- | The total size of all headers in an XA sector, including the sync marker.
syncHeaderSizeXa :: Word
syncHeaderSizeXa = fromIntegral syncHeaderSizeXa'
foreign import ccall safe "cdio/compat/sector.h sync_size_xa"
syncHeaderSizeXa' :: C.CUInt
-- | The amount of padding at the end of an XA sector without error correction,
-- in bytes.
padSizeXa :: Word
padSizeXa = dataSizeRaw - taggedDataSizeRaw
-- | The byte sequence used to mark the start of a disc sector, to allow
-- correcting drift while reading.
sectorSyncHeader :: BS.ByteString
sectorSyncHeader = IO.Unsafe.unsafePerformIO $
BS.packCStringLen (sectorSyncHeader', fromIntegral syncSize)
foreign import ccall safe "cdio/compat/sector.h sector_sync_header"
sectorSyncHeader' :: C.Ptr C.CChar
-- | The amount of data contained in an XA sector without error correction, in
-- bytes.
dataSizeRawXa :: Word
dataSizeRawXa = fromIntegral dataSizeRawXa'
foreign import ccall safe "cdio/compat/sector.h data_size_xa"
dataSizeRawXa' :: C.CUInt
-- | The size of tagged data (counting the subheader) contained in an XA sector
-- without error correction, in bytes.
taggedDataSizeRaw :: Word
taggedDataSizeRaw = fromIntegral taggedDataSizeRaw'
foreign import ccall safe "cdio/compat/sector.h tagged_size_xa"
taggedDataSizeRaw' :: C.CUInt
-- | How many sessions are allowed on a disc.
maxSessions :: Word
maxSessions = fromIntegral maxSessions'
foreign import ccall safe "cdio/compat/sector.h max_session"
maxSessions' :: C.CUInt
-- | The smallest session number on a disc.
minSessionNo :: Word
minSessionNo = fromIntegral minSessionNo'
foreign import ccall safe "cdio/compat/sector.h min_session"
minSessionNo' :: C.CUInt
-- | A shortcut for calculating @'framesPerSec' * 'secsPerMin'@
framesPerMin :: Word
framesPerMin = fromIntegral framesPerMin'
foreign import ccall safe "cdio/compat/sector.h frames_per_min"
framesPerMin' :: C.CUInt
-- | The maximum number of sectors allowed to be stored on a disc.
maxSectors :: Word
maxSectors = fromIntegral maxSectors'
foreign import ccall safe "cdio/compat/sector.h max_sectors"
maxSectors' :: C.CUInt
-- | Minute\/second\/frame structure for addresses. Generally only makes sense
-- for audio discs.
data Msf = Msf
{ minute :: Word
, second :: Word
-- ^ 'secsPerMin'
, frame :: Word
-- ^ 'framesPerSec'
}
deriving ( Show, Read )
instance Eq Msf where
l == r = compare l r == EQ
instance Ord Msf where
compare l r = case minute l' `compare` minute r' of
EQ -> case second l' `compare` second r' of
EQ -> frame l' `compare` frame r'
x -> x
x -> x
where l' = normalize l
r' = normalize r
normalize msf@(Msf m s f)
| f >= framesPerSec = normalize $ Msf m (s + div f framesPerSec) (mod f framesPerSec)
| s >= secsPerMin = Msf (m + div s secsPerMin) (mod s secsPerMin) f
| otherwise = msf
instance Bounded Msf where
minBound = Msf 0 0 0
maxBound = Msf maxBound (secsPerMin - 1) (framesPerSec - 1)
instance S.Storable Msf where
sizeOf _ = msfSizeOf
alignment _ = msfAlign
peek c = do
m <- S.peekByteOff c msfM
s <- S.peekByteOff c msfS
f <- S.peekByteOff c msfF
return $ Msf (fromBcd8 m) (fromBcd8 s) (fromBcd8 f)
poke c hs = do
S.pokeByteOff c msfM . toBcd8 $ minute hs
S.pokeByteOff c msfS . toBcd8 $ second hs
S.pokeByteOff c msfF . toBcd8 $ frame hs
instance Enum Msf where
fromEnum msf = fromInteger
$ toInteger (frame msf)
+ toInteger (second msf) * fromIntegral framesPerSec
+ toInteger (minute msf) * fromIntegral framesPerMin
toEnum i = Msf
{ minute = div i' framesPerMin
, second = flip mod secsPerMin $ div i' framesPerSec
, frame = mod i' framesPerSec
}
where i' = fromIntegral i
pred (Msf 0 0 0) = error "Enum.pred(Msf): tried to take `pred' of minBound"
pred (Msf m s f)
| f >= framesPerSec = pred $ Msf m (s + div f framesPerSec) (mod f framesPerSec)
| s >= secsPerMin = pred $ Msf (m + div s secsPerMin) (mod s secsPerMin) f
| f == 0 && s == 0 = Msf (m - 1) (secsPerMin - 1) (framesPerSec - 1)
| f == 0 = Msf m (s - 1) (framesPerSec - 1)
| otherwise = Msf m s (f - 1)
succ msf@(Msf m s f)
| msf >= maxBound = error "Enum.succ(Msf): tried to take `succ' of maxBound"
| otherwise = let (fd, fm) = overflow f 1 framesPerSec
(sd, sm) = overflow s fd secsPerMin
in Msf (m + sd) sm fm
where -- Relies on unsigned type, and @y + b < maxBound@
overflow x y b
| x > maxBound - y = (div (x - b + y) b + 1, mod (x - b + y) b)
| otherwise = (div (x + y) b, mod (x + y) b)
{- Use @sizeOf Msf@
msfSizeof :: Word
msfSizeof = fromIntegral msfSizeof'
foreign import ccall safe "cdio/compat/sector.h sizeof_msf_const"
msfSizeof' :: C.CSize
-}
{- Never used after definition.
-- | Address in one of the two primary address formats.
data CdromAddr
= MsfAddr Msf
| LbaAddr Lba
deriving ( Eq, Show, Read, Ord )
-- | Note that, while the 'peek' implementation attempts to detect which form
-- the address takes, it may get it wrong and read a 'Msf' address as an 'Lba'
-- or vice versa. If the type is otherwise known, it may be better to read
-- the pointer as that type directly, and wrap into the union manually.
instance S.Storable CdromAddr where
sizeOf _ = adrSizeOf
alignment _ = adrAlign
peek c = do
msf <- S.peek $ C.castPtr c
lba <- S.peek $ C.castPtr c
-- Barring uncleared bits, an LBA has an extra byte.
if mod 0x100 lba /= 0
|| second msf >= secsPerMin
|| frame msf >= framesPerSec
then return $ LbaAddr lba
else return $ MsfAddr msf
poke c (MsfAddr hs) = S.poke (C.castPtr c) hs
poke c (LbaAddr hs) = S.poke (C.castPtr c) hs
-}
-- | Print a logical address as the corresponding timestamp, assuming audio
-- data.
lbaToMsfStr :: Lba -> String
lbaToMsfStr l = IO.Unsafe.unsafePerformIO $
lbaToMsfStr' (fromIntegral l) >>= peekFString
foreign import ccall safe "cdio/compat/sector.h cdio_lba_to_msf_str"
lbaToMsfStr' :: CLba -> IO C.CString
-- | Print a disc timestamp in the standard "MM:SS:FF" format.
msfToStr :: Msf -> String
msfToStr msf = IO.Unsafe.unsafePerformIO $
M.with msf msfToStr' >>= C.peekCString
foreign import ccall safe "cdio/compat/sector.h cdio_msf_to_str"
msfToStr' :: C.Ptr Msf -> IO C.CString
-- | Convert an LBA address into the corresponding LSN.
lbaToLsn :: Lba -> Lsn
lbaToLsn = fromIntegral . lbaToLsn' . fromIntegral
-- C erroneously returns @lba_t@.
foreign import ccall safe "cdio/compat/sector.h cdio_lba_to_lsn"
lbaToLsn' :: CLba -> CLba
-- | Convert an LBA address into the corresponding timestamp, assuming audio
-- data.
lbaToMsf :: Lba -> Msf
lbaToMsf l = IO.Unsafe.unsafePerformIO . M.alloca $ \m' ->
lbaToMsf' (fromIntegral l) m' >> S.peek m'
foreign import ccall safe "cdio/compat/sector.h cdio_lba_to_msf"
lbaToMsf' :: CLba -> C.Ptr Msf -> IO ()
-- | Convert an LSN address into the corresponding LBA.
lsnToLba :: Lsn -> Lba
lsnToLba = fromIntegral . lsnToLba' . fromIntegral
foreign import ccall safe "cdio/compat/sector.h cdio_lsn_to_lba"
lsnToLba' :: CLsn -> CLba
-- | Convert an LSN address into the corresponding timestamp, assuming audio
-- data.
lsnToMsf :: Lsn -> Msf
lsnToMsf l = IO.Unsafe.unsafePerformIO . M.alloca $ \m' ->
lsnToMsf' (fromIntegral l) m' >> S.peek m'
foreign import ccall safe "cdio/compat/sector.h cdio_lsn_to_msf"
lsnToMsf' :: CLsn -> C.Ptr Msf -> IO ()
-- | Convert a timestamp into the corresponding LBA address, assuming audio
-- data.
msfToLba :: Msf -> Lba
msfToLba = fromIntegral . IO.Unsafe.unsafePerformIO . flip M.with msfToLba'
foreign import ccall safe "cdio/compat/sector.h cdio_msf_to_lba"
msfToLba' :: C.Ptr Msf -> IO CLsn
-- | Convert a timestamp into the corresponding LSN address, assuming audio
-- data.
msfToLsn :: Msf -> Lsn
msfToLsn = fromIntegral . IO.Unsafe.unsafePerformIO . flip M.with msfToLsn'
foreign import ccall safe "cdio/compat/sector.h cdio_msf_to_lsn"
msfToLsn' :: C.Ptr Msf -> IO CLsn
{- Not exported
-- | Convert a split timestamp into the corresponding LBA address, assuming
-- audio data.
msf3ToLba :: Word -> Word -> Word -> Lba
msf3ToLba m s f = fromIntegral $ msf3ToLba' (fromIntegral m) (fromIntegral s) (fromIntegral f)
foreign import ccall safe "cdio/compat/sector.h cdio_msf3_to_lba"
msf3ToLba' :: C.CUInt -> C.CUInt -> C.CUInt -> CLba
-- | Read a timestamp in the form MM:SS:FF into the corresponding LBA address,
-- assuming audio data.
mmssffToLba :: String -> Maybe Lba
mmssffToLba s = invalidLba . IO.Unsafe.unsafePerformIO $ C.withCString s mmssffToLba
foreign import ccall safe "cdio/compat/sector.h cdio_mmssff_to_lba"
mmssffToLba' :: C.CString -> IO CLba
-}