def sliding_stretch(self, i_factor, f_factor, start=0, end=None): """ cat: edit desc: stretch by sliding amount args: i_factor: initial factor, num >0; 0.2, 3; f_factor: final, num >0; 0.2, 3; [start: beat/second to begin. defaults beginning] [end: beat/second to end. defaults to end of rec] """ i_factor = inpt_validate(i_factor, "flt", allowed=[0, None]) f_factor = inpt_validate(f_factor, "flt", allowed=[0, None]) start = inpt_validate(start, 'beatsec').to_samps() if end is None: end = self.size_samps() else: end = inpt_validate(end, 'beatsec').to_samps() print(" sliding stretch, from factor {0:.4f}x to {1:.4f}x...".format(i_factor, f_factor)) beginning = self.arr[:start] middle = [] factor_count = 0 factor = i_factor delta_factor = (f_factor - i_factor) / ind(end - start) for i in self.arr[start:end]: factor_count += factor for _ in range(int(factor_count)): middle.append(i) factor_count = factor_count - int(factor_count) factor += delta_factor end = self.arr[end:] self.arr = np.vstack((beginning, middle, end))
def set_length_and_period(self, length=None, period=None): """ cat: edit desc: set the length and period of this rhythm. Give 0 for either argument to keep the current value args: length: length of the rhythm in beats period: standard subdivision of this rhythm, in beats """ if length == 0 and self.length is not None: pass elif length is None: p("Enter a length in beats for this Rhythm") length = inpt('beats') if length != 0: self.length = length elif self.length is None: err_mess( "There is no current value for length, enter a valid value in beats" ) return self.set_length_and_period(None, period) else: self.length = inpt_validate(length, 'beats') if period == 0 and self.period is not None: pass elif period is None: p("Enter the period, or smallest beat that this Rhythm usually lands on", h=True) self.period = inpt('beat') else: self.period = inpt_validate(period, "beat")
def view_waveform(self, start=0, end=None, precision=50): """ cat: info desc: show the waveform of this audio args: [start: seconds/beats to begin view window. default beginning] [end: seconds/beats to end view window. -1 selects end. default end] [precision: percent of how detailed the plot should be. default 50] """ start = inpt_validate(start, 'beatsec') if (end is None) or (end == "-1"): end = self.size_samps() else: end = inpt_validate(end, 'beatsec') if end.samples_value >= self.size_samps(): end = self.size_samps() if end <= start: err_mess("End cannot be before or equal to start") return precision = inpt_validate(precision, 'pcnt', allowed=[5, 10000]) info_block("Generating waveform at {0}%...".format(precision)) anlsys = Analysis(self, start=start, end=end) frame_len = (end - start) / (precision * 2) anlsys.set_frame_lengths(frame_len) left = anlsys.arr[:, 0] right = anlsys.arr[:, 1] anlsys.plot(left, right, fill=True)
def playback(self, duration=5, start=0, first_time=True): """ cat: info desc: playback this recording's audio args: [duration: beats/seconds. default 5] [start: beat/seconds to start at. defualts to beginning] """ duration = inpt_validate(duration, 'beatsec') start = inpt_validate(start, 'beatsec') section_head("Playback of '{0}'".format(self.name)) print(" preparing...") start_ind = ind(start) if duration <= 0: end_ind = self.size_samps() else: end_ind = start_ind + ind(duration * self.rate) arr = self.arr[start_ind : end_ind] arr = self.get_panned_rec(arr) print(" playing...") rate = self.rate.to_rate().round().magnitude try: sd.play(arr, rate) sd.wait() except TypeError as e: if first_time: retry = True err_mess("Error playing back. Trying again") self.arr = [[float(i), float(j)] for i,j in self.arr] self.playback(duration, start, False) else: raise e print(" finished playback")
def add(self, location, value): """ cat: edit desc: add a new marker (see type_hint process for info on the specific types that arguments allow) args: location: the time where this marker should occur, in '{time_units}' value: the value of this marker, in/as a '{val_units}' dev: this docstring is .format()ed in init """ beatsec = inpt_validate(location, self.time_units, allowed=self.time_allowed) value = inpt_validate(value, self.val_units, allowed=self.val_allowed) sample_ind = beatsec.to_samps() self.add_marker(sample_ind, DiscreteMarker(beatsec, value))
def process_child(self, child_name=None): """ cat: edit desc: edit a child member of this object args: [child_name: name of child to edit, omit to list children] """ if len(self.children) == 0: err_mess("This Project has no children to process!") if child_name is None: self.list_children() p("Enter the name of the child you wish to edit") child_name = inpt("name") else: child_name = inpt_validate(child_name, "name") try: child_name = autofill(child_name, [i.name for i in self.children]) except AutofillError as e: err_mess("Child name '{0}' not found".format(e.word)) self.process_child() return child = None for i in self.children: if i.name == child_name: if child is not None: raise UnexpectedIssue("Multiple children with name {0}".format(child_name)) child = i process(child) child.save()
def add(self, location, value, change_type=None): """ cat: edit desc: add a new marker (see type_hint process for info on the specific types that arguments allow) args: location: the time where this marker should occur, in '{time_units}' value: the value of this marker, in/as '{val_units}' [change type: how the value transitions from the previous marker to this one, one of '{change_types}'] dev: this docstring is .format()ed in init """ beatsec = inpt_validate(location, self.time_units, self.time_allowed) sample_ind = beatsec.to_samps() value = inpt_validate(value, self.val_units, self.val_allowed) change_type = self.validate_change_type(change_type) self.add_marker(sample_ind, ContinuousMarker(beatsec, value, change_type))
def set(self, value): """ cat: edit args: value: the new value of this property """ value = inpt_validate(value, self.inpt_mode, self.allowed) setattr(self.caller, self.attrname(), value)
def rename(self, name=None): if name is None: print(" Give this active pair a name: ", end="") name = inpt("obj") print(" named '{0}'".format(name)) else: name = inpt_validate(name, "obj") self.name = name
def repeat(self, times): """ cat: edit args: times: integer number of times to repeat, >=1; 1, 10; """ times = inpt_validate(times, 'int', allowed=[1, None]) print(" repeating {0} times...".format(times)) self.arr = np.vstack([self.arr] * times)
def pan(self, amount): """ cat: edit desc: set pan value args: amount: number from -1 (left) to 1 (right); -1, 1; """ amount = inpt_validate(amount, 'float', allowed=[-1, 1]) print(" Setting pan to {0}...".format(amount)) self.pan_val = amount
def amplify(self, factor): """ cat: edit desc: multiply amplitude by factor. 0-1 to reduce, >1 to amplify args: factor: num >0; 0.5, 1.5; """ factor = inpt_validate(factor, 'float', allowed=[0, 10]) print(" amplifying by {0}x...".format(factor)) self.arr *= factor
def fade_in(self, dur, start=0): """ cat: edit desc: fade in audio args: duration: duration in beats/seconds of fade-in; 0, 10; [start: beat/second to begin. defaults 0] """ seconds = inpt_validate(dur, 'beatsec') start = ind(inpt_validate(start, 'beatsec')) print(" fading in {0} starting at {1}...".format(seconds, start)) length = ind(self.rate * seconds) for i in range(length): try: self.arr[i + start][0] *= i / length self.arr[i + start][1] *= i / length except IndexError: if i + start >= 0: return
def rename(self, name=None): """ cat: meta dev: call this method via super, and implement renaming of files other than datafile and its self.path directory """ old_name = self.name if old_name is not None: old_data_dir = self.get_data_dir() old_datafile_name = self.get_data_filename() if name is None: p("Give this {0} a name".format(self.reltype)) name = inpt("obj") else: name = inpt_validate(name, 'obj') # validate if hasattr(self.parent, "validate_child_name"): if self.parent.validate_child_name(self, name): self.name = name info_block("Named '{0}'".format(name)) else: err_mess("Invalid name") self.rename() else: if Settings.is_debug(): show_error( AttributeError( "Parent obj '{0}' does not have validate_child_name". format(self.parent))) else: try: log_err( "Parent object type {0} has no 'validate_child_name' method" .format(self.parent.reltype)) except AttributeError: log_err( "Parent object '{0}' of '{1}' has no 'validate_child_name' method" .format(self.parent, self)) self.name = name info_block("Named '{0}'".format(name)) # if actual renaming and not an initial naming if old_name is not None: new_data_dir = self.get_data_dir() # rename dir os.rename(old_data_dir, new_data_dir) # rename datafile (which is in newdatadir now) old_datafile = join_path(new_data_dir, old_datafile_name, ext=self.datafile_extension) new_datafile = self.get_datafile_fullpath() os.rename(old_datafile, new_datafile)
def extend(self, length, placement="a"): """ cat: edit desc: extend with silence by a number of seconds/beats args: length: beats/seconds to extend; 0, 1; [placement: "a"=after, "b"=before. default after] """ length = inpt_validate(length, 'beatsec') placement = inpt_validate(placement, "letter", allowed="ab") if placement == "b": before = " before" else: before = "" print(" extending by {0}{1}...".format(length, before)) silence = np.zeros( shape=(ind(self.rate * length), 2) ) if placement == "b": self.arr = np.vstack((silence, self.arr)) else: self.arr = np.vstack((self.arr, silence))
def fade_out(self, dur, end=None): """ cat: edit desc: fade out audio args: duration: duration in beats/seconds of fade-out; 0, 10; [end: beat/second to end. defaults end of audio] """ if end is None: end = self.size_secs() else: end = inpt_validate(end, "beatsec") seconds = inpt_validate(dur, "beatsec") print(" Fading out {0} ending at {1}...".format(seconds, end)) length = ind(self.rate * seconds) for i in range(ind(end) - length, ind(end)): try: self.arr[i][0] *= (length - i) / length self.arr[i][1] *= (length - i) / length except IndexError: pass
def public_process_wrapper(*args): nonlocal allowed, modes allowed = _expand_ellipses(len(modes), allowed) func_args = [ args[0] ] # self as first arg for i in range(len(args) - 1): try: mode = modes[i] except IndexError: func_args.append(args[i+1]) else: val = inpt_validate(args[i+1], mode, allowed=allowed[i]) func_args.append(val) # do default args params = list(signature(method).parameters.values()) for i in range(len(args) - 1, len(modes)): if params[i+1].default == Parameter.empty: break val = inpt_validate(params[i+1].default, modes[i], allowed=allowed[i]) func_args.append(val) return method(*func_args)
def trim(self, left, right=None): """ cat: edit desc: trim to only contain audio between <left> and <right> args: left: beat/second; 0, 5; [right: beat/second. defaults to end; 10, 20;] """ left = inpt_validate(left, 'beatsec') if right is None: print(" trimming first {0}".format(left)) right = self.size_samps() else: right = inpt_validate(right, 'beatsec') print(" trimming everything outside {0} to {1}".format(left, right)) left, right = left.to_secs(), right.to_secs() if left * self.rate > self.size_samps(): print(" > this will empty the recording, confirm? [y/n]: ", end="") if not inpt("yn"): return self.arr = np.empty(shape=(0,2)) else: self.arr = self.arr[ind(left * self.rate) : ind(right * self.rate)]
def move(self, current, new): """ cat: edit desc: move a marker to a new location Args: current: the beat or sec of the marker to move new: the beat or sec to move it to """ old_beatsec = inpt_validate(current, self.time_units, allowed=self.time_allowed) new_beatsec = inpt_validate(new, self.time_units, allowed=self.time_allowed) old_sample_ind = old_beatsec.to_samps() new_samp_ind = new_beatsec.to_samps() try: marker = self.markers[old_sample_ind] except KeyError: err_mess("No marker to move at {0}!".format(old_beatsec)) return marker.beatsec = new_beatsec self.add_marker(new_samp_ind, marker) del self.markers[old_sample_ind]
def delete(self, location): """ cat: edit desc: delete a marker from this controller args: location: beat or sec of the marker to remove """ beatsec = inpt_validate(location, self.time_units, allowed=self.time_allowed) sample_ind = beatsec.to_samps() try: marker = self.markers[sample_ind] info_line("Deleting marker '{0}'".format(marker)) del self.markers[sample_ind] except KeyError: err_mess("No marker to delete at {0}".format(beatsec))
def process_child_sample(self, name=None): """ desc: process a sample contained in this group """ while True: if name is None: self.list_samples() p("Enter the name of the Sample to process") name = inpt('name') else: name = inpt_validate(name, 'name') try: self.samples[name] break except KeyError: err_mess("> Sample '{0}' does not exist!".format(name)) process(self.samples[name])
def stretch(self, factor): """ cat: edit desc: stretch by a factor args: factor: number >0; 0.2, 3 """ factor = inpt_validate(factor, 'float', allowed=[0, None]) print(" stretching by a factor of {0:.4f}...".format(factor)) new_rec = [] factor_count = 0 for i in self.arr: factor_count += factor for _ in range(int(factor_count)): new_rec.append(i) factor_count -= int(factor_count) self.arr = np.asarray(new_rec)
def set_variability(self, var=None): if var is None: p("Enter the variability (in percentage, 0-100%) of this Rhythm") self.variability = inpt('pcnt') else: self.variability = inpt_validate(var, 'pcnt')
def validate_value(self, value): return inpt_validate(value, 'pcnt')
def __set__(self, instance, value): if not isinstance(value, (Units.Quant, Controller)): value = inpt_validate(value, self.inpt_mode, self.allowed) setattr(instance, self.attrname(), value)