pythonista_als_to_midifile_converter

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Both sides previous revision Previous revision
Next revisionBoth sides next revision
pythonista_als_to_midifile_converter [2019/12/09 09:44] – Refined script download instructions _kipythonista_als_to_midifile_converter [2019/12/10 08:09] – Updated code _ki
Line 36: Line 36:
 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 43: Line 45:
 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://github.com/MarkCWirt/MIDIUtil/blob/develop/src/midiutil/MidiFile.py
 +#switch to the "RAW" mode and copy all you see on that big text-page
 +#place it in the "Python Modules/site-packages-3" directory
 +#in a new file called "midiutil_v1_2_1.py"
 +from midiutil_v1_2_1 import MIDIFile
 +
 +
  
 def main(): def main():
Line 53: Line 66:
     inputFile = appex.get_file_path()     inputFile = appex.get_file_path()
     outfile = os.path.splitext(os.path.basename(inputFile))[0] + ".mid"     outfile = os.path.splitext(os.path.basename(inputFile))[0] + ".mid"
 +    targetCC = -1
  
-    #check if we have an ALS which is not renamed +    #some global cleverness digital post-it's
-    #so basically a zip-archive with ALS extension is that of relevance?+
     haveZIP = False     haveZIP = False
 +    haveGadget = False
     try:     try:
         with ZipFile(inputFile) as zf:         with ZipFile(inputFile) as zf:
Line 62: Line 76:
             haveZIP = True             haveZIP = True
     except BadZipfile:     except BadZipfile:
-        print("Info: It is a pure ALS file")+        print("Info: It is an ALS or Gadget file")
  
-    if inputFile.endswith(".zip"or haveZIP == True:+    if inputFile.endswith(".zip"and haveZIP == True:
         print("Importing ZIP archive...")         print("Importing ZIP archive...")
         with ZipFile(inputFile, 'r') as ablezip:         with ZipFile(inputFile, 'r') as ablezip:
Line 76: Line 90:
                     infile = ablezip.extract(elem)                     infile = ablezip.extract(elem)
     elif inputFile.endswith(".als"):     elif inputFile.endswith(".als"):
-        print("Input is direct Ableton ALS file") 
         infile = inputFile         infile = inputFile
 +        with open(infile, 'rb') as test_f:
 +            #Is true if file is gzip
 +            if binascii.hexlify(test_f.read(2)) == b'1f8b':
 +                print("Input is Gadget ALS file")
 +                haveGadget = True
 +                with gzip.open(inputFile, 'rb') as f:
 +                    gadgetContents = f.read().decode("utf-8")
 +            else:
 +                print("Input is plain ALS file")
     else:     else:
         print("filetype not supported...")         print("filetype not supported...")
         sys.exit()         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 +    track           = 0 
-    tree ET.parse(str(infile)) +    channel         = 0 
-    root tree.getroot()+    time            = 0     # In beats 
 +    duration        1     # In beats 
 +    tempo           60    # In BPM 
 +    volume          = 100   # 0-127, as per the MIDI standard
  
 +    #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('Tempo'):     for master in root.iter('Tempo'):
-            for child in master.iter('FloatEvent'): +        for child in master.iter('FloatEvent'): 
-                    tempo = int(float(child.get('Value'))) +            tempo = int(float(child.get('Value'))) 
-                    #print('tempo: ', tempo)+            #print('tempo: ', tempo)
  
     #get amount of tracks to be allocated     #get amount of tracks to be allocated
     for tracks in root.iter('Tracks'):     for tracks in root.iter('Tracks'):
-            numTracks = len(tracks.getchildren()) +        numTracks = len(tracks.getchildren()) 
-            print('Found',str(numTracks),'tracks')+        print('Found',str(numTracks),'tracks')
  
-    #Opening the target MIDI-file +    #Preparing the target MIDI-file 
-    MyMIDI = MIDIFile(numTracks, adjust_origin=True)    # One track, defaults to format 1 (tempo track is created automatically)+    MyMIDI = MIDIFile(numTracks, adjust_origin=True)        #One track, defaults to format 1 (tempo track is created automatically)
     MyMIDI.addTempo(track, time, tempo)     MyMIDI.addTempo(track, time, tempo)
  
-    # Process every MIDI track found+    #Process every MIDI track found
     for tracks in root.iter('Tracks'):     for tracks in root.iter('Tracks'):
-                    for miditracks in tracks.iter('MidiTrack'): +        for miditracks in tracks.iter('MidiTrack'): 
-                            print('\nMIDITRACK ', track)+            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'
 + 
 +            #handling CC stuff 
 +            for envs in miditracks.iter('Envelopes'): 
 +                for clipenvs in envs.iter('ClipEnvelope'): 
 +                    for envtarget in clipenvs.iter('EnvelopeTarget'): 
 +                        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('Value')) == 16200:            #pitchbend 
 +                                targetCC = 0 
 +                            elif int(child.get('Value')) == 16203:          #mod-wheel 
 +                                targetCC = 1 
 +                            elif int(child.get('Value')) == 16111:          #cutoff? 
 +                                targetCC = 74 
 +                            else: 
 +                                targetCC = -1 
 + 
 +                    for autos in clipenvs.iter('Automation'): 
 +                        for events in autos.iter('Events'): 
 +                            for autoevent in events.iter('FloatEvent'):
  
-                            #getting the track-name          +                                ccVal = int(autoevent.get('Value')) 
-                            for child in miditracks.iter('UserName'): +                                ccTim float(autoevent.get('Time')) 
-                                uName child.get('Value') +                                if ccTim < 0: 
-                                #print(uName+                                    ccTim = 0
-                                MyMIDI.addTrackName(track, 0, uName)+
  
-                            #getting the key(s) per miditrack +                                #writing pitchbend informations 
-                            for keytracks in miditracks.iter('KeyTrack'): +                                if targetCC == 0
-                                for child in keytracks.iter('MidiKey')+                                    #print('pitchbend/ time: ', ccTim, - val: ', ccVal) 
-                                    keyt = int(child.get('Value')) +                                    MyMIDI.addPitchWheelEvent(trackchannelccTim, ccVal)
-                                    print('key:', str(keyt) + ','end=' ')+
  
-                                    #getting the notes +                                #writing other CC values 
-                                    mycount = 0 +                                if targetCC != -1 and targetCC != 0: 
-                                    for midiData in keytracks.iter('MidiNoteEvent'): +                                    MyMIDI.addControllerEvent(track, channel, ccTimtargetCCccVal)
-                                        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, keytfloat(tim), float(dur), int(vel)) +
-                                        mycount = mycount + 1 +
-                                    print('processed',int(mycount),'note events')+
  
-                            track = track + 1+            track = track + 1
  
     with tempfile.NamedTemporaryFile(suffix='.mid') as fp:     with tempfile.NamedTemporaryFile(suffix='.mid') as fp:
Line 144: Line 208:
         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 - aka 'bring out the gimp'
         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 151: Line 215:
  
 if __name__ == '__main__': if __name__ == '__main__':
-        main() +    main()
 </file> </file>
  
 {{tag>ableton_live_set midi_scripting }} {{tag>ableton_live_set midi_scripting }}
  • pythonista_als_to_midifile_converter.txt
  • Last modified: 2020/04/22 18:40
  • by MrBlaschke