Skip to content

Commit

Permalink
Merge pull request #20 from offlinemark/l12-cleanup
Browse files Browse the repository at this point in the history
Cleanup Live 12 support and add tests
  • Loading branch information
offlinemark authored Mar 12, 2024
2 parents b3a512a + 899b636 commit 8e2988c
Show file tree
Hide file tree
Showing 5 changed files with 59 additions and 33 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ It provides a high accuracy implementation of **time marker extraction**,
including support for projects with tempo automation.

Supported formats:
- Ableton Live set (.als) [v8-10]
- Ableton Live set (.als) [v8-12]
- FL Studio project (.flp) [v10-11, 20]
- Cue sheet (.cue)

Expand Down
65 changes: 33 additions & 32 deletions dawtool/daw/ableton.py
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,7 @@ def _calc_beat_real_time_fast_path(self):
return self.tempo_automation_events is None or \
len(self.tempo_automation_events) == 1

def _parse_arranger_automation_events(self, contents):
def _parse_events_from_arranger_automation(self, contents):
"""
Only for Ableton 8 and 9.
"""
Expand All @@ -272,44 +272,45 @@ def _parse_arranger_automation_events(self, contents):
events = arranger_auto.find('Events')
return events

def _parse_events_from_main_track(self, contents, main_track_name):
master_track_chunk = self._find_tag(contents, main_track_name)

try:
master_track = ET.fromstring(master_track_chunk)
except ParseError:
raise ValueError('Cannot parse automation')

auto_envelopes = master_track.find('AutomationEnvelopes')
if auto_envelopes is None:
logger.warning('%s: No AutomationEnvelopes found in MasterTrack', self.filename)
return None

envelopes = auto_envelopes.find('Envelopes')
if envelopes is None:
logger.warning('%s: No found in MasterTrack', self.filename)
return None

events = None

for env in envelopes:
pointee_id = env.find('EnvelopeTarget').find('PointeeId').get('Value')
if pointee_id == self.tempo_automation_target_id:
events = env.find('Automation').find('Events')
break

return events

def _parse_automation(self, contents):
"""
Needs to be called after _parse_tempo
"""

events = None

# Ableton 8, 9 store tempo auto differently
if self.version.minorA < 10:
events = self._parse_arranger_automation_events(contents)
events = self._parse_events_from_arranger_automation(contents)
else:
if self.version.minorA in [10,11]:
# This only applies to Ableton 10 and 11
master_track_chunk = self._find_tag(contents, 'MasterTrack')
else:
# This only applies to Ableton 12
master_track_chunk = self._find_tag(contents, 'MainTrack')
try:
master_track = ET.fromstring(master_track_chunk)
except ParseError:
raise ValueError('Cannot parse automation')

auto_envelopes = master_track.find('AutomationEnvelopes')
if auto_envelopes is None:
logger.warning('%s: No AutomationEnvelopes found in MasterTrack', self.filename)
return

envelopes = auto_envelopes.find('Envelopes')
if envelopes is None:
logger.warning('%s: No found in MasterTrack', self.filename)
return

# events = None
for env in envelopes:
pointee_id = env.find('EnvelopeTarget').find('PointeeId').get('Value')
if pointee_id == self.tempo_automation_target_id:
events = env.find('Automation').find('Events')
break
main_track_name = 'MasterTrack' if self.version.minorA in (10, 11) else 'MainTrack'
events = self._parse_events_from_main_track(contents, main_track_name)

if events is None:
return
Expand All @@ -318,7 +319,7 @@ def _parse_automation(self, contents):

def _parse_tempo(self, contents):
if self.version.minorA == 8:
events = self._parse_arranger_automation_events(contents)
events = self._parse_events_from_arranger_automation(contents)
if not events:
# there should always be at least 1 event in general, and especially
# for Ableton 8.
Expand Down
Binary file added tests/als/L12-automation-intense-unaligned.als
Binary file not shown.
Binary file added tests/als/L12-automation.als
Binary file not shown.
25 changes: 25 additions & 0 deletions tests/test_ableton.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,19 @@ def test_als_automation_theo():
assert proj.tempo_automation_events == auto
assert proj.markers == marks

def test_als_automation_theo_l12():
fname = f'{TESTS_DIR_ALS}/L12-automation.als'
with open(fname, 'rb') as f:
proj = load_project(fname, f, theoretical=True)

proj.parse()

auto = [TempoAutomationFloatEvent(id='92', time=-63072000.0, real_time=0.0, value=60.0, curve_control1=None, curve_control2=None, prev_aligned_bpm=None), TempoAutomationFloatEvent(id='290', time=4.0, real_time=4.0, value=60.0, curve_control1=None, curve_control2=None, prev_aligned_bpm=None), TempoAutomationFloatEvent(id='291', time=8.0, real_time=6.772588722239782, value=120.0, curve_control1=None, curve_control2=None, prev_aligned_bpm=None), TempoAutomationFloatEvent(id='596', time=12.0, real_time=8.305065593537753, value=200.0, curve_control1=None, curve_control2=None, prev_aligned_bpm=None)]
marks = [Marker(time=0.0, text='A'), Marker(time=4.0, text='B'), Marker(time=5.6218604324326575, text='D'), Marker(time=6.772588722239782, text='C'), Marker(time=7.635634939595124, text='Z'), Marker(time=8.905065593537753, text='E')]

assert proj.tempo_automation_events == auto
assert proj.markers == marks

def test_als_auto_unaligned_daw():
'als with many points, fairly steep slopes, and points unaligned on 16th notes'
fname = f'{TESTS_DIR_ALS}/automation-intense-unaligned.als'
Expand All @@ -90,6 +103,18 @@ def test_als_auto_unaligned_daw():
assert proj.tempo_automation_events == auto
assert proj.markers == [Marker(time=0.0, text='A'), Marker(time=0.7452763515350631, text='X'), Marker(time=2.335821771598745, text='B'), Marker(time=4.173841066076857, text='D'), Marker(time=5.706896019728953, text='C'), Marker(time=6.905534691333889, text='Z'), Marker(time=7.692021891450212, text='1'), Marker(time=8.974199935476374, text='E'), Marker(time=10.705147339679828, text='YY')]

def test_als_auto_unaligned_daw_l12():
'als with many points, fairly steep slopes, and points unaligned on 16th notes'
fname = f'{TESTS_DIR_ALS}/L12-automation-intense-unaligned.als'
with open(fname, 'rb') as f:
proj = load_project(fname, f, theoretical=False)

proj.parse()
print(proj.tempo_automation_events)
auto = [TempoAutomationFloatEvent(id='598', time=-63072000.0, real_time=0.0, value=120.760574, curve_control1=None, curve_control2=None, prev_aligned_bpm=None), TempoAutomationFloatEvent(id='649', time=1.8125, real_time=0.9005422581048679, value=120.760574, curve_control1=None, curve_control2=None, prev_aligned_bpm=120.760574), TempoAutomationFloatEvent(id='667', time=4.0625, real_time=2.3966117557062345, value=60.0, curve_control1=None, curve_control2=None, prev_aligned_bpm=61.687793722222224), TempoAutomationFloatEvent(id='669', time=4.093925345487846, real_time=2.427177295750691, value=80.6271286, curve_control1=None, curve_control2=None, prev_aligned_bpm=61.687793722222224), TempoAutomationFloatEvent(id='670', time=4.1484021187146185, real_time=2.480163570609616, value=34.5163536, curve_control1=None, curve_control2=None, prev_aligned_bpm=61.687793722222224), TempoAutomationFloatEvent(id='671', time=4.178364343989344, real_time=2.5093060217820256, value=95.9973831, curve_control1=None, curve_control2=None, prev_aligned_bpm=61.687793722222224), TempoAutomationFloatEvent(id='668', time=4.25, real_time=2.578981708028704, value=58.0627136, curve_control1=None, curve_control2=None, prev_aligned_bpm=61.687793722222224), TempoAutomationFloatEvent(id='672', time=4.457870775058275, real_time=2.7937881772788167, value=55.9149513, curve_control1=None, curve_control2=None, prev_aligned_bpm=58.0627136), TempoAutomationFloatEvent(id='712', time=4.548306901431902, real_time=2.8828996005984946, value=72.4000015, curve_control1=None, curve_control2=None, prev_aligned_bpm=63.594430905110116), TempoAutomationFloatEvent(id='700', time=4.80127970987346, real_time=3.113221253516874, value=78.0, curve_control1=None, curve_control2=None, prev_aligned_bpm=76.86483333864655), TempoAutomationFloatEvent(id='600', time=5.0, real_time=3.268340530951092, value=50.3135643, curve_control1=None, curve_control2=None, prev_aligned_bpm=76.86483333864655), TempoAutomationFloatEvent(id='599', time=6.5, real_time=4.455212285145871, value=125.883995, curve_control1=None, curve_control2=None, prev_aligned_bpm=50.3135643), TempoAutomationFloatEvent(id='601', time=7.25, real_time=4.946994580622144, value=40.0667267, curve_control1=None, curve_control2=None, prev_aligned_bpm=125.883995), TempoAutomationFloatEvent(id='291', time=8.0, real_time=5.706896019728953, value=120.0, curve_control1=None, curve_control2=None, prev_aligned_bpm=40.0667267), TempoAutomationFloatEvent(id='602', time=8.5, real_time=5.9305966531965275, value=183.949417, curve_control1=None, curve_control2=None, prev_aligned_bpm=120.0), TempoAutomationFloatEvent(id='603', time=9.5, real_time=6.410345802117857, value=54.1561279, curve_control1=None, curve_control2=None, prev_aligned_bpm=183.949417), TempoAutomationFloatEvent(id='596', time=12.0, real_time=7.860727189077446, value=200.0, curve_control1=None, curve_control2=None, prev_aligned_bpm=54.1561279), TempoAutomationFloatEvent(id='604', time=13.0, real_time=8.27541854608016, value=74.6498032, curve_control1=None, curve_control2=None, prev_aligned_bpm=200.0), TempoAutomationFloatEvent(id='605', time=15.25, real_time=9.587704231552863, value=147.658524, curve_control1=None, curve_control2=None, prev_aligned_bpm=74.6498032)]
assert proj.tempo_automation_events == auto
assert proj.markers == [Marker(time=0.0, text='A'), Marker(time=0.7452763515350631, text='X'), Marker(time=2.335821771598745, text='B'), Marker(time=4.173841066076857, text='D'), Marker(time=5.706896019728953, text='C'), Marker(time=6.905534691333889, text='Z'), Marker(time=7.692021891450212, text='1'), Marker(time=8.974199935476374, text='E'), Marker(time=10.705147339679828, text='YY')]

def test_als_auto_pathological3_daw():
"""
25505 automation points,no slopes, just horizontal and vertical lines
Expand Down

0 comments on commit 8e2988c

Please sign in to comment.