module Sound.MIDI
(
    -- * Encoding
    encodeMidi,
    encodeMidi',
    encodeMidi1,
    encodeMidi1',

    -- * Decoding
    decodeMidi,
    decodeMidi1,
    partitionRealtime,

    -- * Parser and Serializer
    --
    -- | Top level parsing and serializing tools are exposed here. For
    -- fine-grained access, see "Sound.MIDI.Parser" and "Sound.MIDI.Serialize".
    midiParser,
    midiSerializer,

    -- * Re-exports
    module Sound.MIDI.Types
)
where

import Control.Applicative
import Data.Functor.Identity
import qualified Data.ByteString as BS
import qualified Data.ByteString.Lazy as BL
import Data.ByteString.Builder (toLazyByteString, Builder)
import qualified Data.Attoparsec.ByteString as A

import Sound.MIDI.Types
import qualified Sound.MIDI.Parser as P
import qualified Sound.MIDI.Serialize as S

-- | Encode some collection of 'MidiMessage's to a lazy 'BL.ByteString'
encodeMidi :: Foldable t => t MidiMessage -> BL.ByteString
encodeMidi = toLazyByteString . foldMap S.midiMessage
{-# SPECIALISE encodeMidi :: [MidiMessage] -> BL.ByteString #-}

-- | Strict version of 'encodeMidi'
encodeMidi' :: Foldable t => t MidiMessage -> BS.ByteString
encodeMidi' = BL.toStrict . encodeMidi
{-# SPECIALISE encodeMidi' :: [MidiMessage] -> BS.ByteString #-}

-- | Encode a single message to a lazy 'BL.ByteString'
encodeMidi1 :: MidiMessage -> BL.ByteString
encodeMidi1 = encodeMidi . Identity

-- | Strict version of 'encodeMidi1'
encodeMidi1' :: MidiMessage -> BS.ByteString
encodeMidi1' = encodeMidi' . Identity

-- | Decode raw MIDI data from a strict 'ByteString'. Any incomplete data at the
-- beginning will be skipped! This function assumes a normalized MIDI stream,
-- i.e. one in which events are /not/ interrupted by real-time events!
decodeMidi :: BS.ByteString -> Either String [MidiMessage]
decodeMidi = A.parseOnly (P.skipToStatus *> some P.midiMessage)

-- | Decode one event from raw MIDI data in a strict 'ByteString'. Any
-- incomplete data at the beginning will be skipped!
decodeMidi1 :: BS.ByteString -> Either String MidiMessage
decodeMidi1 = A.parseOnly (P.skipToStatus *> P.midiMessage)

-- | Partition an event stream into real-time events and other messages. The
-- first parameter returned will be the real-time events, the second element
-- will be the rest of the stream. Note that this effectively normalizes the
-- second element.
partitionRealtime :: BS.ByteString -> (BS.ByteString, BS.ByteString)
partitionRealtime = BS.partition isRT
    where isRT x = x >= 0xF8 && x <= 0xFF

midiParser :: A.Parser MidiMessage
midiParser = P.midiMessage

midiSerializer :: MidiMessage -> Builder
midiSerializer = S.midiMessage