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 09:35] – Added demo video _kipythonista_als_to_midifile_converter [2020/04/22 18:40] (current) MrBlaschke
Line 3: Line 3:
 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>G-pDfys2H7DzM}}+{{youtube>pDfys2H7DzM}}
  
 \\  \\ 
Line 37: 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 47: 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 58: 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 97: 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 121: 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 134: 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 211: 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:
  • pythonista_als_to_midifile_converter.1575930936.txt.gz
  • Last modified: 2019/12/10 09:35
  • by _ki