defl/tests/test_midi.py

950 lines
38 KiB
Python
Raw Normal View History

2024-09-11 11:14:03 -04:00
#!/usr/bin/env python
# -----------------------------------------------------------------------------
# Name: miditest.py
# Purpose: Unit testing harness for midiutil
#
# Author: Mark Conway Wirt <emergentmusics) at (gmail . com>
#
# Created: 2008/04/17
# Copyright: (c) 2009-2016, Mark Conway Wirt
# License: Please see License.txt for the terms under which this
# software is distributed.
# -----------------------------------------------------------------------------
from __future__ import division, print_function
import sys
import struct
import unittest
from defl.thirdParty.MidiFile import *
from defl.thirdParty.MidiFile import writeVarLength, \
frequencyTransform, returnFrequency, MAJOR, MINOR, SHARPS, FLATS, MIDIFile
class Decoder(object):
'''
An immutable comtainer for MIDI data. This is needed bcause if one indexes
into a byte string in Python 3 one gets an ``int`` as a return.
'''
def __init__(self, data):
self.data = data.decode("ISO-8859-1")
def __len__(self):
return len(self.data)
def __getitem__(self, key):
return self.data[key].encode("ISO-8859-1")
def unpack_into_byte(self, key):
return struct.unpack('>B', self[key])[0]
class TestMIDIUtils(unittest.TestCase):
def testWriteVarLength(self):
self.assertEqual(writeVarLength(0x70), [0x70])
self.assertEqual(writeVarLength(0x80), [0x81, 0x00])
self.assertEqual(writeVarLength(0x1FFFFF), [0xFF, 0xFF, 0x7F])
self.assertEqual(writeVarLength(0x08000000), [0xC0, 0x80, 0x80, 0x00])
def testAddNote(self):
MyMIDI = MIDIFile(1) # a format 1 file, so we increment the track number below
track = 0
channel = 0
pitch = 100
time = 0
duration = 1
volume = 100
MyMIDI.addNote(track, channel, pitch, time, duration, volume)
self.assertEqual(MyMIDI.tracks[1].eventList[0].evtname, "NoteOn")
self.assertEqual(MyMIDI.tracks[1].eventList[0].pitch, pitch)
self.assertEqual(MyMIDI.tracks[1].eventList[0].tick, MyMIDI.time_to_ticks(time))
self.assertEqual(MyMIDI.tracks[1].eventList[0].duration, MyMIDI.time_to_ticks(duration))
self.assertEqual(MyMIDI.tracks[1].eventList[0].volume, volume)
def testShiftTrack(self):
track = 0
channel = 0
pitch = 100
time = 1
duration = 1
volume = 100
MyMIDI = MIDIFile(1)
MyMIDI.addNote(track, channel, pitch, time, duration, volume)
self.assertEqual(MyMIDI.tracks[1].eventList[0].evtname, "NoteOn")
self.assertEqual(MyMIDI.tracks[1].eventList[0].pitch, pitch)
self.assertEqual(MyMIDI.tracks[1].eventList[0].tick, MyMIDI.time_to_ticks(time))
self.assertEqual(MyMIDI.tracks[1].eventList[0].duration, MyMIDI.time_to_ticks(duration))
self.assertEqual(MyMIDI.tracks[1].eventList[0].volume, volume)
MyMIDI.shiftTracks()
self.assertEqual(MyMIDI.tracks[1].eventList[0].tick, 0)
def testDeinterleaveNotes(self):
MyMIDI = MIDIFile(1, adjust_origin=False)
track = 0
channel = 0
pitch = 100
time1 = 0
time2 = 1
duration = 2
volume = 100
MyMIDI.addNote(track, channel, pitch, time1, duration, volume) # on at 0 off at 2
MyMIDI.addNote(track, channel, pitch, time2, duration, volume + 1) # on at 1 off at 3
MyMIDI.close()
# ticks have already been converted to delta ticks
self.assertEqual(MyMIDI.tracks[1].MIDIEventList[0].evtname, 'NoteOn')
self.assertEqual(MyMIDI.tracks[1].MIDIEventList[0].tick, MyMIDI.time_to_ticks(time1))
self.assertEqual(MyMIDI.tracks[1].MIDIEventList[1].evtname, 'NoteOff')
self.assertEqual(MyMIDI.tracks[1].MIDIEventList[1].tick, MyMIDI.time_to_ticks(time2))
self.assertEqual(MyMIDI.tracks[1].MIDIEventList[2].evtname, 'NoteOn')
self.assertEqual(MyMIDI.tracks[1].MIDIEventList[2].tick, MyMIDI.time_to_ticks(time2 - time2))
self.assertEqual(MyMIDI.tracks[1].MIDIEventList[3].evtname, 'NoteOff')
self.assertEqual(
MyMIDI.tracks[1].MIDIEventList[3].tick, MyMIDI.time_to_ticks(time2 - time2 + duration)
)
def testTimeShift(self):
# With one track
MyMIDI = MIDIFile(1, adjust_origin=True)
track = 0
channel = 0
pitch = 100
time1 = 5
duration = 1
volume = 100
MyMIDI.addNote(track, channel, pitch, time1, duration, volume)
MyMIDI.close()
self.assertEqual(MyMIDI.tracks[1].MIDIEventList[0].evtname, 'NoteOn')
self.assertEqual(MyMIDI.tracks[1].MIDIEventList[0].tick, MyMIDI.time_to_ticks(0))
self.assertEqual(MyMIDI.tracks[1].MIDIEventList[1].evtname, 'NoteOff')
self.assertEqual(MyMIDI.tracks[1].MIDIEventList[1].tick, MyMIDI.time_to_ticks(duration))
# With two tracks
track2 = 1
MyMIDI = MIDIFile(2, adjust_origin=True)
MyMIDI.addNote(track, channel, pitch, time1, duration, volume)
time2 = 6
MyMIDI.addNote(track2, channel, pitch, time2, duration, volume)
MyMIDI.close()
# ticks have already been converted to delta ticks
self.assertEqual(MyMIDI.tracks[1].MIDIEventList[0].evtname, 'NoteOn')
self.assertEqual(MyMIDI.tracks[1].MIDIEventList[0].tick, MyMIDI.time_to_ticks(0))
self.assertEqual(MyMIDI.tracks[1].MIDIEventList[1].evtname, 'NoteOff')
self.assertEqual(MyMIDI.tracks[1].MIDIEventList[1].tick, MyMIDI.time_to_ticks(duration))
self.assertEqual(MyMIDI.tracks[2].MIDIEventList[0].evtname, 'NoteOn')
self.assertEqual(MyMIDI.tracks[2].MIDIEventList[0].tick, MyMIDI.time_to_ticks(0 + duration))
self.assertEqual(MyMIDI.tracks[2].MIDIEventList[1].evtname, 'NoteOff')
self.assertEqual(MyMIDI.tracks[2].MIDIEventList[1].tick, MyMIDI.time_to_ticks(0 + duration))
# Negative Time
MyMIDI = MIDIFile(1, adjust_origin=True)
track = 0
channel = 0
pitch = 100
time = -5
duration = 1
volume = 100
MyMIDI.addNote(track, channel, pitch, time, duration, volume)
MyMIDI.close()
self.assertEqual(MyMIDI.tracks[1].MIDIEventList[0].evtname, 'NoteOn')
self.assertEqual(MyMIDI.tracks[1].MIDIEventList[0].tick, MyMIDI.time_to_ticks(0))
self.assertEqual(MyMIDI.tracks[1].MIDIEventList[1].evtname, 'NoteOff')
self.assertEqual(MyMIDI.tracks[1].MIDIEventList[1].tick, MyMIDI.time_to_ticks(0 + duration))
# Negative time, two tracks
MyMIDI = MIDIFile(2, adjust_origin=True)
track = 0
channel = 0
pitch = 100
time = -1
duration = 1
volume = 100
MyMIDI.addNote(track, channel, pitch, time, duration, volume)
track2 = 1
time2 = 0
MyMIDI.addNote(track2, channel, pitch, time2, duration, volume)
MyMIDI.close()
self.assertEqual(MyMIDI.tracks[1].MIDIEventList[0].evtname, 'NoteOn')
self.assertEqual(MyMIDI.tracks[1].MIDIEventList[0].tick, MyMIDI.time_to_ticks(0))
self.assertEqual(MyMIDI.tracks[1].MIDIEventList[1].evtname, 'NoteOff')
self.assertEqual(MyMIDI.tracks[1].MIDIEventList[1].tick, MyMIDI.time_to_ticks(1))
self.assertEqual(MyMIDI.tracks[2].MIDIEventList[0].evtname, 'NoteOn')
self.assertEqual(MyMIDI.tracks[2].MIDIEventList[0].tick, MyMIDI.time_to_ticks(1))
self.assertEqual(MyMIDI.tracks[2].MIDIEventList[1].evtname, 'NoteOff')
self.assertEqual(MyMIDI.tracks[2].MIDIEventList[1].tick, MyMIDI.time_to_ticks(1))
def testFrequency(self):
freq = frequencyTransform(8.1758)
self.assertEqual(freq[0], 0x00)
self.assertEqual(freq[1], 0x00)
self.assertEqual(freq[2], 0x00)
freq = frequencyTransform(8.66196) # 8.6620 in MIDI documentation
self.assertEqual(freq[0], 0x01)
self.assertEqual(freq[1], 0x00)
self.assertEqual(freq[2], 0x00)
freq = frequencyTransform(440.00)
self.assertEqual(freq[0], 0x45)
self.assertEqual(freq[1], 0x00)
self.assertEqual(freq[2], 0x00)
freq = frequencyTransform(440.0016)
self.assertEqual(freq[0], 0x45)
self.assertEqual(freq[1], 0x00)
self.assertEqual(freq[2], 0x01)
freq = frequencyTransform(439.9984)
self.assertEqual(freq[0], 0x44)
self.assertEqual(freq[1], 0x7f)
self.assertEqual(freq[2], 0x7f)
freq = frequencyTransform(8372.0190)
self.assertEqual(freq[0], 0x78)
self.assertEqual(freq[1], 0x00)
self.assertEqual(freq[2], 0x00)
freq = frequencyTransform(8372.062) # 8372.0630 in MIDI documentation
self.assertEqual(freq[0], 0x78)
self.assertEqual(freq[1], 0x00)
self.assertEqual(freq[2], 0x01)
freq = frequencyTransform(13289.7300)
self.assertEqual(freq[0], 0x7F)
self.assertEqual(freq[1], 0x7F)
self.assertEqual(freq[2], 0x7E)
freq = frequencyTransform(12543.8760)
self.assertEqual(freq[0], 0x7F)
self.assertEqual(freq[1], 0x00)
self.assertEqual(freq[2], 0x00)
freq = frequencyTransform(8.2104) # Just plain wrong in documentation, as far as I can tell.
# self.assertEqual(freq[0], 0x0)
# self.assertEqual(freq[1], 0x0)
# self.assertEqual(freq[2], 0x1)
# Test the inverse
testFreq = 15.0
accuracy = 0.00001
x = returnFrequency(frequencyTransform(testFreq))
delta = abs(testFreq - x)
self.assertEqual(delta < (accuracy * testFreq), True)
testFreq = 200.0
x = returnFrequency(frequencyTransform(testFreq))
delta = abs(testFreq - x)
self.assertEqual(delta < (accuracy * testFreq), True)
testFreq = 400.0
x = returnFrequency(frequencyTransform(testFreq))
delta = abs(testFreq - x)
self.assertEqual(delta < (accuracy * testFreq), True)
testFreq = 440.0
x = returnFrequency(frequencyTransform(testFreq))
delta = abs(testFreq - x)
self.assertEqual(delta < (accuracy * testFreq), True)
testFreq = 1200.0
x = returnFrequency(frequencyTransform(testFreq))
delta = abs(testFreq - x)
self.assertEqual(delta < (accuracy * testFreq), True)
testFreq = 5000.0
x = returnFrequency(frequencyTransform(testFreq))
delta = abs(testFreq - x)
self.assertEqual(delta < (accuracy * testFreq), True)
testFreq = 12000.0
x = returnFrequency(frequencyTransform(testFreq))
delta = abs(testFreq - x)
self.assertEqual(delta < (accuracy * testFreq), True)
def testSysEx(self):
MyMIDI = MIDIFile(1)
MyMIDI.addSysEx(0, 0, 0, struct.pack('>B', 0x01))
MyMIDI.close()
data = Decoder(MyMIDI.tracks[1].MIDIdata)
self.assertEqual(MyMIDI.tracks[1].MIDIEventList[0].evtname, 'SysEx')
self.assertEqual(data.unpack_into_byte(0), 0x00)
self.assertEqual(data.unpack_into_byte(1), 0xf0)
self.assertEqual(data.unpack_into_byte(2), 3)
self.assertEqual(data.unpack_into_byte(3), 0x00)
self.assertEqual(data.unpack_into_byte(4), 0x01)
self.assertEqual(data.unpack_into_byte(5), 0xf7)
def testPitchWheel(self):
val = 1000
MyMIDI = MIDIFile(1)
MyMIDI.addPitchWheelEvent(0, 0, 0, val)
MyMIDI.close()
MSB = (val + 8192) >> 7
LSB = (val + 8192) & 0x7F
data = Decoder(MyMIDI.tracks[1].MIDIdata)
self.assertEqual(data.unpack_into_byte(0), 0x00) # time
self.assertEqual(data.unpack_into_byte(1), 224) # Code
self.assertEqual(data.unpack_into_byte(2), LSB)
self.assertEqual(data.unpack_into_byte(3), MSB)
val = -1000
MyMIDI = MIDIFile(1)
MyMIDI.addPitchWheelEvent(0, 0, 0, val)
MyMIDI.close()
MSB = (val + 8192) >> 7
LSB = (val + 8192) & 0x7F
data = Decoder(MyMIDI.tracks[1].MIDIdata)
self.assertEqual(data.unpack_into_byte(0), 0x00) # time
self.assertEqual(data.unpack_into_byte(1), 224) # Code
self.assertEqual(data.unpack_into_byte(2), LSB)
self.assertEqual(data.unpack_into_byte(3), MSB)
def testTempo(self):
tempo = 60
MyMIDI = MIDIFile(1, file_format=2)
MyMIDI.addTempo(0, 0, tempo)
MyMIDI.close()
data = Decoder(MyMIDI.tracks[0].MIDIdata)
self.assertEqual(MyMIDI.tracks[0].MIDIEventList[0].evtname, 'Tempo')
self.assertEqual(data.unpack_into_byte(0), 0x00) # time
self.assertEqual(data.unpack_into_byte(1), 0xff) # Code
self.assertEqual(data.unpack_into_byte(2), 0x51)
self.assertEqual(data.unpack_into_byte(3), 0x03)
self.assertEqual(data[4:7], struct.pack('>L', int(60000000 / tempo))[1:4])
# Also check the format 1 file
tempo = 60
MyMIDI = MIDIFile(2, file_format=1)
MyMIDI.addTempo(1, 0, tempo)
MyMIDI.close()
data = Decoder(MyMIDI.tracks[0].MIDIdata)
self.assertEqual(MyMIDI.tracks[0].MIDIEventList[0].evtname, 'Tempo')
self.assertEqual(data.unpack_into_byte(0), 0x00) # time
self.assertEqual(data.unpack_into_byte(1), 0xff) # Code
self.assertEqual(data.unpack_into_byte(2), 0x51)
self.assertEqual(data.unpack_into_byte(3), 0x03)
self.assertEqual(data[4:7], struct.pack('>L', int(60000000 / tempo))[1:4])
def testCopyright(self):
notice = "2016(C) MCW"
MyMIDI = MIDIFile(1)
MyMIDI.addCopyright(0, 0, notice)
MyMIDI.close()
payload_encoded = notice.encode("ISO-8859-1")
payloadLength = len(payload_encoded)
payloadLengthVar = writeVarLength(payloadLength)
data = Decoder(MyMIDI.tracks[1].MIDIdata)
self.assertEqual(MyMIDI.tracks[1].MIDIEventList[0].evtname, 'Copyright')
self.assertEqual(data.unpack_into_byte(0), 0x00) # time
self.assertEqual(data.unpack_into_byte(1), 0xff) # Code
self.assertEqual(data.unpack_into_byte(2), 0x02) # Subcode
index = 3
for i in range(len(payloadLengthVar)):
self.assertEqual(data.unpack_into_byte(index), payloadLengthVar[i])
index = index + 1
for i in range(len(payload_encoded)):
if sys.version_info < (3, ):
test_char = ord(payload_encoded[i])
else:
test_char = payload_encoded[i]
self.assertEqual(data.unpack_into_byte(index), test_char)
index = index + 1
def testText(self):
text = "2016(C) MCW"
MyMIDI = MIDIFile(1)
MyMIDI.addText(0, 0, text)
MyMIDI.close()
payload_encoded = text.encode("ISO-8859-1")
payloadLength = len(payload_encoded)
payloadLengthVar = writeVarLength(payloadLength)
data = Decoder(MyMIDI.tracks[1].MIDIdata)
self.assertEqual(MyMIDI.tracks[1].MIDIEventList[0].evtname, 'Text')
self.assertEqual(data.unpack_into_byte(0), 0x00) # time
self.assertEqual(data.unpack_into_byte(1), 0xff) # Code
self.assertEqual(data.unpack_into_byte(2), 0x01) # Subcode
index = 3
for i in range(len(payloadLengthVar)):
self.assertEqual(data.unpack_into_byte(index), payloadLengthVar[i])
index = index + 1
for i in range(len(payload_encoded)):
if sys.version_info < (3, ):
test_char = ord(payload_encoded[i])
else:
test_char = payload_encoded[i]
self.assertEqual(data.unpack_into_byte(index), test_char)
index = index + 1
def testTimeSignature(self):
time = 0
track = 0
numerator = 4
denominator = 2
clocks_per_tick = 24
MyMIDI = MIDIFile(1, file_format=2)
MyMIDI.addTimeSignature(track, time, numerator, denominator, clocks_per_tick)
MyMIDI.close()
data = Decoder(MyMIDI.tracks[0].MIDIdata)
self.assertEqual(MyMIDI.tracks[0].MIDIEventList[0].evtname, 'TimeSignature')
self.assertEqual(data.unpack_into_byte(0), 0x00) # time
self.assertEqual(data.unpack_into_byte(1), 0xFF) # Code
self.assertEqual(data.unpack_into_byte(2), 0x58) # subcode
self.assertEqual(data.unpack_into_byte(3), 0x04) # Data length
self.assertEqual(data.unpack_into_byte(4), numerator)
self.assertEqual(data.unpack_into_byte(5), denominator)
self.assertEqual(data.unpack_into_byte(6), clocks_per_tick) # Data length
self.assertEqual(data.unpack_into_byte(7), 0x08) # 32nd notes per quarter note
# We also want to check with a format 1 file, make sure it ends up in
# the tempo track
time = 0
track = 1
numerator = 4
denominator = 2
clocks_per_tick = 24
MyMIDI = MIDIFile(2, file_format=1)
MyMIDI.addTimeSignature(track, time, numerator, denominator, clocks_per_tick)
MyMIDI.close()
data = Decoder(MyMIDI.tracks[0].MIDIdata)
self.assertEqual(MyMIDI.tracks[0].MIDIEventList[0].evtname, 'TimeSignature')
self.assertEqual(data.unpack_into_byte(0), 0x00) # time
self.assertEqual(data.unpack_into_byte(1), 0xFF) # Code
self.assertEqual(data.unpack_into_byte(2), 0x58) # subcode
self.assertEqual(data.unpack_into_byte(3), 0x04) # Data length
self.assertEqual(data.unpack_into_byte(4), numerator)
self.assertEqual(data.unpack_into_byte(5), denominator)
self.assertEqual(data.unpack_into_byte(6), clocks_per_tick) # Data length
self.assertEqual(data.unpack_into_byte(7), 0x08) # 32nd notes per quarter note
def testKeySignature(self):
time = 0
track = 0
accidentals = 3
accidental_type = MINOR
mode = MAJOR
MyMIDI = MIDIFile(1)
MyMIDI.addKeySignature(track, time, accidentals, accidental_type, mode)
MyMIDI.close()
data = Decoder(MyMIDI.tracks[0].MIDIdata)
self.assertEqual(MyMIDI.tracks[0].MIDIEventList[0].evtname, 'KeySignature')
self.assertEqual(data.unpack_into_byte(0), 0x00) # time
self.assertEqual(data.unpack_into_byte(1), 0xFF) # Code
self.assertEqual(data.unpack_into_byte(2), 0x59) # subcode
self.assertEqual(data.unpack_into_byte(3), 0x02) # Event subtype
self.assertEqual(data.unpack_into_byte(4), accidentals * accidental_type)
self.assertEqual(data.unpack_into_byte(5), mode)
def testProgramChange(self):
program = 10
channel = 0
tracknum = 0
realtracknum = tracknum
time = 0.0
MyMIDI = MIDIFile(1)
if MyMIDI.header.numeric_format == 1:
realtracknum = tracknum + 1
MyMIDI.addProgramChange(tracknum, channel, time, program)
MyMIDI.close()
data = Decoder(MyMIDI.tracks[realtracknum].MIDIdata)
self.assertEqual(MyMIDI.tracks[realtracknum].MIDIEventList[0].evtname, 'ProgramChange')
self.assertEqual(data.unpack_into_byte(0), 0x00) # time
self.assertEqual(data.unpack_into_byte(1), 0xC << 4 | channel) # Code
self.assertEqual(data.unpack_into_byte(2), program)
def testChannelPressure(self):
pressure = 10
channel = 0
time = 0.0
tracknum = 0
realtracknum = tracknum
MyMIDI = MIDIFile(1)
if MyMIDI.header.numeric_format == 1:
realtracknum = tracknum + 1
MyMIDI.addChannelPressure(tracknum, channel, time, pressure)
MyMIDI.close()
data = Decoder(MyMIDI.tracks[realtracknum].MIDIdata)
self.assertEqual(MyMIDI.tracks[realtracknum].MIDIEventList[0].evtname, 'ChannelPressure')
self.assertEqual(data.unpack_into_byte(0), 0x00) # time
self.assertEqual(data.unpack_into_byte(1), 0xD0 | channel) # Code
self.assertEqual(data.unpack_into_byte(2), pressure)
def testTrackName(self):
track_name = "track"
MyMIDI = MIDIFile(1)
MyMIDI.addTrackName(0, 0, track_name)
MyMIDI.close()
data = Decoder(MyMIDI.tracks[1].MIDIdata)
self.assertEqual(MyMIDI.tracks[1].MIDIEventList[0].evtname, 'TrackName')
self.assertEqual(data.unpack_into_byte(0), 0x00) # time
self.assertEqual(data.unpack_into_byte(1), 0xFF) # Code
self.assertEqual(data.unpack_into_byte(2), 0x03) # subcodes
def testLongTrackName(self):
track_name = 'long track name ' * 8
MyMIDI = MIDIFile(1)
MyMIDI.addTrackName(0, 0, track_name)
MyMIDI.close()
data = Decoder(MyMIDI.tracks[1].MIDIdata)
self.assertEqual(MyMIDI.tracks[1].MIDIEventList[0].evtname, 'TrackName')
self.assertEqual(data.unpack_into_byte(0), 0x00) # time
self.assertEqual(data.unpack_into_byte(1), 0xFF) # Code
self.assertEqual(data.unpack_into_byte(2), 0x03) # subcodes
def testTuningBank(self):
bank = 1
channel = 0
MyMIDI = MIDIFile(1)
MyMIDI.changeTuningBank(0, 0, 0, bank)
MyMIDI.close()
data = Decoder(MyMIDI.tracks[1].MIDIdata)
self.assertEqual(MyMIDI.tracks[1].MIDIEventList[0].evtname, 'ControllerEvent')
self.assertEqual(data.unpack_into_byte(0), 0x00) # time
self.assertEqual(data.unpack_into_byte(1), 0xB << 4 | channel) # Code
self.assertEqual(data.unpack_into_byte(2), 0x65) # Controller Number
self.assertEqual(data.unpack_into_byte(3), 0x0) # Controller Value
self.assertEqual(data.unpack_into_byte(4), 0x00) # time
self.assertEqual(data.unpack_into_byte(5), 0xB << 4 | channel) # Code
self.assertEqual(data.unpack_into_byte(6), 0x64) # Controller Number
self.assertEqual(data.unpack_into_byte(7), 0x4) # Controller Value
self.assertEqual(data.unpack_into_byte(8), 0x00) # time
self.assertEqual(data.unpack_into_byte(9), 0xB << 4 | channel) # Code
self.assertEqual(data.unpack_into_byte(10), 0x06) # Bank MSB
self.assertEqual(data.unpack_into_byte(11), 0x00) # Value
self.assertEqual(data.unpack_into_byte(12), 0x00) # time
self.assertEqual(data.unpack_into_byte(13), 0xB << 4 | channel) # Code
self.assertEqual(data.unpack_into_byte(14), 0x26) # Bank LSB
self.assertEqual(data.unpack_into_byte(15), bank) # Bank value (bank number)
def testTuningBankWithTimeOrder(self):
bank = 1
MyMIDI = MIDIFile(1)
MyMIDI.changeTuningBank(0, 0, 0, bank, time_order=True)
MyMIDI.close()
data = Decoder(MyMIDI.tracks[1].MIDIdata)
self.assertEqual(MyMIDI.tracks[1].MIDIEventList[0].evtname, 'ControllerEvent')
self.assertEqual(data.unpack_into_byte(0), 0x00) # time
self.assertEqual(data.unpack_into_byte(4), 0x01) # time
self.assertEqual(data.unpack_into_byte(8), 0x01) # time
self.assertEqual(data.unpack_into_byte(12), 0x01) # time
def testTuningProgram(self):
program = 10
channel = 0
MyMIDI = MIDIFile(1)
MyMIDI.changeTuningProgram(0, 0, 0, program)
MyMIDI.close()
data = Decoder(MyMIDI.tracks[1].MIDIdata)
self.assertEqual(MyMIDI.tracks[1].MIDIEventList[0].evtname, 'ControllerEvent')
self.assertEqual(data.unpack_into_byte(0), 0x00) # time
self.assertEqual(data.unpack_into_byte(1), 0xB << 4 | channel) # Code
self.assertEqual(data.unpack_into_byte(2), 0x65) # Controller Number
self.assertEqual(data.unpack_into_byte(3), 0x0) # Controller Value
self.assertEqual(data.unpack_into_byte(4), 0x00) # time
self.assertEqual(data.unpack_into_byte(5), 0xB << 4 | channel) # Code
self.assertEqual(data.unpack_into_byte(6), 0x64) # Controller Number
self.assertEqual(data.unpack_into_byte(7), 0x03) # Controller Value
self.assertEqual(data.unpack_into_byte(8), 0x00) # time
self.assertEqual(data.unpack_into_byte(9), 0xB << 4 | channel) # Code
self.assertEqual(data.unpack_into_byte(10), 0x06) # Bank MSB
self.assertEqual(data.unpack_into_byte(11), 0x00) # Value
self.assertEqual(data.unpack_into_byte(12), 0x00) # time
self.assertEqual(data.unpack_into_byte(13), 0xB << 4 | channel) # Code
self.assertEqual(data.unpack_into_byte(14), 0x26) # Bank LSB
self.assertEqual(data.unpack_into_byte(15), program) # Bank value (bank number)
def testTuningProgramWithTimeOrder(self):
program = 10
MyMIDI = MIDIFile(1)
MyMIDI.changeTuningProgram(0, 0, 0, program, time_order=True)
MyMIDI.close()
data = Decoder(MyMIDI.tracks[1].MIDIdata)
self.assertEqual(MyMIDI.tracks[1].MIDIEventList[0].evtname, 'ControllerEvent')
self.assertEqual(data.unpack_into_byte(0), 0x00) # time
self.assertEqual(data.unpack_into_byte(4), 0x01) # time
self.assertEqual(data.unpack_into_byte(8), 0x01) # time
self.assertEqual(data.unpack_into_byte(12), 0x01) # time
def testNRPNCall(self):
track = 0
time = 0
channel = 0
controller_msb = 1
controller_lsb = 2
data_msb = 3
data_lsb = 4
MyMIDI = MIDIFile(1)
MyMIDI.makeNRPNCall(track, channel, time, controller_msb, controller_lsb, data_msb, data_lsb)
MyMIDI.close()
data = Decoder(MyMIDI.tracks[1].MIDIdata)
self.assertEqual(MyMIDI.tracks[1].MIDIEventList[0].evtname, 'ControllerEvent')
self.assertEqual(data.unpack_into_byte(0), 0x00) # time
self.assertEqual(data.unpack_into_byte(1), 0xB << 4 | channel) # Code
self.assertEqual(data.unpack_into_byte(2), 99) # Controller Number
self.assertEqual(data.unpack_into_byte(3), controller_msb) # Controller Value
self.assertEqual(data.unpack_into_byte(4), 0x00) # time
self.assertEqual(data.unpack_into_byte(5), 0xB << 4 | channel) # Code
self.assertEqual(data.unpack_into_byte(6), 98) # Controller Number
self.assertEqual(data.unpack_into_byte(7), controller_lsb) # Controller Value
self.assertEqual(data.unpack_into_byte(8), 0x00) # time
self.assertEqual(data.unpack_into_byte(9), 0xB << 4 | channel) # Code
self.assertEqual(data.unpack_into_byte(10), 0x06) # Bank MSB
self.assertEqual(data.unpack_into_byte(11), data_msb) # Value
self.assertEqual(data.unpack_into_byte(12), 0x00) # time
self.assertEqual(data.unpack_into_byte(13), 0xB << 4 | channel) # Code
self.assertEqual(data.unpack_into_byte(14), 0x26) # Bank LSB
self.assertEqual(data.unpack_into_byte(15), data_lsb) # Bank value (bank number)
def testNRPNCallWithTimeOrder(self):
track = 0
time = 0
channel = 0
controller_msb = 1
controller_lsb = 2
data_msb = 3
data_lsb = 4
MyMIDI = MIDIFile(1)
MyMIDI.makeNRPNCall(
track, channel, time, controller_msb, controller_lsb, data_msb, data_lsb, time_order=True
)
MyMIDI.close()
data = Decoder(MyMIDI.tracks[1].MIDIdata)
self.assertEqual(MyMIDI.tracks[1].MIDIEventList[0].evtname, 'ControllerEvent')
self.assertEqual(data.unpack_into_byte(0), 0x00) # time
self.assertEqual(data.unpack_into_byte(1), 0xB << 4 | channel) # Code
self.assertEqual(data.unpack_into_byte(2), 99) # Controller Number
self.assertEqual(data.unpack_into_byte(3), controller_msb) # Controller Value
self.assertEqual(data.unpack_into_byte(4), 0x01) # time
self.assertEqual(data.unpack_into_byte(5), 0xB << 4 | channel) # Code
self.assertEqual(data.unpack_into_byte(6), 98) # Controller Number
self.assertEqual(data.unpack_into_byte(7), controller_lsb) # Controller Value
self.assertEqual(data.unpack_into_byte(8), 0x01) # time
self.assertEqual(data.unpack_into_byte(9), 0xB << 4 | channel) # Code
self.assertEqual(data.unpack_into_byte(10), 0x06) # Bank MSB
self.assertEqual(data.unpack_into_byte(11), data_msb) # Value
self.assertEqual(data.unpack_into_byte(12), 0x01) # time
self.assertEqual(data.unpack_into_byte(13), 0xB << 4 | channel) # Code
self.assertEqual(data.unpack_into_byte(14), 0x26) # Bank LSB
self.assertEqual(data.unpack_into_byte(15), data_lsb) # Bank value (bank number)
def testAddControllerEvent(self):
track = 0
time = 0
channel = 3
controller_number = 1
parameter = 2
MyMIDI = MIDIFile(1)
MyMIDI.addControllerEvent(track, channel, time, controller_number, parameter)
MyMIDI.close()
data = Decoder(MyMIDI.tracks[1].MIDIdata)
self.assertEqual(MyMIDI.tracks[1].MIDIEventList[0].evtname, 'ControllerEvent')
self.assertEqual(data.unpack_into_byte(0), 0x00) # time
self.assertEqual(data.unpack_into_byte(1), 0xB << 4 | channel) # Code
self.assertEqual(data.unpack_into_byte(2), controller_number) # Controller Number
self.assertEqual(data.unpack_into_byte(3), parameter) # Controller Value
def testNonRealTimeUniversalSysEx(self):
code = 1
subcode = 2
payload_number = 42
payload = struct.pack('>B', payload_number)
MyMIDI = MIDIFile(1, adjust_origin=False)
# Just for fun we'll use a multi-byte time
time = 1
time_bytes = writeVarLength(time * MyMIDI.ticks_per_quarternote)
MyMIDI.addUniversalSysEx(0, time, code, subcode, payload, realTime=False)
MyMIDI.close()
data = Decoder(MyMIDI.tracks[1].MIDIdata)
self.assertEqual(MyMIDI.tracks[1].MIDIEventList[0].evtname, 'UniversalSysEx')
self.assertEqual(data.unpack_into_byte(0), time_bytes[0]) # Time
self.assertEqual(data.unpack_into_byte(1), time_bytes[1]) # Time
self.assertEqual(data.unpack_into_byte(2), 0xf0) # UniversalSysEx == 0xF0
self.assertEqual(data.unpack_into_byte(3), 5 + len(payload)) # Payload length = 5+actual pyayload
self.assertEqual(data.unpack_into_byte(4), 0x7E) # 0x7E == non-realtime
self.assertEqual(data.unpack_into_byte(5), 0x7F) # Sysex channel (always 0x7F)
self.assertEqual(data.unpack_into_byte(6), code)
self.assertEqual(data.unpack_into_byte(7), subcode)
self.assertEqual(data.unpack_into_byte(8), payload_number) # Data
self.assertEqual(data.unpack_into_byte(9), 0xf7) # End of message
def testRealTimeUniversalSysEx(self):
code = 1
subcode = 2
payload_number = 47
payload = struct.pack('>B', payload_number)
MyMIDI = MIDIFile(1)
MyMIDI.addUniversalSysEx(0, 0, code, subcode, payload, realTime=True)
MyMIDI.close()
data = Decoder(MyMIDI.tracks[1].MIDIdata)
self.assertEqual(MyMIDI.tracks[1].MIDIEventList[0].evtname, 'UniversalSysEx')
self.assertEqual(data.unpack_into_byte(0), 0x00)
self.assertEqual(data.unpack_into_byte(1), 0xf0)
self.assertEqual(data.unpack_into_byte(2), 5 + len(payload))
self.assertEqual(data.unpack_into_byte(3), 0x7F) # 0x7F == real-time
self.assertEqual(data.unpack_into_byte(4), 0x7F)
self.assertEqual(data.unpack_into_byte(5), code)
self.assertEqual(data.unpack_into_byte(6), subcode)
self.assertEqual(data.unpack_into_byte(7), payload_number)
self.assertEqual(data.unpack_into_byte(8), 0xf7)
def testTuning(self):
MyMIDI = MIDIFile(1)
MyMIDI.changeNoteTuning(0, [(1, 440), (2, 880)])
MyMIDI.close()
data = Decoder(MyMIDI.tracks[1].MIDIdata)
self.assertEqual(MyMIDI.tracks[1].MIDIEventList[0].evtname, 'UniversalSysEx')
self.assertEqual(data.unpack_into_byte(0), 0x00)
self.assertEqual(data.unpack_into_byte(1), 0xf0)
self.assertEqual(data.unpack_into_byte(2), 15)
self.assertEqual(data.unpack_into_byte(3), 0x7F)
self.assertEqual(data.unpack_into_byte(4), 0x7F)
self.assertEqual(data.unpack_into_byte(5), 0x08)
self.assertEqual(data.unpack_into_byte(6), 0x02)
self.assertEqual(data.unpack_into_byte(7), 0x00)
self.assertEqual(data.unpack_into_byte(8), 0x2)
self.assertEqual(data.unpack_into_byte(9), 0x1)
self.assertEqual(data.unpack_into_byte(10), 69)
self.assertEqual(data.unpack_into_byte(11), 0)
self.assertEqual(data.unpack_into_byte(12), 0)
self.assertEqual(data.unpack_into_byte(13), 0x2)
self.assertEqual(data.unpack_into_byte(14), 81)
self.assertEqual(data.unpack_into_byte(15), 0)
self.assertEqual(data.unpack_into_byte(16), 0)
self.assertEqual(data.unpack_into_byte(17), 0xf7)
def testWriteFile(self):
# Just to make sure the stream can be written without throwing an error.
MyMIDI = MIDIFile(1)
MyMIDI.addNote(0, 0, 100, 0, 1, 100)
with open("/tmp/test.mid", "wb") as output_file:
MyMIDI.writeFile(output_file)
def testAdjustOrigin(self):
track = 0
channel = 0
pitch = 69
time = 1
duration = 0.1
volume = 64
MyMIDI = MIDIFile(1, adjust_origin=True)
MyMIDI.addNote(track, channel, pitch, time, duration, volume)
time = 1.1
MyMIDI.addNote(track, channel, pitch, time, duration, volume)
MyMIDI.close()
data = Decoder(MyMIDI.tracks[1].MIDIdata)
self.assertEqual(data.unpack_into_byte(0), 0x00) # first time
self.assertEqual(data.unpack_into_byte(8), 0x00) # seconds time
MyMIDI = MIDIFile(1, adjust_origin=False)
time = 0.1
MyMIDI.addNote(track, channel, pitch, time, duration, volume)
time = 0.2
MyMIDI.addNote(track, channel, pitch, time, duration, volume)
MyMIDI.close()
data = Decoder(MyMIDI.tracks[1].MIDIdata)
self.assertEqual(
data.unpack_into_byte(0), MyMIDI.ticks_per_quarternote / 10
) # first time, should be an integer < 127
self.assertEqual(data.unpack_into_byte(8), 0x00) # first time
def testMultiClose(self):
track = 0
channel = 0
pitch = 69
time = 0
duration = 1.0
volume = 64
MyMIDI = MIDIFile(1)
MyMIDI.addNote(track, channel, pitch, time, duration, volume)
MyMIDI.close()
data_length_1 = len(MyMIDI.tracks[0].MIDIdata)
MyMIDI.close()
data_length_2 = len(MyMIDI.tracks[0].MIDIdata)
self.assertEqual(data_length_1, data_length_2)
MyMIDI.tracks[0].closeTrack()
data_length_3 = len(MyMIDI.tracks[0].MIDIdata)
self.assertEqual(data_length_1, data_length_3)
def testEmptyEventList(self):
MyMIDI = MIDIFile(1)
MyMIDI.close()
data_length = len(MyMIDI.tracks[0].MIDIdata)
self.assertEqual(data_length, 4) # Header length 4
def testRemoveDuplicates(self):
# First notes
track = 0
channel = 0
pitch = 69
time = 0
duration = 1
volume = 64
MyMIDI = MIDIFile(1)
MyMIDI.addNote(track, channel, pitch, time, duration, volume) # also adds a corresponding NoteOff
MyMIDI.addNote(track, channel, pitch, time, duration, volume) # also adds a corresponding NoteOff
MyMIDI.close()
self.assertEqual(2, len(MyMIDI.tracks[1].eventList)) # One NoteOn event, one NoteOff event
MyMIDI = MIDIFile(1)
MyMIDI.addNote(track, channel, pitch, time, duration, volume)
pitch = 70
MyMIDI.addNote(track, channel, pitch, time, duration, volume)
MyMIDI.close()
self.assertEqual(4, len(MyMIDI.tracks[1].eventList)) # Two NoteOn events, two NoteOff events
# Next tempo
tempo = 60
track = 0
time = 0
MyMIDI = MIDIFile(1)
MyMIDI.addTempo(track, time, tempo)
MyMIDI.addTempo(track, time, tempo)
MyMIDI.close()
self.assertEqual(1, len(MyMIDI.tracks[0].eventList))
MyMIDI = MIDIFile(1)
MyMIDI.addTempo(track, time, tempo)
tempo = 80
MyMIDI.addTempo(track, time, tempo)
MyMIDI.close()
self.assertEqual(2, len(MyMIDI.tracks[0].eventList))
# Program Number
time = 0
track = 0
program = 10
channel = 0
MyMIDI = MIDIFile(1)
MyMIDI.addProgramChange(track, channel, time, program)
MyMIDI.addProgramChange(track, channel, time, program)
MyMIDI.close()
self.assertEqual(1, len(MyMIDI.tracks[track + 1].eventList))
MyMIDI = MIDIFile(1)
MyMIDI.addProgramChange(track, channel, time, program)
program = 11
MyMIDI.addProgramChange(track, channel, time, program)
MyMIDI.close()
self.assertEqual(2, len(MyMIDI.tracks[track + 1].eventList))
# Track Name
track = 0
time = 0
track_name = "track"
MyMIDI = MIDIFile(1)
MyMIDI.addTrackName(track, time, track_name)
MyMIDI.addTrackName(track, time, track_name)
MyMIDI.close()
self.assertEqual(1, len(MyMIDI.tracks[1].eventList))
MyMIDI = MIDIFile(1)
MyMIDI.addTrackName(track, time, track_name)
track_name = "track 2"
MyMIDI.addTrackName(track, time, track_name)
MyMIDI.close()
self.assertEqual(2, len(MyMIDI.tracks[1].eventList))
# SysEx. These are never removed
track = 0
time = 0
manufacturer = 10
MyMIDI = MIDIFile(1)
MyMIDI.addSysEx(track, time, manufacturer, struct.pack('>B', 0x01))
MyMIDI.addSysEx(track, time, manufacturer, struct.pack('>B', 0x01))
MyMIDI.close()
self.assertEqual(2, len(MyMIDI.tracks[1].eventList))
# UniversalSysEx. Same thing -- never remove
track = 0
time = 0
code = 1
subcode = 2
payload_number = 47
payload = struct.pack('>B', payload_number)
MyMIDI = MIDIFile(1)
MyMIDI.addUniversalSysEx(track, time, code, subcode, payload, realTime=True)
MyMIDI.addUniversalSysEx(track, time, code, subcode, payload, realTime=True)
MyMIDI.close()
self.assertEqual(2, len(MyMIDI.tracks[1].eventList))
def suite():
MIDISuite = unittest.TestLoader().loadTestsFromTestCase(TestMIDIUtils)
return MIDISuite
if __name__ == '__main__':
print("Begining MIDIUtil Test Suite")
MIDISuite = suite()
runner = unittest.TextTestRunner(verbosity=2, stream=sys.stdout)
return_value = not runner.run(MIDISuite).wasSuccessful()
sys.exit(return_value)