def loop_file_callback(unused_addr, args, the_OSC_message_argument): D("loop file callback") # has an argument of an int. message_ok = True the_status_obj = args[0] #get the loop id. /loop/* try: loop_s = unused_addr[6] loop_i = int(loop_s) # get the track index new_track_index = int(the_OSC_message_argument) except Exception as errerr: L("exception with loop_file_callback ") L("Error: {}".format(errerr)) message_ok = False return if (loop_i not in range(1, 5)): message_ok = False if (message_ok): try_load_new_file_by_int(loop_i, new_track_index, the_status_obj) the_status_obj.set_osc_message("{} : {} DONE".format( unused_addr, the_OSC_message_argument)) D("loop_file_callback {} {}".format(loop_i, new_track_index)) else: L("message not processed. loop_file_callback") L("Message received {}".format(the_OSC_message_argument)) L("Message Received {} ".format(unused_addr)) L("Message Received {}".format(args)) the_status_obj.set_osc_message("{} : {} FAIL".format( unused_addr, the_OSC_message_argument)) return
def set_mute(self, mute_state): # set audio to either mute or playing. # need to make sure mute_state is a boolean. if (mute_state != True or mute_state != False): D("ERROR with set_mute: {}".format(str(mute_state))) return self.mute = mute_state D("Audio Mute State: {}".format(str(mute_state))) return
def slice_callback(unused_addr, args, the_OSC_message_argument): D("SliceCallback") #print("{} {} {} ".format(unused_addr, args, the_obj)) message_ok = True if (len(unused_addr) != 15): message_ok = False loop_i = -1 slice_i = -1 new_mode = 'NOT-SET' the_status_obj = args[0] #probably should do a type() check here on the_status_obj try: loop_s = unused_addr[6] slice_s = unused_addr[14] loop_i = int(loop_s) slice_i = int(slice_s) except Exception as jeje: L("exception in handling OSC message {}".format(unused_addr)) L("Exception: {}".format(jeje)) L("{}".format(repr(jeje))) message_ok = False if (loop_i > 4 or loop_i < 1): D("Loop value out of range {} ".format(loop_i)) #do nothing message_ok = False if (slice_i < 0 or slice_i > 7): D("slice value out of range {} ".format(slice_i)) #do nothing message_ok = False #check is the_OSC_message_argument is a correct slice mode new_slice_mode = the_OSC_message_argument if not MODES.is_valid_mode(new_slice_mode): message_ok = False if (message_ok): set_slice_mode(loop_i, slice_i, new_slice_mode, the_status_obj) the_status_obj.set_osc_message("{} : {} DONE".format( unused_addr, the_OSC_message_argument)) else: # log error message #TODO fix this error message L("unable to parse message {} {} ".format(unused_addr, the_OSC_message_argument)) the_status_obj.set_osc_message("{} : {} FAIL".format( unused_addr, the_OSC_message_argument)) return #print("Loop:{} slice:{}".format(loop_i,slice_i)) return
def get_audio(self, number_of_samples): #return audio ret_data = np.zeros([0, 2]) temp_remaining = number_of_samples #if all the slices are muted, return silence. if (self.all_slices_muted or self.loop_mode == LOOP_MODES.STOP or self.detect_all_skip()): ret_data = np.zeros([number_of_samples, 2]) return ret_data # work out which slice to get from # while there is still audio to be put in the buffer, get audio from the slice. while (temp_remaining > 0): temp_state, temp_data = self.audio_slices[ self.current_slice].get_samples(temp_remaining) D( str.format("{} temp_stat:{} temp_data:{} temp_rem:{}", self.name, str(temp_state), len(temp_data), temp_remaining)) ret_data = np.concatenate((ret_data, temp_data)) temp_remaining = temp_remaining - len(temp_data) #detect all skip mode during the middle of buffer playback if (self.detect_all_skip()): temp_data = np.zeros([temp_remaining, 2]) ret_data = np.concatenate((ret_data, temp_data)) # this should set temp_remaining to zero temp_remaining = temp_remaining - len(temp_data) # if returned audio is less than the amount requested, and temp_stat is not set properly, need to note this. if (temp_remaining > 0 and temp_state): #should reset the current slice and then move on. # hand of god style D("ALERT: returned samples was less than requested, and temp_state was still true. " ) D("Slice {}, Slicemode {}, temp_remaining:{} len(temp_data):{} " .format(self.current_slice, self.audio_slices[self.current_slice].play_mode, temp_remaining, len(temp_data))) # reset playback to clean up self.audio_slices[self.current_slice].reset_playback() # set to false to force slice update temp_state = False if (temp_state == False ): #temp_state is false when the slice has run out of samples self.update_next_slice() ret_data = ret_data * self.volume_gain D( str.format("{} slice:{} temp remaining:{} sample length {}", self.name, self.current_slice, temp_remaining, len(ret_data))) return ret_data
def loop_volume_callback(unused_addr, args, the_OSC_message_argument): D("loop_volume_callback") #set the volume level of a loop. #.set_volume(float) #range 0.0 - 1.0 # if(len(unused_addr) != 12): # message_ok = False loop_i = -1 new_volume_level = -1.0 message_ok = True the_status_obj = args[0] #get the loop id. /loop/* try: loop_s = unused_addr[6] loop_i = int(loop_s) except Exception as jeje: L("exception in handling OSC message {}".format(unused_addr)) L("Exception: {}".format(jeje)) L("{}".format(repr(jeje))) message_ok = False if (loop_i > 4 or loop_i < 1): D("Loop value out of range {} ".format(loop_i)) #do nothing message_ok = False #convert the argument to a float. try: new_volume_level = float(the_OSC_message_argument) if (new_volume_level < 0.0): message_ok = False #float parsing error handling. except Exception as flt_error: L("exception handing OSC message {}".format(unused_addr)) L("exception parsing {}".format(the_OSC_message_argument)) L("Exception: {}".format(flt_error)) L("{}".format(rep(flt_error))) message_ok = False if (message_ok): set_loop_volume(loop_i, new_volume_level, the_status_obj) the_status_obj.set_osc_message("{} : {} DONE".format( unused_addr, the_OSC_message_argument)) else: # log error message #TODO fix this error message L("unable to parse message {} {} ".format(unused_addr, the_OSC_message_argument)) the_status_obj.set_osc_message("{} : {} FAIL".format( unused_addr, the_OSC_message_argument)) return
def set_audio_buffer(self,new_audio_buffer, new_slice_points): #divide the audio up into slice objects self.audio_buffer = new_audio_buffer self.slice_points = new_slice_points #slice points are tuples 0 for start, 1 for ending point first = 0 for j in new_slice_points: x=SpnAudioSlice( new_audio_buffer[ j[0]: j[1] ] ) x.set_play_mode(MODES.PLAY) D("made new slice {} {} ".format( repr(x), j, )) self.audio_slices.append(x) D("Completed adding slices") return
def dump_audio_slices(self): # this resets the array containing the audio buffers so we can load a new audio file. # this will cause the slices to loose their state when a new loop is loaded with set_audio_buffer # this would require refactoring set_audio_buffer and how audio is loaded into the loop and slices. self.audio_slices = [] D("audio slices dumped for {}".format(self.name)) return
def try_load_new_file_by_int(loop_number, new_file_number, the_status_obj): # pass an file index number to the system and get the loop to load it. loop_id_s = "/loop/{}".format(loop_number) the_status_obj.load_new_audio_file_by_index(loop_id_s, new_file_number) D("attempted setting loop {} to file index {}".format( loop_number, new_file_number)) return
def set_volume(self, new_volume): # if the volume value is a float then we can set it if(type(new_volume)==type(1.0)): self.volume_gain = new_volume else: D(" Set Volume failed. {} {}".format(type(new_volume),new_volume)) return #limit volume to 0.0 - 1.0 if (self.volume_gain > 1.0): self.volume_gain =1.0 if (self.volume_gain<0.0): self.volume_gain = 0.0 D(" Set Volume {} {}".format(type(new_volume),new_volume)) return
def loop_mode_callback(unused_addr, args, the_OSC_message_argument): D("LoopCallback") #print("{} {} {} ".format(unused_addr, args, the_obj)) message_ok = True if (len(unused_addr) != 12): message_ok = False loop_i = -1 new_mode = 'NOT-SET' the_status_obj = args[0] try: loop_s = unused_addr[6] loop_i = int(loop_s) except Exception as jeje: L("exception in handling OSC message {}".format(unused_addr)) L("Exception: {}".format(jeje)) L("{}".format(repr(jeje))) message_ok = False if (loop_i > 4 or loop_i < 1): D("Loop value out of range {} ".format(loop_i)) #do nothing message_ok = False new_slice_mode = the_OSC_message_argument if not LM.is_valid_mode(new_slice_mode): message_ok = False if (message_ok): set_loop_mode(loop_i, new_slice_mode, the_status_obj) the_status_obj.set_osc_message("{} : {} DONE".format( unused_addr, the_OSC_message_argument)) else: # log error message #TODO fix this error message L("unable to parse message {} {} ".format(unused_addr, the_OSC_message_argument)) the_status_obj.set_osc_message("{} : {} FAIL".format( unused_addr, the_OSC_message_argument)) return #print("Loop:{} slice:{}".format(loop_i,slice_i)) return
def repeat_samples(self, old_array): # newLen = 2*len(old_array) #new_array = np.empty([0, 2]) #for i in old_array: # new_array = np.vstack((new_array, i)) # new_array = np.vstack((new_array, i)) #D("repeated samples old:{} new:{}".format(len(old_array),len(new_array))) new_array = np.repeat(old_array, 2, axis=0) D("np.repeat len-new:{} len-old:{}".format(len(new_array), len(old_array))) return new_array
def detect_all_skip(self): """detect if all slices are silenced and returns true if all slices are in skip mode""" allSkipMode = 0 for s in self.audio_slices: if (s.play_mode == MODES.SKIP): allSkipMode += 1 if (allSkipMode > 7): D("all slices in skip mode") return True else: return False
def SpnPatchParser(patch_file_name): #opens a config file and gets the loop file names and any details in them looper_files = [] #an array of the section names. config = configparser.ConfigParser() config.read(patch_file_name) D("loaded config file {}".format(patch_file_name)) # D("sections loaded: {}".format(config.sections() )) for xerw in config.sections(): #loop through the sections #if the filepath is set make a SpnLoopFile Object if 'filepath' in config[xerw]: temp_path = config[xerw]['filepath'] if test_file_correctness(temp_path): looper_files.append(make_loop_from_section(xerw, config[xerw])) else: W("Error in: {} ;audio file: {}".format( repr(xerw), repr(config[xerw]))) D("done adding sections") return looper_files
def loop_jump_callback(unused_addr, args, the_OSC_message_argument): D("loop jump callback") message_ok = True if (len(unused_addr) != 12): message_ok = False the_status_obj = args[0] try: loop_s = unused_addr[6] loop_i = int(loop_s) jump_i = int(the_OSC_message_argument) except Exception as jj: L("Exception parsing {} {}".format(unused_addr), the_OSC_message_argument) L("Exception {} ".format(jj)) message_ok = False loop_i = -1 jump_i = -1 if (loop_i > 4 or loop_i < 1): D("Loop value out of range {} ".format(loop_i)) message_ok = False if (jump_i > 7 or jump_i < 0): D("jump value out of range {}".format(jump_i)) message_ok = False if (message_ok): set_loop_jump(loop_i, jump_i, the_status_obj) the_status_obj.set_osc_message("{} : {} DONE".format( unused_addr, the_OSC_message_argument)) else: # log error message #TODO fix this error message L("unable to parse message {} {} ".format(unused_addr, the_OSC_message_argument)) the_status_obj.set_osc_message("{} : {} FAIL".format( unused_addr, the_OSC_message_argument)) return
def drop_samples(self, old_array): # drop half the samples in the array so we can double playback speed # set the new length of the array. #new_length = int(len(old_array) / 2) #new_array = np.empty([new_length, 2]) # copy samples #for i in range(0, new_length): # new_array[i] = old_array[i * 2] new_array = old_array[::2] D("drop samples using view len-new:{} len-old:{}".format( len(new_array), len(old_array))) return new_array
def update_next_slice(self): """sets the self.current_slice to the next value""" #TODO updated this so we can have different audio slice pattersn. reverse, random etc. old_slice = self.current_slice nek_slice = { LOOP_MODES.PLAY: self.update_next_slice_forward, LOOP_MODES.REVERSE: self.update_next_slice_reverse, LOOP_MODES.RANDOM: self.update_next_slice_random } if self.loop_mode in nek_slice.keys(): nek_slice[self.loop_mode]() else: update_next_slice_forward() D("{} Updated slice was {} now {} mode {}".format(self.name, old_slice, self.current_slice, self.loop_mode)) return
def __init__(self): self.BufferSources = [] self.stream = None self.mute = False D(" SpnAudioEngine init'd ") return
def set_loop_mode(loop_number, new_mode, the_status_obj): loop_id_s = "/loop/{}".format(loop_number) the_status_obj.loops[loop_id_s].set_loop_mode(new_mode) D("set the loop mode {} : {} ".format(loop_id_s, new_mode))
def set_slice_mode(loop_number, slice_number, new_mode, the_status_obj): loop_id_s = "/loop/{}".format(loop_number) the_status_obj.loops[loop_id_s].set_slice_mode(slice_number,new_mode) D("set the slice mode {} : {} : {} ".format(loop_id_s, slice_number, new_mode))
def repeat_last_sample(self, old_array): if len(old_array) < 1: D("old_array length was too short! {}".format(old_array)) return old_array last_sample = old_array[len(old_array) - 1] return np.vstack((old_array, last_sample))
def get_samples(self, number_of_samples): """ returns BOOL, numpy_array return a numpy array of the number of samples requested. if there are not enough samples left return a shorter length array. If all the samples are returned then BOOL is true, otherwise it is false. This will inform the slice-sequencer that the next slice in the sequence needs to be played. """ # all ok, not going to hit the end of the samples temp_samples_to_play = number_of_samples temp_returned_all_samples = True if ((number_of_samples > self.slice_remaining) and (self.play_mode != MODES.HALF)): # TODO is this incorrect when reading at HALF speed? # uh oh, not all ok, we need to work with a smaller number of samples # TODO add logging here # logging.debug(str.format("hit end of slice:{} ", self.slice_remaining)) temp_samples_to_play = self.slice_remaining temp_returned_all_samples = False if (self.play_mode == MODES.HALF): # half is half speed. so we read half the number of samples from the array and then double each sample. # calculate the number of actual samples we're going to pull from teh array D("HALF samples req: {} , temp_samples to play back: {}".format( number_of_samples, temp_samples_to_play)) temp_samples_to_play = int(number_of_samples / 2) D("HALF samples req: {} , temp_samples to read from slice: {} rem in slice:{}" .format(number_of_samples, temp_samples_to_play, self.slice_remaining)) if (temp_samples_to_play > self.slice_remaining): # if we still have no enought samples left, need to set flags and update values temp_samples_to_play = self.slice_remaining temp_returned_all_samples = False if (self.play_mode == MODES.DOUBLE): # double speed playback. drop samples. temp_samples_to_play = int(number_of_samples * 2) D("D samples req: {} , temp_samples to play back: {} rem in slice:{}" .format(number_of_samples, temp_samples_to_play, self.slice_remaining)) if (temp_samples_to_play > self.slice_remaining): temp_samples_to_play = self.slice_remaining temp_returned_all_samples = False # create an empty numpy array for stereo data. slice_ret_data = np.zeros([0, 2]) if (number_of_samples == 0): # if the number of samples requested is none/zero, return empty return False, slice_ret_data if (self.play_mode == MODES.SKIP): # if the mode is skip, # return an empty slice and also a False so the sequencer knows to get data from the next sample return False, slice_ret_data if (self.play_mode == MODES.RANDOM): # if the slice playback is random, then select a random offset in the sample, # and playback number_of_samples # slice_remaining count must remain accurate. # get a sample pointer somewhere in the slice self.slice_pointer = random.randint( 0, self.slice_length - temp_samples_to_play) if (self.play_mode == MODES.PLAY or self.play_mode == MODES.RANDOM or self.play_mode == MODES.HALF or self.play_mode == MODES.DOUBLE): # forward playback mode slice_ret_data = self.slice_wave_data[self.slice_pointer:( self.slice_pointer + temp_samples_to_play)] # if( self.play_mode == HALF): # dprint("{} ~ {}".format(len(slice_ret_data),temp_samples_to_play)) if (self.play_mode == MODES.MUTE): # if we're muting the playback, we just want to set all teh wave data to 0 # but playback length is the same etc. slice_ret_data = np.zeros([temp_samples_to_play, 2]) if (self.play_mode == MODES.REVERSE): slice_ret_data = np.flipud(self.slice_wave_data[( self.slice_pointer - temp_samples_to_play):self.slice_pointer]) if (self.play_mode == MODES.HALF): # dprint("HALF: old length {}".format(len(slice_ret_data))) slice_ret_data = self.repeat_samples(slice_ret_data) # dprint("HALF: new length {}".format(len(slice_ret_data))) # need to make sure that if we've been asked for an ODD number of samples we're handling it ok if (temp_returned_all_samples and (len(slice_ret_data) != number_of_samples)): # we haven't run out of samples, but length doesn't match. it should only be off by 1 for a divide by 2 # repeat the last sample as a shitty way to pad it out D("HALF: unbalanced. need to add aditional sample: ret {} req {}" .format(len(slice_ret_data), number_of_samples)) while (len(slice_ret_data) < number_of_samples): slice_ret_data = self.repeat_last_sample(slice_ret_data) if (self.play_mode == MODES.DOUBLE): # we're already read twice the samples needed. just need to drop half the samples slice_ret_data = self.drop_samples(slice_ret_data) # add a safety check. this should never get called since we're grabbing twice as many samples as we're asked for. # so it should always be an even number. if (temp_returned_all_samples and (len(slice_ret_data) != number_of_samples)): # we haven't run out of samples, but length doesn't match. it should only be off by 1 # repeat the last sample as a shitty way to pad it out D("DOUBLE: ret {} req {}".format(len(slice_ret_data), number_of_samples)) if (len(slice_ret_data) < number_of_samples): slice_ret_data = self.repeat_last_sample(slice_ret_data) if (len(slice_ret_data) > number_of_samples): # if it's too long, trim off then end sample slice_ret_data = slice_ret_data[0:(number_of_samples - 1)] # cleanup time # update slice_remaining and slice_pointer self.slice_remaining = self.slice_remaining - temp_samples_to_play # number_of_samples if (self.play_mode != MODES.REVERSE): self.slice_pointer = self.slice_pointer + temp_samples_to_play # number_of_samples else: self.slice_pointer = self.slice_pointer - temp_samples_to_play # number_of_samples # check for reset conditions. if ((temp_returned_all_samples is False) or (self.slice_remaining < 1) or (self.slice_pointer < 0) or (self.slice_pointer > self.slice_length)): # TODO add loging here D("slice as run out of samples, and we're resetting the playback counters" ) self.reset_playback() return temp_returned_all_samples, slice_ret_data
def set_loop_volume(loop_number, new_vol, the_status_obj): loop_id_s = "/loop/{}".format(loop_number) the_status_obj.loops[loop_id_s].set_volume(new_vol) D("set {} volume to {}".format(loop_id_s, new_vol)) return
def set_loop_jump(loop_number, new_position, the_status_obj): loop_id_s = "/loop/{}".format(loop_number) the_status_obj.loops[loop_id_s].jump_slice(new_position) D("set {} new focus to {}".format(loop_id_s, new_position)) return
def main(): parser = argparse.ArgumentParser( description=DESC_NAME, formatter_class=argparse.ArgumentDefaultsHelpFormatter) parser.add_argument('--oscport', help='Port OSC listens on', action="store", type=int, default=5080) parser.add_argument('--patchfile', help='Patch file to load', action="store", required=True) parser.add_argument( '--tuirefresh', help= 'refresh rate for UI. This is for slow networked shell connections', action="store", default=20) parser.add_argument('--configfile', help='config file to load NOT IMPLEMENTED', action="store") parser.add_argument('--verbose', help='Turn on verbose mode', action='store_true', default=False) parser.add_argument('--version', help='Display version information', action='version', version='%(prog)s 0.0') args = parser.parse_args() #create the audio engine global audioEngine audioEngine = SpnAudioEngine() #create the status global spoon_fight_status spoon_fight_status = SpoonFightStatus(args.oscport) #create the four loops spoon_fight_status.loops['/loop/1'] = SpnAudioLoop('/loop/1') spoon_fight_status.loops['/loop/2'] = SpnAudioLoop('/loop/2') spoon_fight_status.loops['/loop/3'] = SpnAudioLoop('/loop/3') spoon_fight_status.loops['/loop/4'] = SpnAudioLoop('/loop/4') # add the loop audio to the audioengine for i in spoon_fight_status.loops: audioEngine.add_source(spoon_fight_status.loops[i].get_audio) # open patch file ret_loops = SpnPatchParser.SpnPatchParser(args.patchfile) spoon_fight_status.set_status_message("loaded {} loops".format( len(ret_loops))) for at in ret_loops: spoon_fight_status.add_audio_loop_files(at) i = 0 spoon_fight_status.loops['/loop/1'].set_audio_buffer( ret_loops[i].get_audio_data(), ret_loops[i].get_slice_points()) spoon_fight_status.loops['/loop/1'].set_audio_file_name( ret_loops[i].audio_section_name) spoon_fight_status.loops['/loop/1'].loop_mode = LOOP_MODES.STOP i = 1 spoon_fight_status.loops['/loop/2'].set_audio_buffer( ret_loops[i].get_audio_data(), ret_loops[i].get_slice_points()) spoon_fight_status.loops['/loop/2'].set_audio_file_name( ret_loops[i].audio_section_name) spoon_fight_status.loops['/loop/2'].loop_mode = LOOP_MODES.STOP i = 2 spoon_fight_status.loops['/loop/3'].set_audio_buffer( ret_loops[i].get_audio_data(), ret_loops[i].get_slice_points()) spoon_fight_status.loops['/loop/3'].set_audio_file_name( ret_loops[i].audio_section_name) spoon_fight_status.loops['/loop/3'].loop_mode = LOOP_MODES.STOP i = 3 spoon_fight_status.loops['/loop/4'].set_audio_buffer( ret_loops[i].get_audio_data(), ret_loops[i].get_slice_points()) spoon_fight_status.loops['/loop/4'].set_audio_file_name( ret_loops[i].audio_section_name) spoon_fight_status.loops['/loop/4'].loop_mode = LOOP_MODES.STOP # start the OSC server # returns a tuple of OSC server, and the thread it is in. local_server, local_server_thread = start_OSC_server(spoon_fight_status) spoon_fight_status.set_status_message("started OSC server") audioEngine.start_audio() spoon_fight_status.set_status_message("started audio engine") #TODO put the main loop in here. Screen.wrapper(loop_till_forever) D("shuting down threads") local_server.shutdown() D("OSC Server thread stopped") audioEngine.stop_audio() D("Audio Thread stopped") return
def update_next_slice_reverse(self): self.current_slice -= 1 if (self.current_slice < 0): self.current_slice = 7 D(" current slice wrapped around in reverse mode") return
def set_volume(self, new_volume): # if the volume value is a float then we can set it if (type(new_volume) == type(1.0)): self.volume_gain = new_volume D(" Set Volume {} {}".format(type(new_volume), new_volume)) return
def update_next_slice_forward(self): self.current_slice += 1 if (self.current_slice > 7): self.current_slice = 0 D(" current slice wrapped around in forward mode") return