This is an old revision of the document!
Pythonista - Ableton Live Set (ALS) to MIDIfile converter script
# Ableton MIDI clip zip export to MIDI file converter
# Original script by MrBlaschke
# Usability enhancements by rs2000
# Dec 8, 2019
#
# Original request and idea by Svetlovska
import sys
import os
import tempfile
import xml.etree.ElementTree as ET
import xml.etree as XTree
from midiutil import MIDIFile
import console
import io
import appex
import ui
from zipfile import ZipFile
from zipfile import BadZipfile
from time import sleep
def main():
if not appex.is_running_extension():
print('This script is intended to be run from the sharing extension.')
return
# Catch zip file from external "Open in..." dialog
inputFile = appex.get_file_path()
outfile = os.path.splitext(os.path.basename(inputFile))[0] + ".mid"
#check if we have an ALS which is not renamed
#so basically a zip-archive with ALS extension - is that of relevance?
haveZIP = False
try:
with ZipFile(inputFile) as zf:
print("Info: we have a real ZIP archive")
haveZIP = True
except BadZipfile:
print("Info: It is a pure ALS file")
if inputFile.endswith(".zip") or haveZIP == True:
print("Importing ZIP archive...")
with ZipFile(inputFile, 'r') as ablezip:
# Iterate over the list of file names in given archive
# filter out possible hidden files in "__MACOSX" directories for manually created ZIPs, etc
listOfiles = ablezip.namelist()
for elem in listOfiles:
if not elem.startswith("__") and elem.endswith(".als"):
print('Found:', elem, end=' ')
infile = ablezip.extract(elem)
elif inputFile.endswith(".als"):
print("Input is direct Ableton ALS file")
infile = inputFile
else:
print("filetype not supported...")
sys.exit()
track = 0
channel = 0
time = 0 # In beats
duration = 1 # In beats
tempo = 60 # In BPM
volume = 100 # 0-127, as per the MIDI standard
# Rather parse the file because parsing strings will not clean up bad characters in XML
tree = ET.parse(str(infile))
root = tree.getroot()
#getting the tempo/bpm (rounded) from the Ableton file
for master in root.iter('Tempo'):
for child in master.iter('FloatEvent'):
tempo = int(float(child.get('Value')))
#print('tempo: ', tempo)
#get amount of tracks to be allocated
for tracks in root.iter('Tracks'):
numTracks = len(tracks.getchildren())
print('Found',str(numTracks),'tracks')
#Opening the target MIDI-file
MyMIDI = MIDIFile(numTracks, adjust_origin=True) # One track, defaults to format 1 (tempo track is created automatically)
MyMIDI.addTempo(track, time, tempo)
# Process every MIDI track found
for tracks in root.iter('Tracks'):
for miditracks in tracks.iter('MidiTrack'):
print('\nMIDITRACK ', track)
#getting the track-name
for child in miditracks.iter('UserName'):
uName = child.get('Value')
#print(uName)
MyMIDI.addTrackName(track, 0, uName)
#getting the key(s) per miditrack
for keytracks in miditracks.iter('KeyTrack'):
for child in keytracks.iter('MidiKey'):
keyt = int(child.get('Value'))
print('key:', str(keyt) + ',', end=' ')
#getting the notes
mycount = 0
for midiData in keytracks.iter('MidiNoteEvent'):
tim = midiData.get('Time')
dur = midiData.get('Duration')
vel = midiData.get('Velocity')
#print(tim, dur, vel)
#writing the actual note information to file
#MIDIFile.addNote(track, channel, pitch, time, duration, volume, annotation=None
MyMIDI.addNote(track, channel, keyt, float(tim), float(dur), int(vel))
mycount = mycount + 1
print('processed',int(mycount),'note events')
track = track + 1
with tempfile.NamedTemporaryFile(suffix='.mid') as fp:
MyMIDI.writeFile(fp)
fp.seek(0)
fp.read()
# Open the MIDI file in your app of choice :)
console.open_in(str(fp.name))
#closing and deleting the temporary file
fp.close()
print ('done.')
if __name__ == '__main__':
main()