Differences
This shows you the differences between two versions of the page.
Both sides previous revision Previous revision Next revision | Previous revision Next revisionBoth sides next revision | ||
pythonista_als_to_midifile_converter [2019/12/09 09:31] – Unified title (shown in midi_scripting tag lists) _ki | pythonista_als_to_midifile_converter [2019/12/10 09:35] – Added demo video _ki | ||
---|---|---|---|
Line 1: | Line 1: | ||
====== Pythonista: Ableton Live Set (ALS) to MIDI converter script ====== | ====== Pythonista: Ableton Live Set (ALS) to MIDI converter script ====== | ||
- | This Pythonista script installs a share extension to convert Ableton Live Set export files into MIDI files containing the notes of the exported tracks. | + | This Pythonista script installs a share extension to convert |
+ | {{youtube> | ||
+ | |||
+ | \\ | ||
How to install: | How to install: | ||
- | * Download the python 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 | ||
+ | | ||
+ | * or | ||
+ | * Copy the script code block, open Pythonista, create a new file named ALS_to_MIDI.py and paste the clipboard | ||
* In Pythonista settings/ | * In Pythonista settings/ | ||
* Use the + sign to add a extension | * Use the + sign to add a extension | ||
Line 18: | Line 33: | ||
\\ | \\ | ||
- | |||
<file py ALS_to_MIDI.py> | <file py ALS_to_MIDI.py> | ||
# Ableton MIDI clip zip export to MIDI file converter | # Ableton MIDI clip zip export to MIDI file converter | ||
Line 32: | Line 46: | ||
import xml.etree.ElementTree as ET | import xml.etree.ElementTree as ET | ||
import xml.etree as XTree | import xml.etree as XTree | ||
- | from midiutil import MIDIFile | + | 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 39: | Line 55: | ||
from zipfile import ZipFile | from zipfile import ZipFile | ||
from zipfile import BadZipfile | from zipfile import BadZipfile | ||
+ | import gzip | ||
+ | import binascii | ||
from time import sleep | from time import sleep | ||
+ | |||
+ | #custom (newer) version - ahead of the Pythonista version | ||
+ | #get the code from: https:// | ||
+ | #switch to the " | ||
+ | #place it in the " | ||
+ | #in a new file called " | ||
+ | from midiutil_v1_2_1 import MIDIFile | ||
+ | |||
+ | |||
def main(): | def main(): | ||
Line 49: | Line 76: | ||
inputFile = appex.get_file_path() | inputFile = appex.get_file_path() | ||
outfile = os.path.splitext(os.path.basename(inputFile))[0] + " | outfile = os.path.splitext(os.path.basename(inputFile))[0] + " | ||
+ | targetCC = -1 | ||
- | #check if we have an ALS which is not renamed | + | #some global cleverness |
- | #so basically a zip-archive with ALS extension | + | |
haveZIP = False | haveZIP = False | ||
+ | haveGadget = False | ||
try: | try: | ||
with ZipFile(inputFile) as zf: | with ZipFile(inputFile) as zf: | ||
Line 58: | Line 86: | ||
haveZIP = True | haveZIP = True | ||
except BadZipfile: | except BadZipfile: | ||
- | print(" | + | print(" |
- | if inputFile.endswith(" | + | if inputFile.endswith(" |
print(" | print(" | ||
with ZipFile(inputFile, | with ZipFile(inputFile, | ||
Line 72: | Line 100: | ||
infile = ablezip.extract(elem) | infile = ablezip.extract(elem) | ||
elif inputFile.endswith(" | elif inputFile.endswith(" | ||
- | print(" | ||
infile = inputFile | infile = inputFile | ||
+ | with open(infile, | ||
+ | #Is true if file is gzip | ||
+ | if binascii.hexlify(test_f.read(2)) == b' | ||
+ | print(" | ||
+ | haveGadget = True | ||
+ | with gzip.open(inputFile, | ||
+ | gadgetContents = f.read().decode(" | ||
+ | else: | ||
+ | print(" | ||
else: | else: | ||
print(" | print(" | ||
sys.exit() | sys.exit() | ||
- | track = 0 | ||
- | channel | ||
- | time = 0 # In beats | ||
- | duration | ||
- | tempo = 60 # In BPM | ||
- | volume | ||
- | # Rather parse the file because parsing strings will not clean up bad characters in XML | + | track = 0 |
- | | + | channel |
- | | + | time = 0 # In beats |
+ | | ||
+ | | ||
+ | volume | ||
+ | #Parse the data/file because parsing strings will not clean up bad characters in XML | ||
+ | if haveGadget == True: | ||
+ | #some people need always special treatment - handle them with care... | ||
+ | tree = ElementTree(fromstring(gadgetContents)) | ||
+ | else: | ||
+ | tree = ET.parse(str(infile)) | ||
+ | |||
+ | root = tree.getroot() | ||
#getting the tempo/bpm (rounded) from the Ableton file | #getting the tempo/bpm (rounded) from the Ableton file | ||
for master in root.iter(' | for master in root.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(' | ||
- | | + | |
- | print(' | + | print(' |
- | #Opening | + | #Preparing |
- | MyMIDI = MIDIFile(numTracks, | + | MyMIDI = MIDIFile(numTracks, |
MyMIDI.addTempo(track, | MyMIDI.addTempo(track, | ||
- | # Process every MIDI track found | + | #Process every MIDI track found |
for tracks in root.iter(' | for tracks in root.iter(' | ||
- | | + | |
- | print(' | + | print(' |
+ | |||
+ | #getting the track-name | ||
+ | for child in miditracks.iter(' | ||
+ | uName = child.get(' | ||
+ | # | ||
+ | MyMIDI.addTrackName(track, | ||
+ | |||
+ | #getting the key(s) per miditrack | ||
+ | for keytracks in miditracks.iter(' | ||
+ | for child in keytracks.iter(' | ||
+ | keyt = int(child.get(' | ||
+ | print(' | ||
+ | |||
+ | #getting the notes | ||
+ | mycount = 0 | ||
+ | for midiData in keytracks.iter(' | ||
+ | tim = midiData.get(' | ||
+ | dur = midiData.get(' | ||
+ | vel = midiData.get(' | ||
+ | #print(tim, dur, vel) | ||
+ | #writing the actual note information to file | ||
+ | # | ||
+ | MyMIDI.addNote(track, | ||
+ | mycount = mycount + 1 | ||
+ | print(' | ||
+ | |||
+ | #handling CC stuff | ||
+ | for envs in miditracks.iter(' | ||
+ | for clipenvs in envs.iter(' | ||
+ | for envtarget in clipenvs.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 | ||
+ | # | ||
+ | |||
+ | if int(child.get(' | ||
+ | targetCC = 0 | ||
+ | elif int(child.get(' | ||
+ | targetCC = 1 | ||
+ | elif int(child.get(' | ||
+ | targetCC = 74 | ||
+ | else: | ||
+ | targetCC = -1 | ||
+ | |||
+ | for autos in clipenvs.iter(' | ||
+ | for events in autos.iter(' | ||
+ | for autoevent in events.iter(' | ||
- | #getting the track-name | + | ccVal = int(autoevent.get('Value')) |
- | for child in miditracks.iter('UserName'): | + | |
- | | + | |
- | # | + | ccTim = 0 |
- | | + | |
- | | + | |
- | for keytracks in miditracks.iter(' | + | |
- | | + | |
- | | + | MyMIDI.addPitchWheelEvent(track, channel, ccTim, ccVal) |
- | print('key:', | + | |
- | | + | |
- | | + | if targetCC != -1 and targetCC != 0: |
- | | + | MyMIDI.addControllerEvent(track, channel, |
- | tim = midiData.get(' | + | |
- | dur = midiData.get(' | + | |
- | vel = midiData.get(' | + | |
- | #print(tim, dur, vel) | + | |
- | #writing the actual note information to file | + | |
- | # | + | |
- | | + | |
- | mycount = mycount + 1 | + | |
- | print(' | + | |
- | | + | |
with tempfile.NamedTemporaryFile(suffix=' | with tempfile.NamedTemporaryFile(suffix=' | ||
Line 140: | Line 218: | ||
fp.seek(0) | fp.seek(0) | ||
fp.read() | fp.read() | ||
- | # Open the MIDI file in your app of choice | + | # Open the MIDI file in your app of choice |
console.open_in(str(fp.name)) | console.open_in(str(fp.name)) | ||
#closing and deleting the temporary file | #closing and deleting the temporary file | ||
Line 147: | Line 225: | ||
if __name__ == ' | if __name__ == ' | ||
- | | + | |
</ | </ | ||
- | {{tag> | + | {{tag> |