diff --git a/AudioDevice.py b/AudioDevice.py index 51c9301..0b1cb2c 100644 --- a/AudioDevice.py +++ b/AudioDevice.py @@ -181,6 +181,14 @@ class AudioDevice(object): return Result(smpN) + def buffer_sample_ms( self ): + r = self.buffer_sample_count() + + if r: + r.value = int(r.value * 1000.0 / self.srate) + + return r + def linear_buffer( self ): smpN = self.buffer_sample_count() diff --git a/common.py b/common.py new file mode 100644 index 0000000..c849c42 --- /dev/null +++ b/common.py @@ -0,0 +1,17 @@ +import yaml,types + +def parse_yaml_cfg( fn ): + """Parse the YAML configuration file.""" + + cfg = None + + with open(fn,"r") as f: + cfgD = yaml.load(f, Loader=yaml.FullLoader) + + cfg = types.SimpleNamespace(**cfgD['p_ac']) + + return cfg + + + + diff --git a/convert.py b/convert.py index fe1f103..dd69e39 100644 --- a/convert.py +++ b/convert.py @@ -4,7 +4,7 @@ from shutil import copyfile def event_times( eventTimeFn ): eventL = [] - + velL = [] with open(eventTimeFn,"r") as f: rdr = csv.reader(f) @@ -14,21 +14,25 @@ def event_times( eventTimeFn ): beginMs = int(row[1]) elif row[0] == 'key_down': key_downMs = int(row[1]) - beginMs + vel = int(row[3]) elif row[0] == 'key_up': key_upMs = row[1] eventL.append( [ key_downMs, key_downMs+1000 ] ) + velL.append( vel ) - return eventL + return eventL,velL -def pulse_lengths( pulseLenFn ): +def pulse_lengths( pulseLenFn, velL ): with open(pulseLenFn,'rb') as f: d = pickle.load(f) msL = d['msL'] # note: first posn in table is a multiplier - return [ msL[i]*msL[0] for i in range(1,len(msL))] + msL = [ msL[i]*msL[0] for i in range(1,len(msL))] + usL = [ msL[vel-1] for vel in velL ] + return usL def convert( inDir, outDir ): @@ -41,40 +45,57 @@ def convert( inDir, outDir ): if os.path.isdir(idir): - eventTimeFn = os.path.join( idir, "labels_0.csv" ) + id = 0 + while True: - eventTimeL = event_times(eventTimeFn) - - pulseTimeFn = os.path.join( idir, "table_0.pickle") + eventTimeFn = os.path.join( idir, "labels_%i.csv" % (id) ) - pulseUsL = pulse_lengths( pulseTimeFn ) + if not os.path.isfile( eventTimeFn ): + break + + eventTimeL,velL = event_times(eventTimeFn) - pitch = idir.split("/")[-1] + pulseTimeFn = os.path.join( idir, "table_%i.pickle" % (id)) - - d = { - "pulseUsL":pulseUsL, - "pitchL":[ pitch ], - "noteDurMs":1000, - "pauseDurMs":0, - "holdDutyPct":50, - "eventTimeL":eventTimeL, - "beginMs":0 - } + pulseUsL = pulse_lengths( pulseTimeFn, velL ) - odir = os.path.join( outDir, pitch ) - if not os.path.isdir(odir): - os.mkdir(odir) + pitch = idir.split("/")[-1] - with open(os.path.join( odir, "seq.json" ),"w") as f: - f.write(json.dumps( d )) + if not pitch.isdigit(): + break - copyfile( os.path.join(idir,"audio_0.wav"), os.path.join(odir,"audio.wav")) + d = { + "pulseUsL":pulseUsL, + "pitchL":[ pitch ], + "noteDurMs":1000, + "pauseDurMs":0, + "holdDutyPct":50, + "eventTimeL":eventTimeL, + "beginMs":0 + } + + odir = os.path.join( outDir, pitch ) + if not os.path.isdir(odir): + os.mkdir(odir) + + odir = os.path.join( odir, "%i" % (id) ) + if not os.path.isdir(odir): + os.mkdir(odir) + + with open(os.path.join( odir, "seq.json" ),"w") as f: + f.write(json.dumps( d )) + + copyfile( os.path.join(idir,"audio_%i.wav" % (id)), os.path.join(odir,"audio.wav")) + + id += 1 if __name__ == "__main__": inDir = "/home/kevin/temp/picadae_ac_2/full_map" - outDir = "/home/kevin/temp/p_ac_3_cvt" + outDir = "/home/kevin/temp/p_ac_3_cvt/full_map" + + #inDir = "/home/kevin/temp/picadae_ac_2/week_0" + #outDir = "/home/kevin/temp/p_ac_3_cvt/week_0" convert( inDir, outDir ) diff --git a/p_ac.py b/p_ac.py index a77e563..7b3dcb2 100644 --- a/p_ac.py +++ b/p_ac.py @@ -1,4 +1,4 @@ -import sys,os,argparse,yaml,types,logging,select,time,json +import sys,os,argparse,types,logging,select,time,json from datetime import datetime import multiprocessing @@ -7,6 +7,8 @@ from multiprocessing import Process, Pipe from picadae_api import Picadae from AudioDevice import AudioDevice from result import Result +from common import parse_yaml_cfg +from plot_seq import form_resample_pulse_time_list class AttackPulseSeq: """ Sequence a fixed chord over a list of attack pulse lengths.""" @@ -26,8 +28,9 @@ class AttackPulseSeq: self.next_ms = 0 # Time of next event (note-on or note_off) self.eventTimeL = [] # Onset/offset time of each note [ [onset_ms,offset_ms] ] self.beginMs = 0 + self.playOnlyFl = False - def start( self, ms, outDir, pitchL, pulseUsL ): + def start( self, ms, outDir, pitchL, pulseUsL, playOnlyFl=False ): self.outDir = outDir # directory to write audio file and results self.pitchL = pitchL # chord to play self.pulseUsL = pulseUsL # one onset pulse length in microseconds per sequence element @@ -35,16 +38,27 @@ class AttackPulseSeq: self.pulse_idx = 0 self.state = 'note_on' self.next_ms = ms + 500 # wait for 500ms to play the first note (this will guarantee that there is some empty space in the audio file before the first note) - self.eventTimeL = [[0,0]] * len(pulseUsL) # initialize the event time + self.eventTimeL = [[0,0] for _ in range(len(pulseUsL))] # initialize the event time self.beginMs = ms - self.audio.record_enable(True) # start recording audio + self.playOnlyFl = playOnlyFl + + for pitch in pitchL: + self.api.set_pwm( pitch, self.holdDutyPct ) + + if not playOnlyFl: + self.audio.record_enable(True) # start recording audio self.tick(ms) # play the first note def stop(self, ms): self._send_note_off() # be sure that all notes are actually turn-off - self.audio.record_enable(False) # stop recording audio + + if not self.playOnlyFl: + self.audio.record_enable(False) # stop recording audio + self._disable() # disable this sequencer - self._write() # write the results + + if not self.playOnlyFl: + self._write() # write the results def is_enabled(self): return self.state is not None @@ -75,7 +89,8 @@ class AttackPulseSeq: def _note_on( self, ms ): - self.eventTimeL[ self.pulse_idx ][0] = ms - self.beginMs + #self.eventTimeL[ self.pulse_idx ][0] = ms - self.beginMs + self.eventTimeL[ self.pulse_idx ][0] = self.audio.buffer_sample_ms().value self.next_ms = ms + self.noteDurMs self.state = 'note_off' @@ -84,7 +99,8 @@ class AttackPulseSeq: print("note-on:",pitch,self.pulse_idx) def _note_off( self, ms ): - self.eventTimeL[ self.pulse_idx ][1] = ms - self.beginMs + #self.eventTimeL[ self.pulse_idx ][1] = ms - self.beginMs + self.eventTimeL[ self.pulse_idx ][1] = self.audio.buffer_sample_ms().value self.next_ms = ms + self.pauseDurMs self.state = 'note_on' @@ -130,19 +146,17 @@ class CalibrateKeys: self.cfg = cfg self.seq = AttackPulseSeq( audioDev, api, noteDurMs=1000, pauseDurMs=1000, holdDutyPct=50 ) - self.label = None self.pulseUsL = None self.chordL = None self.pitch_idx = -1 - def start( self, ms, label, chordL, pulseUsL ): + def start( self, ms, chordL, pulseUsL, playOnlyFl=False ): if len(chordL) > 0: - self.label = label self.pulseUsL = pulseUsL self.chordL = chordL self.pitch_idx = -1 - self._start_next_chord( ms ) + self._start_next_chord( ms, playOnlyFl ) def stop( self, ms ): @@ -158,12 +172,13 @@ class CalibrateKeys: # if the sequencer is done playing if not self.seq.is_enabled(): - self._start_next_chord( ms ) # ... else start the next sequence + self._start_next_chord( ms, self.seq.playOnlyFl ) # ... else start the next sequence return None - def _start_next_chord( self, ms ): + def _start_next_chord( self, ms, playOnlyFl ): + self.pitch_idx += 1 # if the last chord in chordL has been played ... @@ -179,15 +194,44 @@ class CalibrateKeys: os.mkdir( outDir ) # form the output directory as "