コード例 #1
0
def adjust_sequence_times(sequence, delta_time):
    # pylint: disable-msg=no-member
    retimed_sequence = NoteSequence()
    retimed_sequence.CopyFrom(sequence)
    for note in retimed_sequence.notes:
        note.start_time += delta_time
        note.end_time += delta_time
    retimed_sequence.total_time += delta_time
    return retimed_sequence
コード例 #2
0
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
コード例 #3
0
    def run(self):
        start_time = time()
        self._captor = self._midi_hub.start_capture(self._qpm, start_time)

        if not self._clock_signal and self._metronome_channel is not None:
            self._midi_hub.start_metronome(self._qpm,
                                           start_time,
                                           channel=self._metronome_channel)

        # Register callbacks
        if self._end_call_signal is not None:
            self._captor.register_callback(self._end_call_callback,
                                           signal=self._end_call_signal)
        if self._panic_signal is not None:
            self._captor.register_callback(self._panic_callback,
                                           signal=self._panic_signal)
        if self._mutate_signal is not None:
            self._captor.register_callback(self._mutate_callback,
                                           signal=self._mutate_signal)

        # Keep track of the end of the previous tick time.
        last_tick_time = time()

        # Keep track of the duration of a listen state.
        listen_ticks = 0

        # Start with an empty response sequence.
        response_sequence = NoteSequence()
        response_start_time = 0
        response_duration = 0

        # Get handles for all required players
        player_melody = self._midi_hub.start_playback(response_sequence,
                                                      playback_channel=1,
                                                      allow_updates=True)
        player_bass = self._midi_hub.start_playback(response_sequence,
                                                    playback_channel=2,
                                                    allow_updates=True)
        player_chords = self._midi_hub.start_playback(response_sequence,
                                                      playback_channel=3,
                                                      allow_updates=True)
        player_drums = self._midi_hub.start_playback(response_sequence,
                                                     playback_channel=9,
                                                     allow_updates=True)

        # Song structure data
        part_in_song = 0  # index to STRUCTURE list
        bars_played = 0  # absolute number of bars played
        bars_played_for_part = 0  # number of bars played for the current part
        total_bars = self.STRUCTURE.duration(bars=True)
        part_duration = self.STRUCTURE[part_in_song].duration(bars=True)

        # Enter loop at each clock tick.
        for captured_sequence in self._captor.iterate(
                signal=self._clock_signal, period=self._tick_duration):
            if self._stop_signal.is_set():
                break
            if self._panic.is_set():
                response_sequence = NoteSequence()
                player_melody.update_sequence(response_sequence)
                player_bass.update_sequence(response_sequence)
                player_chords.update_sequence(response_sequence)
                player_drums.update_sequence(response_sequence)
                self._panic.clear()

            tick_time = captured_sequence.total_time

            # Set to current QPM, since it might have changed.
            if not self._clock_signal and self._metronome_channel is not None:
                self._midi_hub.start_metronome(self._qpm,
                                               tick_time,
                                               channel=self._metronome_channel)
            captured_sequence.tempos[0].qpm = self._qpm

            tick_duration = tick_time - last_tick_time

            if bars_played_for_part > part_duration:
                part_in_song += 1
                bars_played_for_part = 0
            if part_in_song >= len(self.STRUCTURE):
                break
            start_of_part = bars_played_for_part == 0
            part_duration = self.STRUCTURE[part_in_song].duration(bars=True)
            part = self.STRUCTURE[part_in_song]
            logging.info(
                "Current Part: {} ({}) | Bars (Part): {}/{} | Bars (Total): {}/{}"
                .format(part.name, part_in_song, bars_played_for_part,
                        part_duration, bars_played, total_bars))

            response_start_time = tick_time
            capture_start_time = self._captor.start_time
            response_duration = part_duration * tick_duration

            last_tick_time = tick_time
            bars_played += 1
            bars_played_for_part += 1

            # If we are not at the beginning of a new part, there's nothing to do.
            if not start_of_part:
                continue

            if self.MELODY_CACHE[part.name]:
                logging.info("Pulling melody sequence from cache")
                melody_sequence = self.MELODY_CACHE[part.name].sequence
                response_start_time = self.MELODY_CACHE[
                    part.name].response_start_time
            else:
                logging.info("Generating new melody sequence")
                melody_sequence = self._generate(
                    0, captured_sequence, capture_start_time,
                    response_start_time,
                    response_start_time + response_duration)
                self.MELODY_CACHE[part.name] = CacheItem(
                    melody_sequence, capture_start_time)

            if self.BASS_CACHE[part.name]:
                logging.info("Pulling bass sequence from cache")
                bass_sequence = self.BASS_CACHE[part.name].sequence
                response_start_time = self.BASS_CACHE[
                    part.name].response_start_time
            else:
                logging.info("Generating new bass sequence")
                bass_sequence = self._generate(
                    1, captured_sequence, capture_start_time,
                    response_start_time,
                    response_start_time + response_duration)
                self.BASS_CACHE[part.name] = CacheItem(bass_sequence,
                                                       capture_start_time)

            if self.DRUM_CACHE[part.name]:
                logging.info("Pulling drum sequence from cache")
                drum_sequence = self.DRUM_CACHE[part.name].sequence
                response_start_time = self.DRUM_CACHE[
                    part.name].response_start_time
            else:
                logging.info("Generating new drum sequence")
                drum_sequence = self._generate(
                    2, captured_sequence, capture_start_time,
                    response_start_time,
                    response_start_time + response_duration)
                self.DRUM_CACHE[part.name] = CacheItem(drum_sequence,
                                                       capture_start_time)

            size = getsizeof(self.MELODY_CACHE) + getsizeof(
                self.BASS_CACHE) + getsizeof(self.DRUM_CACHE)
            logging.info("Cache Size: {}KB".format(size // 8))

            chord_sequence, notes = NoteSequence(), []
            chords = part.get_midi_chords()
            for i, chord in enumerate(chords):
                for note in generate_midi_chord(chord, i / 2, 0.5):
                    notes.append(note)
            notes = [
                Note(note.pitch, note.velocity,
                     note.start + response_start_time,
                     note.start + note.end + response_start_time)
                for note in notes
            ]
            add_track_to_sequence(chord_sequence, 0, notes)

            # If it took too long to generate, push the response to next tick.
            if (time() - response_start_time) >= tick_duration / 4:
                push_ticks = ((time() - response_start_time) // tick_duration +
                              1)
                response_start_time += push_ticks * tick_duration
                melody_sequence = adjust_sequence_times(
                    melody_sequence, push_ticks * tick_duration)
                bass_sequence = adjust_sequence_times(
                    bass_sequence, push_ticks * tick_duration)
                chord_sequence = adjust_sequence_times(
                    chord_sequence, push_ticks * tick_duration)
                drum_sequence = adjust_sequence_times(
                    drum_sequence, push_ticks * tick_duration)
                self.MELODY_CACHE[
                    part.name].response_start_time = response_start_time
                self.BASS_CACHE[
                    part.name].response_start_time = response_start_time
                self.DRUM_CACHE[
                    part.name].response_start_time = response_start_time
                logging.warning(
                    "Response too late. Pushing back {} ticks.".format(
                        push_ticks))

            # Start response playback. Specify start_time to avoid stripping initial events due to generation lag.
            player_melody.update_sequence(melody_sequence,
                                          start_time=response_start_time)
            player_bass.update_sequence(bass_sequence,
                                        start_time=response_start_time)
            player_chords.update_sequence(chord_sequence,
                                          start_time=response_start_time)
            player_drums.update_sequence(drum_sequence,
                                         start_time=response_start_time)

        player_melody.stop()
        player_bass.stop()
        player_chords.stop()
        player_drums.stop()