audio/midi
Reading, writing and playing standard MIDI files.
import audio.midi
A MIDI file holds notes, not sound: which note, when, how hard and on what instrument. That makes it tiny, and it makes it editable in a way a recording is not.
This package reads and writes standard MIDI files, and Render turns one into sound through audio/synth, so a file of notes becomes a clip you can save as WAV or FLAC.
Things to know
- Everything is in ticks, and
divisionsays how many ticks a quarter note is.TicksToSecondsneeds the tempo as well, which is why it takes the whole song. - A note is two events, on and off.
AddNotewrites both, andNotespairs them back up and gives you lengths in seconds. - A note on with velocity 0 means a note off, and the reader turns it into one so you never have to check.
- Release a song with
FreeSong: it owns a vector per track plus the vector of tracks. Renderuses a single plain envelope for every note. For anything more, takeNotesand render it yourself.
Types
Event
struct Event| Name | Type | Description |
|---|---|---|
tick | int = 0 | When it happens, in ticks from the start. |
status | int = 0 | Which kind of event it is. |
channel | int = 0 | Which of the sixteen channels it is on. |
data1 | int = 0 | The first byte, a note number or a controller. |
data2 | int = 0 | The second byte, a velocity or a value. |
meta | int = 0 | Which meta event it is, when the status says meta. |
value | i64 = 0 | The number a meta event carries, such as a tempo. |
import audio.midi
import compress
func main:
mut f := midi.NewSong(480)
println f.division, f.format, f.tempo as int
println midi.DefaultDivision
mut t := midi.NewTrack()
midi.AddTempo(t, 0, 120.0)
midi.AddProgram(t, 0, 0, 0)
midi.AddControl(t, 0, 7, 100, 0)
mut s := 0
until s < 4:
midi.AddNote(t, s * 480, 240, 60 + s * 2, 100, 0)
s += 1
end
println t.events.len()
midi.AddTrack(f, t)
println f.tracks.len(), midi.TotalTicks(f)
one := t.events.get(1)
println one.tick, one.status == midi.NoteOn, one.data1, one.data2, one.channel
println midi.NoteOn, midi.NoteOff, midi.ControlChange, midi.ProgramChange, midi.MetaEvent
enc := midi.EncodeMIDI(f)
println compress.BufLen(enc) > 22, midi.IsMIDI(enc.data, enc.len)
mut empty := midi.NewSong(480)
println compress.BufLen(midi.EncodeMIDI(empty))
midi.FreeSong(empty)
compress.BufFree(enc)
midi.FreeSong(f)
endTrack
struct Track| Name | Type | Description |
|---|---|---|
events | Vector | The events. |
The example for this item is shown with the ones above: midi-Write.salam
Song
struct Song| Name | Type | Description |
|---|---|---|
format | int = 0 | Which SMF format it is, 0 for one track and 1 for several. |
division | int = 0 | Ticks per quarter note, which is what every tick is measured in. |
tempo | i64 = 500000 | Microseconds per quarter note. 500000 is 120 beats per minute. |
tracks | Vector | The tracks themselves. |
The example for this item is shown with the ones above: midi-Write.salam
Constants and variables
NoteOff
const NoteOff: i32 = 128The example for this item is shown with the ones above: midi-Write.salam
NoteOn
const NoteOn: i32 = 144The example for this item is shown with the ones above: midi-Write.salam
ControlChange
const ControlChange: i32 = 176The example for this item is shown with the ones above: midi-Write.salam
ProgramChange
const ProgramChange: i32 = 192The example for this item is shown with the ones above: midi-Write.salam
Meta event numbers Meta…
| Name | Value |
|---|---|
MetaEvent | 255 |
MetaTempo | 81 |
MetaTrackName | 3 |
MetaEndOfTrack | 47 |
MetaTimeSignature | 88 |
The example for this item is shown with the ones above: midi-Write.salam
DefaultDivision
const DefaultDivision: i32 = 480The example for this item is shown with the ones above: midi-Write.salam
Functions
IsMIDI
func IsMIDI(p: void*, n: int): boolimport audio
import audio.midi
import compress
import filepath
import math
import mem
import os
func main:
mut f := midi.NewSong(480)
mut t := midi.NewTrack()
midi.AddTempo(t, 0, 120.0)
mut s := 0
until s < 4:
midi.AddNote(t, s * 480, 240, 60 + s * 2, 100, 0)
s += 1
end
midi.AddTrack(f, t)
enc := midi.EncodeMIDI(f)
println midi.IsMIDI(enc.data, enc.len), midi.IsMIDI(enc.data, 8)
println audio.FormatName(audio.DetectFormat(enc.data, enc.len))
mut ok := false
mut back := midi.DecodeMIDI(enc.data, enc.len, ok)
println ok, back.division, back.tracks.len()
println math.Abs(midi.Bpm(back) - 120.0) < 0.01
println midi.TotalTicks(back)
println midi.TicksToSeconds(back, 480), midi.SecondsToTicks(back, 0.5)
mut notes := midi.Notes(back)
println notes.len(), notes.get(0).note, notes.get(3).note
println notes.get(1).start, notes.get(0).length
notes.free()
rendered := midi.Render(back, 22050, 1)
println rendered.frames > 0, audio.Peak(rendered) > 0.05
println midi.ProgramName(0), midi.ProgramName(200) == ""
path := filepath.Join(os.TempDir(), "salam_midi_example.mid")
defer mem.Free(path as void*)
println midi.SaveMIDI(f, path)
mut dok := false
mut disk := midi.OpenMIDI(path, dok)
println dok, disk.tracks.len()
mut gone := false
missing := filepath.Join(os.TempDir(), "salam_midi_none.mid")
defer mem.Free(missing as void*)
midi.OpenMIDI(missing, gone)
println gone
mut bad := false
midi.DecodeMIDI(enc.data, 8, bad)
println bad
compress.BufFree(enc)
mut r := rendered
audio.Free(r)
midi.FreeSong(f)
midi.FreeSong(back)
midi.FreeSong(disk)
endNewSong
func NewSong(division: int): SongThe example for this item is shown with the ones above: midi-Write.salam
NewTrack
func NewTrack(): TrackThe example for this item is shown with the ones above: midi-Write.salam
AddTrack
func AddTrack(f &: Song, t: Track)The example for this item is shown with the ones above: midi-Write.salam
AddNote
func AddNote(t &: Track, tick: int, length: int, note: int, velocity: int, channel: int)The example for this item is shown with the ones above: midi-Write.salam
AddTempo
func AddTempo(t &: Track, tick: int, bpm: f64)The example for this item is shown with the ones above: midi-Write.salam
AddProgram
func AddProgram(t &: Track, tick: int, program: int, channel: int)The example for this item is shown with the ones above: midi-Write.salam
AddControl
func AddControl(t &: Track, tick: int, controller: int, value: int, channel: int)The example for this item is shown with the ones above: midi-Write.salam
FreeSong
func FreeSong(f &: Song)The example for this item is shown with the ones above: midi-Write.salam
TicksToSeconds
func TicksToSeconds(f: Song, ticks: int): f64The example for this item is shown with the ones above: midi-Read.salam
SecondsToTicks
func SecondsToTicks(f: Song, seconds: f64): intThe example for this item is shown with the ones above: midi-Read.salam
Bpm
func Bpm(f: Song): f64The example for this item is shown with the ones above: midi-Read.salam
TotalTicks
func TotalTicks(f: Song): intThe example for this item is shown with the ones above: midi-Read.salam
DecodeMIDI
func DecodeMIDI(p: void*, n: int, ok &: bool): SongThe example for this item is shown with the ones above: midi-Read.salam
EncodeMIDI
func EncodeMIDI(f: Song): compress.ByteBufThe example for this item is shown with the ones above: midi-Write.salam
OpenMIDI
func OpenMIDI(path: str, ok &: bool): SongThe example for this item is shown with the ones above: midi-Read.salam
SaveMIDI
func SaveMIDI(f: Song, path: str): boolThe example for this item is shown with the ones above: midi-Write.salam
Notes
func Notes(f: Song): Vector<synth.Note>The example for this item is shown with the ones above: midi-Read.salam
Render
func Render(f: Song, rate: int, channels: int): audio.ClipThe example for this item is shown with the ones above: midi-Read.salam
ProgramName
func ProgramName(program: int): strThe example for this item is shown with the ones above: midi-Read.salam