#!/usr/bin/env python # ----------------------------------------------------------------------------- # Name: miditest.py # Purpose: Unit testing harness for midiutil # # Author: Mark Conway Wirt # # 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)