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 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 09:00] – New instructions _ki
Line 5: Line 5:
 \\  \\ 
 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' and then 'Import File'+    * Goto the [[https://github.com/MarkCWirt/MIDIUtil/blob/develop/src/midiutil/MidiFile.py|midiutils official github webpage]] and press the RAW button to open the source. 
 +    * 'Select all' is currently broken in Safarai for IOS 13, so you need to double-tap to start a selection and move the first selection marker to the start of the text and then the second selection marker to the bottom of this about 2000lines long text. (Thank Apple for this inconvience). Then select copy to copy the whole source code to the clipboard. 
 +    * **Another approach** is to use the Readle Documents browser that allows to download the file to and then open that file to get a 'Select All' and 'Copy' action 
 +    * 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 'Create' 
 +    * Paste the clipboard, the content should be 1836 lines long. 
 + 
 +  * After installing the above file, either  
 +    * Download the python script by using the button above the code, long press the file in the files app, select share, choose 'Run Pythonista Script' and then 'Import File'
   * 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 36: Line 44:
 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 53:
 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 74:
     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 84:
             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 98:
                     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 216:
         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 223:
  
 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