Skip to content

Commit

Permalink
Load text events and lyrics for all tracks (#192)
Browse files Browse the repository at this point in the history
* Loading text events and lyrics for all tracks

* new line at eof

* documentation add
  • Loading branch information
gulnazaki authored Oct 21, 2020
1 parent 24681fc commit 5e3db4b
Show file tree
Hide file tree
Showing 3 changed files with 77 additions and 12 deletions.
7 changes: 7 additions & 0 deletions pretty_midi/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,13 @@
:members:
:undoc-members:
``pretty_midi.Text``
=====================
.. autoclass:: Text
:members:
:undoc-members:
Utility functions
=================
.. autofunction:: key_number_to_key_name
Expand Down
22 changes: 22 additions & 0 deletions pretty_midi/containers.py
Original file line number Diff line number Diff line change
Expand Up @@ -199,3 +199,25 @@ def __repr__(self):

def __str__(self):
return '"{}" at {:.2f} seconds'.format(self.text, self.time)

class Text(object):
"""Timestamped text event.
Attributes
----------
text : str
The text.
time : float
The time it occurs in seconds.
"""

def __init__(self, text, time):
self.text = text
self.time = time

def __repr__(self):
return 'Text(text="{}", time={})'.format(
self.text.replace('"', r'\"'), self.time)

def __str__(self):
return '"{}" at {:.2f} seconds'.format(self.text, self.time)
60 changes: 48 additions & 12 deletions pretty_midi/pretty_midi.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,11 @@
import copy
import functools
import six
from heapq import merge

from .instrument import Instrument
from .containers import (KeySignature, TimeSignature, Lyric, Note,
PitchBend, ControlChange)
PitchBend, ControlChange, Text)
from .utilities import (key_name_to_key_number, qpm_to_bpm)

# The largest we'd ever expect a tick to be
Expand Down Expand Up @@ -46,6 +47,8 @@ class PrettyMIDI(object):
List of :class:`pretty_midi.TimeSignature` objects.
lyrics : list
List of :class:`pretty_midi.Lyric` objects.
text_events : list
List of :class:`pretty_midi.Text` objects.
"""

def __init__(self, midi_file=None, resolution=220, initial_tempo=120.):
Expand Down Expand Up @@ -118,6 +121,8 @@ def __init__(self, midi_file=None, resolution=220, initial_tempo=120.):
self.time_signature_changes = []
# Empty lyrics list
self.lyrics = []
# Empty text events list
self.text_events = []

def _load_tempo_changes(self, midi_data):
"""Populates ``self._tick_scales`` with tuples of
Expand Down Expand Up @@ -157,7 +162,8 @@ def _load_tempo_changes(self, midi_data):
def _load_metadata(self, midi_data):
"""Populates ``self.time_signature_changes`` with ``TimeSignature``
objects, ``self.key_signature_changes`` with ``KeySignature`` objects,
and ``self.lyrics`` with ``Lyric`` objects.
``self.lyrics`` with ``Lyric`` objects and ``self.text_events`` with
``Text`` objects.
Parameters
----------
Expand All @@ -170,6 +176,7 @@ def _load_metadata(self, midi_data):
self.key_signature_changes = []
self.time_signature_changes = []
self.lyrics = []
self.text_events = []

for event in midi_data.tracks[0]:
if event.type == 'key_signature':
Expand All @@ -184,9 +191,31 @@ def _load_metadata(self, midi_data):
self.__tick_to_time[event.time])
self.time_signature_changes.append(ts_obj)

elif event.type == 'lyrics':
self.lyrics.append(Lyric(
event.text, self.__tick_to_time[event.time]))
# We search for lyrics and text events on all tracks
# Lists of lyrics and text events lists, for every track
tracks_with_lyrics = []
tracks_with_text_events = []
for track in midi_data.tracks:
# Track specific lists that get appended if not empty
lyrics = []
text_events = []
for event in track:
if event.type == 'lyrics':
lyrics.append(Lyric(
event.text, self.__tick_to_time[event.time]))
elif event.type == 'text':
text_events.append(Text(
event.text, self.__tick_to_time[event.time]))

if lyrics:
tracks_with_lyrics.append(lyrics)
if text_events:
tracks_with_text_events.append(text_events)

# We merge the already sorted lists for every track, based on time
self.lyrics = list(merge(*tracks_with_lyrics, key=lambda x: x.time))
self.text_events = list(merge(*tracks_with_text_events, key=lambda x: x.time))


def _update_tick_to_time(self, max_tick):
"""Creates ``self.__tick_to_time``, a class member array which maps
Expand Down Expand Up @@ -421,7 +450,7 @@ def get_end_time(self):
"""
# Get end times from all instruments, and times of all meta-events
meta_events = [self.time_signature_changes, self.key_signature_changes,
self.lyrics]
self.lyrics, self.text_events]
times = ([i.get_end_time() for i in self.instruments] +
[e.time for m in meta_events for e in m] +
self.get_tempo_changes()[0].tolist())
Expand Down Expand Up @@ -1130,6 +1159,8 @@ def adjust_meta(events):
adjust_meta(self.key_signature_changes)
# Adjust lyrics
adjust_meta(self.lyrics)
# Adjust text events
adjust_meta(self.text_events)

# Remove all downbeats which appear before the start of original_times
original_downbeats = original_downbeats[
Expand Down Expand Up @@ -1276,14 +1307,15 @@ def event_compare(event1, event2):
'time_signature': lambda e: (2 * 256 * 256),
'key_signature': lambda e: (3 * 256 * 256),
'lyrics': lambda e: (4 * 256 * 256),
'program_change': lambda e: (5 * 256 * 256),
'pitchwheel': lambda e: ((6 * 256 * 256) + e.pitch),
'text_events' :lambda e: (5 * 256 * 256),
'program_change': lambda e: (6 * 256 * 256),
'pitchwheel': lambda e: ((7 * 256 * 256) + e.pitch),
'control_change': lambda e: (
(7 * 256 * 256) + (e.control * 256) + e.value),
'note_off': lambda e: ((8 * 256 * 256) + (e.note * 256)),
(8 * 256 * 256) + (e.control * 256) + e.value),
'note_off': lambda e: ((9 * 256 * 256) + (e.note * 256)),
'note_on': lambda e: (
(9 * 256 * 256) + (e.note * 256) + e.velocity),
'end_of_track': lambda e: (10 * 256 * 256)
(10 * 256 * 256) + (e.note * 256) + e.velocity),
'end_of_track': lambda e: (11 * 256 * 256)
}
# If the events have the same tick, and both events have types
# which appear in the secondary_sort dictionary, use the dictionary
Expand Down Expand Up @@ -1335,6 +1367,10 @@ def event_compare(event1, event2):
for l in self.lyrics:
timing_track.append(mido.MetaMessage(
'lyrics', time=self.time_to_tick(l.time), text=l.text))
# Add text events
for l in self.text_events:
timing_track.append(mido.MetaMessage(
'text', time=self.time_to_tick(l.time), text=l.text))
# Sort the (absolute-tick-timed) events.
timing_track.sort(key=functools.cmp_to_key(event_compare))
# Add in an end of track event
Expand Down

0 comments on commit 5e3db4b

Please sign in to comment.