Player Piano Prep Script

I once transcribed a video game title screen tune into a MIDI file for someone who requested it on a message board. A little later they revealed they were playing the MIDI file on a player piano, and if two notes of the same pitch touched each other (one note ended at the same time the next note began), the player piano didn't have time to react between the notes and it effectively combined them into one note. They asked for some kind of script that would add a gap between notes, but only between notes of the same pitch. At the time, I couldn't think of an easy way to do this, so I just suggested shortening the length of all notes by a small amount.

Recently, the developer of the free MIDI sequencer Sekaiju has released a paid add-on called Sekaiju Application Language (SAL). It costs 500 yen (about 5 dollars), but you can try it out for 30 days.

SAL lets you write scripts to edit events in a MIDI file. Here's how it works:

  1. In a text editor, you write a script in the SAL language, and save it into a .sal file.
  2. In Sekaiju, you select some events in your MIDI file, then go to the Edit menu and start your SAL script. Sekaiju will then edit the selected events according to the commands in your SAL script.

To try out SAL, I revisited the player piano's problem of touching notes. I wrote a SAL script that helps you modify notes in a MIDI file so that:

[touching notes converted to have a gap; overlapping notes converted to have no overlap]

The script assumes all the notes are in the same track, and writes its output on channel 1. So before you run this script, you will have to do some preparation work to put all the notes into one track and on channel 1. Please follow the steps in the Example section that follows.

Example

Download player-piano-prep.zip. The zip file contains the script (player-piano-prep.sal) and an example MIDI file (D-Generation.mid). Open D-Generation.mid in Sekaiju and follow these steps.

A player piano can only play pitched sounds, not percussion, so we want to remove any percussive sounds. In D-Generation.mid, the tracks "Drums", "Melodic percussion" (a woodblock sound) and "Jetpack exhaust" (a helicopter sound) are percussive sounds, so let's get rid of them.

  1. In the Track List window, click once on the track name "Drums" so it has a box around it.
  2. Press the Delete key on the keyboard to delete the track.
    (Or click the Delete Track button [Delete Track button] in the toolbar at the top of the Track List window.)
  3. Do the same thing to delete the tracks "Melodic percussion" and "Jetpack exhaust".

We want all the notes to play with the same sound, so we want to get rid of all the Program Change events. We also don't need multiple instances of Volume events at the beginning, since we are going to eventually put everything on one track and channel. Since a player piano can't change the volume of a note after it's started, it seems okay to remove the Expression controller events also. So for simplicity, let's just delete every event that isn't a note. Exception: We want to keep the tempo, time signature, and key signature events that are in the first track, so we will deselect that track so it isn't affected.

Let's go to the Event List window to help us delete these events.

  1. Go to the View menu and choose Show new Event list window.
    (Or click the Show new Event List Window button [Event List button] in the toolbar at the top of Sekaiju.)

When you open an Event List window, it initially shows every event in the MIDI file. The top-right section of checkboxes lets you change the tracks shown in the event list. the bottom-right section of checkboxes lets you change the events shown in the event list. Let's change the view to hide the first track and hide Note events.

  1. In the top-right section, uncheck the first track "D/Generation title music".
  2. In the bottom-right section, uncheck Note Off(including velocity0 Note On) and Note On.

Now the Event List is showing every event that isn't a Note and isn't in the first track. Let's remove these events.

  1. At the top of the event list, click on the left number heading for the first event.
  2. Scroll to the bottom of the event list.
  3. Hold down the Shift key on the keyboard and click on the left number heading for the last event.
  4. Go to the Edit menu and choose Delete.

You'll notice there are still some End of Track events remaining because End of Track events can't be deleted.

Let's deselect everything and go back to the track list.

  1. Go to the Edit menu and choose Select None.
  2. Close the Event List window.

Except for the first track, the only events that are remaining are Note events. We want to move all these notes into one track and change them to channel 1.

Let's select the all tracks except the first track.

  1. In the Track List window, click on the left number heading for the second track.
  2. Hold down the Shift key on the keyboard and click on the left number heading for the last track.

Now, let's change these events to channel 1.

  1. Go to the Edit menu and choose Modify Event's Channel.
  2. In the Mode section, select Absolute specify.
  3. In the Channel Number or Shift Amount field, enter 1.
  4. Click OK.

And now, let's move these events to the second track.

  1. Go to the Edit menu and choose Modify Event's Track.
  2. In the Unit section, select Absolute specify.
  3. In the Track Index or Shift Amount field, enter 2.
    (If you have selected "Count track number from 0" in the Setup menu Options command, enter 1 instead.)
  4. Click OK.

Now, let's remove the empty tracks.

  1. Go to the Edit menu and choose Select None.
  2. Click on the Name box for the third track so it has a box around it.
  3. Repeatedly press the Delete key on the keyboard to delete the empty tracks at the bottom until just the first and second tracks are left.

The script has to re-insert the last Note event for each pitch back into the track. In the SAL language, when you use the insert command outside of a forEachEvent loop, SAL inserts the event into the active track. If you leave the third or higher track active, the last note of each pitch will be lost because the active track doesn't exist. So, before running the script, let's click on the second track so the last note for each pitch can be re-inserted properly.

  1. Click on the Name box for the second track so it has a box around it.

Now we are ready to run the script.

  1. Go to the Edit menu and choose Select All.
  2. Go to the Edit menu and choose SAL(Sekaiju Application Language).
  3. If you are in the trial period, you will be asked if you want to enter your Authorization Code now, click No to be able to choose a script.
  4. In the Open dialog box, select the player-piano-prep.sal file.

Wait a few seconds for the script to run.

Script Overview

The main feature of the SAL language is the forEachEvent loop that will let you examine each of the events selected in Sekaiju one by one. SAL will let you modify the current event, insert new events to the same track as the current event, or delete the current event.

Because the player-piano-prep.sal script has to compare two different notes to tell what modifications it needs to do, it can't tell if it needs to modify the current note until later. Because of this, the script saves the current note's values then deletes the current note. After it examines the next note, it re-writes the saved note's values with any needed modifications. This means the script actually deletes all of the existing notes and re-writes them as it proceeds.

(SAL Programming Tip: I discovered that if you want to both insert a new event and delete the current event in the same pass of the forEachEvent loop, you have to do the delete last. If you try to delete first then insert you will get the error message "Event is lossed" and your script will stop. The SAL documentation mentions that during a forEachEvent loop, it will insert events on the same track as the current event, so I guess once you have deleted the current event, it can no longer tell which track to use to insert new events.)

My script runs a new forEachEvent loop for every possible pitch (0 to 127). An outer loop increments a variable indicating witch pitch it will look at.

In the forEachEvent loop, it only examines the Note events for the current pitch it is looking at.

It keeps comparing the previous note (A) and the current note (B) of the current pitch it is looking at.

If A and B start at the same time, it will combine them into one note, by using the longest duration from A and B and the highest velocity from A and B.

If A overlaps B or if the gap between A and B is too small, the length of A will be shortened so that there is a gap between A and B.

Here are the message board threads that were the inspiration for creating this script.

VGMusic Forums - D/Generation opening [Internet Archive Wayback Machine]

In this thread, Rachie581 requested a transcription of the opening tune in the video game D/Generation so she could convert it to sheet music and play it on piano. Over the next few days, I worked on transcribing it, posting my progress and the completed results. After I finished the transcription, Rachie581 posted a recording of it played on a piano.

The VGMusic Forums no longer exist, so the link above is to a copy in the Internet Archive Wayback Machine. Here are the things linked in that thread.

At the time I thought it was odd that the beginning of the recording sounded like a beginner pianist, but the end of the recording sounded like an expert pianist.

VGMusic Forums - MIDI note repetition [Internet Archive Wayback Machine]

In this thread, Rachie581 described she had built a player piano, but when two notes of the same pitch are touching (one note ends at the same time the next note begins), her player piano combines them into one note. She asked how commercial player pianos solve this problem. I found some Yamaha player piano manuals that didn't describe the exact same problem, but explained they add a delay to incoming MIDI data. I suggested the delay time might be used to add any needed gap between notes of the same pitch. For her case, I suggested shortening all the notes in the MIDI file as a workaround.

The VGMusic Forums no longer exist, so the link above is to a copy in the Internet Archive Wayback Machine. Here is a local copy of the attachment from my post: Excerpts from Disklavier manuals; Converting milliseconds into ticks.zip.

After Rachie581 revealed she had built a player piano, I listened to her recording again and realized it was actually from her player piano: The recording matched the timing of the MIDI file exactly, and notes of the same pitch that were touching in the MIDI file were combined into one note in the recording.


Home > Programs > Sekaiju Customizations > Player Piano Prep Script

Robert Hart
Posted Jan. 1, 2018