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
|
# SPDX-FileCopyrightText: 2016 Ole Martin Bjorndalen <ombdalen@gmail.com>
#
# SPDX-License-Identifier: MIT
from .meta import MetaMessage
class MidiTrack(list):
@property
def name(self):
"""Name of the track.
This will return the name from the first track_name meta
message in the track, or '' if there is no such message.
Setting this property will update the name field of the first
track_name message in the track. If no such message is found,
one will be added to the beginning of the track with a delta
time of 0."""
for message in self:
if message.type == 'track_name':
return message.name
else:
return ''
@name.setter
def name(self, name):
# Find the first track_name message and modify it.
for message in self:
if message.type == 'track_name':
message.name = name
return
else:
# No track name found, add one.
self.insert(0, MetaMessage('track_name', name=name, time=0))
def copy(self):
return self.__class__(self)
def __getitem__(self, index_or_slice):
# Retrieve item from the MidiTrack
lst = list.__getitem__(self, index_or_slice)
if isinstance(index_or_slice, int):
# If an index was provided, return the list element
return lst
else:
# Otherwise, construct a MidiTrack to return.
# TODO: this make a copy of the list. Is there a better way?
return self.__class__(lst)
def __add__(self, other):
return self.__class__(list.__add__(self, other))
def __mul__(self, other):
return self.__class__(list.__mul__(self, other))
def __repr__(self):
if len(self) == 0:
messages = ''
elif len(self) == 1:
messages = f'[{self[0]}]'
else:
messages = '[\n {}]'.format(',\n '.join(repr(m) for m in self))
return f'{self.__class__.__name__}({messages})'
def _to_abstime(messages, skip_checks=False):
"""Convert messages to absolute time."""
now = 0
for msg in messages:
now += msg.time
yield msg.copy(skip_checks=skip_checks, time=now)
def _to_reltime(messages, skip_checks=False):
"""Convert messages to relative time."""
now = 0
for msg in messages:
delta = msg.time - now
yield msg.copy(skip_checks=skip_checks, time=delta)
now = msg.time
def fix_end_of_track(messages, skip_checks=False):
"""Remove all end_of_track messages and add one at the end.
This is used by merge_tracks() and MidiFile.save()."""
# Accumulated delta time from removed end of track messages.
# This is added to the next message.
accum = 0
for msg in messages:
if msg.type == 'end_of_track':
accum += msg.time
else:
if accum:
delta = accum + msg.time
yield msg.copy(skip_checks=skip_checks, time=delta)
accum = 0
else:
yield msg
yield MetaMessage('end_of_track', time=accum)
def merge_tracks(tracks, skip_checks=False):
"""Returns a MidiTrack object with all messages from all tracks.
The messages are returned in playback order with delta times
as if they were all in one track.
Pass skip_checks=True to skip validation of messages before merging.
This should ONLY be used when the messages in tracks have already
been validated by mido.checks.
"""
messages = []
for track in tracks:
messages.extend(_to_abstime(track, skip_checks=skip_checks))
messages.sort(key=lambda msg: msg.time)
return MidiTrack(
fix_end_of_track(
_to_reltime(messages, skip_checks=skip_checks),
skip_checks=skip_checks,
)
)
|