MidiPlayer

The MidiPlayer is a Player implementation that plays an arrangement on any number of MIDI channels on any number of MIDI devices in realtime using the Java Sound API. The MIDI-imposed limit of channels per MIDI device is 16.

The MidiPlayer supports sending MIDI timing ticks (a.k.a. MIDI sync, based on MIDI timing clock messages) to selected MIDI devices (24 times per beat) and has a feedback algorithm with nanosecond resolution to keep the playing timing accurate. The MidiPlayer currently does not support the professional MIDI timing mode, which sends out MIDI timing ticks 480 times per beat instead of 24 times per beat.

The "groove" tag can be used to enable non-uniform timing (a.k.a. groove patterns) for sending out all MIDI messages (except for MIDI timing messages, which are always sent out with uniform timing). This is a feature that is used in many electronic music tunes. In SoundHelix, you simply provide a comma-separated list of integers that specifiy the relative timings for the ticks. This list is used in a round-robin manner. For example, you can use "120,80" (or equivalently, "12,8" or "3,2") to use a tick timing ratio of 3:2 (the first tick will take 60% of the 2-tick time, the second tick will take 40% of that time). Longer lists like "120,80,110,90" can also be used. For resetting the groove to a uniform timing, simply use a single integer larger than 0 (e.g., "1") or a list of equal integers larger than 0. You can also use the tag to provide a little bit of irregularity into playing to simulate a human playing a piano with non-exact timing, e.g. by using a pattern with an odd (and prime) number of numbers like "101,99,102,97,101,95,102,98,104,97,100". The groove pattern will be applied globally, i.e., for all instruments. It is currently not possible to assign groove patterns on a per-instrument level.

The mapping from sequence velocities to MIDI velocities is done in a linear way. Sequence velocity 0 is mapped to MIDI velocity 0, sequence velocities in the range 1-32767 are linearly mapped to the MIDI velocity range 1-127.

The MidiPlayer supports LFOs for slowly changing MIDI controller parameters during playback. This lets you control parameters like filters, balance, volume, etc., on a per-MIDI-channel level. The time resolution of the LFOs is a tick, so you cannot change a MIDI parameter faster than once per tick. The LFOs can be synchronized to different song or time properties, e.g., the song length, the beat length, to a second, to the period spanned by the first note and the last note of any given instrument (or, starting with version 0.6 an ActivityVector), or (starting with version 0.6) the segment pairs of an ActivityVector. There are many things you can do with LFOs. For example, you can use an LFO for slowly fading out the volume at the end of the song. This can be done by choosing a SawtoothLFO that is synchronized to the song length with an amplitude between 0 and a very high value (say 5000) and using an upper cut-off at the maximum controller value of 127. The LFO value will then be 127 (i.e., volume maximum) during the song - as the LFO will linearly decrease from 5000 to 0 throughout the song and everything above 127 will be limited to 127 - and will have a steep drop down to 0 at the end of the song. Similarly, you can do a fade-in and a fade-out by using TriangleLFO. Note that this cut-off capability is available since version 0.4; earlier versions do not support this. You can of course also "abuse" an LFO for setting a controller value to a constant value throughout the song by using any LFO implementation and setting the minimumAmplitude and maximumAmplitude to the wanted constant.

It is possible to map two different instruments to the same MIDI channel on the same MIDI device. Note that the set of pitches of the two instruments should be disjunctive in this case, otherwise you will get strange effects, because the player does not implement any special handling for this situation (note-ons for the same pitch will be sent twice or the note-on of one instrument's pitch might be immediately disabled by a note-off of the other instrument for the same pitch).

If you are using a General MIDI soundbank, look at the MIDI program mapping for reference here:

Some of the player's parameters (BPM, transposition, groove) can be changed at runtime. Starting with version 0.5, the MidiPlayer supports skipping to any tick of the song during playback, even backwards. This is done by muting all MIDI channels, moving to the specified tick and continuing playing from there. Before version 0.5, skipping backwards was not possible, and skipping forward was implemented by increasing the BPM to a very high value temporarily until the specified tick was reached.

External commands can be run before starting playing and after stopping playing. For example, you might want to remote-control a sound recording software (such as Audacity) to start recording when a song starts and later stop recording and save the result as an MP3 file including the song title in the filename. Note that there is no guarantee that both the commands to run before and after playing are always executed. Whenever SoundHelix is quit abnormally (e.g., by killing the process), no further commands will be run, so it might be possible that the start commands are run without ever running the stop commands.

Starting with version 0.6, initial MIDI controller values can be set using the "controllerValue" tag. This can be used to set relative MIDI volumes, panning values, etc., per MIDI channel at the beginning of the song. Note that raw MIDI controller values are used (0-127 for 7-bit controllers like volume, 0-16383 for 14-bit controllers like pitch bend). These controller settings will also make it into the generated MIDI files.

Exporting generated songs as MIDI files

Starting with version 0.5, the MidiPlayer supports exporting generated songs in MIDI format. For every configured MIDI device a separate MIDI file will be written whose filename is determined by a filename template (see the midiFilename tag). Note that this export is currently pretty basic (e.g., you cannot select which instruments to export, it always exports all instruments). Still, it exports all song data (including MIDI program preselections and LFOs). It is likely that the MIDI export is going to be made more flexible in the future. For a working example for the MIDI export, please see the file SoundHelix-Piano.xml.

Synchronizing to a MIDI device

Since version 0.7 SoundHelix supports synchronizing itself to a MIDI device via the "synchronizationDevice" tag. From this device, SoundHelix can receive MIDI timing ticks, enabling the device to remote-control SoundHelix' BPM speed. In addition, the device can send START, STOP and CONTINUE MIDI messages. For remote-controlling the BPM, SoundHelix uses a sliding window of timings received from the device, whose size can be configured via the maxWindowSize attribute. A larger window size will make the BPM calculation smoother, while increasing the time SoundHelix needs to converge to a BPM change. If the window is smaller, SoundHelix converges faster, but then timing glitches have a stronger effect. You can also configure how many timing ticks will be needed in order for the BPM calculation to begin via the minWindowSize attribute. Basically, this defines the initial window size, which then grows to the configured final window size. Until the initial window is filled, SoundHelix will use the BPM configured in the XML file.

Configuration

Tag Attribute Type # Example Description
bpm - int 1 136 The number of beats per minute for playback.
transposition - int 1 65 The number of halftones all non-rhythmic tracks should be transposed up by. Note that the pitches used by SoundHelix are normalized to 0 (which equals C in octave 0) whereas MIDI normalizes pitches to 60. Look here for more details.
groove - list of ints 0-1 120,80 The relative tick timing duration ratios ("120,80" means a tick timing ratio of 120:80 = 3:2). The values are used in a round-robin manner. The list can contain any number of ratios. The groove is disabled if you use a single number or a list of equal numbers. You can also use the tag to provide a little bit of irregularity into playing to simulate a human playing a piano with non-exact timing, e.g. by using a pattern with an odd number of numbers like "101,99,102,99,101,102,98". Experiment!
beforePlayWaitTicks - int 0-1 16 The number of ticks to wait before starting playback of a song. If timing ticks are enabled, they are already sent during this wait period.
beforePlayCommands - list of strings 0-1 /usr/bin/perl start-recording.pl The external commands to run (semicolon-separated) before playing starts (directly before waiting the beforePlayWaitTicks). The commands may contain the following placeholders: ${songName} (the literal song name including spaces, etc.), ${safeSongName} (the song name where unsafe characters are converted to underscores), ${randomSeed} (the random seed in signed decimal notation). All run commands must return with exit code 0, otherwise an exception will be thrown. Available since version 0.3.
afterPlayWaitTicks - int 0-1 16 The number of ticks to wait after finishing playback of a song and before muting all MIDI channels. If timing ticks are enabled, they are still sent during this wait period.
afterPlayCommands - list of strings 0-1 /usr/bin/perl stop-recording.pl The external commands to run (semicolon-separated) after playing has stopped (directly after waiting the afterPlayWaitTicks). See beforePlayCommands tag for details. Available since version 0.3.
synchronizationDevice waitForStart boolean 0-1 true Indicates if the player should wait before playing until a START MIDI message is received. This waiting is done before the beforePlayWaitTicks waiting. During this time, MIDI timing ticks can be received. If set to false, playing starts immediately with the song's own BPM (i.e. not the BPM from the synchronization device), but as soon as the minimum window size has been reached, this switches to the calculated BPM. To avoid such BPM switching, make sure the time for filling up the minimum window size is not larger than the time waited for the beforePlayWaitTicks (the time for filling the minimum window size depends on the window size and the remote BPM, the waiting time depends on the waiting ticks and the song's BPM). Defaults to true.
synchronizationDevice minWindowSize int 0-1 24 The minimum number of MIDI timing ticks to receive before starting the evaluation of the BPM. Defaults to 24.
synchronizationDevice maxWindowSize int 0-1 24 The maximum sliding window size to keep for BPM calculation. A larger window size averages timing glitches out more evenly but reacts more slowly to BPM changes, a smaller window size is more prone to irregularities in timing but reacts more quickly to BPM changes.
synchronizationDevice - string 1 timingDevice The name of the MIDI device to connect to for receiving MIDI timing ticks and START, CONTINUE and STOP messages.
device name string 1 device1 The internal name of the MIDI device for later reference by the <map>, <controllerValue> and <controllerLFO> tags.
device clockSynchronization boolean 0-1 true Boolean flag indicating whether MIDI timing ticks should be sent to the device. The timing ticks are sent 24 times per beat, independent of the configured groove.
device - list of strings 1-n Java Sound Synthesizer The comma-separated list of MIDI devices to check. The first MIDI device of the list (in list order) that is available on the platform will be used.
midiFilename - string 0-1 midifiles/${safeSongName}_-_${safeDeviceName}.mid Defines the template for saving MIDI files. If defined, one MIDI file per configured device will be saved, otherwise the MIDI file export is disabled. If you use more than one MIDI device, make sure that you include ${safeDeviceName}" in the template, otherwise the filename will not be unique for each MIDI device. The MIDI files will be saved relative to the current directory of SoundHelix. Make sure that the target directory (if any, "midifiles" in the example) exists. Available since version 0.5.
map instrument string 1 percussion The name of the instrument to map.
map device string 1 device1 The name of the device to map the instrument to (as defined name in the "device" tag).
map channel int 1 10 The MIDI channel number of the device to map the instrument to (between 1 and 16).
map program int 0-1 10 The MIDI program to preselect on the MIDI channel (between 1 and 128). If this attribute is not used then the channel's program is not changed.
controllerValue device string 0-1 device1 The MIDI device to remote-control (as defined name in the "device" tag).
controllerValue channel int 0-1 10 The MIDI channel on the device to remote-control.
controllerValue controller string 1 modulationWheel The MIDI controller to remote-control. Allowed controllers are "pitchBend", "modulationWheel", "breath", "footPedal", "volume", "balance", "pan", "expression", "effect1", "effect2", "variation", "timbre", "releaseTime", "attackTime" and "brightness".
controllerValue - int 0-n 120 The initial value of the MIDI controller, which is set at the start of the song. For 7-bit controllers this value has to be between 0 and 127, for 14-bit controllers it has to be from 0 to 16383. Available since version 0.6.
controllerLFO - - 0-n - The controller LFO container.
controllerLFO/lfo class class 1 SineLFO The name of the LFO implementation to use.
controllerLFO/rotationUnit - string 1 activity The rotation (a.k.a. period) unit to use for the LFO. These rotation units are allowed:
  • song - speed and phase are set so that one full rotation from the beginning to the end of the song is made
  • beat - speed and phase are set so that one full rotation from the beginning to the end of a beat is made
  • second - speed and phase are set so that one full rotation is done per second
  • activity - speed and phase are set so that the LFO performs one full rotation from the activity start of the given instrument (or, starting with version 0.6, ActivityVector) until the activity end of the given instrument/ActivityVector specified in the <instrument> tag
  • segmentPair - speed is set dynamically so that for every segment pair (activity and pause segment) of an ActivityVector the LFO performs on full rotation, the first half in the activity segment and the second half in the pause segment (available since version 0.6).
controllerLFO/instrument - string 0-1 percussion The name of the instrument to base the activity check on. This tag is required only if the rotation unit is set to "activity" and no ActivityVector has been specified.
controllerLFO/activityVector - string 0-1 base The name of an ActivityVector to base the activity check on. This tag is required only if the rotation unit is set to "segmentPair" or if the rotation unit is "activity" and no instrument has been specified. Available since version 0.6.
controllerLFO/speed - double 1 1 The number of full rotations to perform within one rotation unit.
controllerLFO/phase - double 1 0 The phase shift of the LFO in number of rotations. 0.25 is a shift by 90 degrees, 0.5 by 180 degrees, etc.
controllerLFO/minAmplitude - int 1 0 The minimum amplitude value of the LFO. Available since version 0.4.
controllerLFO/maxAmplitude - int 1 1000 The maximum amplitude value of the LFO. Available since version 0.4.
controllerLFO/minValue - int 1 0 The minimum value of the LFO. Can be used to lower-bound the LFO value if the LFO's minimum amplitude is smaller than this value, otherwise this will have no effect. Defaults to -infinity. Available since version 0.4.
controllerLFO/maxValue - int 1 127 The maximum value of the LFO. Can be used to upper-bound the LFO value if the LFO's maximum amplitude is greater than this value, otherwise this will have no effect. Defaults to +infinity. Available since version 0.4.
controllerLFO/minimum - int 1 0 The minimum amplitude value of the LFO. Deprecated since version 0.4, use controllerLFO/minAmplitude instead.
controllerLFO/maximum - int 1 127 The maximum amplitude value of the LFO. Deprecated since version 0.4, use controllerLFO/maxAmplitude instead.
controllerLFO/controller - string 1 modulationWheel The MIDI controller to remote-control. See the controllers listed in the "controllerValue" tag. In addition, a pseudo controller "milliBPM" is available for changing the player's playback speed in BPM.
controllerLFO/device - string 0-1 device1 The MIDI device to remote-control. Needed for all controllers except for the "milliBPM" controller.
controllerLFO/channel - int 0-1 10 The MIDI channel on the device to remote-control. Needed for all controllers except for the "milliBPM" controller.
instrumentControllerLFO - - 0-n - The instrument controller LFO container. Used to map a conditional LFO of an instrument to a MIDI controller. Available since version 0.8.
instrumentControllerLFO/instrument - string 1 percussion The name of the instrument to select the conditional LFO from.
instrumentControllerLFO/lfo - string 1 bassLFO The name of the instrument's conditional LFO.
instrumentControllerLFO/minAmplitude - int 1 0 The minimum amplitude value of the LFO.
instrumentControllerLFO/maxAmplitude - int 1 1000 The maximum amplitude value of the LFO.
instrumentControllerLFO/minValue - int 1 0 The minimum value of the LFO. Can be used to lower-bound the LFO value if the LFO's minimum amplitude is smaller than this value, otherwise this will have no effect. Defaults to -infinity.
instrumentControllerLFO/maxValue - int 1 127 The maximum value of the LFO. Can be used to upper-bound the LFO value if the LFO's maximum amplitude is greater than this value, otherwise this will have no effect. Defaults to +infinity.
instrumentControllerLFO/controller - string 1 modulationWheel The MIDI controller to remote-control. See the controllers listed in the "controllerValue" tag. In addition, a pseudo controller "milliBPM" is available for changing the player's playback speed in BPM.
instrumentControllerLFO/device - string 0-1 device1 The MIDI device to remote-control. Needed for all controllers except for the "milliBPM" controller.
instrumentControllerLFO/channel - int 0-1 10 The MIDI channel on the device to remote-control. Needed for all controllers except for the "milliBPM" controller.

The LFOs' functions can be reversed horizontally (mirrored at the Y axis) by using a negative speed instead of a positive speed. They can be reversed vertically (mirrored at the X axis) by swapping the minimum and the maximum value.

LFO values are evaluated once per tick, so a value can not be changed more often than once per tick. If the LFO value has changed with regard to the previous value (or if it is the beginning of the song), the new value is sent to the corresponding MIDI channel, otherwise nothing is sent.

Configuration example


<player class="MidiPlayer">
  <bpm><random min="120" max="140" type="normal" variance="7"/></bpm>
  <transposition><random min="64" max="68"/></transposition>
  <beforePlayWaitTicks>0</beforePlayWaitTicks>
  <afterPlayWaitTicks>16</afterPlayWaitTicks>
  <groove><random list="100|110,90|115,85|125,75,115,85"/></groove>
  <!-- write MIDI files into the subdirectory "midifiles" -->
  <midiFilename>midifiles/${safeSongName}-${safeDeviceName}.mid</midiFilename> 
  <device name="device1" clockSynchronization="true">Microsoft GS Wavetable Synth,Microsoft GS Wavetable SW Synth,Java Sound Synthesizer,Gervill</device>
  <map instrument="arpeggio" device="device1" channel="1" program="1"/>
  <map instrument="accomp" device="device1" channel="2" program="1"/>
  <map instrument="melody" device="device1" channel="3" program="2"/>
  <map instrument="pad" device="device1" channel="4" program="92"/>
  <map instrument="bass" device="device1" channel="5" program="40"/>
  <map instrument="randombass" device="device1" channel="6" program="40"/>
  <map instrument="percussion" device="device1" channel="10"/>
  <controllerLFO>
    <lfo class="TriangleLFO"/>
    <rotationUnit>activity</rotationUnit>
    <instrument>acid</instrument>
    <speed>1</speed>
    <phase>0</phase>
    <minAmplitude>0</minAmplitude>
    <maxAmplitude>127</maxAmplitude>
    <controller>modulationWheel</controller>
    <channel>5</channel>
    <device>device1</device>
  </controllerLFO>
  <controllerLFO>
    <lfo class="TriangleLFO"/>
    <rotationUnit>beat</rotationUnit>
    <speed>0.1</speed>
    <minAmplitude>-5000</minAmplitude>
    <maxAmplitude>30000</maxAmplitude>
    <minValue>0</minValue>
    <maxValue>16383</maxValue>
    <controller>pitchBend</controller>
    <channel>1</channel>
    <device>device1</device>
  </controllerLFO>
  <controllerLFO>
    <lfo class="SineLFO"/>
    <rotationUnit>beat</rotationUnit>
    <speed>0.02</speed>
    <minValue>100000</minValue>
    <maxValue>150000</maxValue>
    <controller>milliBPM</controller>
  </controllerLFO>
</player>

Comments

A thought.

So if the on the fly feature was implimented in a future release, it would be possible to change the music based on where a familiar player or enemy was encountered using the xyz coordinates and assigning a random beat or instrument based on the name of the encountered object?

Add new comment