Skip to content

Commit

Permalink
better AAF track logic, added AAF_CPSTRACK_METHOD
Browse files Browse the repository at this point in the history
  • Loading branch information
benhackbarth committed Jan 5, 2021
1 parent 2019885 commit a166e5c
Show file tree
Hide file tree
Showing 6 changed files with 54 additions and 73 deletions.
2 changes: 1 addition & 1 deletion audioguide/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -388,7 +388,7 @@ def write_concatenate_output_files(self):

for cpsfile in allusedcpsfiles:
this_aaf.addSoundfileResource(cpsfile, self.AnalInterface.rawData[cpsfile]['info'])
this_aaf.makeCpsTracks(self.outputEvents, self.cps.data['vcToCorpusName'])
this_aaf.makeCpsTracks(self.outputEvents, self.cps.data['vcToCorpusName'], self.ops.AAF_CPSTRACK_METHOD)
this_aaf.done()
dict_of_files_written['AAF_FILEPATH'] = self.ops.AAF_FILEPATH
self.p.log( "Wrote aaf file %s\n"%self.ops.AAF_FILEPATH )
Expand Down
71 changes: 42 additions & 29 deletions audioguide/aaf.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,17 +20,19 @@ def __init__(self, aaf_filepath, aaf_sr=44100):
self.f = aaf2.open(aaf_filepath, "w")
self.aaf_sr = aaf_sr
self.filepath_to_mob = {}
self.trackcnt = 0
self.trackcnt = {'tgt': 0, 'cps': 0}
self.alltrackcnt = 0
self.comp_mob = self.f.create.CompositionMob("Composition")
self.f.content.mobs.append(self.comp_mob)


def _add_track(self, seq, name):
def _add_track(self, seq, name, type='cps'):
timeline_slot = self.comp_mob.create_timeline_slot("%i" % self.aaf_sr)
timeline_slot['PhysicalTrackNumber'].value = self.trackcnt
timeline_slot['PhysicalTrackNumber'].value = self.alltrackcnt
timeline_slot.name = name
timeline_slot.segment = seq
self.trackcnt += 1
self.trackcnt[type] += 1
self.alltrackcnt += 1

def addSoundfileResource(self, cpsfullpath, infodict):
'''each target/corpus soundfile used in the concatenation must be passed to this function to register the filepath, sr, format, duration, etc'''
Expand All @@ -44,47 +46,58 @@ def addSoundfileResource(self, cpsfullpath, infodict):
def makeTgtTrack(self, tgtobj):
sequence = self.f.create.Sequence(media_kind="sound")
sequence.components.append(self.filepath_to_mob[tgtobj.filename].create_source_clip(slot_id=1, start=int(tgtobj.startSec*self.aaf_sr), length=int((tgtobj.endSec-tgtobj.startSec)*self.aaf_sr))) # sound
self._add_track(sequence, 'target')
self._add_track(sequence, 'target', type='tgt')


def makeCpsTracks(self, eventlist, vcToCorpusName):
def makeCpsTracks(self, eventlist, vcToCorpusName, aaf_track_method):
grand_old_dict = {}
for e in eventlist:
if e.voiceID not in grand_old_dict: grand_old_dict[e.voiceID] = {}
if e.sfchnls not in grand_old_dict[e.voiceID]: grand_old_dict[e.voiceID][e.sfchnls] = []
grand_old_dict[e.voiceID][e.sfchnls].append((int(e.timeInScore*self.aaf_sr), int((e.timeInScore+e.duration)*self.aaf_sr), int(e.sfSkip*self.aaf_sr), e.filename))
# organize all selected notes into tracks with no overlapping sound segments and with unique channel counts
all_tracks = []
for voice in grand_old_dict:
track_string = util.cpsPathToTrackName(vcToCorpusName[voice])
for chn_cnout in grand_old_dict[voice]:
tracks = [[]]
for startidx, stopidx, skipidx, fullpath in grand_old_dict[voice][chn_cnout]:
track_find_idx = 0
while True:
if len(tracks[track_find_idx]) == 0 or tracks[track_find_idx][-1][1] < startidx: break
track_find_idx += 1
if track_find_idx > len(tracks)-1: tracks.append([])
tracks[track_find_idx].append((startidx, stopidx, skipidx, fullpath))
all_tracks.append(("%s %ich"%(track_string, chn_cnout), tracks))

for trackstring, tracks in all_tracks:
for tidx, t in enumerate(tracks):
if aaf_track_method == 'cpsidx':
sortkey = (e.voiceID, e.sfchnls)
trackname = "%s %ich"%(util.cpsPathToTrackName(vcToCorpusName[e.voiceID]), e.sfchnls)
elif aaf_track_method == 'minimum':
sortkey = (e.sfchnls)
trackname = "cps %ich"%(e.sfchnls)
if not sortkey in grand_old_dict: grand_old_dict[sortkey] = [trackname, []]
grand_old_dict[sortkey][1].append((e.powerSeg, int(e.timeInScore*self.aaf_sr), int((e.timeInScore+e.duration)*self.aaf_sr), int(e.sfSkip*self.aaf_sr), e.filename))

ordering = list(grand_old_dict.keys())
ordering.sort()

for sortkey in ordering:
track_assign = {}
grand_old_dict[sortkey][1].sort(reverse=True) # loudest sounds first
for power, startidx, stopidx, skipidx, fullpath in grand_old_dict[sortkey][1]:
tidx = 0
while True:
if tidx not in track_assign: track_assign[tidx] = []
if True in [startidx <= stop and start <= stopidx for start, stop, skip, fpath in track_assign[tidx]]: tidx += 1
else: break
track_assign[tidx].append((startidx, stopidx, skipidx, fullpath))

track_assign = [(k, v) for k, v in track_assign.items()]
track_assign.sort()
for tidx, t in track_assign:
# make a track
sequence = self.f.create.Sequence(media_kind="sound")
idx_cnt = 0
for e in t:
for e in sorted(t, key=lambda x: x[0]):
if e[0] > idx_cnt: # add silence
sequence.components.append(self.f.create.Filler("sound", e[0]-idx_cnt)) # silence
# add sound clip
sequence.components.append(self.filepath_to_mob[e[3]].create_source_clip(slot_id=1, start=e[2], length=e[1]-e[0])) # sound
idx_cnt = e[1]
self._add_track(sequence, trackstring)
self._add_track(sequence, grand_old_dict[sortkey][0])


def done(self):

def done(self, verbose=True):
'''close the aaf file'''
self.f.close()
if verbose and self.trackcnt['tgt'] == 0:
print("Wrote %i corpus tracks to the AAF output"%(self.trackcnt['cps']))
elif verbose:
print("Wrote target track and %i corpus tracks to the AAF output"%(self.trackcnt['cps']))



Expand Down
2 changes: 1 addition & 1 deletion audioguide/defaults.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@

########### AAF ########
AAF_INCLUDE_TARGET = False

AAF_CPSTRACK_METHOD = 'cpsidx'

################ DESCRIPTOR COMPUTATION SETTINGS ################
DESCRIPTOR_DATABASE_SIZE_LIMIT = 1
Expand Down
42 changes: 2 additions & 40 deletions audioguide/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -157,9 +157,7 @@ def testVariable(vtype, v):

# AAF
'AAF_INCLUDE_TARGET': ['True or False'],



'AAF_CPSTRACK_METHOD': ['cpsidx', 'minimum'],

################# DESCRIPTOR COMPUTATION SETTINGS ################
'DESCRIPTOR_DATABASE_SIZE_LIMIT': ['a number greater than zero'],
Expand Down Expand Up @@ -257,6 +255,7 @@ def testVariable(vtype, v):

"AAF_FILEPATH": "output",
"AAF_INCLUDE_TARGET": "output",
"AAF_CPSTRACK_METHOD": "output",

"DESCRIPTOR_DATABASE_SIZE_LIMIT": "concate",
"DESCRIPTOR_DATABASE_AGE_LIMIT": "concate",
Expand Down Expand Up @@ -286,40 +285,3 @@ def testVariable(vtype, v):

for k in UserVar_types:
assert k in OptionChangeToProgramRun

def UpdatedOpsTestForChanges(currentops, newops):
changed = []
setops = {}
for k, v in newops.items():
if v == getattr(currentops, k): continue
changed.append(OptionChangeToProgramRun[k])
setops[k] = v
REINIT = False
EVAL_TARGET = False
EVAL_CORPUS = False
EVAL_NORM = False
EVAL_CONCATE = False
EVAL_OUTPUT = False
if 'reinit' in changed:
return True, True, True, True, True, True
else:
if 'target' in changed:
EVAL_TARGET = True
EVAL_NORM = True
EVAL_CONCATE = True
EVAL_OUTPUT = True
if 'corpus' in changed:
EVAL_CORPUS = True
EVAL_NORM = True
EVAL_CONCATE = True
EVAL_OUTPUT = True
if 'norm' in changed:
EVAL_NORM = True
EVAL_CONCATE = True
EVAL_OUTPUT = True
if 'concate' in changed:
EVAL_CONCATE = True
EVAL_OUTPUT = True
if 'output' in changed:
EVAL_OUTPUT = True
return REINIT, EVAL_TARGET, EVAL_CORPUS, EVAL_NORM, EVAL_CONCATE, EVAL_OUTPUT, setops
2 changes: 1 addition & 1 deletion docs_v1.7.html
Original file line number Diff line number Diff line change
Expand Up @@ -831,7 +831,7 @@ <h2 id="Normalization">Normalization</h2>
<p>-> <a href="https://www.youtube.com/watch?v=rHQI-jXd6iU&t=0s">Watch a related discussion from the AudioGuide Tutorial</a>.</p>
<p>AudioGuide concatenations can be written to .aaf files and opened in Logic/Pro Tools. See examples/11-logicOutput.py</p>

<p><b>AAF_FILEPATH</b> (type=<span style="color:red">string/None</span>, default=<span style="color:green">None</span>) Create an Advanced Authoring Format file for loading into Logic/Pro Tools.</p><p><b>AAF_INCLUDE_TARGET</b> (type=<span style="color:red">bool</span>, default=<span style="color:green">True</span>) If True, adds the target soundfile to the Advanced Authoring Format output.</p></div>
<p><b>AAF_FILEPATH</b> (type=<span style="color:red">string/None</span>, default=<span style="color:green">None</span>) Create an Advanced Authoring Format file for loading into Logic/Pro Tools.</p><p><b>AAF_INCLUDE_TARGET</b> (type=<span style="color:red">bool</span>, default=<span style="color:green">True</span>) If True, adds the target soundfile to the Advanced Authoring Format output.</p><p><b>AAF_CPSTRACK_METHOD</b> (type=<span style="color:red">string</span>, default=<span style="color:green">'cpsidx'</span>) A string specifying how to write selected corpus sounds into Advanced Authoring Format tracks. By default it is 'cpsidx'</p><ul><li><b>cpsidx</b> - Puts corpus sounds into AAF tracks according to their idx in the CORPUS list and their channel count.<li><b>minimum</b> - Mixes all corpus sounds into a minimal number of AAF tracks.</ul></div>



Expand Down
8 changes: 7 additions & 1 deletion examples/11-logicOutput.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,13 @@
SUPERIMPOSE = si(maxSegment=3)


CSOUND_CSD_FILEPATH = None # don't render csound

AAF_FILEPATH = 'output/output.aaf' # our AAF output filepath

AAF_INCLUDE_TARGET = True # this will include the target sound in the aaf file
CSOUND_CSD_FILEPATH = None # don't render csound

AAF_CPSTRACK_METHOD = 'cpsidx' # how cps tracks are written to the AAF file, also try 'minimum'



0 comments on commit a166e5c

Please sign in to comment.