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}>"
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.
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