def process_project(self): if self.current_open_proj is None: err_mess("No project is currently open! Open or create one") else: process(self.current_open_proj) self.current_open_proj.save() self.current_open_proj = None
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 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 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 process_validate(command, obj): """ check command as valid or one of the shortcuts return True on valid """ if command == []: err_mess("No command entered") command = "None" elif command == ['']: err_mess( "Only alphanumeric characters, spaces, and underscores are allowed" ) elif command[0] in ("q", "quit"): p("Save before exiting?") if inpt("yn"): obj.save() info_block("Exiting processing of {0} '{1}'...".format( obj.reltype, obj.name)) raise Cancel(obj) elif command[0] in ("o", "options"): obj.options() elif command[0] in ("h", "help"): pass # callback handled during inpt() else: return True return False
def __init__(self, rel_id, reltype, name, path, parent, custom_path=False, **kwargs): super().__init__(**kwargs) self.parent = parent self.reltype = reltype self.name = name if path is None and not custom_path: if self.parent is not None: self.path = self.parent.get_data_dir() else: if Settings.is_debug(): err_mess( "Warning: Setting relative path for {0} '{1}'".format( self.reltype, self.name)) self.path = "./" else: raise UnexpectedIssue("Path is None, with no parent") os.makedirs(self.get_data_dir(), exist_ok=True) else: self.path = path # path not including object's own directory self.rel_id = rel_id if rel_id is not None else RelGlobals.get_next_id( ) if path is not None and reltype != "Program": os.makedirs(self.get_data_dir(), exist_ok=True)
def validate_change_type(self, change_type): if change_type is None: return self.change_type_options[0] while change_type not in self.valid_change_types: err_mess("Invalid change type '{0}'".format(change_type)) p("Select one of: {0}".format(", ".join(self.valid_change_types))) change_type = inpt('alphanum') return change_type
def handle_bad_args(e, method_name, args, obj): message = str(e) if isinstance(e, TypeError) and 'positional argument' in message: command = complete_args(e, method_name, args, obj) do_command(command, obj) elif isinstance(e, ValueError): err_mess("Argument entered incorrectly: {0}".format(message)) else: show_error(e)
def undo(self): """ cat: save desc: reverts to previous state (maximum of 5 times) """ section_head("Undoing...") if len(self.recents) != 0: self.arr = self.recents.pop(0) else: err_mess("No history to revert to!")
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 add_sample(self, new_sample): if isinstance(new_sample, Recording): if not self.validate_child_name(new_sample, new_sample.name): err_mess( "Sample with name '{0}' already exists in Sample Group '{1}'! You may rename it and add it again" .format(new_sample.name, self.name)) else: new_sample = Recording(mode="create", parent=self, reltype="Sample") self.samples[new_sample.name] = new_sample
def plot(self): """ cat: info desc: plot this controller over time """ if not self.markers: err_mess("No data to plot!") return inds, vals = self.generate() channel = np.asarray(list(zip(inds, vals))) rel_plot(left_or_mono=channel, start=0, end=max(inds), rate=self.rate)
def handle_not_found(e, command, obj): process = command[0] if isinstance(command, list) else command message = str(e) if isinstance(e, (TypeError, KeyError, AutofillError, NoSuchProcess)): if isinstance(e, TypeError) and 'not callable' not in message: show_error(e) if isinstance(e, AutofillError): process = e.word err_mess("Process '{0}' does not exist: {1}".format(process, message)) else: show_error(e)
def random_method(self): """ desc: implement random sound-editing on recording, with random args. Introduce a little anarchy! cat: edit """ public_methods = self.get_all_public_methods() public_edits = [i for i in public_methods if get_reldata(i, "category") == Category.EDIT] method = rd.choice(public_edits) args = method.get_random_defaults() try: method(*args) except Exception as e: err_mess("Random Process error:") show_error(e)
def plot(self, left, right=None, plot_type="line", fill=None, title=None): if left.shape[0] == 0: err_mess("No data to plot!") else: rel_plot( left, start=self.start, end=self.end, rate=self.rate, right=right, fill=fill, plot_type=plot_type, title=title, obj_name=self.obj.name, obj_type=self.obj.reltype )
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 create_proj(self): """ desc: create a new project """ section_head("Creating New Project") while True: try: self.current_open_proj = Project(parent=self, mode="create") break except FileExistsError: err_mess( "The directory already exists and cannot be overwritten. Choose another name or location" ) self.projects[self.current_open_proj. name] = self.current_open_proj.get_data_dir() self.write_proj_file()
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 __init__(self, parent, doc_line): self.optional = False if doc_line[0] == "[": doc_line = doc_line.strip("[").strip("]") self.optional = True name,rest = doc_line.split(":", maxsplit=1) self.desc = rest.split(';')[0].strip() self.name = name.strip() self.defaults = [None, None] defaults = rest.split(";") if len(defaults) > 1: defaults = defaults[1].split(',') try: self.defaults[0] = float(defaults[0]) self.defaults[1] = float(defaults[1]) except: err_mess("Badly formed defaults on '{0}': {1}".format(parent, defaults))
def property(self, prop_name): """ cat: property desc: edit a property args: property name: one of the following: dev: the possible args are generated in options on display """ all_prop_names = self.get_all_prop_names() if not all_prop_names: err_mess("This {0} has no properties to edit".format(self.reltype)) try: prop_name = autofill(prop_name, all_prop_names) except AutofillError as e: err_mess("This {0} has property named '{1}'".format( self.reltype, e.word)) else: # RelProp.process() method getattr(self, prop_name).process()
def process(): section_head("Editing User Settings") method_strs = [func for func in dir(Settings) if "__" not in func and is_public_process(getattr(Settings, func))] while True: info_title("Available processes:") info_list(method_strs) p("What process to run?") proc = inpt("alphanum") try: proc = autofill(proc, method_strs) method = getattr(Settings, proc) if is_public_process(method): method() else: raise AttributeError except AutofillError as e: err_mess("No process matches '{0}'!".format(e.word)) except AttributeError: err_mess("No process matches '{0}'!".format(proc))
def _do_public_process(method, name=None): """ add reldata, convert docstring """ if name is None: name = method.__name__ add_reldata(method, "public", True) add_reldata(method, "name", name) add_reldata(method, "desc", "") doc = method.__doc__ if doc is not None: doc = [ j for j in [re.sub(r"\s+", " ", i.strip()) for i in doc.split('\n')] if j not in ("", " ") ] args_now = False for line in doc: try: title, content = line.split(":")[0].lower(), line.split(":")[1] if title == "dev": break if not args_now: if title in ("desc", "descrip", "description"): add_reldata(method, "desc", content.strip()) elif title in ("catg", "cat", "category", "catgry", "categry"): cat = Category.get(content.strip()) add_reldata(method, "category", cat) elif title in ("args", "arguments", "arg"): args_now = True else: add_reldata_arg(method, line) except Exception as e: err_mess("Error reading docstring method object data from method '" + method.__name__ + "'", trailing_newline=False) info_line("Docline: '" + str(line) + "'", indent=8) info_line("Exception: " + str(e), indent=8) return method
def open_proj(self): """ desc: open a project """ section_head("Opening Project") self.list_projects() p("Enter name of project you want to open, or 'see <name>' to see info about that project" ) name_input = inpt("split", "obj") see = False if name_input[0] == "see": try: proj_name = name_input[1] except IndexError: err_mess("No name provided to 'see'") self.open_proj() return see = True else: proj_name = name_input[0] try: proj_path = self.projects[proj_name] except KeyError: try: proj_name = autofill(proj_name, self.projects.keys(), "name") except AutofillError as e: err_mess("No project matches name '{0}'".format(e.word)) return proj_path = self.projects[proj_name] if proj_path == "None": err_mess("Project {0} is missing".format(proj_name)) self.handle_missing_proj(proj_name) return if see: self.see_proj(proj_name, proj_path) self.open_proj() return try: loader = ProjectLoader("{0}.Project".format(proj_name), proj_path, self) self.current_open_proj = loader.get_proj() except FileNotFoundError as e: err_mess("File Not Found: '{0}'".format(e.filename)) self.handle_missing_proj(proj_name) return
def sd_select_device(device_type='in'): """ type: in or out returns [index, name] of device """ info_title("Devices by index ({0} found):".format(len(sd.query_devices()))) for i in str(sd.query_devices()).split('\n'): info_line(i) while True: p("Enter desired device index") device_ind = inpt('int') try: device = sd.query_devices()[device_ind] except IndexError: err_mess("No device with index {0}".format(device_ind)) continue if device_type in ('in', 'input'): if device['max_input_channels'] < 1: err_mess("Device cannot be used as an input") continue elif device_type in ('out', 'output'): if device['max_output_channels'] < 1: err_mess("Device cannot be used as an output") device_name = device['name'] info_block("'{0}' selected".format(device_name)) return [device_ind, device_name]
def validate_child_name(self, child, new_name): if new_name in self.projects: err_mess("Project named '{0}' already exists!".format(new_name)) return False if new_name == "see": err_mess("'see' is a protected keyword. Choose another name") return False # if no path, we have to wait until after path is selected to check. # this is verified in create_proj() if child.path is not None: newpath = join_path(child.path, child.get_data_filename(new_name), is_dir=True) if os.path.exists(newpath): err_mess( "Something already exists at path '{0}'".format(newpath)) return False # update projects file try: del self.projects[child.name] except KeyError: pass self.projects[new_name] = child.get_data_dir() self.write_proj_file() return True
def complete_args(e, method_name, args, obj): """ handle wrong number of args """ if method_name not in str(e): show_error(e) err_mess("Wrong number of arguments: {0}".format(str(e))) info_title("Arguments for {0}: ".format(method_name), indent=4) obj.get_process(method_name)._rel_data.display_args() # too many command_str = "\n " + method_name + " " if "were given" in str(e): p("Enter intended arguments", start=command_str) new_args = inpt('split', 'arg') return [method_name] + new_args # not enough else: if len(args) > 0: command_str += " ".join(args) + " " p("Complete arguments", start=command_str) new_args = inpt('split', 'arg') return [method_name] + args + new_args
def __init__(self, rec, start=0, end=None): """ analysis of rec, with start and end as samps """ self.obj = rec self.rate = rec.rate.to_rate().magnitude self.start = start self.end = end if end is None: self.end = self.obj.size_samps().magnitude if self.end <= self.start: err_mess("End cannot be before or equal to start") return self.arr = rec.arr[self.start:self.end] self.mono_arr = np.mean(self.arr, axis=1) self.samp_len = self.end - self.start self.sec_len = self.samp_len / self.rate self.average_amplitude = np.mean(np.abs(self.mono_arr)) self.frame_length = None self.frame_step = None
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 add_beats(self): """ dev: create beats through prompts or pass 'beat' param to set (??) """ info_block("Creating new Rhythm '{0}'".format(self.name)) while True: info_block( "Enter a beat as <place in rhythm> <optional: length> ('q' to finish, 'i' for more info)", indent=2, hang=2, trailing_newline=False) print(" : ", end="") try: new_beat = inpt("split", "beat", help_callback=self.rhythms_help) except Cancel: info_block("Finished creating Rhythm '{0}'".format(self.name)) return if not (1 <= len(new_beat) <= 2): err_mess( "Wrong number of arguments! From 1 to 2 are required, {0} were supplied" .format(len(new_beat))) continue if secs(new_beat[0]) >= secs(str(self.length) + 'b'): err_mess( "This beat begins after the end of the rhythm! Try again") continue self.beats.append(new_beat) while len(new_beat) < 3: new_beat.append(0) if new_beat[1] == 0: new_beat[1] = "all" info_line(" Added beat - " + self.beat_repr(new_beat))