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
pythonista_als_to_midifile_converter [2019/12/10 08:09] – Updated code _kipythonista_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>pDfys2H7DzM}}
  
 \\  \\ 
 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 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://github.com/MarkCWirt/MIDIUtil/blob/develop/src/midiutil/MidiFile.py #get the code from: https://github.com/MarkCWirt/MIDIUtil/blob/develop/src/midiutil/MidiFile.py
Line 87: Line 98:
             for elem in listOfiles:             for elem in listOfiles:
                 if not elem.startswith("__") and elem.endswith(".als"):                 if not elem.startswith("__") and elem.endswith(".als"):
-                    print('Found:', elem, end=' ')+                    #print('Found:', elem, end=' ')
                     infile = ablezip.extract(elem)                     infile = ablezip.extract(elem)
     elif inputFile.endswith(".als"):     elif inputFile.endswith(".als"):
Line 111: Line 122:
     tempo           = 60    # In BPM     tempo           = 60    # In BPM
     volume          = 100   # 0-127, as per the MIDI standard     volume          = 100   # 0-127, as per the MIDI standard
 +
 +    toffset                 = 0         # for calculating time-offsets in multi scenes
 +    timeoff                 = 0         # store for temp offsets
  
     #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('FloatEvent'):         for child in master.iter('FloatEvent'):
             tempo = int(float(child.get('Value')))             tempo = int(float(child.get('Value')))
-            #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(list(tracks.findall('MidiTrack'))) 
-        print('Found',str(numTracks),'tracks')+        print('Found',str(numTracks),'track(s) with', tempo, 'BPM')
  
     #Preparing 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)        #tempo track is created automatically
     MyMIDI.addTempo(track, time, tempo)     MyMIDI.addTempo(track, time, tempo)
  
-    #Process every MIDI track found +    #Give me aaaallll you've got 
-    for tracks in root.iter('Tracks'): +    for miditrack in root.findall('.//MidiTrack'): 
-        for miditracks in tracks.iter('MidiTrack'): +        #resetting the time offset data 
-            print('\nMIDITRACK ', track)+        toffset = 0 
 +        timeoff = 0
  
-            #getting the track-name +        #getting track data (name, etc) 
-            for child in miditracks.iter('UserName'): +        for uname in miditrack.findall('.//UserName'): 
-                uName child.get('Value'+            trackname uname.attrib.get('Value'
-                #print(uName+            print('\nProcessing track: ', trackname
-                MyMIDI.addTrackName(track, 0, uName)+            MyMIDI.addTrackName(track, 0, trackname)
  
-            #getting the key(s) per miditrack +        for clipslot in miditrack.findall('.//MainSequencer/ClipSlotList/ClipSlot'): 
-            for keytracks in miditracks.iter('KeyTrack'): +            #looping the amount of clips 
-                for child in keytracks.iter('MidiKey'): +            for midiclip in clipslot.findall('.//ClipSlot/Value/MidiClip'): 
-                    keyt int(child.get('Value')) +                #raising the time offset for the next clip inside this track 
-                    print('key:', str(keyt) ',', end=' ')+                toffset toffset timeoff
  
-                    #getting the notes +                #get the clip-length 
-                    mycount = 0 +                for loopinfo in midiclip.findall('.//Loop'): 
-                    for midiData in keytracks.iter('MidiNoteEvent'): +                    le loopinfo.find('LoopEnd') 
-                        tim midiData.get('Time') +                    #store the next time offset 
-                        dur = midiData.get('Duration'+                    timeoff = float(le.attrib.get('Value'))
-                        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 noteinfo in midiclip.findall('.//Notes/KeyTracks'): 
-            for envs in miditracks.iter('Envelopes'): +                    print('\tAmount of note events: ', len(noteinfo.getchildren()))
-                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 +                    for keytracks in noteinfo: 
-                                targetCC = 0 +                        for key in keytracks.findall('.//MidiKey'): 
-                            elif int(child.get('Value')) == 16203:          #mod-wheel +                            keyt = int(key.attrib.get('Value')) 
-                                targetCC 1 +                            print('\t\tProcessing key', str(keyt)) 
-                            elif int(child.get('Value')) == 16111:          #cutoff? +                        #getting the notes 
-                                targetCC = 74 +                        for notes in keytracks.findall('.//Notes/MidiNoteEvent'): 
-                            else: +                            tim = float(notes.attrib.get('Time')) + float(toffset) 
-                                targetCC = -1+                            dur float(notes.attrib.get('Duration')) 
 +                            vel = int(notes.attrib.get('Velocity')) 
 +                            MyMIDI.addNote(track, channel, keyt, tim, dur, vel)
  
-                    for autos in clipenvs.iter('Automation'): +                #getting automation data 
-                        for events in autos.iter('Events'): +                for envelopes in midiclip.findall('.//Envelopes/Envelopes'): 
-                            for autoevent in events.iter('FloatEvent'):+                    for clipenv in envelopes: 
 +                        #get the automation internal id 
 +                        autoid = int(clipenv.find('.//EnvelopeTarget/PointeeId').attrib.get('Value')) 
 +                        if autoid == 16200           #pitchbend 
 +                            targetCC = 0 
 +                            print('\tFound CC-data for: Pitch'
 +                        elif autoid == 16203:          #mod-wheel 
 +                            targetCC = 1 
 +                            print('\tFound CC-data for: Modulation') 
 +                        elif autoid == 16111         #cutoff? 
 +                            targetCC = 74 
 +                            print('\tFound CC-data for: Cutoff'
 +                        else: 
 +                            targetCC = -1 
 +                            print('\n!! Found unhandled CC data. Contact developer for integration. Thanks!')
  
-                                ccVal int(autoevent.get('Value')) +                        #get the automation values for each envelope 
-                                ccTim float(autoevent.get('Time'))+                        for automs in clipenv.findall('.//Automation/Events'): 
 +                            for aevents in automs: 
 +                                eventvals aevents.attrib 
 +                                ccTim = float(eventvals.get('Time')) 
 +                                ccVal int(eventvals.get('Value'))
                                 if ccTim < 0:                                 if ccTim < 0:
-                                    ccTim = 0+                                    ccTim = 0   
  
                                 #writing pitchbend informations                                 #writing pitchbend informations
                                 if targetCC == 0:                                 if targetCC == 0:
-                                    #print('pitchbend/ time: ', ccTim, ' - val: ', ccVal) 
                                     MyMIDI.addPitchWheelEvent(track, channel, ccTim, ccVal)                                     MyMIDI.addPitchWheelEvent(track, channel, ccTim, ccVal)
  
Line 201: Line 220:
                                 if targetCC != -1 and targetCC != 0:                                 if targetCC != -1 and targetCC != 0:
                                     MyMIDI.addControllerEvent(track, channel, ccTim, targetCC, ccVal)                                     MyMIDI.addControllerEvent(track, channel, ccTim, targetCC, ccVal)
- +        track = track + 1
-            track = track + 1+
  
     with tempfile.NamedTemporaryFile(suffix='.mid') as fp:     with tempfile.NamedTemporaryFile(suffix='.mid') as fp:
Line 218: Line 236:
 </file> </file>
  
-{{tag>ableton_live_set midi_scripting }}+{{tag>ableton_live_set midi_scripting video}}
  • pythonista_als_to_midifile_converter.1575925763.txt.gz
  • Last modified: 2019/12/10 08:09
  • by _ki