Differences
This shows you the differences between two versions of the page.
| Both sides previous revision Previous revision Next revision | Previous revision | ||
| pythonista_als_to_midifile_converter [2019/12/10 08:09] – Updated code _ki | pythonista_als_to_midifile_converter [2020/04/22 18:40] (current) – MrBlaschke | ||
|---|---|---|---|
| Line 2: | Line 2: | ||
| This Pythonista script installs a share extension to convert [[Ableton|Ableton Live Set export files]] into MIDI files containing the notes of the exported tracks. | This Pythonista script installs a share extension to convert [[Ableton|Ableton Live Set export files]] into MIDI files containing the notes of the exported tracks. | ||
| + | |||
| + | {{youtube> | ||
| \\ | \\ | ||
| How to install: | How to install: | ||
| - | * Either | + | * First you need to install a newer version of the midiutil |
| - | * Download the python script, long press the file in the files app, select share, choose 'Run Pythonista Script' | + | * Goto the [[https:// |
| + | * ' | ||
| + | * **Another approach** is to use the Readle Documents browser that allows to download the file to and then open that file to get a ' | ||
| + | * Open Pythonista and create a new file using the + button, choose 'Emtpy script' | ||
| + | * In the following dialog enter the name **midiutil_v1_2_1.py** exactly, select **site-package-3** as output folder and press ' | ||
| + | * Paste the clipboard, the content should be 1836 lines long. | ||
| + | |||
| + | * After installing the above file, either | ||
| + | * Download the python script | ||
| * or | * or | ||
| * Copy the script code block, open Pythonista, create a new file named ALS_to_MIDI.py and paste the clipboard | * Copy the script code block, open Pythonista, create a new file named ALS_to_MIDI.py and paste the clipboard | ||
| Line 27: | Line 37: | ||
| # Original script by MrBlaschke | # Original script by MrBlaschke | ||
| # Usability enhancements by rs2000 | # Usability enhancements by rs2000 | ||
| - | # Dec 8, 2019 | + | # Dec 11, 2019, V.04 |
| + | # | ||
| + | # greatly enhanced version that handles multiple scenes and clip offsets | ||
| + | # resulted in new parser engine | ||
| + | # request by @SpookyZoo | ||
| # | # | ||
| # Original request and idea by Svetlovska | # Original request and idea by Svetlovska | ||
| Line 37: | Line 51: | ||
| import xml.etree as XTree | import xml.etree as XTree | ||
| from xml.etree.ElementTree import fromstring, ElementTree | from xml.etree.ElementTree import fromstring, ElementTree | ||
| - | #thisis the old (default) library which does not support pitch-bend-data | ||
| - | #from midiutil import MIDIFile | ||
| import console | import console | ||
| import io | import io | ||
| Line 48: | Line 60: | ||
| import binascii | import binascii | ||
| from time import sleep | from time import sleep | ||
| - | |||
| #custom (newer) version - ahead of the Pythonista version | #custom (newer) version - ahead of the Pythonista version | ||
| #get the code from: https:// | #get the code from: https:// | ||
| Line 87: | Line 98: | ||
| for elem in listOfiles: | for elem in listOfiles: | ||
| if not elem.startswith(" | if not elem.startswith(" | ||
| - | print(' | + | |
| infile = ablezip.extract(elem) | infile = ablezip.extract(elem) | ||
| elif inputFile.endswith(" | elif inputFile.endswith(" | ||
| Line 111: | Line 122: | ||
| tempo = 60 # In BPM | tempo = 60 # In BPM | ||
| volume | volume | ||
| + | |||
| + | toffset | ||
| + | timeoff | ||
| #Parse the data/file because parsing strings will not clean up bad characters in XML | #Parse the data/file because parsing strings will not clean up bad characters in XML | ||
| Line 124: | Line 138: | ||
| for child in master.iter(' | for child in master.iter(' | ||
| tempo = int(float(child.get(' | tempo = int(float(child.get(' | ||
| - | # | ||
| #get amount of tracks to be allocated | #get amount of tracks to be allocated | ||
| for tracks in root.iter(' | for tracks in root.iter(' | ||
| - | numTracks = len(tracks.getchildren()) | + | numTracks = len(list(tracks.findall(' |
| - | print(' | + | print(' |
| #Preparing the target MIDI-file | #Preparing the target MIDI-file | ||
| - | MyMIDI = MIDIFile(numTracks, | + | MyMIDI = MIDIFile(numTracks, |
| MyMIDI.addTempo(track, | MyMIDI.addTempo(track, | ||
| - | #Process every MIDI track found | + | #Give me aaaallll you've got |
| - | for tracks | + | for miditrack |
| - | for miditracks in tracks.iter('MidiTrack' | + | # |
| - | | + | toffset = 0 |
| + | timeoff = 0 | ||
| - | | + | |
| - | for child in miditracks.iter(' | + | for uname in miditrack.findall('.//UserName' |
| - | | + | |
| - | #print(uName) | + | print(' |
| - | MyMIDI.addTrackName(track, | + | MyMIDI.addTrackName(track, |
| - | #getting the key(s) per miditrack | + | |
| - | | + | # |
| - | for child in keytracks.iter('MidiKey'): | + | |
| - | | + | # |
| - | print(' | + | toffset |
| - | | + | |
| - | | + | for loopinfo |
| - | | + | |
| - | | + | #store the next time offset |
| - | dur = midiData.get(' | + | |
| - | vel = midiData.get(' | + | |
| - | #print(tim, dur, vel) | + | |
| - | | + | |
| - | # | + | |
| - | MyMIDI.addNote(track, | + | |
| - | mycount = mycount + 1 | + | |
| - | print('processed', | + | |
| - | #handling CC stuff | + | |
| - | | + | |
| - | for clipenvs in envs.iter(' | + | |
| - | | + | |
| - | for child in envtarget: | + | |
| - | #this might be the CC-ID target based on 16200 | + | |
| - | #it is possibly not that easy because i found values of 16111 which makes up for a CC of -88 | + | |
| - | #damnit | + | |
| - | #print(child.tag, child.attrib) | + | |
| - | if int(child.get(' | + | for keytracks in noteinfo: |
| - | | + | for key in keytracks.findall(' |
| - | | + | keyt = int(key.attrib.get(' |
| - | | + | print(' |
| - | | + | |
| - | targetCC = 74 | + | for notes in keytracks.findall(' |
| - | | + | |
| - | targetCC = -1 | + | |
| + | | ||
| + | | ||
| - | | + | #getting automation data |
| - | for events | + | |
| - | for autoevent in events.iter('FloatEvent'): | + | for clipenv |
| + | #get the automation internal id | ||
| + | autoid = int(clipenv.find('.// | ||
| + | if autoid == 16200: # | ||
| + | | ||
| + | print(' | ||
| + | elif autoid == 16203: | ||
| + | targetCC = 1 | ||
| + | print('\tFound CC-data for: Modulation') | ||
| + | elif autoid == 16111: # | ||
| + | targetCC = 74 | ||
| + | print(' | ||
| + | else: | ||
| + | targetCC = -1 | ||
| + | print(' | ||
| - | ccVal = int(autoevent.get('Value')) | + | #get the automation values for each envelope |
| - | | + | for automs in clipenv.findall(' |
| + | for aevents in automs: | ||
| + | eventvals | ||
| + | ccTim = float(eventvals.get('Time')) | ||
| + | | ||
| if ccTim < 0: | if ccTim < 0: | ||
| - | ccTim = 0 | + | ccTim = 0 |
| #writing pitchbend informations | #writing pitchbend informations | ||
| if targetCC == 0: | if targetCC == 0: | ||
| - | # | ||
| MyMIDI.addPitchWheelEvent(track, | MyMIDI.addPitchWheelEvent(track, | ||
| Line 201: | Line 220: | ||
| if targetCC != -1 and targetCC != 0: | if targetCC != -1 and targetCC != 0: | ||
| MyMIDI.addControllerEvent(track, | MyMIDI.addControllerEvent(track, | ||
| - | + | | |
| - | | + | |
| with tempfile.NamedTemporaryFile(suffix=' | with tempfile.NamedTemporaryFile(suffix=' | ||
| Line 218: | Line 236: | ||
| </ | </ | ||
| - | {{tag> | + | {{tag> |