midiedit - Edits a MIDI file


 midiedit filename.mid       # uses the new Curses app, with sound
 midiedit -o 128:0 filename.mid    # uses ALSA port 128:0 as synth
 midiedit -d filename.mid  # uses your EDITOR on a MIDI dump
 midiedit -v               # prints the Version number


Midiedit is a MIDI-file editor which now (since version 1.3) has a choice of two user-interface modes.

In the new default mode, it uses Curses to offer a purpose-designed user-interface and MIDI::ALSA to play the notes to your synth.

In the older lower-tech mode, it uses your favourite text-editor to edit the human-readable text-format provided by MIDI's $opus->dump function.


In the Curses mode, which is the default, midiedit edits a MIDI file with a purpose-designed user-interface which re-uses some keystrokes inspired by vi, e.g.:   i=insert   m=mark   g=goto   k/Up/j/Down=+-1event   u=undo   ^R=redo   /=find   ?=reversefind   n=findnext   N=findprevious   w=write   q=quit   .=last_edit_again,   plus a few others, e.g.:   e=edit_event   D=delete_event   R=define_a_range   r=operate_on_that_range   f=file_operations   z=all_sounds_off   and ^H/Backspace=return_where_you_were_before_the_Find

Inspired by mplayer, the spacebar toggles between Play and Pause, the Left and Right arrow keys move by 1 second, the PageUp and PageDown keys move by 10 seconds, and the Home and End keys move to the start and end of the file, and [ and ] or { and } change the Replay-speed. The available keystrokes are displayed in the bottom four lines of the screen.

The events are displayed in note-form, i.e. with a start-time and a duration. There are no separate note_on and note_off events, which solves the matching-ons-and-offs problem. All times are displayed in milliseconds.

The start-times can be displayed either as incremental times (since the previous event), or as absolute times (since the beginning). The + and - keys switch between these modes; the default mode is incremental. The behaviour of Edit, Insert and Delete adapts to the display-mode; for example with incremental times, deleting a note shortens the whole file by the deleted millisecond increment, but with absolute times deleting a note just removes that note and leaves the duration of the whole file unchanged.

Since version 3.0,   /=find also allows search criteria such as >62 or <25 or >=60 or <=72 or !=9 or >59&<73 which, when combined with .=repeat_last_edit, make it easier to do things like "move that high bit of the piano solo into a different channel".

Since version 4.5, a Range can be defined by pressing upper-case R once at each end of the desired range. Once defined, the Range can be operated on, using a lower-case r, in various ways
( c=compand d=delete m=mixer p=pitch q=quantise r=repeat t=tempo v=vol w=write_to_file )
largely modelled on the corresponding midisox effects. For details of what the compand effect does, see :
  midisox --help-effect=compand

Since version 7.7, after a Find (/, ?, n or N), ctrl-H or Backspace returns you to where you were before the Find.

You can specify your choice of synth at the command line with a -o 128:0 option, or else with the ALSA_OUTPUT_PORTS environment variable. The special value -o 0 silences the output (e.g. you might want to edit something while listening to something else).   Since Version 2.4, you may supply a comma-separated list of ports, e.g. -o Roland,TiMidity:1

As well as the MIDI-Perl CPAN module, this mode also uses the Curses module for the user-interface, and the MIDI::ALSA module to play the file to your synth. The user-interface is likely to evolve over the next months.


In the older, low-tech -d mode, midiedit edits a MIDI file in the human-readable text-format provided by MIDI's $opus->dump function.

perldoc MIDI::Event   documents the format in which the various MIDI-events are represented. They are represented with incremental times (in ticks) and with separate note_on and note_off events, so you have to keep track of matching note_ons and note_offs.

The text format representing the MIDI is executable Perl source, so as you edit, you should preserve valid Perl syntax. If the edited file has syntax errors, you will be asked if you want to re-edit it, and if you reply No then the original file will not get over-written.

If you've changed the text, and then decide you want to quit without overwriting the MIDI file, then you have to deliberately mess up the Perl syntax (e.g. make sure the brackets are unbalanced).


The current version of midiedit is available by http at www.pjb.com.au/midi/free/midiedit
To install midiedit, save it to disc, move it into your $PATH, make it executable, and if necessary edit the first line to reflect where perl is installed on your system.

midiedit uses the MIDI and Curses and MIDI::ALSA CPAN modules.
The non-Curses mode also uses Peter Billam's Term::Clui CPAN module.


8.2, 20210913, can edit lyric events
8.1, 20210912, displays lyric events with " quotes
8.0, 20210906, displays lyric events better
7.9, 20180731, sub addl truncates correctly for $col > 2
7.8, 20160701, -d with multi-track midi-files resets ticks to 0 each track
7.7, 20151106, after a Find, ctrl-H or Backspace returns to where you were
7.6, 20151105, finding a marker matches on a substring; also a bug fix, ignore /r or /R if no range is set
7.5, 20151031, like 7.4, except it also works for multiple un-do's
7.4, 20151030, 'u' leaves Ievent as before the last edit, not as after the event previous to that
7.3, 20151008, edit_again now works for markers and text events
7.2, 20151003, fix bug which introduced undefined events
7.1, 20151002, event2str defends against unprintable chars in text events
7.0, 20150513, displays also Metronome Click and Metronome Bell
6.9, 20150411, all_sounds_off also turns all notes off
6.8, 20150223, display channel_after_touch column-aligned as cha_aftertouch
6.7, 20141201, skip refreshes if <10ms to next event
6.6, 20141104, removed misleading 'm=mark' help-text
6.5, 20140703, display_events controller_change events display GM controller-names
6.4, 20140702, display_events patch_change events display GM patch-names
6.3, 20140612, ask_filename handles backspace, range_write will not overwrite
6.2, 20140611, File Include works corresponding to range_write, filename-completion gets its trailing space stripped, and range_write appends a marker to represent time-to-next-note
6.1, 20140610, range_write no longer has big pause at the start
6.0, 20130404, display_events remembers the Ped/* state by channel
5.9, 20130323, find_marker with null text finds the next marker
5.8, 20130321, bug fixed in range_quantise, as in version 5.4 of midisox
5.7, 20130302, range_repeat moves cursor down if it lay after RangeEnd
5.6, 20130301, range_delete moves cursor up if it lay after RangeEnd
5.5, 20130218, u=undo and ^R=redo seem to work
5.4, 20121028, replay_setup outputs bankchange before the patchchange
5.3, 20121001, replay_setup (and hence time_travel) works both forwards and backwards
5.2, 20120930, KEY_UP uses event_travel() to cope with dt=0
5.1, 20120930, works with MIDI::ALSA 1.15; PolyOn=127 fixed
5.0, 20120916, edit_event uses p=pitch not n=note, to avoid conflict with n=find_next
4.9, 20120910, f=file_menu: f=fork n=new s=save q=quit
4.8, 20120908, KEY_UP uses time-travel(), so as to get the right patch
4.7, 20120903, display_events clears lines after EOF; needed by range_delete
4.6, 20120628, rw = range_write now works
4.5, 20120624, R and r range-operations largely work
4.4, 20120613, event-fields correctly displayed also in edit-mode
4.3, 20120612, more consistent redo and undo; find_event uses time_travel
4.2, 20120609, can search for long gaps /l or short gaps /s
4.1, 20120608, becomes Paused at EOF; channel,note in bold if note-on
4.0, 20120604, j,k keys also available in Edit Mode
3.9, 20120604, '.'=repeat also offered in Edit Mode, if applicable
3.8, 20120604, find offers s=short_gap, l=long_gap and t=time (==go_to)
3.7, 20120604, '.' repeats also edit of dt if IncrementalTimes
3.6, 20120529, tracks shorter than screen-height don't get extended with nulls
3.5, 20120527, add g = go_to()
3.4, 20120525, displays most recent Ped and *
3.3, 20120510, displays currently on notes
3.2, 20120502, can now insert bank_change (= 2 control_changes)
3.1, 20120326, ] and [ or } and { change the ReplaySpeed
3.0, 20120110, find_match offers find >5&<15&!=9 searches
2.9, 20120108, '.' repeats last edit (if event-types match)
2.8, 20111126, find works if cha=0 or value=0
2.7, 20111107, edit_event dialogue updated as changes are made
2.6, 20111103, uses the new MIDI-ALSA 1.11 to handle ALSA-ports by their names
2.5, 20111029, column-titles better reflect the event-types
2.4, 20111028, the OutputPort can be a comma-separated list
2.3, 20111027, merges multiple tracks; z=all_sounds_off
2.2, 20111027, entering PAUSED mode causes all_sounds_off
2.1, 20111027, displays note-string in main window
2.0, 20111027, doesn't try to connect if $OutputPort undefined or "0"
1.9, 20111022, Phaser Depth and Poly On displayed correctly
1.8, 20111021, displays notes with ~ and _ correctly
1.7, 20110926, handles non-millisec-tick files correctly
1.6, 20110917, in edit-mode, display_this_event shows the changes as they are made
1.5, 20110910, Up/Down in edit-mode play the new note
1.4, 20110909, in edit mode, Up and Down don't leave edit-mode
1.3, 20110820, the new Curses app is the default
1.2, 20110708, displays helpful comments
1.1, 20060728, first working version


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


midiedit_demo.mp4   A live demo taken from a talk at the OSDC conference