def list_children(self): """ cat: info """ info_block("Children in '{0}':".format(self.name)) children = ["{0} ({1})".format(i.name, i.reltype) for i in self.children] info_list(children)
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 save_metadata(self): """ define parse_write_meta(dict: attrs) to define which attrs to write """ info_block("saving {0} '{1}' metadata...".format( self.reltype, self.name)) # must copy list, otherwise we will edit this object's __dict__ attrs = {k: v for k, v in vars(self).items()} attrs = self.parse_write_meta(attrs) try: del attrs["_rel_data"] except KeyError: pass attrs["__module__"] = self.__class__.__module__ attrs["__class__"] = self.__class__.__name__ from src.project_loader import RelTypeEncoder attrs = RelTypeEncoder.parse_container_obj_sets( attrs, self.get_data_dir()) fullpath = self.get_datafile_fullpath() with open(fullpath, 'w') as f: json.dump(attrs, fp=f, cls=RelTypeEncoder, indent=2)
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 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 record_live(self): """ record -- but get this -- live! """ section_head("Record mode") device_ind, device_name = sd_select_device() p("Enter recording duration (in seconds)") record_time = inpt('beatsec') p('Choose sample rate to record at, in samples per second. Hit enter to use default 44100') rate = inpt('int', required=False) if rate == '': rate = 44100 self.rate = Units.rate(rate) print(" Press Enter to begin recording, or 'q' to quit: ", end="") inpt("none", required=False) time.sleep(0.05) section_head("Recording at input {0} ({1}) for {2}".format(device_ind, \ device_name, record_time)) sd.default.channels = 2 recording = sd.rec( ind(record_time * self.rate), self.rate.magnitude, device=device_name) sd.wait() info_block("Finished recording") self.arr = recording if len(self.arr.shape) < 2: transpose = self.arr.reshape(-1, 1) self.arr = np.hstack((transpose, transpose)) self.source_block = { 'live recording': "input '{0}'".format(device_name) }
def rhythms_help(self): info_block( "Rhythms are made up of multiple beats. Each beat follows the format of " + \ "<place> <length> <optional: start>, all of which are in beat notation: ", trailing_newline=False ) info_block( "<place>: the time at which this beat starts within the rhythm. For example" +\ " 'qn' would mean this beat starts one quarter-note after the beginning of the rhythm", trailing_newline=False ) info_block( "<length>: how long the snippet of the sample will be. 'qn' would mean it lasts for one quarter-note. " +\ "no entry, or 0, means that the entire sample will be used", trailing_newline=False ) info_block( "<start>: an optional parameter, which selects where in the sample to start playback " +\ "for this one beat, as if trimming it on the front. 'qn' means the beat would begin " +\ "from one quarter-note in to the sample", trailing_newline=False ) print(" 3q 1q") info_block( "will create a beat that start on the third quarter of the rhythm, and" + \ "lasts for one quarter note" ) p("Do you need info on how to create properly formed beats?", o="y/n") if inpt('yn'): beat_options()
def find_peaks(self, frames): """ returns peaks as (sample index, slope) """ info_block("Finding peaks...") # find highest positive slopes between frames slopes = [] frm_ind = 0 while frm_ind < len(frames): # (index, avg amp) highest_slope = 0 compare_ind = frm_ind + 1 # get highest slope from this frame, with no dips in between while ( # while frame is highest than last and not past end compare_ind < len(frames) ) and ( frames[compare_ind][1] > frames[compare_ind - 1][1] ): # 1000 is arbitrary, just to make the numbers nicer new_slope = 1000 * (frames[compare_ind][1] - frames[frm_ind][1]) / (compare_ind - frm_ind) highest_slope = new_slope if new_slope > highest_slope else highest_slope compare_ind += 1 if highest_slope != 0: slopes.append((frames[frm_ind][0], highest_slope)) frm_ind += 1 return np.asarray(slopes)
def filter_peaks(self, peaks): """ returns peaks as (sample_index, slope) """ info_block("Filtering peaks...") avg_slope = np.mean(peaks[:,1]) peaks = peaks[peaks[:,1] >= avg_slope] sorted_peaks = NpOps.sort(peaks, 1) p_ind = 0 while p_ind < sorted_peaks.shape[0]: comp_ind = p_ind + 1 while comp_ind < sorted_peaks.shape[0]: comp_val = sorted_peaks[comp_ind][0] # remove smaller items. 4 seems to get them all if (comp_val - (5 * self.frame_length) < sorted_peaks[p_ind][0] < comp_val + (5 * self.frame_length) ): sorted_peaks = np.delete(sorted_peaks, comp_ind, 0) else: comp_ind += 1 p_ind += 1 return sorted_peaks # (sample_index, slope)
def save_globals(): """ dct: dict of vars to save """ info_block("Saving global data...") dct = {i:getattr(_RelGlobalsContainer, i) for i in _RelGlobalsContainer._write_vars} with open(RelGlobals.data_file(), "w") as f: json.dump(dct, f, indent=2)
def info(self): """ cat: info """ info_block("{0} '{1}'".format(self.reltype, self.name)) info_line("Stored at '{0}'".format(self.path)) info_line("Samplerate: {0}".format(self.rate)) self.list_children()
def list_beats(self): sorted_beats = selection_sort(self.beats, ind=0, func_on_val=secs, func_args=[60, 'val'], low_to_high=True) for i in sorted_beats: info_block("- " + self.beat_repr(i), newlines=False)
def save_settings(): """ save settings to settingsfile """ info_block("Saving settings...") attrs = [attr for attr in dir(_SettingsContainer) if "__" not in attr and attr != "_defaults"] dct = {i:getattr(_SettingsContainer, i) for i in attrs} with open(RelGlobals.settings_file(), "w") as f: json.dump(dct, f, indent=2)
def mute(self): """ cat: edit desc: mute an active pair """ info_block("Choose an active pair to mute") act = self.choose('active') if act is not None: act.muted = True
def process_message(self): """ this is only called when the prop is not a controller """ try: val = self.get_val() except AttributeError: info_block("{0} '{1}' has no value assigned yet. Use the 'set' process".format(self.reltype, self.name), indent=2) else: info_block("{0} '{1}' has a value of {2}".format(self.reltype, self.name, val), indent=2)
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 init_settings(): info_block("Loading settings...") if RelGlobals.settings_file() is None: raise AttributeError("settings_file attr not yet set in _RelGlobalContainer") try: with open(RelGlobals.settings_file(), "r") as f: dct = json.load(f) except FileNotFoundError: dct = _SettingsContainer._defaults for k,v in dct.items(): setattr(_SettingsContainer, k, v)
def save_audio(self): """ base wav audio saving. requires 'rate' and 'arr' attributes """ info_block("saving audio of {0} '{1}'...".format( self.reltype, self.name)) if self.arr is None: info_line("no audio to save...") else: sf.write(self.get_audiofile_fullpath(), self.arr, self.rate.magnitude)
def info(self): """ desc: display this objects data cat: info dev: essentially just an expanded repr. repr is also defined in super() """ info_block("{0} '{1}'".format(self.reltype, self.name)) info_line("sourced from {0}: {1}".format(self.source_block[0], self.source_block[1])) for ind in range(1, len(self.source_block) // 2): print(" {0}: {1}".format(self.source_block[2 * ind], self.source_block[2 * ind + 1])) info_line("parent: {0}".format(self.parent)) info_line("rate: {0}".format(self.rate)) info_line("size: {0:.4f}, {1:,}".format(self.size_secs(), self.size_samps())) info_line("pan: {0}".format(self.pan_val))
def init_globals(): """ read vars from data file """ info_block("Loading global data...") if RelGlobals.data_file() is None: raise AttributeError("data_file attr not yet set in _RelGlobalContainer") try: with open(RelGlobals.data_file(), "r") as f: dct = json.load(f) except FileNotFoundError: dct = {i:getattr(_RelGlobalsContainer, i) for i in _RelGlobalsContainer._write_vars} for k,v in dct.items(): setattr(_RelGlobalsContainer, k, v)
def list_samples(self): """ desc: list samples in this sampler cat: info dev: returns bool, false if nothing to list """ print("\n Samples loaded into '{0}':".format(self.name)) empty = True for obj in self.smps: info_list(str(obj)) empty = False if empty: info_block("No samples loaded") return False
def list_rhythms(self): """ desc: list rhythms in this sampler cat: info dev: returns bool, false if nothing to list """ print("\n Rhythms loaded into '{0}':".format(self.name)) empty = True for obj in self.rhythms: empty = False info_list(str(obj)) if empty: info_block("No rhythms loaded") return False
def see_proj(self, proj_name, proj_path): info_block("Previewing Project '{0}'".format(proj_name)) fullpath = join_path( proj_path, proj_name + ".Project." + RelSavedObj.datafile_extension) with open(fullpath, "r") as f: data = json.load(f) info_title("Path: " + data["path"]) info_line("Audio file: " + str(data["file"])) info_line("Children:") children = [ re.sub(r"<.*>", "", i).split(".") for i in data["children"] ] children = ["{0} '{1}'".format(i[1], i[0]) for i in children] info_list(children)
def read_file(self, file_path=None): """ reads files for recording object init takes multiple formats (via PyDub and Soundfile) updates self.source, self.arr, self.rate """ if file_path is None: print(" Choose an input sound file...") time.sleep(1) file_path = input_file() info_block("Reading audio file...") t1 = time.time() # Handling file types _, _, ext = split_path(file_path) if ext != "wav": try: not_wav = pd.from_file(file_path, file_path.ext) not_wav.export(".temp_soundfile.wav", format="wav") file_path = ".temp_soundfile.wav" except FileNotFoundError: print(" > unable to find file '{0}'".format(file_path)) print(" > make sure to include .wav/.mp3/etc extension") return self.read_file() # self.source_block["file"] = file_path # Reading and Processing File try: self.arr, rate = sf.read(file_path) self.rate = Units.rate(rate) except RuntimeError: print( " > unable to find or read '{0}'. Is that the correct extension?" .format(file_path)) return self.read_file() try: os.remove(".temp_soundfile.wav") except FileNotFoundError: pass if len(self.arr.shape) < 2: self.arr = NpOps.stereoify(self.arr) t2 = time.time() info_line( "sound file '{0}' read successfully in {1:.4f} seconds".format( file_path, t2 - t1))
def projects_menu(self): """ top menu for opening or creating projects """ while True: section_head("Projects Menu") # choose open type: open or create while self.current_open_proj is None: if bool(self.projects): p("Open existing project (O) or Create new (C)?") open_or_create = inpt("letter", allowed="oc") if open_or_create == 'c': self.create_proj() else: self.open_proj() else: info_block( "No existing projects. Defaulting to create new") self.create_proj() self.process_project()
def options(self): """ cat: info desc: list all process options that can be run on this object (shortcut 'o') """ nl() with style("cyan"): info_block("{CATEGORY}", indent=2) info_line("- {Process}") info_line("{arguments in order, optional if in [square brackets]}", indent=8) nl() meths = {} for mth in self.get_all_public_methods(): cat = get_reldata(mth, "category") try: meths[cat].append(mth) except KeyError: meths[cat] = [mth] categories = list(meths.keys()) # sort by category.value, which is the string representation of that category categories.sort(key=lambda x: x.value) for cat in categories: with style("cyan"): info_line(cat.value.upper(), indent=2) for method in meths[cat]: method._rel_data.display() if cat == Category.PROPERTY: prop_names = self.get_all_prop_names() if not prop_names: info_line("(no properties to edit)", indent=10) else: for i in prop_names: info_line("* " + i, indent=10)
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))
def get_frames_mono(self, **kwargs): info_block("Calculating frames...") return self.get_frames(self.mono_arr, **kwargs)
def get_frames_right(self, **kwargs): info_block("Calculating right channel frames...") return self.get_frames(self.arr[:, 1], **kwargs)
def debug_off(): info_block("Debug is now off") _SettingsContainer.debug = False