Differences

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

Link to this comparison view

Both sides previous revision Previous revision
pythonista_als_to_midifile_converter [2019/12/10 09:35]
_ki Added demo video
pythonista_als_to_midifile_converter [2019/12/12 06:39] (current)
_ki Updated script
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.txt
  • Last modified: 2019/12/12 06:39
  • by _ki