midi-simple-0.1.0.0: src/Sound/MIDI/Types.hs
{-# LANGUAGE DeriveGeneric #-}
{-# OPTIONS_GHC -funbox-strict-fields #-}
module Sound.MIDI.Types
(
-- * MIDI messages
MidiMessage (..),
-- * Basic MIDI types
ChannelVoice (..),
ChannelMode (..),
SystemCommon (..),
SystemRealTime (..),
SystemExclusive (..),
VendorId (..),
-- * Numeric MIDI data
--
-- | Only use the direct constructors when you can assure that the values
-- fit into 7 bits! In general you should prefer the smart constructors. For
-- values outside of the 7 bit range, the numbers should generally wrap
-- around, but no guarantees are made!
Channel (..),
mkChannel,
Pitch (..),
mkPitch,
middleC,
Patch (..),
mkPatch,
Velocity (..),
mkVelocity,
Touch (..),
mkTouch,
Controller (..),
mkController,
PositionPointer (..),
mkPositionPointer,
toClocks,
-- * Helper functions
to4Bit,
to7Bit,
to14Bit
)
where
import Data.Word
import Data.ByteString (ByteString)
import Data.Bits
import GHC.Generics
-- | A data type representing midi messages. Messages can be categorized into 5
-- subcategories
--
-- 1. __Channel Voice Messages__. Start, stop, or alter sounds being played.
-- 2. __Channel Mode Messages__. Control messages affecting the entire
-- channel.
-- 3. __System Real-Time Messages__. Used by sequencers to regulate and
-- synchronize timing.
-- 4. __System Common Messages__. Used for song selection, position
-- pointers, etc.
-- 5. __System Exclusive Messages__. Used for device-specific extensions to
-- the MIDI protocol.
--
data MidiMessage
= ChannelVoice ChannelVoice
| ChannelMode ChannelMode
| SystemCommon SystemCommon
| SystemRealTime SystemRealTime
| SystemExclusive SystemExclusive
deriving (Eq, Show, Ord, Read, Generic)
-- | Type holding channel voice messages. Channel Voice messages transmit
-- real-time performance data over a single channel. Examples include "note-on"
-- messages which contain a MIDI note number that specifies the note's pitch, a
-- velocity value that indicates how forcefully the note was played, and the
-- channel number; "note-off" messages that end a note; program change messages
-- that change a device's patch; and control changes that allow adjustment of an
-- instrument's parameters.
data ChannelVoice
= NoteOff !Channel !Pitch !Velocity
| NoteOn !Channel !Pitch !Velocity
| Aftertouch !Channel !Pitch !Touch
| ControlChange !Channel !Controller !Word8
| PatchChange !Channel !Patch
| ChannelPressure !Channel !Touch
| PitchBend !Channel !Word16
deriving (Eq, Show, Ord, Read, Generic)
-- | A type for channel mode messages. Mode messages determine how an instrument
-- will receive all subsequent voice messages. This includes whether the
-- receiver will play notes monophonically or polyphonically and whether it will
-- respond only to data sent on one specific voice channel or all of them.
data ChannelMode
= AllSoundOff !Channel
| ResetAllControllers !Channel
| LocalControl !Channel !Bool
| AllNotesOff !Channel
| OmniOff !Channel
| OmniOn !Channel
| MonoOn !Channel !Word8
| PolyOn !Channel
deriving (Eq, Show, Ord, Read, Generic)
-- | A type for system common messages. System common messages are intended for
-- all receivers in the system.
data SystemCommon
= MTCQuarter !Word8
| SongPosition !PositionPointer
| SongSelect !Word8
| TuneRequest
| EOX
deriving (Eq, Show, Ord, Read, Generic)
-- | System real time messages. The MIDI System Real Time messages are used to
-- synchronize all of the MIDI clock-based equipment within a system, such as
-- sequencers and drum machines. Most of the System Real Time messages are
-- normally ignored by keyboard instruments and synthesizers. To help ensure
-- accurate timing, System Real Time messages are given priority over other
-- messages, and these single-byte messages may occur anywhere in the data
-- stream (a Real Time message may appear between the status byte and data byte
-- of some other MIDI message).
data SystemRealTime
= TimingClock
| Start
| Continue
| Stop
| ActiveSensing
| SystemReset
deriving (Eq, Show, Ord, Read, Generic)
-- | System exclusive messages. System Exclusive messages may be used to send
-- data such as patch parameters or sample data between MIDI devices.
-- Manufacturers of MIDI equipment may define their own formats for System
-- Exclusive data. Manufacturers are granted unique identification (ID) numbers
-- by the MMA or the JMSC, and the manufacturer ID number is included as part of
-- the System Exclusive message. See 'VendorId'.
--
-- The representation used here is deliberately generic. Special sets of system
-- exclusive messages can be implemented on top of this type.
data SystemExclusive
= Exclusive !VendorId ByteString
deriving (Eq, Show, Ord, Read, Generic)
-- | Data type encapsulating vendor ID numbers as used in 'SystemExclusive'.
-- They have one of two possible formats:
--
-- 1. A one byte ID (represented by 'VendorIdShort')
-- 2. A three byte ID, which must begin with @0x00@. ('VendorIdLong')
data VendorId
= VendorIdShort !Word8
| VendorIdLong !Word8 !Word8
deriving (Eq, Show, Ord, Read, Generic)
to7Bit :: Integral a => a -> Word8
to7Bit = (.&. 0x7F) . fromIntegral
to4Bit :: Integral a => a -> Word8
to4Bit = (.&. 0x0F) . fromIntegral
newtype Channel = Channel { getChannel :: Word8 }
deriving (Eq, Show, Ord, Read)
mkChannel :: Integral a => a -> Channel
mkChannel = Channel . to4Bit
newtype Pitch = Pitch { getPitch :: Word8 }
deriving (Eq, Show, Ord, Read)
mkPitch :: Integral a => a -> Pitch
mkPitch = Pitch . to7Bit
-- | The middle C on a piano as defined by the MIDI specification. This can
-- serve as a reference value for working with pitches.
middleC :: Pitch
middleC = Pitch 60
newtype Velocity = Velocity { getVelocity :: Word8 }
deriving (Eq, Show, Ord, Read)
mkVelocity :: Integral a => a -> Velocity
mkVelocity = Velocity . to7Bit
newtype Touch = Touch { getTouch :: Word8 }
deriving (Eq, Show, Ord, Read)
mkTouch :: Integral a => a -> Touch
mkTouch = Touch . to7Bit
newtype Controller = Controller { getController :: Word8 }
deriving (Eq, Show, Ord, Read)
mkController :: Integral a => a -> Controller
mkController = Controller . to7Bit
newtype Patch = Patch { getPatch :: Word8 }
deriving (Eq, Show, Ord, Read)
mkPatch :: Integral a => a -> Patch
mkPatch = Patch . to7Bit
newtype PositionPointer = PositionPointer { getPositionPointer :: Word16 }
deriving (Eq, Show, Ord, Read)
to14Bit :: Integral a => a -> Word16
to14Bit = (.&. 0x3FFF) . fromIntegral
mkPositionPointer :: Integral a => a -> PositionPointer
mkPositionPointer = PositionPointer . to14Bit
-- | Convert a 'PositionPointer', used to indicate song position, to the MIDI
-- clock. Song Position Pointer is always multiplied by 6 times the MIDI clocks
-- (F8H). Thus the smallest Song Position change is 6 MIDI clocks, or 1/16 note.
toClocks :: PositionPointer -> Int
toClocks = (* 6) . fromIntegral . getPositionPointer
{-# INLINEABLE toClocks #-}