saysynth.core.midi_track

The MidiTrack class enables the translation notes in a midi file into Apple's SpeechSynthesis DSL.

  1"""
  2The MidiTrack class enables the translation notes in
  3a midi file into Apple's SpeechSynthesis DSL.
  4<center><img src="/assets/img/sun-wavy.png"></img></center>
  5"""
  6from typing import List, Optional, Union
  7
  8from ..constants import SAY_SEGMENT_SILENCE_DURATION
  9from ..lib import midi
 10from ..utils import bpm_to_time, frange
 11from .base import SayObject
 12from .lyrics import Lyrics
 13from .note import Note
 14from .segment import Segment
 15
 16
 17class MidiTrack(SayObject):
 18    def __init__(
 19        self,
 20        midi_file: str,
 21        loops: int = 1,
 22        # start position
 23        start: Optional[int] = None,
 24        start_bpm: Optional[Union[float, int]] = 120,
 25        start_count: Union[str, float, int] = 0,
 26        start_time_sig: str = "4/4",
 27        text: Optional[str] = None,
 28        **kwargs,
 29    ):
 30        """
 31        Generate a melody from a monophonic midifile
 32        Args:
 33            midi_file: A path to the midi file
 34            loops: the number of times to loop the pattern in the midi file.
 35            # start position
 36            start: The number of milliseconds of silence to add to the beginning of the track.
 37            start_bpm: A BPM to use when calculating the number of milliseconds of silence to add to the beginning of the track.
 38            start_count: A count to use when calculating the number of milliseconds of silence to add to the beginning of the track.
 39            start_time_sig: A time signature to use when calculating the number of milliseconds of silence to add to the beginning of the track.
 40            text: The text to to "sing"
 41        **note_options: Additional options to pass to `Note`.
 42                        These will affect how each note of the
 43                        arpeggiator sounds.
 44        """
 45        self.midi_file = midi_file
 46        self.loops = loops
 47
 48        self.start = start
 49        if not self.start:
 50            self.start = bpm_to_time(start_bpm, start_count, start_time_sig)
 51        self.start_segment_count = (
 52            int(self.start / SAY_SEGMENT_SILENCE_DURATION) + 1
 53        )
 54        self._note_options = kwargs
 55        self._start_segments = []
 56        # text / lyrics
 57        self.lyrics = None
 58        if text:
 59            # HACK: add padding to prevent skipping phonemes
 60            self.lyrics = Lyrics(" . " + text + " . ")
 61
 62    @property
 63    def notes(self) -> List[Note]:
 64        """
 65        The generated list of `Note` in the MidiTrack.
 66        """
 67        _notes = []
 68        start_at_phoneme = 0
 69        last_note_length = 0
 70        for _ in range(0, self.loops):
 71            for note in midi.process(self.midi_file):
 72                note_kwargs = {**self._note_options, **note}
 73                # handle text / phoneme:
 74                if self.lyrics:
 75                    phonemes = self.lyrics.get_phonemes(
 76                        start_at=start_at_phoneme
 77                    )
 78                    if len(phonemes) == 0:
 79                        start_at_phoneme = 0
 80                        phonemes = self.lyrics.get_phonemes(
 81                            start_at=start_at_phoneme
 82                        )
 83                    note_kwargs["phoneme"] = phonemes
 84                note = Note(**note_kwargs)
 85                last_note_length = note.n_segments
 86                start_at_phoneme += last_note_length
 87                _notes.append(note)
 88        return _notes
 89
 90    @property
 91    def segments(self) -> List[Segment]:
 92        """
 93        The generated list of `Segment` in the Arp.
 94        """
 95        return [segment for note in self.notes for segment in note.segments]
 96
 97    @property
 98    def n_notes(self) -> int:
 99        """
100        The number of Notes in the Arp.
101        """
102        return len(self.notes)
103
104    @property
105    def n_segments(self) -> int:
106        """
107        The number of Segments in the Arp.
108        """
109        return sum([note.n_segments for note in self.notes])
110
111    @property
112    def _start_text(self) -> str:
113        """
114        Add starting silence to the MidiTrack.
115        """
116        if not self.start:
117            return ""
118        _start_segments = []
119        time_breaks = list(
120            frange(0.0, self.start, SAY_SEGMENT_SILENCE_DURATION, 10)
121        )[1:]
122        for _, total_time in enumerate(time_breaks):
123            _start_segments.append(
124                Segment(
125                    type="silence",
126                    velocity=0,
127                    duration=SAY_SEGMENT_SILENCE_DURATION,
128                )
129            )
130
131        if (
132            total_time < self.start
133            and len(_start_segments) < self.start_segment_count
134        ):
135            # add final silent step
136            dur = self.start - total_time
137            _start_segments.append(
138                Segment(type="silence", velocity=0, duration=dur)
139            )
140        return "\n".join(_start_segments)
141
142    def to_text(self):
143        """
144        Render this MidiTrack as Apple SpeechSynthesis DSL text
145        """
146        note_texts = (n.to_text() for n in self.notes)
147        return self._start_text + "\n" + "\n".join(note_texts)
148
149    def __repr__(self):
150        return f"<MidiTrack {self.midi_file}>"
class MidiTrack(saysynth.core.base.SayObject):
 18class MidiTrack(SayObject):
 19    def __init__(
 20        self,
 21        midi_file: str,
 22        loops: int = 1,
 23        # start position
 24        start: Optional[int] = None,
 25        start_bpm: Optional[Union[float, int]] = 120,
 26        start_count: Union[str, float, int] = 0,
 27        start_time_sig: str = "4/4",
 28        text: Optional[str] = None,
 29        **kwargs,
 30    ):
 31        """
 32        Generate a melody from a monophonic midifile
 33        Args:
 34            midi_file: A path to the midi file
 35            loops: the number of times to loop the pattern in the midi file.
 36            # start position
 37            start: The number of milliseconds of silence to add to the beginning of the track.
 38            start_bpm: A BPM to use when calculating the number of milliseconds of silence to add to the beginning of the track.
 39            start_count: A count to use when calculating the number of milliseconds of silence to add to the beginning of the track.
 40            start_time_sig: A time signature to use when calculating the number of milliseconds of silence to add to the beginning of the track.
 41            text: The text to to "sing"
 42        **note_options: Additional options to pass to `Note`.
 43                        These will affect how each note of the
 44                        arpeggiator sounds.
 45        """
 46        self.midi_file = midi_file
 47        self.loops = loops
 48
 49        self.start = start
 50        if not self.start:
 51            self.start = bpm_to_time(start_bpm, start_count, start_time_sig)
 52        self.start_segment_count = (
 53            int(self.start / SAY_SEGMENT_SILENCE_DURATION) + 1
 54        )
 55        self._note_options = kwargs
 56        self._start_segments = []
 57        # text / lyrics
 58        self.lyrics = None
 59        if text:
 60            # HACK: add padding to prevent skipping phonemes
 61            self.lyrics = Lyrics(" . " + text + " . ")
 62
 63    @property
 64    def notes(self) -> List[Note]:
 65        """
 66        The generated list of `Note` in the MidiTrack.
 67        """
 68        _notes = []
 69        start_at_phoneme = 0
 70        last_note_length = 0
 71        for _ in range(0, self.loops):
 72            for note in midi.process(self.midi_file):
 73                note_kwargs = {**self._note_options, **note}
 74                # handle text / phoneme:
 75                if self.lyrics:
 76                    phonemes = self.lyrics.get_phonemes(
 77                        start_at=start_at_phoneme
 78                    )
 79                    if len(phonemes) == 0:
 80                        start_at_phoneme = 0
 81                        phonemes = self.lyrics.get_phonemes(
 82                            start_at=start_at_phoneme
 83                        )
 84                    note_kwargs["phoneme"] = phonemes
 85                note = Note(**note_kwargs)
 86                last_note_length = note.n_segments
 87                start_at_phoneme += last_note_length
 88                _notes.append(note)
 89        return _notes
 90
 91    @property
 92    def segments(self) -> List[Segment]:
 93        """
 94        The generated list of `Segment` in the Arp.
 95        """
 96        return [segment for note in self.notes for segment in note.segments]
 97
 98    @property
 99    def n_notes(self) -> int:
100        """
101        The number of Notes in the Arp.
102        """
103        return len(self.notes)
104
105    @property
106    def n_segments(self) -> int:
107        """
108        The number of Segments in the Arp.
109        """
110        return sum([note.n_segments for note in self.notes])
111
112    @property
113    def _start_text(self) -> str:
114        """
115        Add starting silence to the MidiTrack.
116        """
117        if not self.start:
118            return ""
119        _start_segments = []
120        time_breaks = list(
121            frange(0.0, self.start, SAY_SEGMENT_SILENCE_DURATION, 10)
122        )[1:]
123        for _, total_time in enumerate(time_breaks):
124            _start_segments.append(
125                Segment(
126                    type="silence",
127                    velocity=0,
128                    duration=SAY_SEGMENT_SILENCE_DURATION,
129                )
130            )
131
132        if (
133            total_time < self.start
134            and len(_start_segments) < self.start_segment_count
135        ):
136            # add final silent step
137            dur = self.start - total_time
138            _start_segments.append(
139                Segment(type="silence", velocity=0, duration=dur)
140            )
141        return "\n".join(_start_segments)
142
143    def to_text(self):
144        """
145        Render this MidiTrack as Apple SpeechSynthesis DSL text
146        """
147        note_texts = (n.to_text() for n in self.notes)
148        return self._start_text + "\n" + "\n".join(note_texts)
149
150    def __repr__(self):
151        return f"<MidiTrack {self.midi_file}>"
MidiTrack( midi_file: str, loops: int = 1, start: Optional[int] = None, start_bpm: Union[int, float, NoneType] = 120, start_count: Union[str, float, int] = 0, start_time_sig: str = '4/4', text: Optional[str] = None, **kwargs)
19    def __init__(
20        self,
21        midi_file: str,
22        loops: int = 1,
23        # start position
24        start: Optional[int] = None,
25        start_bpm: Optional[Union[float, int]] = 120,
26        start_count: Union[str, float, int] = 0,
27        start_time_sig: str = "4/4",
28        text: Optional[str] = None,
29        **kwargs,
30    ):
31        """
32        Generate a melody from a monophonic midifile
33        Args:
34            midi_file: A path to the midi file
35            loops: the number of times to loop the pattern in the midi file.
36            # start position
37            start: The number of milliseconds of silence to add to the beginning of the track.
38            start_bpm: A BPM to use when calculating the number of milliseconds of silence to add to the beginning of the track.
39            start_count: A count to use when calculating the number of milliseconds of silence to add to the beginning of the track.
40            start_time_sig: A time signature to use when calculating the number of milliseconds of silence to add to the beginning of the track.
41            text: The text to to "sing"
42        **note_options: Additional options to pass to `Note`.
43                        These will affect how each note of the
44                        arpeggiator sounds.
45        """
46        self.midi_file = midi_file
47        self.loops = loops
48
49        self.start = start
50        if not self.start:
51            self.start = bpm_to_time(start_bpm, start_count, start_time_sig)
52        self.start_segment_count = (
53            int(self.start / SAY_SEGMENT_SILENCE_DURATION) + 1
54        )
55        self._note_options = kwargs
56        self._start_segments = []
57        # text / lyrics
58        self.lyrics = None
59        if text:
60            # HACK: add padding to prevent skipping phonemes
61            self.lyrics = Lyrics(" . " + text + " . ")

Generate a melody from a monophonic midifile

Arguments:
  • midi_file: A path to the midi file
  • loops: the number of times to loop the pattern in the midi file.
  • # start position
  • start: The number of milliseconds of silence to add to the beginning of the track.
  • start_bpm: A BPM to use when calculating the number of milliseconds of silence to add to the beginning of the track.
  • start_count: A count to use when calculating the number of milliseconds of silence to add to the beginning of the track.
  • start_time_sig: A time signature to use when calculating the number of milliseconds of silence to add to the beginning of the track.
  • text: The text to to "sing"

**note_options: Additional options to pass to Note. These will affect how each note of the arpeggiator sounds.

The generated list of Note in the MidiTrack.

The generated list of Segment in the Arp.

n_notes: int

The number of Notes in the Arp.

n_segments: int

The number of Segments in the Arp.

def to_text(self):
143    def to_text(self):
144        """
145        Render this MidiTrack as Apple SpeechSynthesis DSL text
146        """
147        note_texts = (n.to_text() for n in self.notes)
148        return self._start_text + "\n" + "\n".join(note_texts)

Render this MidiTrack as Apple SpeechSynthesis DSL text