module Termtab.Export.MIDI (exportMidi) where
import Codec.Midi qualified as MIDI
import Data.Map.Strict qualified as Map
import Termtab.Types
exportMidi :: FilePath -> Song -> IO ()
exportMidi :: FilePath -> Song -> IO ()
exportMidi FilePath
path Song
song =
FilePath -> Midi -> IO ()
MIDI.exportFile FilePath
path (Song -> Midi
songToMidi Song
song)
songToMidi :: Song -> MIDI.Midi
songToMidi :: Song -> Midi
songToMidi Song
song =
MIDI.Midi
{ fileType :: FileType
MIDI.fileType = FileType
MIDI.MultiTrack
, timeDiv :: TimeDiv
MIDI.timeDiv = Int -> TimeDiv
MIDI.TicksPerBeat Int
ticksPerQuarter
, tracks :: [Track Int]
MIDI.tracks = Song -> Track Int
tempoTrack Song
song Track Int -> [Track Int] -> [Track Int]
forall a. a -> [a] -> [a]
: (Track -> Track Int) -> [Track] -> [Track Int]
forall a b. (a -> b) -> [a] -> [b]
map (Song -> Track -> Track Int
trackToMidi Song
song) (Song -> [Track]
songTracks Song
song)
}
ticksPerQuarter :: Int
ticksPerQuarter :: Int
ticksPerQuarter = Int
480
tempoTrack :: Song -> MIDI.Track MIDI.Ticks
tempoTrack :: Song -> Track Int
tempoTrack Song
song =
let Tempo Int
bpm = Song -> Tempo
songTempo Song
song
microsecondsPerBeat :: Int
microsecondsPerBeat = Int
60000000 Int -> Int -> Int
forall a. Integral a => a -> a -> a
`div` Int
bpm
in [(Int
0, Int -> Message
MIDI.TempoChange Int
microsecondsPerBeat), (Int
0, Message
MIDI.TrackEnd)]
trackToMidi :: Song -> Track -> MIDI.Track MIDI.Ticks
trackToMidi :: Song -> Track -> Track Int
trackToMidi Song
song Track
track =
let measures :: [Measure]
measures = Song -> [Measure]
songMeasures Song
song
events :: Track Int
events = ((Int, Measure) -> Track Int) -> [(Int, Measure)] -> Track Int
forall (t :: * -> *) a b. Foldable t => (a -> [b]) -> t a -> [b]
concatMap (Track -> (Int, Measure) -> Track Int
measureToEvents Track
track) ([Int] -> [Measure] -> [(Int, Measure)]
forall a b. [a] -> [b] -> [(a, b)]
zip [Int
0 ..] [Measure]
measures)
in Track Int
events Track Int -> Track Int -> Track Int
forall a. [a] -> [a] -> [a]
++ [(Int
0, Message
MIDI.TrackEnd)]
measureToEvents :: Track -> (Int, Measure) -> [(MIDI.Ticks, MIDI.Message)]
measureToEvents :: Track -> (Int, Measure) -> Track Int
measureToEvents Track
track (Int
mi, Measure
_measure) =
let beats :: [Beat]
beats = [Beat] -> ([Beat] -> [Beat]) -> Maybe [Beat] -> [Beat]
forall b a. b -> (a -> b) -> Maybe a -> b
maybe [] [Beat] -> [Beat]
forall a. a -> a
id (MeasureIndex -> Map MeasureIndex [Beat] -> Maybe [Beat]
forall k a. Ord k => k -> Map k a -> Maybe a
Map.lookup (Int -> MeasureIndex
MeasureIndex Int
mi) (Track -> Map MeasureIndex [Beat]
trackBeats Track
track))
MidiChannel Int
ch = Track -> MidiChannel
trackChannel Track
track
in (Beat -> Track Int) -> [Beat] -> Track Int
forall (t :: * -> *) a b. Foldable t => (a -> [b]) -> t a -> [b]
concatMap (Int -> Beat -> Track Int
beatToEvents Int
ch) [Beat]
beats
beatToEvents :: Int -> Beat -> [(MIDI.Ticks, MIDI.Message)]
beatToEvents :: Int -> Beat -> Track Int
beatToEvents Int
ch Beat
beat
| Beat -> Bool
beatIsRest Beat
beat = [(Duration -> Int
durationToTicks (Beat -> Duration
beatDuration Beat
beat), Int -> Int -> Int -> Message
MIDI.NoteOff Int
ch Int
0 Int
0)]
| [Note] -> Bool
forall a. [a] -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
null (Beat -> [Note]
beatNotes Beat
beat) = [(Duration -> Int
durationToTicks (Beat -> Duration
beatDuration Beat
beat), Int -> Int -> Int -> Message
MIDI.NoteOff Int
ch Int
0 Int
0)]
| Bool
otherwise =
let dur :: Int
dur = Duration -> Int
durationToTicks (Beat -> Duration
beatDuration Beat
beat)
noteOns :: Track Int
noteOns =
[ (Int
0, Int -> Int -> Int -> Message
MIDI.NoteOn Int
ch (Int
p Int -> Int -> Int
forall a. Integral a => a -> a -> a
`mod` Int
128) (Int
v Int -> Int -> Int
forall a. Integral a => a -> a -> a
`mod` Int
128))
| Note
n <- Beat -> [Note]
beatNotes Beat
beat
, let Pitch Int
p = Note -> Pitch
notePitch Note
n
, let Velocity Int
v = Note -> Velocity
noteVelocity Note
n
]
noteOffs :: Track Int
noteOffs =
[ (Int
0, Int -> Int -> Int -> Message
MIDI.NoteOff Int
ch (Int
p Int -> Int -> Int
forall a. Integral a => a -> a -> a
`mod` Int
128) Int
0)
| Note
n <- Beat -> [Note]
beatNotes Beat
beat
, let Pitch Int
p = Note -> Pitch
notePitch Note
n
]
in case (Track Int
noteOns, Track Int
noteOffs) of
([], Track Int
_) -> [(Int
dur, Int -> Int -> Int -> Message
MIDI.NoteOff Int
ch Int
0 Int
0)]
(Track Int
_, []) -> Track Int
noteOns Track Int -> Track Int -> Track Int
forall a. [a] -> [a] -> [a]
++ [(Int
dur, Int -> Int -> Int -> Message
MIDI.NoteOff Int
ch Int
0 Int
0)]
(Track Int
_, (Int
_, Message
firstOff) : Track Int
restOffs) ->
Track Int
noteOns Track Int -> Track Int -> Track Int
forall a. [a] -> [a] -> [a]
++ [(Int
dur, Message
firstOff)] Track Int -> Track Int -> Track Int
forall a. [a] -> [a] -> [a]
++ Track Int
restOffs
durationToTicks :: Duration -> Int
durationToTicks :: Duration -> Int
durationToTicks Duration
Whole = Int
ticksPerQuarter Int -> Int -> Int
forall a. Num a => a -> a -> a
* Int
4
durationToTicks Duration
Half = Int
ticksPerQuarter Int -> Int -> Int
forall a. Num a => a -> a -> a
* Int
2
durationToTicks Duration
Quarter = Int
ticksPerQuarter
durationToTicks Duration
Eighth = Int
ticksPerQuarter Int -> Int -> Int
forall a. Integral a => a -> a -> a
`div` Int
2
durationToTicks Duration
Sixteenth = Int
ticksPerQuarter Int -> Int -> Int
forall a. Integral a => a -> a -> a
`div` Int
4
durationToTicks Duration
Thirty2nd = Int
ticksPerQuarter Int -> Int -> Int
forall a. Integral a => a -> a -> a
`div` Int
8
durationToTicks (Dotted Duration
d) = Duration -> Int
durationToTicks Duration
d Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Duration -> Int
durationToTicks Duration
d Int -> Int -> Int
forall a. Integral a => a -> a -> a
`div` Int
2
durationToTicks (Triplet Duration
d) = Duration -> Int
durationToTicks Duration
d Int -> Int -> Int
forall a. Num a => a -> a -> a
* Int
2 Int -> Int -> Int
forall a. Integral a => a -> a -> a
`div` Int
3