1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187
|
# SPDX-FileCopyrightText: 2017 Ole Martin Bjorndalen <ombdalen@gmail.com>
#
# SPDX-License-Identifier: MIT
import io
from pytest import raises
from mido.messages import Message
from mido.midifiles.meta import KeySignatureError, MetaMessage
from mido.midifiles.midifiles import MidiFile, MidiTrack
HEADER_ONE_TRACK = """
4d 54 68 64 # MThd
00 00 00 06 # Chunk size
00 01 # Type 1
00 01 # 1 track
00 78 # 120 ticks per beat
"""
def parse_hexdump(hexdump):
data = bytearray()
for line in hexdump.splitlines():
data += bytearray.fromhex(line.split('#')[0])
return data
def read_file(hexdump, clip=False):
return MidiFile(file=io.BytesIO(parse_hexdump(hexdump)), clip=clip)
def test_no_tracks():
assert read_file("""
4d 54 68 64 # MThd
00 00 00 06 # Chunk size
00 01 # Type 1
00 00 # 0 tracks
00 78 # 120 ticks per beat
""").tracks == []
def test_single_message():
assert read_file(HEADER_ONE_TRACK + """
4d 54 72 6b # MTrk
00 00 00 04
20 90 40 40 # note_on
""").tracks[0] == [Message('note_on', note=64, velocity=64, time=32)]
def test_too_long_message():
with raises(IOError):
read_file(HEADER_ONE_TRACK + """
4d 54 72 6b # MTrk
00 00 00 04
00 ff 03 ff ff 7f # extremely long track name message
""")
def test_two_tracks():
mid = read_file("""
4d54 6864 0000 0006 0001 0002 0040 # Header
4d54 726b 0000 0008 00 90 40 10 40 80 40 10 # Track 0
4d54 726b 0000 0008 00 90 47 10 40 80 47 10 # Track 1
""")
assert len(mid.tracks) == 2
# TODO: add some more tests here.
def test_empty_file():
with raises(EOFError):
read_file("")
def test_eof_in_track():
with raises(EOFError):
read_file(HEADER_ONE_TRACK + """
4d 54 72 6b # MTrk
00 00 00 01 # Chunk size
# Oops, no data here.
""")
def test_invalid_data_byte_no_clipping():
# TODO: should this raise IOError?
with raises(IOError):
read_file(HEADER_ONE_TRACK + """
4d 54 72 6b # MTrk
00 00 00 04 # Chunk size
00 90 ff 40 # note_on note=255 velocity=64
""")
def test_invalid_data_byte_with_clipping_high():
midi_file = read_file(HEADER_ONE_TRACK + """
4d 54 72 6b # MTrk
00 00 00 04 # Chunk size
00 90 ff 40 # note_on note=255 velocity=64
""", clip=True)
assert midi_file.tracks[0][0].note == 127
def test_meta_messages():
# TODO: should this raise IOError?
mid = read_file(HEADER_ONE_TRACK + """
4d 54 72 6b # MTrk
00 00 00 0c # Chunk size
00 ff 03 04 54 65 73 74 # track_name name='Test'
00 ff 2f 00 # end_of_track
""")
track = mid.tracks[0]
assert track[0] == MetaMessage('track_name', name='Test')
assert track[1] == MetaMessage('end_of_track')
def test_meta_message_bad_key_sig_throws_key_signature_error_sharps():
with raises(KeySignatureError):
read_file(HEADER_ONE_TRACK + """
4d 54 72 6b # MTrk
00 00 00 09 # Chunk size
00 ff 59 02 08 # Key Signature with 8 sharps
00 ff 2f 00 # end_of_track
""")
def test_meta_message_bad_key_sig_throws_key_signature_error_flats():
with raises(KeySignatureError):
read_file(HEADER_ONE_TRACK + """
4d 54 72 6b # MTrk
00 00 00 09 # Chunk size
00 ff 59 02 f8 # Key Signature with 8 flats
00 ff 2f 00 # end_of_track
""")
def test_meta_messages_with_length_0():
"""sequence_number and midi_port with no data bytes should be accepted.
In rare cases these messages have length 0 and thus no data
bytes. (See issues 42 and 93.) It is unclear why these messages
are missing their data. It could be cased by a bug in the software
that created the files.
So far this has been fixed by adding a test to each of these two
meta message classes. If the problem appears with other message
types it may be worth finding a more general solution.
"""
mid = read_file(HEADER_ONE_TRACK + """
4d 54 72 6b # MTrk
00 00 00 17
00 ff 00 00 # sequence_number with no data bytes (should be 2).
00 ff 21 00 # midi_port with no data bytes (should be 1).
00 ff 00 02 00 01 # sequence_number with correct number of data bytes (2).
00 ff 21 01 01 # midi_port with correct number of data bytes (1).
00 ff 2f 00
""")
assert mid.tracks[0] == [
MetaMessage('sequence_number', number=0),
MetaMessage('midi_port', port=0),
MetaMessage('sequence_number', number=1),
MetaMessage('midi_port', port=1),
MetaMessage('end_of_track'),
]
def test_midifile_repr():
midifile = MidiFile(type=1, ticks_per_beat=123, tracks=[
MidiTrack([
Message('note_on', channel=1, note=2, time=3),
Message('note_off', channel=1, note=2, time=3)]),
MidiTrack([
MetaMessage('sequence_number', number=5),
Message('note_on', channel=2, note=6, time=9),
Message('note_off', channel=2, note=6, time=9)]),
])
midifile_eval = eval(repr(midifile)) # noqa: S307
for track, track_eval in zip(midifile.tracks, midifile_eval.tracks):
for m1, m2 in zip(track, track_eval):
assert m1 == m2
|