MIDI.lua

MIDI.lua - Reading, writing and manipulating MIDI data

local MIDI = require 'MIDI'

local my_score = { 96, -- ticks per beat { -- first track {'patch_change', 0, 1, 8}, {'note', 5, 96, 1, 25, 98}, {'note', 101, 96, 1, 29, 98}, }, -- end of first track }

-- Going through a score within a Lua program... channels = {[2]=true, [3]=true, [5]=true, [8]=true, [13]=true} for itrack = 2,#my_score do -- skip 1st element, which is ticks for k,event in ipairs(my_score[itrack]) do if event[1] == 'note' then -- for example, do something to all notes end -- to work on events in only particular channels... channelindex = MIDI.Event2channelindex[event[1]] if channelindex and channels[event[channelindex]] then -- do something to channels 2,3,5,8 and 13 end end end

local midifile = assert(io.open('f.mid','w')) midifile:write(MIDI.score2midi(my_score)) midifile:close()

This module offers functions: concatenate_scores(), grep(), merge_scores(), mix_opus_tracks(), mix_scores(), midi2opus(), midi2score(), opus2midi(), opus2score(), play_score(), score2midi(), score2opus(), score2stats(), score_type(), segment(), timeshift() and to_millisecs(), where "midi" means the MIDI-file bytes (as can be put in a .mid file, or piped into aplaymidi), and opus and score are list-structures as inspired by Sean Burke's MIDI-Perl CPAN module.

The opus is a direct translation of the midi-file-events (see opus2midi()), where the times are delta-times, in ticks, since the previous event:

{'note_on', dtime, channel, note, velocity} -- in an opus {'note_off', dtime, channel, note, velocity} -- in an opus

The score is more human-centric (see score2opus()); it uses absolute times, and combines the separate note_on and note_off events into one "note" event, with a duration:

{'note', start_time, duration, channel, note, velocity} -- in a score

MIDI.lua is a call-compatible translation into Lua of the Python module MIDI.py;

see www.pjb.com.au/midi/MIDI.html

As an example, the script in_c.lua when run as lua in_c.lua -s 999 -q -m 20

generated in_c.mp3 ( see en.wikipedia.org/wiki/In_C )

concatenate_scores(), grep(), merge_scores(), mix_opus_tracks(), mix_scores(), midi2opus(), midi2score(), opus2midi(), opus2score(), play_score(), score2midi(), score2opus(), score2stats(), score_type(), segment(), timeshift() and to_millisecs()

The opus is a direct translation of the midi-file-events, where the times are delta-times, in ticks, since the previous event.

{'note_on', dtime, channel, note, velocity} -- in an opus {'note_off', dtime, channel, note, velocity} -- in an opus

The score is more human-centric; it uses absolute times, and combines the separate note_on and note_off events into one "note" event, with a duration:

{'note', start_time, duration, channel, note, velocity} -- in a score

Events (in an opus structure):

{'note_off', dtime, channel, note, velocity} -- in an opus {'note_on', dtime, channel, note, velocity} -- in an opus {'key_after_touch', dtime, channel, note, velocity} {'control_change', dtime, channel, controller(0-127), value(0-127)} {'patch_change', dtime, channel, patch} {'channel_after_touch', dtime, channel, velocity} {'pitch_wheel_change', dtime, channel, pitch_wheel} {'text_event', dtime, text} {'copyright_text_event', dtime, text} {'track_name', dtime, text} {'instrument_name', dtime, text} {'lyric', dtime, text} {'marker', dtime, text} {'cue_point', dtime, text} {'text_event_08', dtime, text} {'text_event_09', dtime, text} {'text_event_0a', dtime, text} {'text_event_0b', dtime, text} {'text_event_0c', dtime, text} {'text_event_0d', dtime, text} {'text_event_0e', dtime, text} {'text_event_0f', dtime, text} {'end_track', dtime} {'set_tempo', dtime, tempo} {'smpte_offset', dtime, hr, mn, se, fr, ff} {'time_signature', dtime, nn, dd, cc, bb} {'key_signature', dtime, sf, mi} {'sequencer_specific', dtime, raw} {'raw_meta_event', dtime, command(0-255), raw} {'sysex_f0', dtime, raw} {'sysex_f7', dtime, raw} {'song_position', dtime, song_pos} {'song_select', dtime, song_number} {'tune_request', dtime}

channel = a value 0 to 15 controller = 0 to 127 (see www.pjb.com.au/muscript/gm.html#cc) dtime = time measured in ticks, 0 to 268435455 velocity = a value 0 (soft) to 127 (loud) note = a value 0 to 127 (middle-C is 60) patch = 0 to 127 (see www.pjb.com.au/muscript/gm.html ) pitch_wheel = a value -8192 to 8191 (\x1FFF) raw = 0 or more bytes of binary data (for sysex events see below) sequence_number = a value 0 to 65,535 (\xFFFF) song_pos = a value 0 to 16,383 (\x3FFF) song_number = a value 0 to 127 tempo = microseconds per crochet (quarter-note), 0 to 16777215 text = a string of 0 or more bytes of ASCII text ticks = the number of ticks per crochet (quarter-note)

In sysex_f0 events, the raw data must not start with a \xF0 byte, since this gets added automatically;

but it must end with an explicit \xF7 byte !

In the very unlikely case that you ever need to split sysex data into one sysex_f0 followed by one or more sysex_f7s, then only the last of those sysex_f7 events must end with the explicit \xF7 byte (again, the raw data of individual sysex_f7 events must not start with any \xF7 byte, since this gets added automatically).

Number2patch In this table the index is the patch-number (0 to 127), and the value is its corresponding General-MIDI Patch (on Channels other than 9). See: www.pjb.com.au/muscript/gm.html#patch Notenum2percussion In this table the index is the note-number (35 to 81), and the value is its corresponding General-MIDI Percussion instrument (on Channel 9). See: www.pjb.com.au/muscript/gm.html#perc Event2channelindex In this table the index is the event-name (see EVENTS), and the value is the position within the event-array at which the Channel-number occurs. It is very useful for manipulating particular channels within a score (see SYNOPSIS)

This module is available as a LuaRock in luarocks.org/modules/peterbillam so you should be able to install it with the command:

$ su Password: # luarocks install midi

or:

# luarocks install http://www.pjb.com.au/comp/lua/MIDI-6.8-0.rockspec

The test script used during development is www.pjb.com.au/comp/lua/test_mi.lua

which requires the DataDumper.lua module.

You should be able to install the luaposix module with:

# luarocks install luaposix

and datadumper with either:

# luarocks install datadumper

or, if you're using Lua 5.3:

# luarocks install http://www.pjb.com.au/comp/lua/datadumper-1.1-0.rockspec

20170917 6.8 fix 153: bad argument #1 to 'char', and round dtime 20160702 6.7 to_millisecs() now handles set_tempo across multiple Tracks 20150921 6.5 segment restores controllers as well as patch and tempo 20150920 6.4 segment respects a set_tempo exactly on the start time 20150628 6.3 absent any set_tempo, default is 120bpm (see MIDI filespec 1.1) 20150422 6.2 works with lua5.3 20140609 6.1 switch pod and doc over to using moonrocks 20140108 6.0 in lua5.2 require('posix') returns the posix table 20120504 5.9 add the contents of mid_opus_tracks() 20111129 5.7 _encode handles empty tracks; score2stats num_notes_by_channel 20111111 5.6 fix patch 45 and 46 in Number2patch, should be Pizz and Harp 20110115 5.5 add mix_opus_tracks() 20110126 5.4 "previous message repeated N times" to save space on stderr 20110126 5.3 robustness fix if one note_on and multiple note_offs 20110125 5.2 opus2score terminates unended notes at the end of the track 20110124 5.1 the warnings in midi2opus display track_num 20110122 5.0 sysex2midimode.get pythonism eliminated 20110119 4.9 copyright_text_event "time" item was missing 20110110 4.8 note_on with velocity=0 treated as a note-off 20110109 4.7 many global vars localised, passes lualint :-) 20110108 4.6 duplicate int2sevenbits removed, passes lualint -r 20110108 4.5 related end_track bugs fixed around line 516 20110108 4.4 null text_event bug fixed 20101026 4.3 segment() remembers all patch_changes, not just the list values 20101010 4.2 play_score() uses posix.fork if available 20101009 4.2 merge_scores() moves aside conflicting channels correctly 20101006 4.1 concatenate_scores() deepcopys also its 1st score 20101006 4.1 segment() uses start_time and end_time named arguments 20101005 4.1 timeshift() must not pad the set_tempo command 20101003 4.0 pitch2note_event must be chapitch2note_event 20100918 3.9 set_sequence_number supported, FWIW 20100918 3.8 timeshift and segment accept named args 20100913 3.7 first released version

Peter J Billam, www.pjb.com.au/comp/contact.html