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
Last revisionBoth sides next revision
pythonista_als_to_midifile_converter [2019/12/10 09:00] – New instructions _kipythonista_als_to_midifile_converter [2019/12/12 06:39] – Updated script _ki
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>G-pDfys2H7DzM}}
  
 \\  \\ 
Line 35: 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 45: 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 56: 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 95: 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 119: 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 132: 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 209: 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 226: Line 236:
 </file> </file>
  
-{{tag>ableton_live_set midi_scripting }}+{{tag>ableton_live_set midi_scripting video}}
  • pythonista_als_to_midifile_converter.txt
  • Last modified: 2020/04/22 18:40
  • by MrBlaschke