module Termtab.Parser.MIDI (parseMidi) where
import Data.Map.Strict qualified as Map
import Data.Maybe (mapMaybe)
import Data.Text qualified as T
import Codec.Midi qualified as MIDI
import Termtab.Types
parseMidi :: FilePath -> IO (Either ParseError Song)
parseMidi :: FilePath -> IO (Either ParseError Song)
parseMidi FilePath
path = do
result <- FilePath -> IO (Either FilePath Midi)
MIDI.importFile FilePath
path
case result of
Left FilePath
err -> Either ParseError Song -> IO (Either ParseError Song)
forall a. a -> IO a
forall (m :: * -> *) a. Monad m => a -> m a
return (ParseError -> Either ParseError Song
forall a b. a -> Either a b
Left (FilePath -> ParseError
ParseErrorMalformed FilePath
err))
Right Midi
mf -> Either ParseError Song -> IO (Either ParseError Song)
forall a. a -> IO a
forall (m :: * -> *) a. Monad m => a -> m a
return (Song -> Either ParseError Song
forall a b. b -> Either a b
Right (Midi -> Song
convertMidi Midi
mf))
convertMidi :: MIDI.Midi -> Song
convertMidi :: Midi -> Song
convertMidi Midi
mf =
Song
{ songTitle :: Text
songTitle = Text
""
, songArtist :: Text
songArtist = Text
""
, songTempo :: Tempo
songTempo = [Track Int] -> Tempo
forall t. [Track t] -> Tempo
tempoFromTracks (Midi -> [Track Int]
MIDI.tracks Midi
mf)
, songTracks :: [Track]
songTracks = (Int -> Track Int -> Track) -> [Int] -> [Track Int] -> [Track]
forall a b c. (a -> b -> c) -> [a] -> [b] -> [c]
zipWith Int -> Track Int -> Track
forall t. Int -> Track t -> Track
convertTrack [Int
0 ..] (Midi -> [Track Int]
MIDI.tracks Midi
mf)
, songMeasures :: [Measure]
songMeasures = []
}
tempoFromTracks :: [MIDI.Track t] -> Tempo
tempoFromTracks :: forall t. [Track t] -> Tempo
tempoFromTracks [Track t]
ts =
case [Int -> Tempo
Tempo (Int
60000000 Int -> Int -> Int
forall a. Integral a => a -> a -> a
`div` Int
us) | (t
_, MIDI.TempoChange Int
us) <- [Track t] -> Track t
forall (t :: * -> *) a. Foldable t => t [a] -> [a]
concat [Track t]
ts] of
(Tempo
t : [Tempo]
_) -> Tempo
t
[] -> Int -> Tempo
Tempo Int
120
convertTrack :: Int -> MIDI.Track t -> Track
convertTrack :: forall t. Int -> Track t -> Track
convertTrack Int
idx Track t
events =
Track
{ trackName :: Text
trackName = Text
"Track " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> FilePath -> Text
T.pack (Int -> FilePath
forall a. Show a => a -> FilePath
show Int
idx)
, trackInstrument :: Instrument
trackInstrument = MidiProgram -> Instrument
Standard (Int -> MidiProgram
MidiProgram Int
0)
, trackChannel :: MidiChannel
trackChannel = Int -> MidiChannel
MidiChannel Int
0
, trackBeats :: Map MeasureIndex [Beat]
trackBeats = [(MeasureIndex, [Beat])] -> Map MeasureIndex [Beat]
forall k a. Ord k => [(k, a)] -> Map k a
Map.fromList [(Int -> MeasureIndex
MeasureIndex Int
0, Track t -> [Beat]
forall t. Track t -> [Beat]
beatsFromEvents Track t
events)]
}
beatsFromEvents :: MIDI.Track t -> [Beat]
beatsFromEvents :: forall t. Track t -> [Beat]
beatsFromEvents = (Note -> Beat) -> [Note] -> [Beat]
forall a b. (a -> b) -> [a] -> [b]
map Note -> Beat
singleNoteBeat ([Note] -> [Beat]) -> (Track t -> [Note]) -> Track t -> [Beat]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ((t, Message) -> Maybe Note) -> Track t -> [Note]
forall a b. (a -> Maybe b) -> [a] -> [b]
mapMaybe (t, Message) -> Maybe Note
forall t. (t, Message) -> Maybe Note
toNote
toNote :: (t, MIDI.Message) -> Maybe Note
toNote :: forall t. (t, Message) -> Maybe Note
toNote (t
_, MIDI.NoteOn Int
_ Int
k Int
v)
| Int
v Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
> Int
0 =
Note -> Maybe Note
forall a. a -> Maybe a
Just
Note
{ notePitch :: Pitch
notePitch = Int -> Pitch
Pitch Int
k
, noteVelocity :: Velocity
noteVelocity = Int -> Velocity
Velocity Int
v
, noteEffects :: [NoteEffect]
noteEffects = []
, noteString :: Maybe StringIndex
noteString = Maybe StringIndex
forall a. Maybe a
Nothing
, noteFret :: Maybe FretNumber
noteFret = Maybe FretNumber
forall a. Maybe a
Nothing
}
toNote (t, Message)
_ = Maybe Note
forall a. Maybe a
Nothing
singleNoteBeat :: Note -> Beat
singleNoteBeat :: Note -> Beat
singleNoteBeat Note
note =
Beat
{ beatDuration :: Duration
beatDuration = Duration
Quarter
, beatNotes :: [Note]
beatNotes = [Note
note]
, beatIsRest :: Bool
beatIsRest = Bool
False
}