/gerbil-midi

A bunch of MIDI thingies as a Gerbil Scheme package

Primary LanguageSchemeMIT LicenseMIT

Gerbil MIDI

Gerbil MIDI is a simple way of interacting with MIDI events and files. It can read Standard MIDI format files.

Installation

gxpkg install github.com/drewc/gerbil-midi

Usage

We’ll import the package.

(import :drewc/midi :std/iter :gerbil/gambit)

MIDI is all about events, and their sequence delta time. Delta times are stored as ticks, and the header gives us the time division “Ticks Per Quarter Note”.

(def (ticks-per-quarter-note SMF-file)
  (SMF-file-division SMF-file))

Somewhere, before the event we are processing, is a set-tempo event, which gives us the number of microseconds per quarter note. We’ll just use the first we find.

A MTrk chunk (AKA a MIDI track) has MTrk-event’s that themselves contain midi-event’s.

We’ll make a simple predicate to see what is what.

(def (MTrk-event-event? pred)
  (lambda (e) (pred (MTrk-event-event e))))

And use it to find the first set-tempo.

(def (microseconds-per-quarter-note SMF-file)
  (set-tempo-microseconds
   (MTrk-event-event 
    (find (MTrk-event-event? set-tempo?)
         ;; A track chunk has events
         (track-chunk-events
          ;; which we ref as follows
          (SMF-file-track-ref SMF-file 0))))))

Because we’ll be using thread-sleep!, we need it in seconds. We know how much time a quarter note takes, and we know how many ticks there are per quarter note. That means we can combine them to get the number of seconds per tick. Because the delta time is the number of ticks, multiplying the time per tick with the number of ticks gives us the delta time in seconds.

(def (delta-time-in-seconds delta-time microseconds-per-quarter-note ticks-per-quarter-note)
  (let* ((seconds-per-quarter-note (/ microseconds-per-quarter-note 1000000))
         (seconds-per-tick (/ seconds-per-quarter-note ticks-per-quarter-note)))
    (* delta-time seconds-per-tick)))

For testing, we have no way to output MIDI notes (yet!). But, there are MIDI files with lyrics, and we have such a file in the ./test/sultans folder.

It is a Format 0 file, which makes it simple to process in time.

(def test-sequence-SMF-filename
  "~/.gerbil/pkg/github.com/drewc/gerbil-midi/midi/test/sultans/DIRE STRAITS.Sultans of swing K.mid")

Putting it all together, we have a sleep-delta-time!, and the console starts singing.

(def (test-sequence (file test-sequence-SMF-filename))
  (def SMF-file (read-SMF-file file))
  (def mus (microseconds-per-quarter-note SMF-file))
  (def ticks (ticks-per-quarter-note SMF-file))

  (def (sleep-delta-time! event)
    (thread-sleep! (delta-time-in-seconds (MTrk-event-delta-time event) mus ticks))) 

  ;; Now, loop over the events and sleep the required time, only outputting the
  ;; lyrics.
  (displayln "Introduction Instrumental... takes a while")
  (for (e (track-chunk-events (SMF-file-track-ref SMF-file 0)))
    (sleep-delta-time! e)
    (when (lyric? (MTrk-event-event e))
      (display (text-event->string (MTrk-event-event e))))))

Try it out in a terminal!

$ time gxi -e '(import :drewc/midi/test/usage)' -e '(test-sequence)'
Introduction Instrumental... take a while

You get a shiver in the dark
It''s been raining in the park but meantime
[ ...cut for space saving ability... ]
We are the Sultans,
we are the Sultans of Swing
real    3m45.348s
user    0m0.549s
sys     0m0.085s

Usage Files

<<import>>

(export test-sequence)

<<ticks-per-qn>>

<<pred>>

<<mu-per-qn>>

<<delta-time-in-seconds>>

<<test-sequence>>