module Sound.MIDI.Message.Channel.Voice (
T(..), get, putWithStatus,
ControllerValue, PitchBendRange, Pressure,
isNote, isNoteOn, isNoteOff, zeroKey,
explicitNoteOff, implicitNoteOff,
realFromControllerValue,
bankSelect, modulation, breathControl, footControl, portamentoTime,
dataEntry, mainVolume, balance, panorama, expression,
generalPurpose1, generalPurpose2, generalPurpose3, generalPurpose4,
vectorX, vectorY,
bankSelectMSB, modulationMSB, breathControlMSB, footControlMSB,
portamentoTimeMSB, dataEntryMSB, mainVolumeMSB, balanceMSB,
panoramaMSB, expressionMSB, generalPurpose1MSB, generalPurpose2MSB,
generalPurpose3MSB, generalPurpose4MSB, bankSelectLSB, modulationLSB,
breathControlLSB, footControlLSB, portamentoTimeLSB, dataEntryLSB,
mainVolumeLSB, balanceLSB, panoramaLSB, expressionLSB,
generalPurpose1LSB, generalPurpose2LSB, generalPurpose3LSB, generalPurpose4LSB,
sustain, porta, sustenuto, softPedal, hold2,
generalPurpose5, generalPurpose6, generalPurpose7, generalPurpose8,
extDepth, tremoloDepth, chorusDepth, celesteDepth, phaserDepth,
dataIncrement, dataDecrement,
nonRegisteredParameterLSB, nonRegisteredParameterMSB,
registeredParameterLSB, registeredParameterMSB,
Pitch, fromPitch, toPitch,
Velocity, fromVelocity, toVelocity,
Program, fromProgram, toProgram,
CtrlP.Controller, CtrlP.fromController, CtrlP.toController,
increasePitch, subtractPitch, frequencyFromPitch,
maximumVelocity, normalVelocity, realFromVelocity,
) where
import qualified Sound.MIDI.ControllerPrivate as CtrlP
import qualified Sound.MIDI.Controller as Ctrl
import Sound.MIDI.Parser.Primitive
import qualified Sound.MIDI.Parser.Class as Parser
import Control.Monad (liftM, liftM2, )
import qualified Sound.MIDI.Writer.Status as StatusWriter
import qualified Sound.MIDI.Writer.Basic as Writer
import qualified Sound.MIDI.Bit as Bit
import Sound.MIDI.Monoid ((+#+))
import Data.Ix (Ix)
import Sound.MIDI.Utility (checkRange,
quantityRandomR, boundedQuantityRandom, chooseQuantity,
enumRandomR, boundedEnumRandom, chooseEnum, )
import Test.QuickCheck (Arbitrary(arbitrary), )
import qualified Test.QuickCheck as QC
import System.Random (Random(random, randomR), )
data T =
NoteOff Pitch Velocity
| NoteOn Pitch Velocity
| PolyAftertouch Pitch Pressure
| ProgramChange Program
| Control Ctrl.T ControllerValue
| PitchBend PitchBendRange
| MonoAftertouch Pressure
deriving (Show, Eq, Ord)
instance Arbitrary T where
arbitrary =
QC.frequency $
(10, liftM2 NoteOff arbitrary arbitrary) :
(10, liftM2 NoteOn arbitrary arbitrary) :
( 1, liftM2 PolyAftertouch arbitrary (QC.choose (0,127))) :
( 1, liftM ProgramChange arbitrary) :
( 1, liftM2 Control arbitrary (QC.choose (0,127))) :
( 1, liftM PitchBend (QC.choose (0,12))) :
( 1, liftM MonoAftertouch (QC.choose (0,127))) :
[]
instance Random Pitch where
random = boundedEnumRandom
randomR = enumRandomR
instance Arbitrary Pitch where
arbitrary = chooseEnum
instance Random Velocity where
random = boundedQuantityRandom fromVelocity toVelocity
randomR = quantityRandomR fromVelocity toVelocity
instance Arbitrary Velocity where
arbitrary = chooseQuantity fromVelocity toVelocity
instance Random Program where
random = boundedEnumRandom
randomR = enumRandomR
instance Arbitrary Program where
arbitrary = chooseEnum
isNote :: T -> Bool
isNote (NoteOn _ _) = True
isNote (NoteOff _ _) = True
isNote _ = False
isNoteOn :: T -> Bool
isNoteOn (NoteOn _ v) = v > toVelocity 0
isNoteOn _ = False
isNoteOff :: T -> Bool
isNoteOff (NoteOn _ v) = v == toVelocity 0
isNoteOff (NoteOff _ _) = True
isNoteOff _ = False
explicitNoteOff :: T -> T
explicitNoteOff msg =
case msg of
NoteOn p v ->
if v == toVelocity 0
then NoteOff p $ toVelocity 64
else msg
_ -> msg
implicitNoteOff :: T -> T
implicitNoteOff msg =
case msg of
NoteOff p v ->
if v == toVelocity 64
then NoteOn p $ toVelocity 0
else msg
_ -> msg
type PitchBendRange = Int
type Pressure = Int
type ControllerValue = Ctrl.Value
newtype Pitch = Pitch {fromPitch :: Int} deriving (Show, Eq, Ord, Ix)
newtype Velocity = Velocity {fromVelocity :: Int} deriving (Show, Eq, Ord)
newtype Program = Program {fromProgram :: Int} deriving (Show, Eq, Ord, Ix)
toPitch :: Int -> Pitch
toPitch = checkRange "Pitch" Pitch
toVelocity :: Int -> Velocity
toVelocity = checkRange "Velocity" Velocity
toProgram :: Int -> Program
toProgram = checkRange "Program" Program
instance Enum Pitch where
toEnum = toPitch
fromEnum = fromPitch
instance Enum Program where
toEnum = toProgram
fromEnum = fromProgram
increasePitch :: Int -> Pitch -> Pitch
increasePitch d = toPitch . (d+) . fromPitch
subtractPitch :: Pitch -> Pitch -> Int
subtractPitch (Pitch p0) (Pitch p1) = p1p0
frequencyFromPitch :: (Floating a) => Pitch -> a
frequencyFromPitch (Pitch p) =
440 * 2 ** (fromIntegral (p + 3 6*12) / 12)
instance Bounded Pitch where
minBound = Pitch 0
maxBound = Pitch 127
instance Bounded Velocity where
minBound = Velocity 0
maxBound = Velocity 127
instance Bounded Program where
minBound = Program 0
maxBound = Program 127
zeroKey :: Pitch
zeroKey = toPitch 48
normalVelocity, maximumVelocity :: Velocity
normalVelocity = Velocity 64
maximumVelocity = maxBound
realFromVelocity :: (Fractional b) => Velocity -> b
realFromVelocity (Velocity x) =
fromIntegral (x fromVelocity normalVelocity) /
fromIntegral (fromVelocity maximumVelocity fromVelocity normalVelocity)
maximumControllerValue :: Num a => a
maximumControllerValue = 127
realFromControllerValue :: (Integral a, Fractional b) => a -> b
realFromControllerValue x = fromIntegral x / maximumControllerValue
bankSelect, modulation, breathControl, footControl, portamentoTime,
dataEntry, mainVolume, balance, panorama, expression,
generalPurpose1, generalPurpose2, generalPurpose3, generalPurpose4,
vectorX, vectorY :: Ctrl.T
bankSelect = bankSelectMSB
modulation = modulationMSB
breathControl = breathControlMSB
footControl = footControlMSB
portamentoTime = portamentoTimeMSB
dataEntry = dataEntryMSB
mainVolume = mainVolumeMSB
balance = balanceMSB
panorama = panoramaMSB
expression = expressionMSB
generalPurpose1 = generalPurpose1MSB
generalPurpose2 = generalPurpose2MSB
generalPurpose3 = generalPurpose3MSB
generalPurpose4 = generalPurpose4MSB
vectorX = generalPurpose1
vectorY = generalPurpose2
bankSelectMSB, modulationMSB, breathControlMSB, footControlMSB,
portamentoTimeMSB, dataEntryMSB,
mainVolumeMSB, balanceMSB, panoramaMSB, expressionMSB,
generalPurpose1MSB, generalPurpose2MSB,
generalPurpose3MSB, generalPurpose4MSB :: Ctrl.T
bankSelectLSB, modulationLSB, breathControlLSB, footControlLSB,
portamentoTimeLSB, dataEntryLSB,
mainVolumeLSB, balanceLSB, panoramaLSB, expressionLSB,
generalPurpose1LSB, generalPurpose2LSB,
generalPurpose3LSB, generalPurpose4LSB :: Ctrl.T
sustain, porta, sustenuto, softPedal, hold2,
generalPurpose5, generalPurpose6, generalPurpose7, generalPurpose8,
extDepth, tremoloDepth, chorusDepth, celesteDepth, phaserDepth :: Ctrl.T
dataIncrement, dataDecrement,
nonRegisteredParameterLSB, nonRegisteredParameterMSB,
registeredParameterLSB, registeredParameterMSB :: Ctrl.T
bankSelectMSB = toEnum 0x00
modulationMSB = toEnum 0x01
breathControlMSB = toEnum 0x02
footControlMSB = toEnum 0x04
portamentoTimeMSB = toEnum 0x05
dataEntryMSB = toEnum 0x06
mainVolumeMSB = toEnum 0x07
balanceMSB = toEnum 0x08
panoramaMSB = toEnum 0x0A
expressionMSB = toEnum 0x0B
generalPurpose1MSB = toEnum 0x10
generalPurpose2MSB = toEnum 0x11
generalPurpose3MSB = toEnum 0x12
generalPurpose4MSB = toEnum 0x13
bankSelectLSB = toEnum 0x20
modulationLSB = toEnum 0x21
breathControlLSB = toEnum 0x22
footControlLSB = toEnum 0x24
portamentoTimeLSB = toEnum 0x25
dataEntryLSB = toEnum 0x26
mainVolumeLSB = toEnum 0x27
balanceLSB = toEnum 0x28
panoramaLSB = toEnum 0x2A
expressionLSB = toEnum 0x2B
generalPurpose1LSB = toEnum 0x30
generalPurpose2LSB = toEnum 0x31
generalPurpose3LSB = toEnum 0x32
generalPurpose4LSB = toEnum 0x33
sustain = toEnum 0x40
porta = toEnum 0x41
sustenuto = toEnum 0x42
softPedal = toEnum 0x43
hold2 = toEnum 0x45
generalPurpose5 = toEnum 0x50
generalPurpose6 = toEnum 0x51
generalPurpose7 = toEnum 0x52
generalPurpose8 = toEnum 0x53
extDepth = toEnum 0x5B
tremoloDepth = toEnum 0x5C
chorusDepth = toEnum 0x5D
celesteDepth = toEnum 0x5E
phaserDepth = toEnum 0x5F
dataIncrement = toEnum 0x60
dataDecrement = toEnum 0x61
nonRegisteredParameterLSB = toEnum 0x62
nonRegisteredParameterMSB = toEnum 0x63
registeredParameterLSB = toEnum 0x64
registeredParameterMSB = toEnum 0x65
get :: Parser.C parser => Int -> Int -> Parser.Fragile parser T
get code firstData =
let pitch = toPitch firstData
getVel = liftM toVelocity get1
in case code of
08 -> liftM (NoteOff pitch) getVel
09 -> liftM (NoteOn pitch) getVel
10 -> liftM (PolyAftertouch pitch) get1
11 -> liftM (Control (toEnum firstData)) get1
12 -> return (ProgramChange (toProgram firstData))
13 -> return (MonoAftertouch firstData)
14 -> liftM (\msb -> PitchBend (firstData+128*msb)) get1
_ -> Parser.giveUp ("invalid Voice message code:" ++ show code)
putWithStatus :: Writer.C writer =>
(Int -> StatusWriter.T writer) -> T -> StatusWriter.T writer
putWithStatus putChan e =
let putC code bytes =
putChan code +#+
StatusWriter.fromWriter (Writer.putByteList (map fromIntegral bytes))
in case e of
NoteOff p v -> putC 8 [fromPitch p, fromVelocity v]
NoteOn p v -> putC 9 [fromPitch p, fromVelocity v]
PolyAftertouch p pr -> putC 10 [fromPitch p, pr]
Control cn cv -> putC 11 [fromEnum cn, cv]
ProgramChange pn -> putC 12 [fromProgram pn]
MonoAftertouch pr -> putC 13 [pr]
PitchBend pb ->
let (hi,lo) = Bit.splitAt 7 pb in putC 14 [lo,hi]