def __init__(self, multiply: Union[Real, Envelope, Sequence] = 1, add: Union[Real, Envelope, Sequence] = 0): self.multiply = Envelope.from_list(multiply) if isinstance( multiply, Sequence) else multiply self.add_amount = Envelope.from_list(add) if isinstance( add, Sequence) else add
def _assert_curves_are_valid(minima_curve: expenvelope.Envelope, maxima_curve: expenvelope.Envelope): """Helper method that asserts the curves are a valid min/max pair.""" # make sure both curves have equal duration try: assert minima_curve.end_time() == maxima_curve.end_time() except AssertionError: message = ( "Found unequal duration when comparing 'minima_curve' (with end_time =" " '{}') and 'maxima_curve' (with end_time = '{}').".format( minima_curve.end_time(), maxima_curve.end_time())) message += " Make sure both curves have equal duration." raise ValueError(message) # compare all local extrema to make sure that at any time # point minima_curve < maxima_curve points_to_compare = ( minima_curve.local_extrema(include_saddle_points=True) + maxima_curve.local_extrema(include_saddle_points=True) + [0, minima_curve.end_time()]) for time in points_to_compare: try: assert minima_curve.value_at(time) < maxima_curve.value_at( time) except AssertionError: message = ( "At time '{}' 'minima_curve' isn't smaller than 'maxima_curve'!" .format(time)) raise ValueError(message)
def _convert_params_to_envelopes_if_needed(self): # if we've been given extra parameters of playback, and their values are envelopes written as lists, etc. # this converts them all to Envelope objects for param, value in self.iterate_extra_parameters_and_values(): if hasattr(value, '__len__'): self["param_" + param] = Envelope.from_list(value) self.temp["parameters_that_came_from_lists"].add(param)
def register_note(self, instrument: ScampInstrument, note_info: dict) -> None: """ Called when an instrument wants to register that it finished a note, records note in all transcriptions :param instrument: the ScampInstrument that played the note :param note_info: the note info dictionary on that note, containing time stamps, parameter changes, etc. """ assert note_info[ "end_time_stamp"] is not None, "Cannot register unfinished note!" param_change_segments = note_info["parameter_change_segments"] if note_info["start_time_stamp"].time_in_master == note_info[ "end_time_stamp"].time_in_master: return # loop through all the transcriptions in progress for performance, clock, clock_start_beat, units in self._transcriptions_in_progress: # figure out the start_beat and length relative to this transcription's clock and start beat start_beat_in_clock = Transcriber._resolve_time_stamp( note_info["start_time_stamp"], clock, units) end_beat_in_clock = Transcriber._resolve_time_stamp( note_info["end_time_stamp"], clock, units) note_start_beat = start_beat_in_clock - clock_start_beat note_length = end_beat_in_clock - start_beat_in_clock # handle split points (if applicable) by creating a note length sections tuple note_length_sections = None if len(note_info["split_points"]) > 0: note_length_sections = [] last_split = note_start_beat for split_point in note_info["split_points"]: split_point_beat = Transcriber._resolve_time_stamp( split_point, clock, units) note_length_sections.append(split_point_beat - last_split) last_split = split_point_beat if end_beat_in_clock > last_split: note_length_sections.append(end_beat_in_clock - last_split) note_length_sections = tuple(note_length_sections) # get curves for all the parameters extra_parameters = {} for param in note_info["parameter_start_values"]: if param in param_change_segments and len( param_change_segments[param]) > 0: levels = [note_info["parameter_start_values"][param]] # keep track of this in case of gaps between segments beat_of_last_level_recorded = start_beat_in_clock durations = [] curve_shapes = [] for param_change_segment in param_change_segments[param]: # no need to transcribe a param_change_segment that was aborted immediately if param_change_segment.duration == 0 and \ param_change_segment.end_level == param_change_segment.start_level: continue param_start_beat_in_clock = Transcriber._resolve_time_stamp( param_change_segment.start_time_stamp, clock, units) param_end_beat_in_clock = Transcriber._resolve_time_stamp( param_change_segment.end_time_stamp, clock, units) # if there's a gap between the last level we recorded and this segment, we need to fill it with # a flat segment that holds the last level recorded if param_start_beat_in_clock > beat_of_last_level_recorded: durations.append(param_start_beat_in_clock - beat_of_last_level_recorded) levels.append(levels[-1]) curve_shapes.append(0) durations.append(param_end_beat_in_clock - param_start_beat_in_clock) levels.append(param_change_segment.end_level) curve_shapes.append(param_change_segment.curve_shape) beat_of_last_level_recorded = param_end_beat_in_clock # again, if we end the curve early, then we need to add a flat filler segment if beat_of_last_level_recorded < note_start_beat + note_length: durations.append(note_start_beat + note_length - beat_of_last_level_recorded) levels.append(levels[-1]) curve_shapes.append(0) # assign to specific variables for pitch and volume, otherwise put in a dictionary of extra params if param == "pitch": # note that if the length of levels is 1, then there's been no meaningful animation # so just act like it's not animated. This probably shouldn't really come up. (It was # coming up before with zero-length notes, but now those are just skipped anyway.) if len(levels) == 1: pitch = levels[0] else: pitch = Envelope.from_levels_and_durations( levels, durations, curve_shapes) elif param == "volume": if len(levels) == 1: volume = levels[0] else: volume = Envelope.from_levels_and_durations( levels, durations, curve_shapes) else: if len(levels) == 1: extra_parameters[param] = levels[0] else: extra_parameters[ param] = Envelope.from_levels_and_durations( levels, durations, curve_shapes) else: # assign to specific variables for pitch and volume, otherwise put in a dictionary of extra params if param == "pitch": pitch = note_info["parameter_start_values"]["pitch"] elif param == "volume": volume = note_info["parameter_start_values"]["volume"] else: extra_parameters[param] = note_info[ "parameter_start_values"][param] for instrument_part in performance.get_parts_by_instrument( instrument): # it'd be kind of weird for more than one part to have the same instrument, but if they did, # I suppose that each part should transcribe the note instrument_part.new_note( note_start_beat, note_length_sections if note_length_sections is not None else note_length, pitch, volume, note_info["properties"])
# 3 of the License, or (at your option) any later version. # # # # This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; # # without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. # # See the GNU General Public License for more details. # # # # You should have received a copy of the GNU General Public License along with this program. # # If not, see <http://www.gnu.org/licenses/>. # # ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ # from expenvelope import Envelope import math # Ways of constructing Envelopes envelope_from_levels_and_durations = Envelope((1.0, 0.2, 0.6, 0), (2.0, 1.0, 3.0)) # alternative: `Envelope.from_levels_and_durations((1.0, 0.2, 0.6, 0), (2.0, 1.0, 3.0))` envelope_from_levels_and_durations.show_plot( "Envelope from levels and durations") envelope_from_levels_and_durations_with_curve_shapes = Envelope( (1.0, 0.2, 0.6, 0), (2.0, 1.0, 3.0), curve_shapes=(2, 2, -3)) # alternative: `Envelope.from_levels_and_durations((1.0, 0.2, 0.6, 0), (2.0, 1.0, 3.0), curve_shapes=(2, 2, -3))` envelope_from_levels_and_durations_with_curve_shapes.show_plot( "Envelope with curve shapes") envelope_from_levels = Envelope.from_levels((1.0, 0.2, 0.6, 0), length=3) envelope_from_levels.show_plot("Envelope from levels and total length") envelope_from_points = Envelope.from_points((-1, 5), (1, 6), (5, -2)) envelope_from_points.show_plot("Envelope from points")
def _convert_params_to_envelopes_if_needed(self): # if we've been given extra parameters of playback, and their values are envelopes written as lists, etc. # this converts them all to Envelope objects for key, value in self.items(): if (key.startswith("param_") or key.endswith("_param")) and isinstance(value, (list, tuple)): self[key] = Envelope.from_list(value)