CoolFace
Apppublic

avans06/Audio-To-MIDI-And-Advanced-Renderer

sourceHugging Facecc-by-nc-4.0updated 1y agoView on Hugging Face
37likes
MIDI.py2134 linesDownload Raw Back to src
1#! /usr/bin/python32 3r'''###############################################################################4###################################################################################5#6#7#	Tegridy MIDI X Module (TMIDI X / tee-midi eks)8#9#   NOTE: TMIDI X Module starts after the partial MIDI.py module @ line 145010#11#	Based upon MIDI.py module v.6.7. by Peter Billam / pjb.com.au12#13#	Project Los Angeles14#15#	Tegridy Code 202516#17#   https://github.com/Tegridy-Code/Project-Los-Angeles18#19#20###################################################################################21###################################################################################22#   Copyright 2025 Project Los Angeles / Tegridy Code23#24#   Licensed under the Apache License, Version 2.0 (the "License");25#   you may not use this file except in compliance with the License.26#   You may obtain a copy of the License at27#28#   http://www.apache.org/licenses/LICENSE-2.029#30#   Unless required by applicable law or agreed to in writing, software31#   distributed under the License is distributed on an "AS IS" BASIS,32#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.33#   See the License for the specific language governing permissions and34#   limitations under the License.35###################################################################################36###################################################################################37#38#	PARTIAL MIDI.py Module v.6.7. by Peter Billam39#   Please see TMIDI 2.3/tegridy-tools repo for full MIDI.py module code40# 41#   Or you can always download the latest full version from:42#43#   https://pjb.com.au/44#   https://peterbillam.gitlab.io/miditools/45#	46#	Copyright 2020 Peter Billam47#48###################################################################################49###################################################################################50# unsupported 20091104 ...51#     ['set_sequence_number', dtime, sequence]52#     ['raw_data', dtime, raw]53 54# 20150914   jimbo1qaz   MIDI.py str/bytes bug report55# I found a MIDI file which had Shift-JIS titles. When midi.py decodes it as56# latin-1, it produces a string which cannot even be accessed without raising57# a UnicodeDecodeError.  Maybe, when converting raw byte strings from MIDI,58# you should keep them as bytes, not improperly decode them.  However, this59# would change the API.  (ie: text = a "string" ? of 0 or more bytes).  It60# could break compatiblity, but there's not much else you can do to fix the bug61# https://en.wikipedia.org/wiki/Shift_JIS62 63This module offers functions:  concatenate_scores(), grep(),64merge_scores(), mix_scores(), midi2opus(), midi2score(), opus2midi(),65opus2score(), play_score(), score2midi(), score2opus(), score2stats(),66score_type(), segment(), timeshift() and to_millisecs(),67where "midi" means the MIDI-file bytes (as can be put in a .mid file,68or piped into aplaymidi), and "opus" and "score" are list-structures69as inspired by Sean Burke's MIDI-Perl CPAN module.70 71Warning: Version 6.4 is not necessarily backward-compatible with72previous versions, in that text-data is now bytes, not strings.73This reflects the fact that many MIDI files have text data in74encodings other that ISO-8859-1, for example in Shift-JIS.75 76Download MIDI.py from   http://www.pjb.com.au/midi/free/MIDI.py77and put it in your PYTHONPATH.  MIDI.py depends on Python3.78 79There is also a call-compatible translation into Lua of this80module: see http://www.pjb.com.au/comp/lua/MIDI.html81 82Backup web site: https://peterbillam.gitlab.io/miditools/83 84The "opus" is a direct translation of the midi-file-events, where85the times are delta-times, in ticks, since the previous event.86 87The "score" is more human-centric; it uses absolute times, and88combines the separate note_on and note_off events into one "note"89event, with a duration:90 ['note', start_time, duration, channel, note, velocity] # in a "score"91 92  EVENTS (in an "opus" structure)93     ['note_off', dtime, channel, note, velocity]       # in an "opus"94     ['note_on', dtime, channel, note, velocity]        # in an "opus"95     ['key_after_touch', dtime, channel, note, velocity]96     ['control_change', dtime, channel, controller(0-127), value(0-127)]97     ['patch_change', dtime, channel, patch]98     ['channel_after_touch', dtime, channel, velocity]99     ['pitch_wheel_change', dtime, channel, pitch_wheel]100     ['text_event', dtime, text]101     ['copyright_text_event', dtime, text]102     ['track_name', dtime, text]103     ['instrument_name', dtime, text]104     ['lyric', dtime, text]105     ['marker', dtime, text]106     ['cue_point', dtime, text]107     ['text_event_08', dtime, text]108     ['text_event_09', dtime, text]109     ['text_event_0a', dtime, text]110     ['text_event_0b', dtime, text]111     ['text_event_0c', dtime, text]112     ['text_event_0d', dtime, text]113     ['text_event_0e', dtime, text]114     ['text_event_0f', dtime, text]115     ['end_track', dtime]116     ['set_tempo', dtime, tempo]117     ['smpte_offset', dtime, hr, mn, se, fr, ff]118     ['time_signature', dtime, nn, dd, cc, bb]119     ['key_signature', dtime, sf, mi]120     ['sequencer_specific', dtime, raw]121     ['raw_meta_event', dtime, command(0-255), raw]122     ['sysex_f0', dtime, raw]123     ['sysex_f7', dtime, raw]124     ['song_position', dtime, song_pos]125     ['song_select', dtime, song_number]126     ['tune_request', dtime]127 128  DATA TYPES129     channel = a value 0 to 15130     controller = 0 to 127 (see http://www.pjb.com.au/muscript/gm.html#cc )131     dtime = time measured in "ticks", 0 to 268435455132     velocity = a value 0 (soft) to 127 (loud)133     note = a value 0 to 127  (middle-C is 60)134     patch = 0 to 127 (see http://www.pjb.com.au/muscript/gm.html )135     pitch_wheel = a value -8192 to 8191 (0x1FFF)136     raw = bytes, of length 0 or more  (for sysex events see below)137     sequence_number = a value 0 to 65,535 (0xFFFF)138     song_pos = a value 0 to 16,383 (0x3FFF)139     song_number = a value 0 to 127140     tempo = microseconds per crochet (quarter-note), 0 to 16777215141     text = bytes, of length 0 or more142     ticks = the number of ticks per crochet (quarter-note)143 144   In sysex_f0 events, the raw data must not start with a \xF0 byte,145   since this gets added automatically;146   but it must end with an explicit \xF7 byte!147   In the very unlikely case that you ever need to split sysex data148   into one sysex_f0 followed by one or more sysex_f7s, then only the149   last of those sysex_f7 events must end with the explicit \xF7 byte150   (again, the raw data of individual sysex_f7 events must not start151   with any \xF7 byte, since this gets added automatically).152 153   Since version 6.4, text data is in bytes, not in a ISO-8859-1 string.154 155 156  GOING THROUGH A SCORE WITHIN A PYTHON PROGRAM157    channels = {2,3,5,8,13}158    itrack = 1   # skip 1st element which is ticks159    while itrack < len(score):160        for event in score[itrack]:161            if event[0] == 'note':   # for example,162                pass  # do something to all notes163            # or, to work on events in only particular channels...164            channel_index = MIDI.Event2channelindex.get(event[0], False)165            if channel_index and (event[channel_index] in channels):166                pass  # do something to channels 2,3,5,8 and 13167        itrack += 1168 169'''170 171###################################################################################172 173__version__ = "25.7.8"174 175print('=' * 70)176print('TMIDIX Python module')177print('Version:', __version__)178print('=' * 70)179print('Loading module...')180 181###################################################################################182 183import sys, struct, copy184 185Version = '6.7'186VersionDate = '20201120'187# 20201120 6.7 call to bytest() removed, and protect _unshift_ber_int188# 20160702 6.6 to_millisecs() now handles set_tempo across multiple Tracks189# 20150921 6.5 segment restores controllers as well as patch and tempo190# 20150914 6.4 text data is bytes or bytearray, not ISO-8859-1 strings191# 20150628 6.3 absent any set_tempo, default is 120bpm (see MIDI file spec 1.1)192# 20150101 6.2 all text events can be 8-bit; let user get the right encoding193# 20141231 6.1 fix _some_text_event; sequencer_specific data can be 8-bit194# 20141230 6.0 synth_specific data can be 8-bit195# 20120504 5.9 add the contents of mid_opus_tracks()196# 20120208 5.8 fix num_notes_by_channel() ; should be a dict197# 20120129 5.7 _encode handles empty tracks; score2stats num_notes_by_channel198# 20111111 5.6 fix patch 45 and 46 in Number2patch, should be Harp199# 20110129 5.5 add mix_opus_tracks() and event2alsaseq()200# 20110126 5.4 "previous message repeated N times" to save space on stderr201# 20110125 5.2 opus2score terminates unended notes at the end of the track202# 20110124 5.1 the warnings in midi2opus display track_num203# 21110122 5.0 if garbage, midi2opus returns the opus so far204# 21110119 4.9 non-ascii chars stripped out of the text_events205# 21110110 4.8 note_on with velocity=0 treated as a note-off206# 21110108 4.6 unknown F-series event correctly eats just one byte207# 21011010 4.2 segment() uses start_time, end_time named params208# 21011005 4.1 timeshift() must not pad the set_tempo command209# 21011003 4.0 pitch2note_event must be chapitch2note_event210# 21010918 3.9 set_sequence_number supported, FWIW211# 20100913 3.7 many small bugfixes; passes all tests212# 20100910 3.6 concatenate_scores enforce ticks=1000, just like merge_scores213# 20100908 3.5 minor bugs fixed in score2stats214# 20091104 3.4 tune_request now supported215# 20091104 3.3 fixed bug in decoding song_position and song_select216# 20091104 3.2 unsupported: set_sequence_number tune_request raw_data217# 20091101 3.1 document how to traverse a score within Python218# 20091021 3.0 fixed bug in score2stats detecting GM-mode = 0219# 20091020 2.9 score2stats reports GM-mode and bank msb,lsb events220# 20091019 2.8 in merge_scores, channel 9 must remain channel 9 (in GM)221# 20091018 2.7 handles empty tracks gracefully222# 20091015 2.6 grep() selects channels223# 20091010 2.5 merge_scores reassigns channels to avoid conflicts224# 20091010 2.4 fixed bug in to_millisecs which now only does opusses225# 20091010 2.3 score2stats returns channels & patch_changes, by_track & total226# 20091010 2.2 score2stats() returns also pitches and percussion dicts227# 20091010 2.1 bugs: >= not > in segment, to notice patch_change at time 0228# 20091010 2.0 bugs: spurious pop(0) ( in _decode sysex229# 20091008 1.9 bugs: ISO decoding in sysex; str( not int( in note-off warning230# 20091008 1.8 add concatenate_scores()231# 20091006 1.7 score2stats() measures nticks and ticks_per_quarter232# 20091004 1.6 first mix_scores() and merge_scores()233# 20090424 1.5 timeshift() bugfix: earliest only sees events after from_time234# 20090330 1.4 timeshift() has also a from_time argument235# 20090322 1.3 timeshift() has also a start_time argument236# 20090319 1.2 add segment() and timeshift()237# 20090301 1.1 add to_millisecs()238 239_previous_warning = ''  # 5.4240_previous_times = 0     # 5.4241_no_warning = False242 243#------------------------------- Encoding stuff --------------------------244 245def opus2midi(opus=[], text_encoding='ISO-8859-1'):246    r'''The argument is a list: the first item in the list is the "ticks"247parameter, the others are the tracks. Each track is a list248of midi-events, and each event is itself a list; see above.249opus2midi() returns a bytestring of the MIDI, which can then be250written either to a file opened in binary mode (mode='wb'),251or to stdout by means of:   sys.stdout.buffer.write()252 253my_opus = [254    96, 255    [   # track 0:256        ['patch_change', 0, 1, 8],   # and these are the events...257        ['note_on',   5, 1, 25, 96],258        ['note_off', 96, 1, 25, 0],259        ['note_on',   0, 1, 29, 96],260        ['note_off', 96, 1, 29, 0],261    ],   # end of track 0262]263my_midi = opus2midi(my_opus)264sys.stdout.buffer.write(my_midi)265'''266    if len(opus) < 2:267        opus=[1000, [],]268    tracks = copy.deepcopy(opus)269    ticks = int(tracks.pop(0))270    ntracks = len(tracks)271    if ntracks == 1:272        format = 0273    else:274        format = 1275 276    my_midi = b"MThd\x00\x00\x00\x06"+struct.pack('>HHH',format,ntracks,ticks)277    for track in tracks:278        events = _encode(track, text_encoding=text_encoding)279        my_midi += b'MTrk' + struct.pack('>I',len(events)) + events280    _clean_up_warnings()281    return my_midi282 283 284def score2opus(score=None, text_encoding='ISO-8859-1'):285    r'''286The argument is a list: the first item in the list is the "ticks"287parameter, the others are the tracks. Each track is a list288of score-events, and each event is itself a list.  A score-event289is similar to an opus-event (see above), except that in a score:290 1) the times are expressed as an absolute number of ticks291    from the track's start time292 2) the pairs of 'note_on' and 'note_off' events in an "opus"293    are abstracted into a single 'note' event in a "score":294    ['note', start_time, duration, channel, pitch, velocity]295score2opus() returns a list specifying the equivalent "opus".296 297my_score = [298    96,299    [   # track 0:300        ['patch_change', 0, 1, 8],301        ['note', 5, 96, 1, 25, 96],302        ['note', 101, 96, 1, 29, 96]303    ],   # end of track 0304]305my_opus = score2opus(my_score)306'''307    if len(score) < 2:308        score=[1000, [],]309    tracks = copy.deepcopy(score)310    ticks = int(tracks.pop(0))311    opus_tracks = []312    for scoretrack in tracks:313        time2events = dict([])314        for scoreevent in scoretrack:315            if scoreevent[0] == 'note':316                note_on_event = ['note_on',scoreevent[1],317                 scoreevent[3],scoreevent[4],scoreevent[5]]318                note_off_event = ['note_off',scoreevent[1]+scoreevent[2],319                 scoreevent[3],scoreevent[4],scoreevent[5]]320                if time2events.get(note_on_event[1]):321                   time2events[note_on_event[1]].append(note_on_event)322                else:323                   time2events[note_on_event[1]] = [note_on_event,]324                if time2events.get(note_off_event[1]):325                   time2events[note_off_event[1]].append(note_off_event)326                else:327                   time2events[note_off_event[1]] = [note_off_event,]328                continue329            if time2events.get(scoreevent[1]):330               time2events[scoreevent[1]].append(scoreevent)331            else:332               time2events[scoreevent[1]] = [scoreevent,]333 334        sorted_times = []  # list of keys335        for k in time2events.keys():336            sorted_times.append(k)337        sorted_times.sort()338 339        sorted_events = []  # once-flattened list of values sorted by key340        for time in sorted_times:341            sorted_events.extend(time2events[time])342 343        abs_time = 0344        for event in sorted_events:  # convert abs times => delta times345            delta_time = event[1] - abs_time346            abs_time = event[1]347            event[1] = delta_time348        opus_tracks.append(sorted_events)349    opus_tracks.insert(0,ticks)350    _clean_up_warnings()351    return opus_tracks352 353def score2midi(score=None, text_encoding='ISO-8859-1'):354    r'''355Translates a "score" into MIDI, using score2opus() then opus2midi()356'''357    return opus2midi(score2opus(score, text_encoding), text_encoding)358 359#--------------------------- Decoding stuff ------------------------360 361def midi2opus(midi=b'', do_not_check_MIDI_signature=False):362    r'''Translates MIDI into a "opus".  For a description of the363"opus" format, see opus2midi()364'''365    my_midi=bytearray(midi)366    if len(my_midi) < 4:367        _clean_up_warnings()368        return [1000,[],]369    id = bytes(my_midi[0:4])370    if id != b'MThd':371        _warn("midi2opus: midi starts with "+str(id)+" instead of 'MThd'")372        _clean_up_warnings()373        if do_not_check_MIDI_signature == False:374          return [1000,[],]375    [length, format, tracks_expected, ticks] = struct.unpack(376     '>IHHH', bytes(my_midi[4:14]))377    if length != 6:378        _warn("midi2opus: midi header length was "+str(length)+" instead of 6")379        _clean_up_warnings()380        return [1000,[],]381    my_opus = [ticks,]382    my_midi = my_midi[14:]383    track_num = 1   # 5.1384    while len(my_midi) >= 8:385        track_type   = bytes(my_midi[0:4])386        if track_type != b'MTrk':387            #_warn('midi2opus: Warning: track #'+str(track_num)+' type is '+str(track_type)+" instead of b'MTrk'")388            pass389        [track_length] = struct.unpack('>I', my_midi[4:8])390        my_midi = my_midi[8:]391        if track_length > len(my_midi):392            _warn('midi2opus: track #'+str(track_num)+' length '+str(track_length)+' is too large')393            _clean_up_warnings()394            return my_opus   # 5.0395        my_midi_track = my_midi[0:track_length]396        my_track = _decode(my_midi_track)397        my_opus.append(my_track)398        my_midi = my_midi[track_length:]399        track_num += 1   # 5.1400    _clean_up_warnings()401    return my_opus402 403def opus2score(opus=[]):404    r'''For a description of the "opus" and "score" formats,405see opus2midi() and score2opus().406'''407    if len(opus) < 2:408        _clean_up_warnings()409        return [1000,[],]410    tracks = copy.deepcopy(opus)  # couple of slices probably quicker...411    ticks = int(tracks.pop(0))412    score = [ticks,]413    for opus_track in tracks:414        ticks_so_far = 0415        score_track = []416        chapitch2note_on_events = dict([])   # 4.0417        for opus_event in opus_track:418            ticks_so_far += opus_event[1]419            if opus_event[0] == 'note_off' or (opus_event[0] == 'note_on' and opus_event[4] == 0):  # 4.8420                cha = opus_event[2]421                pitch = opus_event[3]422                key = cha*128 + pitch423                if chapitch2note_on_events.get(key):424                    new_event = chapitch2note_on_events[key].pop(0)425                    new_event[2] = ticks_so_far - new_event[1]426                    score_track.append(new_event)427                elif pitch > 127:428                    pass #_warn('opus2score: note_off with no note_on, bad pitch='+str(pitch))429                else:430                    pass #_warn('opus2score: note_off with no note_on cha='+str(cha)+' pitch='+str(pitch))431            elif opus_event[0] == 'note_on':432                cha = opus_event[2]433                pitch = opus_event[3]434                key = cha*128 + pitch435                new_event = ['note',ticks_so_far,0,cha,pitch, opus_event[4]]436                if chapitch2note_on_events.get(key):437                    chapitch2note_on_events[key].append(new_event)438                else:439                    chapitch2note_on_events[key] = [new_event,]440            else:441                opus_event[1] = ticks_so_far442                score_track.append(opus_event)443        # check for unterminated notes (Oisín) -- 5.2444        for chapitch in chapitch2note_on_events:445            note_on_events = chapitch2note_on_events[chapitch]446            for new_e in note_on_events:447                new_e[2] = ticks_so_far - new_e[1]448                score_track.append(new_e)449                pass #_warn("opus2score: note_on with no note_off cha="+str(new_e[3])+' pitch='+str(new_e[4])+'; adding note_off at end')450        score.append(score_track)451    _clean_up_warnings()452    return score453 454def midi2score(midi=b'', do_not_check_MIDI_signature=False):455    r'''456Translates MIDI into a "score", using midi2opus() then opus2score()457'''458    return opus2score(midi2opus(midi, do_not_check_MIDI_signature))459 460def midi2ms_score(midi=b'', do_not_check_MIDI_signature=False):461    r'''462Translates MIDI into a "score" with one beat per second and one463tick per millisecond, using midi2opus() then to_millisecs()464then opus2score()465'''466    return opus2score(to_millisecs(midi2opus(midi, do_not_check_MIDI_signature)))467 468def midi2single_track_ms_score(midi_path_or_bytes, 469                                recalculate_channels = False, 470                                pass_old_timings_events= False, 471                                verbose = False, 472                                do_not_check_MIDI_signature=False473                                ):474    r'''475Translates MIDI into a single track "score" with 16 instruments and one beat per second and one476tick per millisecond477'''478 479    if type(midi_path_or_bytes) == bytes:480      midi_data = midi_path_or_bytes481 482    elif type(midi_path_or_bytes) == str:483      midi_data = open(midi_path_or_bytes, 'rb').read() 484 485    score = midi2score(midi_data, do_not_check_MIDI_signature)486 487    if recalculate_channels:488 489      events_matrixes = []490 491      itrack = 1492      events_matrixes_channels = []493      while itrack < len(score):494          events_matrix = []495          for event in score[itrack]:496              if event[0] == 'note' and event[3] != 9:497                event[3] = (16 * (itrack-1)) + event[3]498                if event[3] not in events_matrixes_channels:499                  events_matrixes_channels.append(event[3])500 501              events_matrix.append(event)502          events_matrixes.append(events_matrix)503          itrack += 1504 505      events_matrix1 = []506      for e in events_matrixes:507        events_matrix1.extend(e)508 509      if verbose:510        if len(events_matrixes_channels) > 16:511          print('MIDI has', len(events_matrixes_channels), 'instruments!', len(events_matrixes_channels) - 16, 'instrument(s) will be removed!')512 513      for e in events_matrix1:514        if e[0] == 'note' and e[3] != 9:515          if e[3] in events_matrixes_channels[:15]:516            if events_matrixes_channels[:15].index(e[3]) < 9:517              e[3] = events_matrixes_channels[:15].index(e[3])518            else:519              e[3] = events_matrixes_channels[:15].index(e[3])+1520          else:521            events_matrix1.remove(e)522        523        if e[0] in ['patch_change', 'control_change', 'channel_after_touch', 'key_after_touch', 'pitch_wheel_change'] and e[2] != 9:524          if e[2] in [e % 16 for e in events_matrixes_channels[:15]]:525            if [e % 16 for e in events_matrixes_channels[:15]].index(e[2]) < 9:526              e[2] = [e % 16 for e in events_matrixes_channels[:15]].index(e[2])527            else:528              e[2] = [e % 16 for e in events_matrixes_channels[:15]].index(e[2])+1529          else:530            events_matrix1.remove(e)531    532    else:533      events_matrix1 = []534      itrack = 1535     536      while itrack < len(score):537          for event in score[itrack]:538            events_matrix1.append(event)539          itrack += 1    540 541    opus = score2opus([score[0], events_matrix1])542    ms_score = opus2score(to_millisecs(opus, pass_old_timings_events=pass_old_timings_events))543 544    return ms_score545 546#------------------------ Other Transformations ---------------------547 548def to_millisecs(old_opus=None, desired_time_in_ms=1, pass_old_timings_events = False):549    r'''Recallibrates all the times in an "opus" to use one beat550per second and one tick per millisecond.  This makes it551hard to retrieve any information about beats or barlines,552but it does make it easy to mix different scores together.553'''554    if old_opus == None:555        return [1000 * desired_time_in_ms,[],]556    try:557        old_tpq  = int(old_opus[0])558    except IndexError:   # 5.0559        _warn('to_millisecs: the opus '+str(type(old_opus))+' has no elements')560        return [1000 * desired_time_in_ms,[],]561    new_opus = [1000 * desired_time_in_ms,]562    # 6.7 first go through building a table of set_tempos by absolute-tick563    ticks2tempo = {}564    itrack = 1565    while itrack < len(old_opus):566        ticks_so_far = 0567        for old_event in old_opus[itrack]:568            if old_event[0] == 'note':569                raise TypeError('to_millisecs needs an opus, not a score')570            ticks_so_far += old_event[1]571            if old_event[0] == 'set_tempo':572                ticks2tempo[ticks_so_far] = old_event[2]573        itrack += 1574    # then get the sorted-array of their keys575    tempo_ticks = []  # list of keys576    for k in ticks2tempo.keys():577        tempo_ticks.append(k)578    tempo_ticks.sort()579    # then go through converting to millisec, testing if the next580    # set_tempo lies before the next track-event, and using it if so.581    itrack = 1582    while itrack < len(old_opus):583        ms_per_old_tick = 400 / old_tpq  # float: will round later 6.3584        i_tempo_ticks = 0585        ticks_so_far = 0586        ms_so_far = 0.0587        previous_ms_so_far = 0.0588 589        if pass_old_timings_events:590          new_track = [['set_tempo',0,1000000 * desired_time_in_ms],['old_tpq', 0, old_tpq]]  # new "crochet" is 1 sec591        else:592          new_track = [['set_tempo',0,1000000 * desired_time_in_ms],]  # new "crochet" is 1 sec593        for old_event in old_opus[itrack]:594            # detect if ticks2tempo has something before this event595            # 20160702 if ticks2tempo is at the same time, leave it596            event_delta_ticks = old_event[1] * desired_time_in_ms597            if (i_tempo_ticks < len(tempo_ticks) and598              tempo_ticks[i_tempo_ticks] < (ticks_so_far + old_event[1]) * desired_time_in_ms):599                delta_ticks = tempo_ticks[i_tempo_ticks] - ticks_so_far600                ms_so_far += (ms_per_old_tick * delta_ticks * desired_time_in_ms)601                ticks_so_far = tempo_ticks[i_tempo_ticks]602                ms_per_old_tick = ticks2tempo[ticks_so_far] / (1000.0*old_tpq * desired_time_in_ms)603                i_tempo_ticks += 1604                event_delta_ticks -= delta_ticks605            new_event = copy.deepcopy(old_event)  # now handle the new event606            ms_so_far += (ms_per_old_tick * old_event[1] * desired_time_in_ms)607            new_event[1] = round(ms_so_far - previous_ms_so_far)608 609            if pass_old_timings_events:610              if old_event[0] != 'set_tempo':611                  previous_ms_so_far = ms_so_far612                  new_track.append(new_event)613              else:614                  new_event[0] = 'old_set_tempo'615                  previous_ms_so_far = ms_so_far616                  new_track.append(new_event)617            else:618              if old_event[0] != 'set_tempo':619                  previous_ms_so_far = ms_so_far620                  new_track.append(new_event)621            ticks_so_far += event_delta_ticks622        new_opus.append(new_track)623        itrack += 1624    _clean_up_warnings()625    return new_opus626 627def event2alsaseq(event=None):   # 5.5628    r'''Converts an event into the format needed by the alsaseq module,629http://pp.com.mx/python/alsaseq630The type of track (opus or score) is autodetected.631'''632    pass633 634def grep(score=None, channels=None):635    r'''Returns a "score" containing only the channels specified636'''637    if score == None:638        return [1000,[],]639    ticks = score[0]640    new_score = [ticks,]641    if channels == None:642        return new_score643    channels = set(channels)644    global Event2channelindex645    itrack = 1646    while itrack < len(score):647        new_score.append([])648        for event in score[itrack]:649            channel_index = Event2channelindex.get(event[0], False)650            if channel_index:651                if event[channel_index] in channels:652                    new_score[itrack].append(event)653            else:654                new_score[itrack].append(event)655        itrack += 1656    return new_score657 658def score2stats(opus_or_score=None):659    r'''Returns a dict of some basic stats about the score, like660bank_select (list of tuples (msb,lsb)),661channels_by_track (list of lists), channels_total (set),662general_midi_mode (list),663ntracks, nticks, patch_changes_by_track (list of dicts),664num_notes_by_channel (list of numbers),665patch_changes_total (set),666percussion (dict histogram of channel 9 events),667pitches (dict histogram of pitches on channels other than 9),668pitch_range_by_track (list, by track, of two-member-tuples),669pitch_range_sum (sum over tracks of the pitch_ranges),670'''671    bank_select_msb = -1672    bank_select_lsb = -1673    bank_select = []674    channels_by_track = []675    channels_total    = set([])676    general_midi_mode = []677    num_notes_by_channel = dict([])678    patches_used_by_track  = []679    patches_used_total     = set([])680    patch_changes_by_track = []681    patch_changes_total    = set([])682    percussion = dict([]) # histogram of channel 9 "pitches"683    pitches    = dict([]) # histogram of pitch-occurrences channels 0-8,10-15684    pitch_range_sum = 0   # u pitch-ranges of each track685    pitch_range_by_track = []686    is_a_score = True687    if opus_or_score == None:688        return {'bank_select':[], 'channels_by_track':[], 'channels_total':[],689         'general_midi_mode':[], 'ntracks':0, 'nticks':0,690         'num_notes_by_channel':dict([]),691         'patch_changes_by_track':[], 'patch_changes_total':[],692         'percussion':{}, 'pitches':{}, 'pitch_range_by_track':[],693         'ticks_per_quarter':0, 'pitch_range_sum':0}694    ticks_per_quarter = opus_or_score[0]695    i = 1   # ignore first element, which is ticks696    nticks = 0697    while i < len(opus_or_score):698        highest_pitch = 0699        lowest_pitch = 128700        channels_this_track = set([])701        patch_changes_this_track = dict({})702        for event in opus_or_score[i]:703            if event[0] == 'note':704                num_notes_by_channel[event[3]] = num_notes_by_channel.get(event[3],0) + 1705                if event[3] == 9:706                    percussion[event[4]] = percussion.get(event[4],0) + 1707                else:708                    pitches[event[4]]    = pitches.get(event[4],0) + 1709                    if event[4] > highest_pitch:710                        highest_pitch = event[4]711                    if event[4] < lowest_pitch:712                        lowest_pitch = event[4]713                channels_this_track.add(event[3])714                channels_total.add(event[3])715                finish_time = event[1] + event[2]716                if finish_time > nticks:717                    nticks = finish_time718            elif event[0] == 'note_off' or (event[0] == 'note_on' and event[4] == 0):  # 4.8719                finish_time = event[1]720                if finish_time > nticks:721                    nticks = finish_time722            elif event[0] == 'note_on':723                is_a_score = False724                num_notes_by_channel[event[2]] = num_notes_by_channel.get(event[2],0) + 1725                if event[2] == 9:726                    percussion[event[3]] = percussion.get(event[3],0) + 1727                else:728                    pitches[event[3]]    = pitches.get(event[3],0) + 1729                    if event[3] > highest_pitch:730                        highest_pitch = event[3]731                    if event[3] < lowest_pitch:732                        lowest_pitch = event[3]733                channels_this_track.add(event[2])734                channels_total.add(event[2])735            elif event[0] == 'patch_change':736                patch_changes_this_track[event[2]] = event[3]737                patch_changes_total.add(event[3])738            elif event[0] == 'control_change':739                if event[3] == 0:  # bank select MSB740                    bank_select_msb = event[4]741                elif event[3] == 32:  # bank select LSB742                    bank_select_lsb = event[4]743                if bank_select_msb >= 0 and bank_select_lsb >= 0:744                    bank_select.append((bank_select_msb,bank_select_lsb))745                    bank_select_msb = -1746                    bank_select_lsb = -1747            elif event[0] == 'sysex_f0':748                if _sysex2midimode.get(event[2], -1) >= 0:749                    general_midi_mode.append(_sysex2midimode.get(event[2]))750            if is_a_score:751                if event[1] > nticks:752                    nticks = event[1]753            else:754                nticks += event[1]755        if lowest_pitch == 128:756            lowest_pitch = 0757        channels_by_track.append(channels_this_track)758        patch_changes_by_track.append(patch_changes_this_track)759        pitch_range_by_track.append((lowest_pitch,highest_pitch))760        pitch_range_sum += (highest_pitch-lowest_pitch)761        i += 1762 763    return {'bank_select':bank_select,764            'channels_by_track':channels_by_track,765            'channels_total':channels_total,766            'general_midi_mode':general_midi_mode,767            'ntracks':len(opus_or_score)-1,768            'nticks':nticks,769            'num_notes_by_channel':num_notes_by_channel,770            'patch_changes_by_track':patch_changes_by_track,771            'patch_changes_total':patch_changes_total,772            'percussion':percussion,773            'pitches':pitches,774            'pitch_range_by_track':pitch_range_by_track,775            'pitch_range_sum':pitch_range_sum,776            'ticks_per_quarter':ticks_per_quarter}777 778#----------------------------- Event stuff --------------------------779 780_sysex2midimode = {781    "\x7E\x7F\x09\x01\xF7": 1,782    "\x7E\x7F\x09\x02\xF7": 0,783    "\x7E\x7F\x09\x03\xF7": 2,784}785 786# Some public-access tuples:787MIDI_events = tuple('''note_off note_on key_after_touch788control_change patch_change channel_after_touch789pitch_wheel_change'''.split())790 791Text_events = tuple('''text_event copyright_text_event792track_name instrument_name lyric marker cue_point text_event_08793text_event_09 text_event_0a text_event_0b text_event_0c794text_event_0d text_event_0e text_event_0f'''.split())795 796Nontext_meta_events = tuple('''end_track set_tempo797smpte_offset time_signature key_signature sequencer_specific798raw_meta_event sysex_f0 sysex_f7 song_position song_select799tune_request'''.split())800# unsupported: raw_data801 802# Actually, 'tune_request' is is F-series event, not strictly a meta-event...803Meta_events = Text_events + Nontext_meta_events804All_events  = MIDI_events + Meta_events805 806# And three dictionaries:807Number2patch = {   # General MIDI patch numbers:8080:'Acoustic Grand',8091:'Bright Acoustic',8102:'Electric Grand',8113:'Honky-Tonk',8124:'Electric Piano 1',8135:'Electric Piano 2',8146:'Harpsichord',8157:'Clav',8168:'Celesta',8179:'Glockenspiel',81810:'Music Box',81911:'Vibraphone',82012:'Marimba',82113:'Xylophone',82214:'Tubular Bells',82315:'Dulcimer',82416:'Drawbar Organ',82517:'Percussive Organ',82618:'Rock Organ',82719:'Church Organ',82820:'Reed Organ',82921:'Accordion',83022:'Harmonica',83123:'Tango Accordion',83224:'Acoustic Guitar(nylon)',83325:'Acoustic Guitar(steel)',83426:'Electric Guitar(jazz)',83527:'Electric Guitar(clean)',83628:'Electric Guitar(muted)',83729:'Overdriven Guitar',83830:'Distortion Guitar',83931:'Guitar Harmonics',84032:'Acoustic Bass',84133:'Electric Bass(finger)',84234:'Electric Bass(pick)',84335:'Fretless Bass',84436:'Slap Bass 1',84537:'Slap Bass 2',84638:'Synth Bass 1',84739:'Synth Bass 2',84840:'Violin',84941:'Viola',85042:'Cello',85143:'Contrabass',85244:'Tremolo Strings',85345:'Pizzicato Strings',85446:'Orchestral Harp',85547:'Timpani',85648:'String Ensemble 1',85749:'String Ensemble 2',85850:'SynthStrings 1',85951:'SynthStrings 2',86052:'Choir Aahs',86153:'Voice Oohs',86254:'Synth Voice',86355:'Orchestra Hit',86456:'Trumpet',86557:'Trombone',86658:'Tuba',86759:'Muted Trumpet',86860:'French Horn',86961:'Brass Section',87062:'SynthBrass 1',87163:'SynthBrass 2',87264:'Soprano Sax',87365:'Alto Sax',87466:'Tenor Sax',87567:'Baritone Sax',87668:'Oboe',87769:'English Horn',87870:'Bassoon',87971:'Clarinet',88072:'Piccolo',88173:'Flute',88274:'Recorder',88375:'Pan Flute',88476:'Blown Bottle',88577:'Skakuhachi',88678:'Whistle',88779:'Ocarina',88880:'Lead 1 (square)',88981:'Lead 2 (sawtooth)',89082:'Lead 3 (calliope)',89183:'Lead 4 (chiff)',89284:'Lead 5 (charang)',89385:'Lead 6 (voice)',89486:'Lead 7 (fifths)',89587:'Lead 8 (bass+lead)',89688:'Pad 1 (new age)',89789:'Pad 2 (warm)',89890:'Pad 3 (polysynth)',89991:'Pad 4 (choir)',90092:'Pad 5 (bowed)',90193:'Pad 6 (metallic)',90294:'Pad 7 (halo)',90395:'Pad 8 (sweep)',90496:'FX 1 (rain)',90597:'FX 2 (soundtrack)',90698:'FX 3 (crystal)',90799:'FX 4 (atmosphere)',908100:'FX 5 (brightness)',909101:'FX 6 (goblins)',910102:'FX 7 (echoes)',911103:'FX 8 (sci-fi)',912104:'Sitar',913105:'Banjo',914106:'Shamisen',915107:'Koto',916108:'Kalimba',917109:'Bagpipe',918110:'Fiddle',919111:'Shanai',920112:'Tinkle Bell',921113:'Agogo',922114:'Steel Drums',923115:'Woodblock',924116:'Taiko Drum',925117:'Melodic Tom',926118:'Synth Drum',927119:'Reverse Cymbal',928120:'Guitar Fret Noise',929121:'Breath Noise',930122:'Seashore',931123:'Bird Tweet',932124:'Telephone Ring',933125:'Helicopter',934126:'Applause',935127:'Gunshot',936}937Notenum2percussion = {   # General MIDI Percussion (on Channel 9):93835:'Acoustic Bass Drum',93936:'Bass Drum 1',94037:'Side Stick',94138:'Acoustic Snare',94239:'Hand Clap',94340:'Electric Snare',94441:'Low Floor Tom',94542:'Closed Hi-Hat',94643:'High Floor Tom',94744:'Pedal Hi-Hat',94845:'Low Tom',94946:'Open Hi-Hat',95047:'Low-Mid Tom',95148:'Hi-Mid Tom',95249:'Crash Cymbal 1',95350:'High Tom',95451:'Ride Cymbal 1',95552:'Chinese Cymbal',95653:'Ride Bell',95754:'Tambourine',95855:'Splash Cymbal',95956:'Cowbell',96057:'Crash Cymbal 2',96158:'Vibraslap',96259:'Ride Cymbal 2',96360:'Hi Bongo',96461:'Low Bongo',96562:'Mute Hi Conga',96663:'Open Hi Conga',96764:'Low Conga',96865:'High Timbale',96966:'Low Timbale',97067:'High Agogo',97168:'Low Agogo',97269:'Cabasa',97370:'Maracas',97471:'Short Whistle',97572:'Long Whistle',97673:'Short Guiro',97774:'Long Guiro',97875:'Claves',97976:'Hi Wood Block',98077:'Low Wood Block',98178:'Mute Cuica',98279:'Open Cuica',98380:'Mute Triangle',98481:'Open Triangle',985}986 987Event2channelindex = { 'note':3, 'note_off':2, 'note_on':2,988 'key_after_touch':2, 'control_change':2, 'patch_change':2,989 'channel_after_touch':2, 'pitch_wheel_change':2990}991 992################################################################993# The code below this line is full of frightening things, all to994# do with the actual encoding and decoding of binary MIDI data.995 996def _twobytes2int(byte_a):997    r'''decode a 16 bit quantity from two bytes,'''998    return (byte_a[1] | (byte_a[0] << 8))999 1000def _int2twobytes(int_16bit):1001    r'''encode a 16 bit quantity into two bytes,'''1002    return bytes([(int_16bit>>8) & 0xFF, int_16bit & 0xFF])1003 1004def _read_14_bit(byte_a):1005    r'''decode a 14 bit quantity from two bytes,'''1006    return (byte_a[0] | (byte_a[1] << 7))1007 1008def _write_14_bit(int_14bit):1009    r'''encode a 14 bit quantity into two bytes,'''1010    return bytes([int_14bit & 0x7F, (int_14bit>>7) & 0x7F])1011 1012def _ber_compressed_int(integer):1013    r'''BER compressed integer (not an ASN.1 BER, see perlpacktut for1014details).  Its bytes represent an unsigned integer in base 128,1015most significant digit first, with as few digits as possible.1016Bit eight (the high bit) is set on each byte except the last.1017'''1018    ber = bytearray(b'')1019    seven_bits = 0x7F & integer1020    ber.insert(0, seven_bits)  # XXX surely should convert to a char ?1021    integer >>= 71022    while integer > 0:1023        seven_bits = 0x7F & integer1024        ber.insert(0, 0x80|seven_bits)  # XXX surely should convert to a char ?1025        integer >>= 71026    return ber1027 1028def _unshift_ber_int(ba):1029    r'''Given a bytearray, returns a tuple of (the ber-integer at the1030start, and the remainder of the bytearray).1031'''1032    if not len(ba):  # 6.71033        _warn('_unshift_ber_int: no integer found')1034        return ((0, b""))1035    byte = ba[0]1036    ba = ba[1:]1037    integer = 01038    while True:1039        integer += (byte & 0x7F)1040        if not (byte & 0x80):1041            return ((integer, ba))1042        if not len(ba):1043            _warn('_unshift_ber_int: no end-of-integer found')1044            return ((0, ba))1045        byte = ba[0]1046        ba = ba[1:]1047        integer <<= 71048 1049 1050def _clean_up_warnings():  # 5.41051    # Call this before returning from any publicly callable function1052    # whenever there's a possibility that a warning might have been printed1053    # by the function, or by any private functions it might have called.1054    if _no_warning:1055        return1056    global _previous_times1057    global _previous_warning1058    if _previous_times > 1:1059        # E:1176, 0: invalid syntax (<string>, line 1176) (syntax-error) ???1060        # print('  previous message repeated '+str(_previous_times)+' times', file=sys.stderr)1061        # 6.71062        sys.stderr.write('  previous message repeated {0} times\n'.format(_previous_times))1063    elif _previous_times > 0:1064        sys.stderr.write('  previous message repeated\n')1065    _previous_times = 01066    _previous_warning = ''1067 1068 1069def _warn(s=''):1070    if _no_warning:1071        return1072    global _previous_times1073    global _previous_warning1074    if s == _previous_warning:  # 5.41075        _previous_times = _previous_times + 11076    else:1077        _clean_up_warnings()1078        sys.stderr.write(str(s) + "\n")1079        _previous_warning = s1080 1081 1082def _some_text_event(which_kind=0x01, text=b'some_text', text_encoding='ISO-8859-1'):1083    if str(type(text)).find("'str'") >= 0:  # 6.4 test for back-compatibility1084        data = bytes(text, encoding=text_encoding)1085    else:1086        data = bytes(text)1087    return b'\xFF' + bytes((which_kind,)) + _ber_compressed_int(len(data)) + data1088 1089 1090def _consistentise_ticks(scores):  # 3.61091    # used by mix_scores, merge_scores, concatenate_scores1092    if len(scores) == 1:1093        return copy.deepcopy(scores)1094    are_consistent = True1095    ticks = scores[0][0]1096    iscore = 11097    while iscore < len(scores):1098        if scores[iscore][0] != ticks:1099            are_consistent = False1100            break1101        iscore += 11102    if are_consistent:1103        return copy.deepcopy(scores)1104    new_scores = []1105    iscore = 01106    while iscore < len(scores):1107        score = scores[iscore]1108        new_scores.append(opus2score(to_millisecs(score2opus(score))))1109        iscore += 11110    return new_scores1111 1112 1113###########################################################################1114def _decode(trackdata=b'', exclude=None, include=None,1115            event_callback=None, exclusive_event_callback=None, no_eot_magic=False):1116    r'''Decodes MIDI track data into an opus-style list of events.1117The options:1118  'exclude' is a list of event types which will be ignored SHOULD BE A SET1119  'include' (and no exclude), makes exclude a list1120       of all possible events, /minus/ what include specifies1121  'event_callback' is a coderef1122  'exclusive_event_callback' is a coderef1123'''1124    trackdata = bytearray(trackdata)1125    if exclude == None:1126        exclude = []1127    if include == None:1128        include = []1129    if include and not exclude:1130        exclude = All_events1131    include = set(include)1132    exclude = set(exclude)1133 1134    # Pointer = 0;  not used here; we eat through the bytearray instead.1135    event_code = -1;  # used for running status1136    event_count = 0;1137    events = []1138 1139    while (len(trackdata)):1140        # loop while there's anything to analyze ...1141        eot = False  # When True, the event registrar aborts this loop1142        event_count += 11143 1144        E = []1145        # E for events - we'll feed it to the event registrar at the end.1146 1147        # Slice off the delta time code, and analyze it1148        [time, trackdata] = _unshift_ber_int(trackdata)1149 1150        # Now let's see what we can make of the command1151        first_byte = trackdata[0] & 0xFF1152        trackdata = trackdata[1:]1153        if (first_byte < 0xF0):  # It's a MIDI event1154            if (first_byte & 0x80):1155                event_code = first_byte1156            else:1157                # It wants running status; use last event_code value1158                trackdata.insert(0, first_byte)1159                if (event_code == -1):1160                    _warn("Running status not set; Aborting track.")1161                    return []1162 1163            command = event_code & 0xF01164            channel = event_code & 0x0F1165 1166            if (command == 0xF6):  # 0-byte argument1167                pass1168            elif (command == 0xC0 or command == 0xD0):  # 1-byte argument1169                parameter = trackdata[0]  # could be B1170                trackdata = trackdata[1:]1171            else:  # 2-byte argument could be BB or 14-bit1172                parameter = (trackdata[0], trackdata[1])1173                trackdata = trackdata[2:]1174 1175            #################################################################1176            # MIDI events1177 1178            if (command == 0x80):1179                if 'note_off' in exclude:1180                    continue1181                E = ['note_off', time, channel, parameter[0], parameter[1]]1182            elif (command == 0x90):1183                if 'note_on' in exclude:1184                    continue1185                E = ['note_on', time, channel, parameter[0], parameter[1]]1186            elif (command == 0xA0):1187                if 'key_after_touch' in exclude:1188                    continue1189                E = ['key_after_touch', time, channel, parameter[0], parameter[1]]1190            elif (command == 0xB0):1191                if 'control_change' in exclude:1192                    continue1193                E = ['control_change', time, channel, parameter[0], parameter[1]]1194            elif (command == 0xC0):1195                if 'patch_change' in exclude:1196                    continue1197                E = ['patch_change', time, channel, parameter]1198            elif (command == 0xD0):1199                if 'channel_after_touch' in exclude:1200                    continue

Showing the first 1,200 of 2134 lines. Download the file for the rest.