Hmm (Haskell MIDI Manipulator) is a Haskell library that allows you to
- Read MIDI files
- Functionally manipulate MIDI in memory
- Write MIDI files
-- examples/info.hs
import Hmm
import System.Environment
main = do
args <- getArgs
let path = head args
midi <- readMidi path
print $ header midi
print $ midiNames midi
Example run:
./info never-gonna-give-you-up.mid
Output:
MidiHeader {format = MultiTrack, ntracks = 17, division = 384}
["E.PIANO 2","SYN BASS 2","CLEAN GTR","MELODY","PICCOLO","SYNTH DRUM","SYNTH DRUM","SAW WAVE","SOPRAN SAX","DRUMS","STRINGS","TRUMPET","BRASS 1","WHISTLE","MUTED GTR","GS/RESET"]
-- examples/transpose.hs
import Hmm
import System.Environment
main = do
args <- getArgs
let (semitonesStr : input : output : _) = args
let semitones = read semitonesStr :: Int
midi <- readMidi input
let midi' = Midi {header = header midi,
tracks = map (transposeTrack semitones) (tracks midi)}
writeMidi output midi'
Example run:
./transpose 7 never-gonna-give-you-up.mid never-gonna-give-you-up-transposed.mid
Now, never-gonna-give-you-up-transposed.mid
contains the original track transposed up by 7 semitones (i.e. the perfect fifth).
# Clone the repo
git clone https://github.com/patztablook22/hmm
# Use cabal to build and install it
cd hmm
cabal build
cabal install --lib
readMidi :: FilePath -> IO Midi
Reads given MIDI file.
main = do
midi <- readMidi "myFile.mid"
writeMidi :: FilePath -> Midi -> IO()
Writes given MIDI into file.
main = do
let midi = ... :: Midi
writeMidi "myFile.mid" midi
semitones :: Note -> Note -> Int
Returns the interval between two notes in semitones.
let fifth = semitones (Note C 2) (Note G 2)
-- returns 7
let back = semitones (Note G 2) (Note C 2)
-- returns -7
transposeNote :: Int -> Note -> Note
Transposes a note by the given interval in semitones.
let fifthUp = transpose 7 (Note C 2)
-- returns (Note G 2)
let fifthDn = transpose (-7) (Note C 2)
-- returns (Note F 1)
transposeSignature :: Int -> Int -> Int
Transposes by a given interval in semitones (1st argument) the given key signature (2nd argument).
let twoFlats = -2
let upMaj3rd = 4
let newSignature = transposeSignature upMaj3rd twoFlats
-- returns 2, i.e. 2 sharps
transposeTrack :: Int -> MidiTrack -> MidiTrack
Transposes by a given interval the entire MIDI track. This includes all note-related events and all key signature events.
let track = ... :: MidiTrack
let octave = 12
let track' = transposeTrack octave track
isTextEvent :: MidiEvent -> Bool
Returns true iff the given MidiEvent contains Text payload.
let a = isTextEvent $ MidiEvent 0 (Text "Never gonna give you up, never gonna let you down.")
-- returns True
let b = isTextEvent $ MidiEvent 0 (NoteOn 1 (Note E 3), 127)
-- returns False
isNameEvent :: MidiEvent -> Bool
Returns true iff the given MidiEvent contains Name payload.
See isTextEvent
.
isSysExEvent :: MidiEvent -> Bool
Returns true iff the given MidiEvent contains SysEx payload.
See isTextEvent
.
isNoteOnEvent :: MidiEvent -> Bool
Returns true iff the given MidiEvent contains NoteOn payload.
See isTextEvent
.
isNoteOffEvent :: MidiEvent -> Bool
Returns true iff the given MidiEvent contains NoteOff payload.
See isTextEvent
.
isNoteEvent :: MidiEvent -> Bool
Returns true iff the given MidiEvent contains NoteOn or NoteOff payload.
See isTextEvent
.
isCopyrightEvent :: MidiEvent -> Bool
Returns true iff the given MidiEvent contains Copyright payload.
See isTextEvent
.
isInstrumentEvent :: MidiEvent -> Bool
Returns true iff the given MidiEvent contains Instrument payload.
See isTextEvent
.
isUnknownMetaEvent :: MidiEvent -> Bool
Returns true iff the given MidiEvent contains UnknownMeta payload.
See isTextEvent
.
merge :: MidiTrack -> MidiTrack -> MidiTrack
Merges two MIDI tracks into one.
let pianoLeftHand = ... :: MidiTrack
let pianoRightHand = ... :: MidiTrack
let pianoBothHands = merge pianoLeftHand pianoRightHand
midiTrackName :: MidiTrack -> Maybe String
Extracts the track's name, if provided by a Name event.
let track = [(MidiEvent 0 (Name "Rick Astley - Never gonna give you up")),
...]
let name = midiTrackName track
-- returns Just "Rick Astley - Never gonna give you up"
midiNames :: Midi -> [String]
Returns the names of all named MidiTracks. See midiTrackName
.
midiTrackInstruments :: MidiTrack -> [String]
Returns the names of all instruments in a single MidiTrack. See midiTrackName
.
midiInstruments :: Midi -> String
Returns the names of all instruments in the entire MIDI. See midiTrackInstruments
.
midiCopyright :: Midi -> Maybe String
Returns the MIDI's copyright if present. Read MIDI specification for copyright placement.
rootedChord :: [Note] -> Maybe Chord
Heuristically interprets given (unordered) list of notes as a rooted chord.
let notes = [Note Fsharp 3,
Note D 3,
Note E 2,
Note B 1,
Note C 1]
let notes' = [Note Csharp 4,
Note G 3,
Note Fsharp 3,
Note E 2]
let notes'' = [Note G 3]
let chord = rootedChord notes
-- returns Just (Chord C Maj [Natural 9, Sharp 11])
let chord' = rootedChord notes'
-- returns Just (Chord E Min [Natural 6, Natural 2])
let chord'' = rootedChord notes''
-- returns Nothing
annotateChords :: ([Note] -> Maybe Chord) -> MidiTrack -> MidiTrack
Adds a chord annotation (Text event containing string chord representation) into the given track whenever the set of currently active notes changes and the annotator function returns (Just _).
let track = ... :: MidiTrack
let annotator = rootedChord
let track' = annotateChords annotator track
PitchClass
Represents the cross-octave pitch class, e.g. C
, Csharp
, D
. This type is concerned purely with pitch, not with theoretical interpretation. Therefore, since MIDI uses the equal temperament, enharmonic equivalence applies, i.e. instead of Dflat
(which is not provided), use Csharp
.
Note
Represents a single note. Consists of its PitchClass
and its Octave
(represented as an Int
).
let note = Note C 2
let note' = Note Asharp 5
Midi
Representation of an entire MIDI object. Consists of
header :: MidiHeader
tracks :: [MidiTrack]
MidiFormat
See MIDI specification for MIDI formats.
MidiHeader
See MIDI specification for the MIDI header. Consists of
format :: MidiFormat
ntracks :: Int
division :: Int
ChurchMode
Represents the diatonic church modes, most importantly Ionian
("Major") and Aeolian
("Minor").
MidiTrack
Represents a single midi track - a list of ordered MidiEvent
s.
MidiEvent
An element of MidiTrack
- consists of its timestamp and Event
payloads.
Event
The payload of a MidiEvent
. Can be of many types, such as NoteOn
, Text
, Copyright
, PitchWheel
.
Chord
A jazz theory influenced representation of a chord. Consists of
- its
PitchClass
- its
ChordType
, e.g.Maj
,Min
,Sus4
- its relevant (the highest natural and all altered)
[Extension]
, e.g.[Flat 7]
see e.g. rootedChord
.