def __init__(self, name: str, bar_start_event, action_server: ActionServer, midi_hub, bundle_name: str, config_name: str, timing: Timing, midi_channel: int = 1, bar_per_loop: int = 2): super(SequenceLooper, self).__init__() self.name = name self._stop_signal = False self._bar_start_event = bar_start_event self._action_server = action_server self._midi_hub = midi_hub self._bundle_filename = bundle_name + ".mag" self._config_name = config_name self._timing = timing self._midi_channel = midi_channel self._bar_per_loop = bar_per_loop model_dir = os.path.join("output", "models") if not os.path.exists(model_dir): os.makedirs(model_dir) self._output_plot = os.path.join("output", "models", self.name + ".html") self._output_midi = os.path.join("output", "models", self.name + ".mid") self._plotter = Plotter(plot_max_length_bar=bar_per_loop, live_reload=True, preset_name="PRESET_SMALL")
def save_plot(sequences: Union[NoteSequence, List[NoteSequence]], output_dir: Optional[str] = None, prefix: str = "sequence", **kwargs): """ Writes the sequences as HTML plot files to the "output" directory, with the filename pattern "<prefix>_<index>_<date_time>" and "html" as extension. :param sequences: a NoteSequence or list of NoteSequence to be saved :param output_dir: an optional subdirectory in the output directory :param prefix: an optional prefix for each file :param kwargs: the keyword arguments to pass to the Plotter instance """ output_dir = os.path.join("output", output_dir) if output_dir else "output" os.makedirs(output_dir, exist_ok=True) if not isinstance(sequences, list): sequences = [sequences] for (index, sequence) in enumerate(sequences): date_and_time = time.strftime("%Y-%m-%d_%H%M%S") filename = f"{prefix}_{index:02}_{date_and_time}.html" path = os.path.join(output_dir, filename) midi = mm.midi_io.note_sequence_to_pretty_midi(sequence) plotter = Plotter(**kwargs) plotter.save(midi, path) print(f"Generated plot file: {os.path.abspath(path)}")
def generate_plot_from_midi(sequence, file_name, file_id): # Writes the resulting plot file to the output directory date_and_time = time.strftime('%Y-%m-%d_%H%M%S') plot_filename = "%s_%s_%s.html" % (file_name, file_id, date_and_time) plot_path = os.path.join( "/Users/reiners/Dropbox/projects/human-drum-grooves/output", plot_filename) pretty_midi = mm.midi_io.note_sequence_to_pretty_midi(sequence) plotter = Plotter() plotter.save(pretty_midi, plot_path) print(f"Generated plot file: {os.path.abspath(plot_path)}")
def convert_midi_to_image(url): file = requests.get(url) open('input.mid', 'wb').write(file.content) pm = PrettyMIDI("input.mid") preset = Preset(plot_width=1000, plot_height=500) plotter = Plotter(preset=preset) plotter.save(pm, 'imageHtml.html') hti = Html2Image() hti.screenshot(html_file='imageHtml.html', save_as='image.png', size=(1000, 500)) return discord.File( r'image.png') if os.path.getsize('image.png') <= 16000000 else None
def save_plot(sequences: Union[NoteSequence, List[NoteSequence]], output_dir: Optional[str] = None, prefix: str = "sequence", **kwargs): output_dir = os.path.join("output", output_dir) if output_dir else "output" os.makedirs(output_dir, exist_ok=True) if not isinstance(sequences, list): sequences = [sequences] for (index, sequence) in enumerate(sequences): date_time = time.strftime("%Y-%m-%d_%H%M%S") filename = f"{prefix}_{index:02}_{date_time}.html" path = os.path.join(output_dir, filename) midi = mm.midi_io.note_sequence_to_pretty_midi(sequence) plotter = Plotter(**kwargs) plotter.save(midi, path) print("Generated plot : {}".format(os.path.abspath(path)))
def generate(unused_argv): mm.notebook_utils.download_bundle("drum_kit_rnn.mag", "bundles") bundle = mm.sequence_generator_bundle.read_bundle_file( os.path.join("bundles", "drum_kit_rnn.mag")) generator_map = drums_rnn_sequence_generator.get_generator_map() generator = generator_map["drum_kit"](checkpoint=None, bundle=bundle) generator.initialize() qpm = 120 num_bars = 3 seconds_per_step = 60.0 / qpm / generator.steps_per_quarter num_steps_per_bar = constants.DEFAULT_STEPS_PER_BAR seconds_per_bar = num_steps_per_bar * seconds_per_step primer_sequence = mm.midi_io.midi_file_to_note_sequence( os.path.join("primers", "Jazz_Drum_Basic_1_bar.mid")) primer_start_time = 0 primer_end_time = primer_start_time + seconds_per_bar generation_start_time = primer_end_time generation_end_time = generation_start_time + (seconds_per_bar * num_bars) generator_options = generator_pb2.GeneratorOptions() generator_options.args['temperature'].float_value = 1.1 generator_options.generate_sections.add(start_time=generation_start_time, end_time=generation_end_time) sequence = generator.generate(primer_sequence, generator_options) plot_file = os.path.join("output", "out.html") pretty_midi = mm.midi_io.note_sequence_to_pretty_midi(sequence) plotter = Plotter() plotter.show(pretty_midi, plot_file) print(f"Generated plot file: {os.path.abspath(plot_file)}") input_ports = [ name for name in mido.get_output_names() if "VirtualMIDISynth" in name or "FLUID Synth" in name ] if not input_ports: raise Exception(f"Cannot find proper input port in: " f"{mido.get_output_names()}") print(f"Playing generated MIDI in input port names: {input_ports}") midi_hub = mh.MidiHub([], input_ports, None) empty_sequence = music_pb2.NoteSequence() player = midi_hub.start_playback(empty_sequence, allow_updates=True) player._channel = 9 # We calculate the length of the generated sequence in seconds, # which gives up the loop time in seconds loop_time = generation_end_time - primer_start_time print(f"Loop time is {loop_time}") # We get the current wall time before the loop starts wall_start_time = time.time() while True: try: # We get the current wall time for this loop start tick_wall_start_time = time.time() sequence_adjusted = music_pb2.NoteSequence() sequence_adjusted.CopyFrom(sequence) sequence_adjusted = adjust_sequence_times(sequence_adjusted, tick_wall_start_time) player.update_sequence(sequence_adjusted, start_time=tick_wall_start_time) # We calculate the elapsed time from the start of the program tick_start_time = time.time() - wall_start_time # We sleep for the remaining time in the loop. It means that whatever # how much time this loop took, we'll be waking up at the proper # next bar. # For example, if the loop needs to be 8 seconds, and we took 2.4 seconds # executing and arriving here, then we'll sleep only 5.6 seconds to wake # up with proper timing. sleep_time = loop_time - (tick_start_time % loop_time) print(f"Sleeping for {sleep_time}") time.sleep(sleep_time) except KeyboardInterrupt: print(f"Stopping") return 0
def generate(unused_argv): # Downloads the bundle from the magenta website mm.notebook_utils.download_bundle("drum_kit_rnn.mag", "bundles") bundle = mm.sequence_generator_bundle.read_bundle_file( os.path.join("bundles", "drum_kit_rnn.mag")) # Initialize the generator "drum_kit" generator_map = drums_rnn_sequence_generator.get_generator_map() generator = generator_map["drum_kit"](checkpoint=None, bundle=bundle) generator.initialize() # Define constants qpm = 120 num_bars = 3 seconds_per_step = 60.0 / qpm / generator.steps_per_quarter num_steps_per_bar = constants.DEFAULT_STEPS_PER_BAR seconds_per_bar = num_steps_per_bar * seconds_per_step # Use a priming sequence primer_sequence = mm.midi_io.midi_file_to_note_sequence( os.path.join("primers", "Jazz_Drum_Basic_1_bar.mid")) primer_start_time = 0 primer_end_time = primer_start_time + seconds_per_bar # Calculates the generation start and end time generation_start_time = primer_end_time generation_end_time = generation_start_time + (seconds_per_bar * num_bars) generator_options = generator_pb2.GeneratorOptions() generator_options.args['temperature'].float_value = 1.1 generator_options.generate_sections.add(start_time=generation_start_time, end_time=generation_end_time) # Generates on primer sequence sequence = generator.generate(primer_sequence, generator_options) # Outputs the plot os.makedirs("output", exist_ok=True) plot_file = os.path.join("output", "out.html") pretty_midi = mm.midi_io.note_sequence_to_pretty_midi(sequence) plotter = Plotter() plotter.show(pretty_midi, plot_file) print(f"Generated plot file: {os.path.abspath(plot_file)}") # We find the proper input port for the software synth # (which is the output port for Magenta) output_ports = [ name for name in mido.get_output_names() if args.midi_port in name ] if not output_ports: raise Exception(f"Cannot find proper output ports in: " f"{mido.get_output_names()}") print(f"Playing generated MIDI in output port names: {output_ports}") # Start a new MIDI hub on that port (output only) midi_hub = MidiHub(input_midi_ports=[], output_midi_ports=output_ports, texture_type=None) # Start on a empty sequence, allowing the update of the # sequence for later. empty_sequence = music_pb2.NoteSequence() player = midi_hub.start_playback(empty_sequence, allow_updates=True) player._channel = 9 # We want a period in seconds of 4 bars (which is the loop # length). Using 240 / qpm, we have a period of 1 bar, or # 2 seconds at 120 qpm. We multiply that by 4 bars. # (using the Decimal class for more accuracy) period = Decimal(240) / qpm period = period * (num_bars + 1) sleeper = concurrency.Sleeper() while True: try: # We get the next tick time by using the period # to find the absolute tick number (since epoch), # and multiplying by the period length. This is # used to sleep until that time. # We also find the current tick time for the player # to update. # (using the Decimal class for more accuracy) now = Decimal(time.time()) tick_number = int(now // period) tick_number_next = tick_number + 1 tick_time = tick_number * period tick_time_next = tick_number_next * period print( f"now {now} tick_time {tick_time} tick_time_next {tick_time_next}" ) # Update the player time to the current tick time sequence_adjusted = music_pb2.NoteSequence() sequence_adjusted.CopyFrom(sequence) sequence_adjusted = adjust_sequence_times(sequence_adjusted, float(tick_time)) player.update_sequence(sequence_adjusted, start_time=float(tick_time)) # Sleep until the next tick time sleeper.sleep_until(float(tick_time_next)) except KeyboardInterrupt: print(f"Stopping") return 0
def generate(unused_argv): """Generates a basic drum sequence of 4 seconds""" # Downloads the bundle from the magenta website, a bundle (.mag file) is a # trained model that is used by magenta mm.notebook_utils.download_bundle("drum_kit_rnn.mag", "bundles") bundle = mm.sequence_generator_bundle.read_bundle_file( os.path.join("bundles", "drum_kit_rnn.mag")) # Initialize the generator "drum_kit", this need to fit the bundle we # downloaded before generator_map = drums_rnn_sequence_generator.get_generator_map() generator = generator_map["drum_kit"](checkpoint=None, bundle=bundle) generator.initialize() # Change the number of steps to be generated, which will define the length # of the generated sequence, used with the qpm. At 120 qpm, this # is equal to 4 seconds num_steps = 32 # Change the temperature: the bigger this value, the more random is the # generation, with 1 being the default value, 1.25 being more random, # 0.75 being less random temperature = 1 # Change the quarter per minute (also called BPM or beat per minute), a # measure of tempo. At 120 qpm, you have two beat per seconds qpm = 120 # Calculate the number of seconds per step, useful to find the total time # of the generation in seconds. The steps per quarter in the generator is by # default 4, so the generator will generate 8 steps per bar # TODO why 8 steps per bar seconds_per_step = 60.0 / qpm / generator.steps_per_quarter total_seconds = num_steps * seconds_per_step # The generator options are the parameters passed to the generator, why is # the temperature and the generation section, from 0 to the total of seconds, # where the generator will generate notes generator_options = generator_pb2.GeneratorOptions() generator_options.args['temperature'].float_value = temperature generator_options.generate_sections.add(start_time=0, end_time=total_seconds) # Generates the notes from the arguments, starting with an empty note # sequence (no primer) empty_note_sequence = music_pb2.NoteSequence() sequence = generator.generate(empty_note_sequence, generator_options) # Outputs the midi file in the output directory midi_file = os.path.join("output", "out.mid") mm.midi_io.note_sequence_to_midi_file(sequence, midi_file) print("Generated midi file: " + str(os.path.abspath(midi_file))) # Outputs the plot file in the output directory and opens your browser for # visualisation plot_file = os.path.join("output", "out.html") print("Generated plot file: " + str(os.path.abspath(plot_file))) pretty_midi = mm.midi_io.note_sequence_to_pretty_midi(sequence) plotter = Plotter() plotter.show(pretty_midi, plot_file) return 0
def generate(bundle_name: str, sequence_generator, generator_id: str, qpm: float = constants.DEFAULT_QUARTERS_PER_MINUTE, primer_filename: str = None, condition_on_primer: bool = False, inject_primer_during_generation: bool = False, total_length_bars: int = 4, temperature: float = 1.0, beam_size: int = 1, branch_factor: int = 1, steps_per_iteration: int = 1, write_midi_to_disk: bool = False, write_plot_to_disk: bool = False) -> music_pb2.NoteSequence: """Generates and returns a new sequence given the sequence generator. Uses the bundle name to download the bundle in the "bundles" directory if it doesn't already exist, then uses the sequence generator and the generator id to get the generator. Parameters can be provided for the generation phase. The MIDI and plot files can be written to disk. :param bundle_name: The bundle name to be downloaded and generated with. :param sequence_generator: The sequence generator module, which is the python module in the corresponding models subfolder. :param generator_id: The id of the generator configuration, this is the model's configuration. :param qpm: The QPM for the generated sequence. If a primer is provided, the primer QPM will be used and this parameter ignored. :param primer_filename: The filename for the primer, which will be taken from the "primers" directory. If left empty, and empty note sequence will be used. :param condition_on_primer: https://github.com/tensorflow/magenta/tree/master/magenta/models/polyphony_rnn#generate-a-polyphonic-sequence :param inject_primer_during_generation: https://github.com/tensorflow/magenta/tree/master/magenta/models/polyphony_rnn#generate-a-polyphonic-sequence :param total_length_bars: The total length of the sequence, which contains the added length of the primer and the generated sequence together. This value need to be bigger than the primer length in bars. :param temperature: The temperature value for the generation algorithm, lesser than 1 is less random (closer to the primer), bigger than 1 is more random :param beam_size: The beam size for the generation algorithm, a bigger branch size means the generation algorithm will generate more sequence each iteration, meaning a less random sequence at the cost of more time. :param branch_factor: The branch factor for the generation algorithm, a bigger branch factor means the generation algorithm will keep more sequence candidates at each iteration, meaning a less random sequence at the cost of more time. :param steps_per_iteration: The number of steps the generation algorithm generates at each iteration, a bigger steps per iteration meaning there are less iterations in total because more steps gets generated each time. :param write_midi_to_disk: True to write the resulting sequence to disk as a MIDI file in the "output" folder. The filename naming pattern is: "GeneratorName_GeneratorId_DateTime.mid". :param write_plot_to_disk: True to write the resulting plot to disk as a HTML file in the "output" folder. The filename naming pattern is: "GeneratorName_GeneratorId_DateTime.html". :returns The generated NoteSequence """ # Downloads the bundle from the magenta website, a bundle (.mag file) is a # trained model that is used by magenta mm.notebook_utils.download_bundle(bundle_name, "bundles") bundle = mm.sequence_generator_bundle.read_bundle_file( os.path.join("bundles", bundle_name)) # Initialize the generator from the generator id, this need to fit the # bundle we downloaded before, and choose the model's configuration. generator_map = sequence_generator.get_generator_map() generator = generator_map[generator_id](checkpoint=None, bundle=bundle) generator.initialize() # Gets the primer sequence that is fed into the model for the generator, # which will generate a sequence based on this one. # If no primer sequence is given, the primer sequence is initialized # to an empty note sequence if primer_filename: primer_sequence = mm.midi_io.midi_file_to_note_sequence( os.path.join("primers", primer_filename)) else: primer_sequence = music_pb2.NoteSequence() # Gets the QPM from the primer sequence. If it wasn't provided, take the # parameters that defaults to Magenta's default if primer_sequence.tempos: if len(primer_sequence.tempos) > 1: raise Exception("No support for multiple tempos") qpm = primer_sequence.tempos[0].qpm else: qpm = qpm # Gets the time signature from the primer sequence. If it wasn't provided, # we initialize it to a default 4/4 if primer_sequence.time_signatures: if len(primer_sequence.time_signatures) > 1: raise Exception("No support for multiple time signatures") primer_time_signature = primer_sequence.time_signatures[0] else: primer_time_signature = primer_sequence.time_signatures.add() primer_time_signature.time = 0 primer_time_signature.numerator = 4 primer_time_signature.denominator = 4 # Calculates the seconds per 1 step, which changes depending on the QPM value # (steps per quarter in generators are mostly 4) seconds_per_step = 60.0 / qpm / generator.steps_per_quarter # Calculate the number of steps per bar, which changes from the time # signature. If we have a 3/4 time signature, steps per bar is 12, # if 4/4, steps per bar is 16. steps_per_quarter_note = constants.DEFAULT_STEPS_PER_QUARTER num_steps_per_bar = (primer_time_signature.numerator * steps_per_quarter_note) # Calculate how many seconds per bar for the generation time seconds_per_bar = num_steps_per_bar * seconds_per_step print("Seconds per step: " + str(seconds_per_step)) print("Steps per bar: " + str(num_steps_per_bar)) print("Seconds per bar: " + str(seconds_per_bar)) # Calculates the primer sequence length in bars and time by taking the # total time (which is the end of the last note) and finding the next bar # start time. primer_sequence_length_bars = math.ceil(primer_sequence.total_time / seconds_per_bar) primer_sequence_length_time = primer_sequence_length_bars * seconds_per_bar # Calculates the start and the end of the primer sequence. # We add a negative delta to the end, because if we don't some generators # won't start the generation right at the beginning of the bar, they will # start at the next step, meaning we'll have a small gap between the primer # and the generated sequence. primer_end_adjust = (0.00001 if primer_sequence_length_time > 0 else 0) primer_start_time = 0 primer_end_time = (primer_start_time + primer_sequence_length_time - primer_end_adjust) # Calculates the generation time by taking the total time and substracting # the primer time. The resulting generation time needs to be bigger than zero. generation_length_bars = total_length_bars - primer_sequence_length_bars if generation_length_bars <= 0: raise Exception("Total length in bars too small " + "(" + str(total_length_bars) + ")" + ", needs to be at least one bar bigger than primer " + "(" + str(primer_sequence_length_bars) + ")") generation_length_time = generation_length_bars * seconds_per_bar # Calculates the generate start and end time, the start time will contain # the previously added negative delta from the primer end time. # We remove the generation end time delta to end the generation # on the last bar. generation_start_time = primer_end_time generation_end_time = (generation_start_time + generation_length_time + primer_end_adjust) # Showtime print("Primer time: [" + str(primer_start_time) + ", " + str(primer_end_time) + "]") print("Generation time: [" + str(generation_start_time) + ", " + str(generation_end_time) + "]") # Pass the given parameters, the generator options are common for all models, # except for condition_on_primer and no_inject_primer_during_generation # which are specific to polyphonic models generator_options = generator_pb2.GeneratorOptions() generator_options.args['temperature'].float_value = temperature generator_options.args['beam_size'].int_value = beam_size generator_options.args['branch_factor'].int_value = branch_factor generator_options.args[ 'steps_per_iteration'].int_value = steps_per_iteration generator_options.args[ 'condition_on_primer'].bool_value = condition_on_primer generator_options.args['no_inject_primer_during_generation'].bool_value = ( not inject_primer_during_generation) generator_options.generate_sections.add(start_time=generation_start_time, end_time=generation_end_time) # Generates the sequence. The resulting sequence do not have time signature # so we add the same as the primer. sequence = generator.generate(primer_sequence, generator_options) time_signature = sequence.time_signatures.add() time_signature.time = 0 time_signature.numerator = primer_time_signature.numerator time_signature.denominator = primer_time_signature.denominator # Writes the resulting midi file to the output directory if write_midi_to_disk: date_and_time = time.strftime('%Y-%m-%d_%H%M%S') generator_name = str(generator.__class__).split(".")[2] midi_filename = "%s_%s_%s.mid" % (generator_name, generator_id, date_and_time) midi_path = os.path.join("output", midi_filename) mm.midi_io.note_sequence_to_midi_file(sequence, midi_path) print("Generated midi file: " + str(os.path.abspath(midi_path))) # Writes the resulting plot file to the output directory if write_plot_to_disk: date_and_time = time.strftime('%Y-%m-%d_%H%M%S') generator_name = str(generator.__class__).split(".")[2] plot_filename = "%s_%s_%s.html" % (generator_name, generator_id, date_and_time) plot_path = os.path.join("output", plot_filename) pretty_midi = mm.midi_io.note_sequence_to_pretty_midi(sequence) plotter = Plotter() plotter.save(pretty_midi, plot_path) print("Generated plot file: " + str(os.path.abspath(plot_path))) return sequence
def generate(bundle_name: str, sequence_generator, generator_id: str, primer_filename: str = None, qpm: float = DEFAULT_QUARTERS_PER_MINUTE, total_length_steps: int = 64, temperature: float = 1.0, beam_size: int = 1, branch_factor: int = 1, steps_per_iteration: int = 1) -> NoteSequence: """Generates and returns a new sequence given the sequence generator. Uses the bundle name to download the bundle in the "bundles" directory if it doesn't already exist, then uses the sequence generator and the generator id to get the generator. Parameters can be provided for the generation phase. The MIDI and plot files are written to disk in the "output" folder, with the filename pattern "<generator_name>_<generator_id>_<date_time>" with "mid" or "html" as extension respectively. :param bundle_name: The bundle name to be downloaded and generated with. :param sequence_generator: The sequence generator module, which is the python module in the corresponding models subfolder. :param generator_id: The id of the generator configuration, this is the model's configuration. :param primer_filename: The filename for the primer, which will be taken from the "primers" directory. If left empty, and empty note sequence will be used. :param qpm: The QPM for the generated sequence. If a primer is provided, the primer QPM will be used and this parameter ignored. :param total_length_steps: The total length of the sequence, which contains the added length of the primer and the generated sequence together. This value need to be bigger than the primer length in bars. :param temperature: The temperature value for the generation algorithm, lesser than 1 is less random (closer to the primer), bigger than 1 is more random :param beam_size: The beam size for the generation algorithm, a bigger branch size means the generation algorithm will generate more sequence each iteration, meaning a less random sequence at the cost of more time. :param branch_factor: The branch factor for the generation algorithm, a bigger branch factor means the generation algorithm will keep more sequence candidates at each iteration, meaning a less random sequence at the cost of more time. :param steps_per_iteration: The number of steps the generation algorithm generates at each iteration, a bigger steps per iteration meaning there are less iterations in total because more steps gets generated each time. :returns The generated NoteSequence """ # Downloads the bundle from the magenta website, a bundle (.mag file) is a # trained model that is used by magenta mm.notebook_utils.download_bundle(bundle_name, "bundles") bundle = mm.sequence_generator_bundle.read_bundle_file( os.path.join("bundles", bundle_name)) # Initialize the generator from the generator id, this need to fit the # bundle we downloaded before, and choose the model's configuration. generator_map = sequence_generator.get_generator_map() generator = generator_map[generator_id](checkpoint=None, bundle=bundle) generator.initialize() # Gets the primer sequence that is fed into the model for the generator, # which will generate a sequence based on this one. # If no primer sequence is given, the primer sequence is initialized # to an empty note sequence if primer_filename: primer_sequence = mm.midi_io.midi_file_to_note_sequence( os.path.join("primers", primer_filename)) else: primer_sequence = NoteSequence() # Gets the QPM from the primer sequence. If it wasn't provided, take the # parameters that defaults to Magenta's default if primer_sequence.tempos: if len(primer_sequence.tempos) > 1: raise Exception("No support for multiple tempos") qpm = primer_sequence.tempos[0].qpm # Calculates the seconds per 1 step, which changes depending on the QPM value # (steps per quarter in generators are mostly 4) seconds_per_step = 60.0 / qpm / getattr(generator, "steps_per_quarter", 4) # Calculates the primer sequence length in steps and time by taking the # total time (which is the end of the last note) and finding the next step # start time. primer_sequence_length_steps = math.ceil(primer_sequence.total_time / seconds_per_step) primer_sequence_length_time = primer_sequence_length_steps * seconds_per_step # Calculates the start and the end of the primer sequence. # We add a negative delta to the end, because if we don't some generators # won't start the generation right at the beginning of the bar, they will # start at the next step, meaning we'll have a small gap between the primer # and the generated sequence. primer_end_adjust = (0.00001 if primer_sequence_length_time > 0 else 0) primer_start_time = 0 primer_end_time = (primer_start_time + primer_sequence_length_time - primer_end_adjust) # Calculates the generation time by taking the total time and substracting # the primer time. The resulting generation time needs to be bigger than zero. generation_length_steps = total_length_steps - primer_sequence_length_steps if generation_length_steps <= 0: raise Exception("Total length in steps too small " + "(" + str(total_length_steps) + ")" + ", needs to be at least one bar bigger than primer " + "(" + str(primer_sequence_length_steps) + ")") generation_length_time = generation_length_steps * seconds_per_step # Calculates the generate start and end time, the start time will contain # the previously added negative delta from the primer end time. # We remove the generation end time delta to end the generation # on the last bar. generation_start_time = primer_end_time generation_end_time = (generation_start_time + generation_length_time + primer_end_adjust) # Showtime print(f"Primer time: [{primer_start_time}, {primer_end_time}]") print(f"Generation time: [{generation_start_time}, {generation_end_time}]") # Pass the given parameters, the generator options are common for all models generator_options = GeneratorOptions() generator_options.args['temperature'].float_value = temperature generator_options.args['beam_size'].int_value = beam_size generator_options.args['branch_factor'].int_value = branch_factor generator_options.args['steps_per_iteration'].int_value = steps_per_iteration generator_options.generate_sections.add( start_time=generation_start_time, end_time=generation_end_time) # Generates the sequence, add add the time signature # back to the generated sequence sequence = generator.generate(primer_sequence, generator_options) # Writes the resulting midi file to the output directory date_and_time = time.strftime('%Y-%m-%d_%H%M%S') generator_name = str(generator.__class__).split(".")[2] midi_filename = "%s_%s_%s.mid" % (generator_name, generator_id, date_and_time) midi_path = os.path.join("output", midi_filename) mm.midi_io.note_sequence_to_midi_file(sequence, midi_path) print(f"Generated midi file: {os.path.abspath(midi_path)}") # Writes the resulting plot file to the output directory date_and_time = time.strftime('%Y-%m-%d_%H%M%S') generator_name = str(generator.__class__).split(".")[2] plot_filename = "%s_%s_%s.html" % (generator_name, generator_id, date_and_time) plot_path = os.path.join("output", plot_filename) pretty_midi = mm.midi_io.note_sequence_to_pretty_midi(sequence) plotter = Plotter() plotter.save(pretty_midi, plot_path) print(f"Generated plot file: {os.path.abspath(plot_path)}") return sequence
def generate(unused_argv): """Generates a basic drum sequence of 4 seconds based on a hard coded primer""" mm.notebook_utils.download_bundle("drum_kit_rnn.mag", "bundles") bundle = mm.sequence_generator_bundle.read_bundle_file( os.path.join("bundles", "drum_kit_rnn.mag")) generator_map = drums_rnn_sequence_generator.get_generator_map() generator = generator_map["drum_kit"](checkpoint=None, bundle=bundle) generator.initialize() num_steps = 32 temperature = 1 qpm = 120 seconds_per_step = 60.0 / qpm / generator.steps_per_quarter total_seconds = num_steps * seconds_per_step # Creates a primer sequence that is fed into the model for the generator, # which will generate a sequence based on this one # A DrumTrack models a drum sequence by step, so you have step 1 being the # midi note 36 (bass drum), followed by 3 steps of silence (those four steps # constitutes the first beat or quarter), followed by both notes 36 and 41 # being struck at the same time (followed by silence by these are optional) # TODO why quarter # TODO better example # TODO check midi note meaning primer_drums = magenta.music.DrumTrack([ frozenset(pitches) for pitches in [(36, ), (), (), (), (36, 41), (), (), ()] ]) primer_sequence = primer_drums.to_sequence(qpm=120) primer_end_time = primer_sequence.total_time # Defines the start and end of the generation, which starts at the step # after the end of the primer (we'll see in 03.py this calculation makes # it harder to fall on proper beats) and ends at total seconds # The complete generation will thus contain the primer and the total length # needs to be at least the size of the primer generation_start_time = primer_end_time + seconds_per_step generation_end_time = total_seconds generator_options = generator_pb2.GeneratorOptions() generator_options.args['temperature'].float_value = temperature generator_options.generate_sections.add(start_time=generation_start_time, end_time=generation_end_time) # We are using the primer sequence here instead of an empty sequence sequence = generator.generate(primer_sequence, generator_options) midi_file = os.path.join("output", "out.mid") mm.midi_io.note_sequence_to_midi_file(sequence, midi_file) print("Generated midi file: " + str(os.path.abspath(midi_file))) plot_file = os.path.join("output", "out.html") print("Generated plot file: " + str(os.path.abspath(plot_file))) pretty_midi = mm.midi_io.note_sequence_to_pretty_midi(sequence) plotter = Plotter() plotter.show(pretty_midi, plot_file) return 0
from visual_midi import Plotter import magenta.music as mm import os import sys import glob f = '*.mid' d = os.getcwd() for file in glob.glob(os.path.join(d, f)): sequence = mm.midi_io.midi_file_to_note_sequence( os.path.join("simplemidi", file)) plot_filename = "%s.html" % (file) plot_path = os.path.join("output", plot_filename) pretty_midi = mm.midi_io.note_sequence_to_pretty_midi(sequence) plotter = Plotter() plotter.show(pretty_midi, plot_path) plotter.save(pretty_midi, plot_path) print("Generated plot file: " + str(os.path.abspath(plot_path)))
def generate(unused_argv): mm.notebook_utils.download_bundle("drum_kit_rnn.mag", "bundles") bundle = mm.sequence_generator_bundle.read_bundle_file( os.path.join("bundles", "drum_kit_rnn.mag")) generator_map = drums_rnn_sequence_generator.get_generator_map() generator = generator_map["drum_kit"](checkpoint=None, bundle=bundle) generator.initialize() qpm = 120 num_bars = 3 seconds_per_step = 60.0 / qpm / generator.steps_per_quarter num_steps_per_bar = constants.DEFAULT_STEPS_PER_BAR seconds_per_bar = num_steps_per_bar * seconds_per_step # This time we get the primer from disk instead of hard coding it primer_sequence = mm.midi_io.midi_file_to_note_sequence( os.path.join("primers", "Jazz_Drum_Basic_1_bar.mid")) primer_start_time = 0 primer_end_time = primer_start_time + seconds_per_bar generation_start_time = primer_end_time generation_end_time = generation_start_time + (seconds_per_bar * num_bars) generator_options = generator_pb2.GeneratorOptions() generator_options.args['temperature'].float_value = 1.1 generator_options.generate_sections.add(start_time=generation_start_time, end_time=generation_end_time) sequence = generator.generate(primer_sequence, generator_options) plot_file = os.path.join("output", "out.html") pretty_midi = mm.midi_io.note_sequence_to_pretty_midi(sequence) plotter = Plotter() plotter.show(pretty_midi, plot_file) print("Generated plot file: " + str(os.path.abspath(plot_file))) # We find the proper input port for the software synth, # this should work on all platforms (if you followed the # installation instructions) input_ports = [ name for name in mido.get_output_names() if "VirtualMIDISynth" in name or "FLUID Synth" in name ] if not input_ports: print("Cannot find proper input port in " + str(mido.get_output_names())) print("Playing generated MIDI in input port names: " + str(input_ports)) # Start a new MIDI hub on that port (incoming only) midi_hub = mh.MidiHub([], input_ports, None) # Start on a empty sequence, allowing the update of the # sequence for later. We don't especially need that right # now, because we could play the sequence immediately, but # it will be useful for later examples to have a player to # update new sequences with. empty_sequence = music_pb2.NoteSequence() player = midi_hub.start_playback(empty_sequence, allow_updates=True) # Remember that GM 1 compatible synthesizer will play the drums # sound bank if the MIDI channel is 10 (but the channel is zero # indexed in Magenta MIDI hub so you have to use 9). player._channel = 9 # Now we can play our sequence, but we need to adjust it first. # The MIDI player will play the sequence according to wall time, # but our sequence starts at 0. # Create a new empty note sequence, copy the sequence # we want to play in the empty sequence, then move the # start of the sequence by wall_start_time amount wall_start_time = time.time() sequence_adjusted = music_pb2.NoteSequence() sequence_adjusted.CopyFrom(sequence) sequence_adjusted = adjust_sequence_times(sequence_adjusted, wall_start_time) # The update sequence is the equivalent of "play" player.update_sequence(sequence_adjusted, start_time=wall_start_time) # We "join" on the thread, meaning the call will block # until the player has finished. Because the thread # never stops, this call will block indefinitely. By # adding a timeout of generation_end_time, the call will # return after the end of the sequence being played. try: player.join(generation_end_time) except KeyboardInterrupt: # The KeyboardInterrupt is important if you want to press # CTRL-C during the playback to stop the player. return 0 finally: return 0
def run(self): # TODO sequence = self._primer_sequence player = self._midi_hub.start_playback(sequence, allow_updates=True) # TODO seconds_per_step = 60.0 / self._qpm / self._sequence_generator.steps_per_quarter num_steps_per_bar = self._sequence_generator.steps_per_quarter * 2 seconds_per_bar = num_steps_per_bar * seconds_per_step seconds_per_loop = self._bar_per_loop * seconds_per_bar # TODO MOVE plotter = Plotter(max_bar=16, live_reload=True) pretty_midi = pm.PrettyMIDI() pretty_midi.instruments.append(pm.Instrument(0)) pretty_midi.instruments[0].append(pm.Note(100, 36, 0, 1)) # TODO wall_start_time = time.time() # TODO for bar_count in range(0, sys.maxsize): # TODO cursor_time = bar_count * seconds_per_loop # TODO sequence_adjusted = music_pb2.NoteSequence() sequence_adjusted.CopyFrom(sequence) sequence_adjusted = adjust_sequence_times(sequence_adjusted, wall_start_time) player.update_sequence(sequence_adjusted, start_time=cursor_time) # TODO MOVE TO UTILS pretty_sequence = mm.midi_io.note_sequence_to_pretty_midi(sequence) for instrument in pretty_sequence.instruments: for note in instrument.notes: pretty_midi.instruments[0].notes.append(note) plotter.show(pretty_midi, self._output_file) # TODO loop_start_time = cursor_time loop_end_time = loop_start_time + seconds_per_loop generation_start_time = loop_end_time generation_end_time = generation_start_time + seconds_per_loop generator_options = generator_pb2.GeneratorOptions() generator_options.args['temperature'].float_value = 1 generator_options.generate_sections.add( start_time=generation_start_time, end_time=generation_end_time) # TODO if bar_count % self._num_loops == 0: print("GENERATING") sequence = self._sequence_generator.generate( sequence, generator_options) sequence = sequences_lib.trim_note_sequence( sequence, generation_start_time, generation_end_time) else: print("LOOPING") sequence = sequences_lib.trim_note_sequence( sequence, loop_start_time, loop_end_time) sequence = sequences_lib.shift_sequence_times( sequence, seconds_per_loop) # TODO 1 wake up per bar sleep_time = seconds_per_loop - ( (time.time() - wall_start_time) % seconds_per_loop) time.sleep(sleep_time)
def generate(unused_argv): # Downloads the bundle from the magenta website, a bundle (.mag file) is a # trained model that is used by magenta mm.notebook_utils.download_bundle("drum_kit_rnn.mag", "bundles") bundle = mm.sequence_generator_bundle.read_bundle_file( os.path.join("bundles", "drum_kit_rnn.mag")) # Initialize the generator "drum_kit", this need to fit the bundle we # downloaded before generator_map = drums_rnn_sequence_generator.get_generator_map() generator = generator_map["drum_kit"](checkpoint=None, bundle=bundle) generator.initialize() # We will generate 3 bars, so with a # 1 bar primer we'll have 4 bars total num_bars = 3 qpm = 120 # The steps per quarter for this generator # is 4 steps per quarter seconds_per_step = 60.0 / qpm / generator.steps_per_quarter # We are using a default 16 steps per bar, which is # 4/4 music sampled at 4 steps per quarter note num_steps_per_bar = constants.DEFAULT_STEPS_PER_BAR # We calculate how many seconds per bar for # the generation time seconds_per_bar = num_steps_per_bar * seconds_per_step print(f"Seconds per step: {seconds_per_step}") print(f"Seconds per bar: {seconds_per_bar}") # Creates a primer sequence that is fed into the model for the generator, # which will generate a sequence based on this one # A DrumTrack models a drum sequence by step, so you have step 1 being the # midi note 36 (bass drum), followed by 3 steps of silence (those four steps # constitutes the first beat or quarter), followed by both notes 36 and 41 # being struck at the same time (followed by silence by these are optional) primer_drums = mm.DrumTrack([ frozenset(pitches) for pitches in [ (38, 51), (), (36, ), (), (38, 44, 51), (), (36, ), (), (), (), (38, ), (), (38, 44), (), (36, 51), (), ] ]) primer_sequence = primer_drums.to_sequence(qpm=qpm) # We store those time because the generation # will start after the end of the primer primer_start_time = 0 primer_end_time = primer_start_time + seconds_per_bar # We calculate the generation start and end # for a duration of num_bars generation_start_time = primer_end_time generation_end_time = generation_start_time + (seconds_per_bar * num_bars) print(f"Primer start and end:" f"[{primer_start_time}, {primer_end_time}]") print(f"Generation start and end:" f"[{generation_start_time}, {generation_end_time}]") # The generator interface is common for all models generator_options = generator_pb2.GeneratorOptions() # Add a bit of temperature for more flavor temperature = 1.1 print(f"Temperature: {temperature}") generator_options.args['temperature'].float_value = temperature # Defines the generation section generator_options.generate_sections.add(start_time=generation_start_time, end_time=generation_end_time) # We are using the primer sequence here instead of an empty sequence, # the resulting sequence is a NoteSequence instance sequence = generator.generate(primer_sequence, generator_options) # Write the resulting midi file to the output directory midi_file = os.path.join("output", "out.mid") mm.midi_io.note_sequence_to_midi_file(sequence, midi_file) print(f"Generated midi file: {os.path.abspath(midi_file)}") # Write the resulting plot file to the output directory plot_file = os.path.join("output", "out.html") pretty_midi = mm.midi_io.note_sequence_to_pretty_midi(sequence) plotter = Plotter() plotter.show(pretty_midi, plot_file) print(f"Generated plot file: {os.path.abspath(plot_file)}") return 0
def generate(unused_argv): # Downloads the bundle from the magenta website mm.notebook_utils.download_bundle("drum_kit_rnn.mag", "bundles") bundle = mm.sequence_generator_bundle.read_bundle_file( os.path.join("bundles", "drum_kit_rnn.mag")) # Initialize the generator "drum_kit" generator_map = drums_rnn_sequence_generator.get_generator_map() generator = generator_map["drum_kit"](checkpoint=None, bundle=bundle) generator.initialize() # Define constants qpm = 120 num_bars = 3 seconds_per_step = 60.0 / qpm / generator.steps_per_quarter num_steps_per_bar = constants.DEFAULT_STEPS_PER_BAR seconds_per_bar = num_steps_per_bar * seconds_per_step # Use a priming sequence primer_sequence = mm.midi_io.midi_file_to_note_sequence( os.path.join("primers", "Jazz_Drum_Basic_1_bar.mid")) primer_start_time = 0 primer_end_time = primer_start_time + seconds_per_bar # Calculates the generation start and end time generation_start_time = primer_end_time generation_end_time = generation_start_time + (seconds_per_bar * num_bars) generator_options = generator_pb2.GeneratorOptions() generator_options.args['temperature'].float_value = 1.1 generator_options.generate_sections.add(start_time=generation_start_time, end_time=generation_end_time) # Generates on primer sequence sequence = generator.generate(primer_sequence, generator_options) # Outputs the plot os.makedirs("output", exist_ok=True) plot_file = os.path.join("output", "out.html") pretty_midi = mm.midi_io.note_sequence_to_pretty_midi(sequence) plotter = Plotter() plotter.show(pretty_midi, plot_file) print(f"Generated plot file: {os.path.abspath(plot_file)}") # We find the proper input port for the software synth # (which is the output port for Magenta) output_ports = [ name for name in mido.get_output_names() if args.midi_port in name ] if not output_ports: raise Exception(f"Cannot find proper output ports in: " f"{mido.get_output_names()}") print(f"Playing generated MIDI in output port names: {output_ports}") # Start a new MIDI hub on that port (output only) midi_hub = MidiHub(input_midi_ports=[], output_midi_ports=output_ports, texture_type=None) # Start on a empty sequence, allowing the update of the # sequence for later. We don't especially need that right # now, because we could play the sequence immediately, but # it will be useful for later examples to have a player to # update new sequences with. empty_sequence = music_pb2.NoteSequence() player = midi_hub.start_playback(empty_sequence, allow_updates=True) # Remember that GM 1 compatible synthesizer will play the drums # sound bank if the MIDI channel is 10 (but the channel is zero # indexed in Magenta MIDI hub so you have to use 9). player._channel = 9 # Now we can play our sequence, but we need to adjust it first. # The MIDI player will play the sequence according to wall time, # but our sequence starts at 0. # Create a new empty note sequence, copy the sequence # we want to play in the empty sequence, then move the # start of the sequence by wall_start_time amount wall_start_time = time.time() sequence_adjusted = music_pb2.NoteSequence() sequence_adjusted.CopyFrom(sequence) sequence_adjusted = adjust_sequence_times(sequence_adjusted, wall_start_time) # The update sequence is the equivalent of "play" player.update_sequence(sequence_adjusted, start_time=wall_start_time) # We "join" on the thread, meaning the call will block # until the player has finished. Because the thread # never stops, this call will block indefinitely. By # adding a timeout of generation_end_time, the call will # return after the end of the sequence being played. try: print(f"Playing for {generation_end_time}") player.join(generation_end_time) except KeyboardInterrupt: # The KeyboardInterrupt is important if you want to press # CTRL-C during the playback to stop the player. print(f"Stopping") return 0
def _save_plot(sequence, path): midi = mm.midi_io.note_sequence_to_pretty_midi(sequence) plotter = Plotter(**plot_kwargs) plotter.save(midi, path) logger.info( self._logging_format.format(file_type="plot", path=path))
def generate(unused_argv): """Generates a basic drum sequence of 4 seconds based on a hard coded primer""" # TODO doc mm.notebook_utils.download_bundle("drum_kit_rnn.mag", "bundles") bundle = mm.sequence_generator_bundle.read_bundle_file( os.path.join("bundles", "drum_kit_rnn.mag")) generator_map = drums_rnn_sequence_generator.get_generator_map() generator = generator_map["drum_kit"](checkpoint=None, bundle=bundle) generator.initialize() qpm = 120 num_bars = 3 seconds_per_step = 60.0 / qpm / generator.steps_per_quarter num_steps_per_bar = constants.DEFAULT_STEPS_PER_BAR seconds_per_bar = num_steps_per_bar * seconds_per_step # Creates a primer sequence that is fed into the model for the generator, # which will generate a sequence based on this one # A DrumTrack models a drum sequence by step, so you have step 1 being the # midi note 36 (bass drum), followed by 3 steps of silence (those four steps # constitutes the first beat or quarter), followed by both notes 36 and 41 # being struck at the same time (followed by silence by these are optional) primer_drums = mm.DrumTrack( [frozenset(pitches) for pitches in [(38, 51), (), (36,), (), (38, 44, 51), (), (36,), (), (), (), (38,), (), (38, 44), (), (36, 51), (), ]]) primer_sequence = primer_drums.to_sequence(qpm=qpm) primer_start_time = 0 primer_end_time = primer_start_time + seconds_per_bar # Defines the start and end of the generation, which starts at the step # after the end of the primer (we'll see in 03.py this calculation makes # it harder to fall on proper beats) and ends at total seconds # The complete generation will thus contain the primer and the total length # needs to be at least the size of the primer # TODO doc generation_start_time = primer_end_time generation_end_time = generation_start_time + (seconds_per_bar * num_bars) generator_options = generator_pb2.GeneratorOptions() generator_options.args['temperature'].float_value = 1.1 generator_options.generate_sections.add( start_time=generation_start_time, end_time=generation_end_time) # We are using the primer sequence here instead of an empty sequence sequence = generator.generate(primer_sequence, generator_options) # TODO doc plot_file = os.path.join("output", "out.html") print("Generated plot file: " + str(os.path.abspath(plot_file))) pretty_midi = mm.midi_io.note_sequence_to_pretty_midi(sequence) plotter = Plotter() plotter.show(pretty_midi, plot_file) # TODO doc input_ports = [name for name in mido.get_output_names() if "VirtualMIDISynth" in name or "FLUID Synth" in name] if not input_ports: print("Cannot find proper input port in " + str(mido.get_output_names())) print("Playing generated MIDI in input port names: " + str(input_ports)) midi_hub = mh.MidiHub([], input_ports, None) # TODO doc empty_sequence = music_pb2.NoteSequence() player = midi_hub.start_playback(empty_sequence, allow_updates=True) player._channel = 9 # TODO doc wall_start_time = time.time() sequence_adjusted = music_pb2.NoteSequence() sequence_adjusted.CopyFrom(sequence) sequence_adjusted = adjust_sequence_times(sequence_adjusted, wall_start_time) player.update_sequence(sequence_adjusted, start_time=wall_start_time) # TODO doc try: player.join(generation_end_time) except KeyboardInterrupt: return 0 finally: return 0
class SequenceLooper(threading.Thread): def __init__(self, name: str, bar_start_event, action_server: ActionServer, midi_hub, bundle_name: str, config_name: str, timing: Timing, midi_channel: int = 1, bar_per_loop: int = 2): super(SequenceLooper, self).__init__() self.name = name self._stop_signal = False self._bar_start_event = bar_start_event self._action_server = action_server self._midi_hub = midi_hub self._bundle_filename = bundle_name + ".mag" self._config_name = config_name self._timing = timing self._midi_channel = midi_channel self._bar_per_loop = bar_per_loop model_dir = os.path.join("output", "models") if not os.path.exists(model_dir): os.makedirs(model_dir) self._output_plot = os.path.join("output", "models", self.name + ".html") self._output_midi = os.path.join("output", "models", self.name + ".mid") self._plotter = Plotter(plot_max_length_bar=bar_per_loop, live_reload=True, preset_name="PRESET_SMALL") def stop(self): self._stop_signal = True def run(self): sequence = music_pb2.NoteSequence() player = self._midi_hub.start_playback(sequence, allow_updates=True) player._channel = self._midi_channel pretty_midi = pm.PrettyMIDI() pretty_midi.instruments.append(pm.Instrument(0)) # Wait for the dreamer and store the time with the delta wall_start_time = time.time() self._bar_start_event.wait() bar_count = 0 while not self._stop_signal: # Number of seconds we should be at the beginning of this loop expected_start_time = self._timing.get_expected_start_time(bar_count) # Number of actual seconds since we started this thread from wall clock, # which is smaller then the expected start time # The difference is between: the actual wakeup time and the expected # (calculated) start time. By keeping this we can adjust the sequence # according to the drift. diff_time = self._timing.get_diff_time(wall_start_time, bar_count) tf.logging.debug("Playing " + str(self._timing.get_timing_args( wall_start_time, bar_count))) # Player sequence_adjusted = music_pb2.NoteSequence() sequence_adjusted.CopyFrom(sequence) sequence_adjusted = adjust_sequence_times(sequence_adjusted, wall_start_time - diff_time) player.update_sequence(sequence_adjusted, start_time=expected_start_time) # Plotter pretty_midi = mm.midi_io.note_sequence_to_pretty_midi(sequence) self._plotter.save(pretty_midi, self._output_plot) pretty_midi.write(self._output_midi) # Sets timing seconds_per_bar = self._timing.get_seconds_per_bar() seconds_per_loop = self._bar_per_loop * seconds_per_bar loop_start_time = expected_start_time loop_end_time = loop_start_time + seconds_per_loop generation_start_time = loop_end_time generation_end_time = generation_start_time + seconds_per_loop action = self._action_server.context.get(self.name, None) tf.logging.debug(str(action) + " " + str([ ("expected_start_time", expected_start_time), ("loop_start_time", loop_start_time), ("loop_end_time", loop_end_time), ("generation_start_time", generation_start_time), ("generation_end_time", generation_end_time)])) if not action: pass elif action is ActionType.LOOP: sequence = sequences.loop(sequence, loop_start_time, loop_end_time, seconds_per_loop) elif action is ActionType.GENERATE: sequence = sequences.generate(sequence, self.name, self._bundle_filename, self._config_name, generation_start_time, generation_end_time) elif action is ActionType.GENERATE_ONCE: sequence = sequences.generate(sequence, self.name, self._bundle_filename, self._config_name, generation_start_time, generation_end_time) self._action_server.context[self.name] = ActionType.LOOP elif action is ActionType.RESET_ONCE: sequence = sequences.reset(sequence, loop_start_time, loop_end_time, seconds_per_loop) self._action_server.context[self.name] = ActionType.LOOP elif action is ActionType.RESET_GENERATE: sequence = sequences.reset(sequence, loop_start_time, loop_end_time, seconds_per_loop) self._action_server.context[self.name] = ActionType.GENERATE_ONCE else: raise Exception(f"Unknown action {action}") while True: # Unlock at the start of the bar self._bar_start_event.wait() bar_count += 1 if bar_count % self._bar_per_loop == 0: break