Skip to content

Latest commit

 

History

History
367 lines (247 loc) · 7.53 KB

README.md

File metadata and controls

367 lines (247 loc) · 7.53 KB

About

Hmm (Haskell MIDI Manipulator) is a Haskell library that allows you to

  • Read MIDI files
  • Functionally manipulate MIDI in memory
  • Write MIDI files

Examples

Dump MIDI info

-- 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"]

Transpose MIDI

-- 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).

Installation

# Clone the repo
git clone https://github.com/patztablook22/hmm

# Use cabal to build and install it
cd hmm
cabal build
cabal install --lib

Functions

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

Data

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 MidiEvents.


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.