Tuning and Microtonalities

One of my interests is microtonalities/non-standard tunings, so support for such explorations has been included in the library.

There are several ways that tuning data can be specified in the MIDI standard, two of the most common being note pitch-bend and bulk tuning dumps. In this library I have implemented the real-time change note tuning of the MIDI tuning standard. I chose that as a first implementation because most of the soft-synthesizers I use support this standard.

Note, however, that implementation of the MIDI tuning standard is somewhat spotty, so you may want to verify that your hardware and/or software supports it before you spend too much time.

Recently the pitch bend message had been implemented. The advantage of the pitch bend is that almost all software and hardware understand it. The disadvantage is that different software and hardware can interpret the values differently.

The main function to support a tuning change is changeNoteTuning.

MIDIFile.changeNoteTuning(track, tunings, sysExChannel=127, realTime=True, tuningProgam=0)

Add a real-time MIDI tuning standard update to a track.

Parameters:
  • track – The track to which the tuning is applied.
  • tunings – A list to tuples representing the tuning. See below for an explanation.
  • sysExChannel – The SysEx channel of the event. This is mapped to “manufacturer ID” in the event which is written. Unless there is a specific reason for changing it, it should be left at its default value.
  • realTime – Speicifes if the Universal SysEx event should be flagged as real-time or non-real-time. As with the sysExChannel argument, this should in general be left at it’s default value.
  • tuningProgram – The tuning program number.

This function specifically implements the “real time single note tuning change” (although the name is misleading, as multiple notes can be included in each event). It should be noted that not all hardware or software implements the MIDI tuning standard, and that which does often does not implement it in its entirety.

The tunings argument is a list of tuples, in (note number, frequency) format. As an example, if one wanted to change the frequency on MIDI note 69 to 500 (it is normally 440 Hz), one could do it thus:

from midiutil.MidiFile import MIDIFile
MyMIDI = MIDIFile(1)
tuning = [(69, 500)]
MyMIDI.changeNoteTuning(0, tuning, tuningProgam=0)

Tuning Program

With some instruments, such as timidity, this is all you need to do: timidity will apply the tuning change to the notes. Other instruments, such as fluidsynth, require that the tuning program be explicitly assigned. This is done with the changeTuningProgram function:

MIDIFile.changeTuningProgram(track, channel, time, program, time_order=False)

Change the tuning program for a selected track

Parameters:
  • track – The track to which the data should be written
  • channel – The channel for the event
  • time – The time of the event
  • program – The tuning program number (0-127)
  • time_order – Preserve the ordering of the component events by ordering in time. See makeRPNCall() for a discussion of when this may be necessary

Note that this is a convenience function, as the same functionality is available from directly sequencing controller events.

The specified tuning should already have been written to the stream with changeNoteTuning.

Tuning Bank

The tuning bank can also be specified (fluidsynth assumes that any tuning you transmit via changeNoteTuning is assigned to bank zero):

MIDIFile.changeTuningBank(track, channel, time, bank, time_order=False)

Change the tuning bank for a selected track

Parameters:
  • track – The track to which the data should be written
  • channel – The channel for the event
  • time – The time of the event
  • bank – The tuning bank (0-127)
  • time_order – Preserve the ordering of the component events by ordering in time. See makeRPNCall() for a discussion of when this may be necessary

Note that this is a convenience function, as the same functionality is available from directly sequencing controller events.

The specified tuning should already have been written to the stream with changeNoteTuning.

An Example

So, as a complete example, the following code fragment would get rid of that pesky 440 Hz A and tell the instrument to use the tuning that you just transmitted:

track   = 0
channel = 0
tuning  = [(69, 500)]
program = 0
bank    = 0
time    = 0
MyMIDI.changeNoteTuning(track, tuning, tuningProgam=program)
MyMIDI.changeTuningBank(track, channel, time, bank) # may or may not be needed
MyMIDI.changeTuningProgram(track, channel, time, program) # ditto

Using Pitch Bend

Pitch bend is a channel level event, meaning that if you pass a pitch wheel event, all notes on that channel will be affected.

MIDIFile.addPitchWheelEvent(track, channel, time, pitchWheelValue)

Add a channel pitch wheel event

Parameters:
  • track – The track to which the event is added.
  • channel – the MIDI channel to assign to the event. [Integer, 0-15]
  • time – The time (in beats) at which the event is placed [Float].
  • pitchWheelValue – 0 for no pitch change. [Integer, -8192-8192]

To Do

  • Implement the tuning change with bank select event type.