CoolFace
Apppublic

Chickaboo/Advanced-MIDI-Renderer

sourceHugging Facecc-by-nc-4.0updated 4mo agoView on Hugging Face
1likes
MIDI.py1733 linesDownload Raw Back to root
1#! /usr/bin/python32# unsupported 20091104 ...3#     ['set_sequence_number', dtime, sequence]4#     ['raw_data', dtime, raw]5 6# 20150914   jimbo1qaz   MIDI.py str/bytes bug report7# I found a MIDI file which had Shift-JIS titles. When midi.py decodes it as8# latin-1, it produces a string which cannot even be accessed without raising9# a UnicodeDecodeError.  Maybe, when converting raw byte strings from MIDI,10# you should keep them as bytes, not improperly decode them.  However, this11# would change the API.  (ie: text = a "string" ? of 0 or more bytes).  It12# could break compatiblity, but there's not much else you can do to fix the bug13# https://en.wikipedia.org/wiki/Shift_JIS14 15r'''16This module offers functions:  concatenate_scores(), grep(),17merge_scores(), mix_scores(), midi2opus(), midi2score(), opus2midi(),18opus2score(), play_score(), score2midi(), score2opus(), score2stats(),19score_type(), segment(), timeshift() and to_millisecs(),20where "midi" means the MIDI-file bytes (as can be put in a .mid file,21or piped into aplaymidi), and "opus" and "score" are list-structures22as inspired by Sean Burke's MIDI-Perl CPAN module.23 24Warning: Version 6.4 is not necessarily backward-compatible with25previous versions, in that text-data is now bytes, not strings.26This reflects the fact that many MIDI files have text data in27encodings other that ISO-8859-1, for example in Shift-JIS.28 29Download MIDI.py from   http://www.pjb.com.au/midi/free/MIDI.py30and put it in your PYTHONPATH.  MIDI.py depends on Python3.31 32There is also a call-compatible translation into Lua of this33module: see http://www.pjb.com.au/comp/lua/MIDI.html34 35Backup web site: https://peterbillam.gitlab.io/miditools/36 37The "opus" is a direct translation of the midi-file-events, where38the times are delta-times, in ticks, since the previous event.39 40The "score" is more human-centric; it uses absolute times, and41combines the separate note_on and note_off events into one "note"42event, with a duration:43 ['note', start_time, duration, channel, note, velocity] # in a "score"44 45  EVENTS (in an "opus" structure)46     ['note_off', dtime, channel, note, velocity]       # in an "opus"47     ['note_on', dtime, channel, note, velocity]        # in an "opus"48     ['key_after_touch', dtime, channel, note, velocity]49     ['control_change', dtime, channel, controller(0-127), value(0-127)]50     ['patch_change', dtime, channel, patch]51     ['channel_after_touch', dtime, channel, velocity]52     ['pitch_wheel_change', dtime, channel, pitch_wheel]53     ['text_event', dtime, text]54     ['copyright_text_event', dtime, text]55     ['track_name', dtime, text]56     ['instrument_name', dtime, text]57     ['lyric', dtime, text]58     ['marker', dtime, text]59     ['cue_point', dtime, text]60     ['text_event_08', dtime, text]61     ['text_event_09', dtime, text]62     ['text_event_0a', dtime, text]63     ['text_event_0b', dtime, text]64     ['text_event_0c', dtime, text]65     ['text_event_0d', dtime, text]66     ['text_event_0e', dtime, text]67     ['text_event_0f', dtime, text]68     ['end_track', dtime]69     ['set_tempo', dtime, tempo]70     ['smpte_offset', dtime, hr, mn, se, fr, ff]71     ['time_signature', dtime, nn, dd, cc, bb]72     ['key_signature', dtime, sf, mi]73     ['sequencer_specific', dtime, raw]74     ['raw_meta_event', dtime, command(0-255), raw]75     ['sysex_f0', dtime, raw]76     ['sysex_f7', dtime, raw]77     ['song_position', dtime, song_pos]78     ['song_select', dtime, song_number]79     ['tune_request', dtime]80 81  DATA TYPES82     channel = a value 0 to 1583     controller = 0 to 127 (see http://www.pjb.com.au/muscript/gm.html#cc )84     dtime = time measured in "ticks", 0 to 26843545585     velocity = a value 0 (soft) to 127 (loud)86     note = a value 0 to 127  (middle-C is 60)87     patch = 0 to 127 (see http://www.pjb.com.au/muscript/gm.html )88     pitch_wheel = a value -8192 to 8191 (0x1FFF)89     raw = bytes, of length 0 or more  (for sysex events see below)90     sequence_number = a value 0 to 65,535 (0xFFFF)91     song_pos = a value 0 to 16,383 (0x3FFF)92     song_number = a value 0 to 12793     tempo = microseconds per crochet (quarter-note), 0 to 1677721594     text = bytes, of length 0 or more95     ticks = the number of ticks per crochet (quarter-note)96 97   In sysex_f0 events, the raw data must not start with a \xF0 byte,98   since this gets added automatically;99   but it must end with an explicit \xF7 byte!100   In the very unlikely case that you ever need to split sysex data101   into one sysex_f0 followed by one or more sysex_f7s, then only the102   last of those sysex_f7 events must end with the explicit \xF7 byte103   (again, the raw data of individual sysex_f7 events must not start104   with any \xF7 byte, since this gets added automatically).105 106   Since version 6.4, text data is in bytes, not in a ISO-8859-1 string.107 108 109  GOING THROUGH A SCORE WITHIN A PYTHON PROGRAM110    channels = {2,3,5,8,13}111    itrack = 1   # skip 1st element which is ticks112    while itrack < len(score):113        for event in score[itrack]:114            if event[0] == 'note':   # for example,115                pass  # do something to all notes116            # or, to work on events in only particular channels...117            channel_index = MIDI.Event2channelindex.get(event[0], False)118            if channel_index and (event[channel_index] in channels):119                pass  # do something to channels 2,3,5,8 and 13120        itrack += 1121 122'''123 124import sys, struct, copy125# sys.stdout = os.fdopen(sys.stdout.fileno(), 'wb')126Version = '6.7'127VersionDate = '20201120'128# 20201120 6.7 call to bytest() removed, and protect _unshift_ber_int129# 20160702 6.6 to_millisecs() now handles set_tempo across multiple Tracks130# 20150921 6.5 segment restores controllers as well as patch and tempo131# 20150914 6.4 text data is bytes or bytearray, not ISO-8859-1 strings132# 20150628 6.3 absent any set_tempo, default is 120bpm (see MIDI file spec 1.1)133# 20150101 6.2 all text events can be 8-bit; let user get the right encoding134# 20141231 6.1 fix _some_text_event; sequencer_specific data can be 8-bit135# 20141230 6.0 synth_specific data can be 8-bit136# 20120504 5.9 add the contents of mid_opus_tracks()137# 20120208 5.8 fix num_notes_by_channel() ; should be a dict138# 20120129 5.7 _encode handles empty tracks; score2stats num_notes_by_channel139# 20111111 5.6 fix patch 45 and 46 in Number2patch, should be Harp140# 20110129 5.5 add mix_opus_tracks() and event2alsaseq()141# 20110126 5.4 "previous message repeated N times" to save space on stderr142# 20110125 5.2 opus2score terminates unended notes at the end of the track143# 20110124 5.1 the warnings in midi2opus display track_num144# 21110122 5.0 if garbage, midi2opus returns the opus so far145# 21110119 4.9 non-ascii chars stripped out of the text_events146# 21110110 4.8 note_on with velocity=0 treated as a note-off147# 21110108 4.6 unknown F-series event correctly eats just one byte148# 21011010 4.2 segment() uses start_time, end_time named params149# 21011005 4.1 timeshift() must not pad the set_tempo command150# 21011003 4.0 pitch2note_event must be chapitch2note_event151# 21010918 3.9 set_sequence_number supported, FWIW152# 20100913 3.7 many small bugfixes; passes all tests153# 20100910 3.6 concatenate_scores enforce ticks=1000, just like merge_scores154# 20100908 3.5 minor bugs fixed in score2stats155# 20091104 3.4 tune_request now supported156# 20091104 3.3 fixed bug in decoding song_position and song_select157# 20091104 3.2 unsupported: set_sequence_number tune_request raw_data158# 20091101 3.1 document how to traverse a score within Python159# 20091021 3.0 fixed bug in score2stats detecting GM-mode = 0160# 20091020 2.9 score2stats reports GM-mode and bank msb,lsb events161# 20091019 2.8 in merge_scores, channel 9 must remain channel 9 (in GM)162# 20091018 2.7 handles empty tracks gracefully163# 20091015 2.6 grep() selects channels164# 20091010 2.5 merge_scores reassigns channels to avoid conflicts165# 20091010 2.4 fixed bug in to_millisecs which now only does opusses166# 20091010 2.3 score2stats returns channels & patch_changes, by_track & total167# 20091010 2.2 score2stats() returns also pitches and percussion dicts168# 20091010 2.1 bugs: >= not > in segment, to notice patch_change at time 0169# 20091010 2.0 bugs: spurious pop(0) ( in _decode sysex170# 20091008 1.9 bugs: ISO decoding in sysex; str( not int( in note-off warning171# 20091008 1.8 add concatenate_scores()172# 20091006 1.7 score2stats() measures nticks and ticks_per_quarter173# 20091004 1.6 first mix_scores() and merge_scores()174# 20090424 1.5 timeshift() bugfix: earliest only sees events after from_time175# 20090330 1.4 timeshift() has also a from_time argument176# 20090322 1.3 timeshift() has also a start_time argument177# 20090319 1.2 add segment() and timeshift()178# 20090301 1.1 add to_millisecs()179 180_previous_warning = ''  # 5.4181_previous_times = 0     # 5.4182#------------------------------- Encoding stuff --------------------------183 184def opus2midi(opus=[]):185    r'''The argument is a list: the first item in the list is the "ticks"186parameter, the others are the tracks. Each track is a list187of midi-events, and each event is itself a list; see above.188opus2midi() returns a bytestring of the MIDI, which can then be189written either to a file opened in binary mode (mode='wb'),190or to stdout by means of:   sys.stdout.buffer.write()191 192my_opus = [193    96, 194    [   # track 0:195        ['patch_change', 0, 1, 8],   # and these are the events...196        ['note_on',   5, 1, 25, 96],197        ['note_off', 96, 1, 25, 0],198        ['note_on',   0, 1, 29, 96],199        ['note_off', 96, 1, 29, 0],200    ],   # end of track 0201]202my_midi = opus2midi(my_opus)203sys.stdout.buffer.write(my_midi)204'''205    if len(opus) < 2:206        opus=[1000, [],]207    tracks = copy.deepcopy(opus)208    ticks = int(tracks.pop(0))209    ntracks = len(tracks)210    if ntracks == 1:211        format = 0212    else:213        format = 1214 215    my_midi = b"MThd\x00\x00\x00\x06"+struct.pack('>HHH',format,ntracks,ticks)216    for track in tracks:217        events = _encode(track)218        my_midi += b'MTrk' + struct.pack('>I',len(events)) + events219    _clean_up_warnings()220    return my_midi221 222 223def score2opus(score=None):224    r'''225The argument is a list: the first item in the list is the "ticks"226parameter, the others are the tracks. Each track is a list227of score-events, and each event is itself a list.  A score-event228is similar to an opus-event (see above), except that in a score:229 1) the times are expressed as an absolute number of ticks230    from the track's start time231 2) the pairs of 'note_on' and 'note_off' events in an "opus"232    are abstracted into a single 'note' event in a "score":233    ['note', start_time, duration, channel, pitch, velocity]234score2opus() returns a list specifying the equivalent "opus".235 236my_score = [237    96,238    [   # track 0:239        ['patch_change', 0, 1, 8],240        ['note', 5, 96, 1, 25, 96],241        ['note', 101, 96, 1, 29, 96]242    ],   # end of track 0243]244my_opus = score2opus(my_score)245'''246    if len(score) < 2:247        score=[1000, [],]248    tracks = copy.deepcopy(score)249    ticks = int(tracks.pop(0))250    opus_tracks = []251    for scoretrack in tracks:252        time2events = dict([])253        for scoreevent in scoretrack:254            if scoreevent[0] == 'note':255                note_on_event = ['note_on',scoreevent[1],256                 scoreevent[3],scoreevent[4],scoreevent[5]]257                note_off_event = ['note_off',scoreevent[1]+scoreevent[2],258                 scoreevent[3],scoreevent[4],scoreevent[5]]259                if time2events.get(note_on_event[1]):260                   time2events[note_on_event[1]].append(note_on_event)261                else:262                   time2events[note_on_event[1]] = [note_on_event,]263                if time2events.get(note_off_event[1]):264                   time2events[note_off_event[1]].append(note_off_event)265                else:266                   time2events[note_off_event[1]] = [note_off_event,]267                continue268            if time2events.get(scoreevent[1]):269               time2events[scoreevent[1]].append(scoreevent)270            else:271               time2events[scoreevent[1]] = [scoreevent,]272 273        sorted_times = []  # list of keys274        for k in time2events.keys():275            sorted_times.append(k)276        sorted_times.sort()277 278        sorted_events = []  # once-flattened list of values sorted by key279        for time in sorted_times:280            sorted_events.extend(time2events[time])281 282        abs_time = 0283        for event in sorted_events:  # convert abs times => delta times284            delta_time = event[1] - abs_time285            abs_time = event[1]286            event[1] = delta_time287        opus_tracks.append(sorted_events)288    opus_tracks.insert(0,ticks)289    _clean_up_warnings()290    return opus_tracks291 292def score2midi(score=None):293    r'''294Translates a "score" into MIDI, using score2opus() then opus2midi()295'''296    return opus2midi(score2opus(score))297 298#--------------------------- Decoding stuff ------------------------299 300def midi2opus(midi=b''):301    r'''Translates MIDI into a "opus".  For a description of the302"opus" format, see opus2midi()303'''304    my_midi=bytearray(midi)305    if len(my_midi) < 4:306        _clean_up_warnings()307        return [1000,[],]308    id = bytes(my_midi[0:4])309    if id != b'MThd':310        _warn("midi2opus: midi starts with "+str(id)+" instead of 'MThd'")311        _clean_up_warnings()312        return [1000,[],]313    [length, format, tracks_expected, ticks] = struct.unpack(314     '>IHHH', bytes(my_midi[4:14]))315    if length != 6:316        _warn("midi2opus: midi header length was "+str(length)+" instead of 6")317        _clean_up_warnings()318        return [1000,[],]319    my_opus = [ticks,]320    my_midi = my_midi[14:]321    track_num = 1   # 5.1322    while len(my_midi) >= 8:323        track_type   = bytes(my_midi[0:4])324        if track_type != b'MTrk':325            _warn('midi2opus: Warning: track #'+str(track_num)+' type is '+str(track_type)+" instead of b'MTrk'")326        [track_length] = struct.unpack('>I', my_midi[4:8])327        my_midi = my_midi[8:]328        if track_length > len(my_midi):329            _warn('midi2opus: track #'+str(track_num)+' length '+str(track_length)+' is too large')330            _clean_up_warnings()331            return my_opus   # 5.0332        my_midi_track = my_midi[0:track_length]333        my_track = _decode(my_midi_track)334        my_opus.append(my_track)335        my_midi = my_midi[track_length:]336        track_num += 1   # 5.1337    _clean_up_warnings()338    return my_opus339 340def opus2score(opus=[]):341    r'''For a description of the "opus" and "score" formats,342see opus2midi() and score2opus().343'''344    if len(opus) < 2:345        _clean_up_warnings()346        return [1000,[],]347    tracks = copy.deepcopy(opus)  # couple of slices probably quicker...348    ticks = int(tracks.pop(0))349    score = [ticks,]350    for opus_track in tracks:351        ticks_so_far = 0352        score_track = []353        chapitch2note_on_events = dict([])   # 4.0354        for opus_event in opus_track:355            ticks_so_far += opus_event[1]356            if opus_event[0] == 'note_off' or (opus_event[0] == 'note_on' and opus_event[4] == 0):  # 4.8357                cha = opus_event[2]358                pitch = opus_event[3]359                key = cha*128 + pitch360                if chapitch2note_on_events.get(key):361                    new_event = chapitch2note_on_events[key].pop(0)362                    new_event[2] = ticks_so_far - new_event[1]363                    score_track.append(new_event)364                elif pitch > 127:365                    pass #_warn('opus2score: note_off with no note_on, bad pitch='+str(pitch))366                else:367                    pass #_warn('opus2score: note_off with no note_on cha='+str(cha)+' pitch='+str(pitch))368            elif opus_event[0] == 'note_on':369                cha = opus_event[2]370                pitch = opus_event[3]371                key = cha*128 + pitch372                new_event = ['note',ticks_so_far,0,cha,pitch, opus_event[4]]373                if chapitch2note_on_events.get(key):374                    chapitch2note_on_events[key].append(new_event)375                else:376                    chapitch2note_on_events[key] = [new_event,]377            else:378                opus_event[1] = ticks_so_far379                score_track.append(opus_event)380        # check for unterminated notes (Oisín) -- 5.2381        for chapitch in chapitch2note_on_events:382            note_on_events = chapitch2note_on_events[chapitch]383            for new_e in note_on_events:384                new_e[2] = ticks_so_far - new_e[1]385                score_track.append(new_e)386                pass #_warn("opus2score: note_on with no note_off cha="+str(new_e[3])+' pitch='+str(new_e[4])+'; adding note_off at end')387        score.append(score_track)388    _clean_up_warnings()389    return score390 391def midi2score(midi=b''):392    r'''393Translates MIDI into a "score", using midi2opus() then opus2score()394'''395    return opus2score(midi2opus(midi))396 397def midi2ms_score(midi=b''):398    r'''399Translates MIDI into a "score" with one beat per second and one400tick per millisecond, using midi2opus() then to_millisecs()401then opus2score()402'''403    return opus2score(to_millisecs(midi2opus(midi)))404 405#------------------------ Other Transformations ---------------------406 407def to_millisecs(old_opus=None):408    r'''Recallibrates all the times in an "opus" to use one beat409per second and one tick per millisecond.  This makes it410hard to retrieve any information about beats or barlines,411but it does make it easy to mix different scores together.412'''413    if old_opus == None:414        return [1000,[],]415    try:416        old_tpq  = int(old_opus[0])417    except IndexError:   # 5.0418        _warn('to_millisecs: the opus '+str(type(old_opus))+' has no elements')419        return [1000,[],]420    new_opus = [1000,]421    # 6.7 first go through building a table of set_tempos by absolute-tick422    ticks2tempo = {}423    itrack = 1424    while itrack < len(old_opus):425        ticks_so_far = 0426        for old_event in old_opus[itrack]:427            if old_event[0] == 'note':428                raise TypeError('to_millisecs needs an opus, not a score')429            ticks_so_far += old_event[1]430            if old_event[0] == 'set_tempo':431                ticks2tempo[ticks_so_far] = old_event[2]432        itrack += 1433    # then get the sorted-array of their keys434    tempo_ticks = []  # list of keys435    for k in ticks2tempo.keys():436        tempo_ticks.append(k)437    tempo_ticks.sort()438    # then go through converting to millisec, testing if the next439    # set_tempo lies before the next track-event, and using it if so.440    itrack = 1441    while itrack < len(old_opus):442        ms_per_old_tick = 500.0 / old_tpq  # float: will round later 6.3443        i_tempo_ticks = 0444        ticks_so_far = 0445        ms_so_far = 0.0446        previous_ms_so_far = 0.0447        new_track = [['set_tempo',0,1000000],]  # new "crochet" is 1 sec448        for old_event in old_opus[itrack]:449            # detect if ticks2tempo has something before this event450            # 20160702 if ticks2tempo is at the same time, leave it451            event_delta_ticks = old_event[1]452            if (i_tempo_ticks < len(tempo_ticks) and453              tempo_ticks[i_tempo_ticks] < (ticks_so_far + old_event[1])):454                delta_ticks = tempo_ticks[i_tempo_ticks] - ticks_so_far455                ms_so_far += (ms_per_old_tick * delta_ticks)456                ticks_so_far = tempo_ticks[i_tempo_ticks]457                ms_per_old_tick = ticks2tempo[ticks_so_far] / (1000.0*old_tpq)458                i_tempo_ticks += 1459                event_delta_ticks -= delta_ticks460            new_event = copy.deepcopy(old_event)  # now handle the new event461            ms_so_far += (ms_per_old_tick * old_event[1])462            new_event[1] = round(ms_so_far - previous_ms_so_far)463            if old_event[0] != 'set_tempo':464                previous_ms_so_far = ms_so_far465                new_track.append(new_event)466            ticks_so_far += event_delta_ticks467        new_opus.append(new_track)468        itrack += 1469    _clean_up_warnings()470    return new_opus471 472def event2alsaseq(event=None):   # 5.5473    r'''Converts an event into the format needed by the alsaseq module,474http://pp.com.mx/python/alsaseq475The type of track (opus or score) is autodetected.476'''477    pass478 479def grep(score=None, channels=None):480    r'''Returns a "score" containing only the channels specified481'''482    if score == None:483        return [1000,[],]484    ticks = score[0]485    new_score = [ticks,]486    if channels == None:487        return new_score488    channels = set(channels)489    global Event2channelindex490    itrack = 1491    while itrack < len(score):492        new_score.append([])493        for event in score[itrack]:494            channel_index = Event2channelindex.get(event[0], False)495            if channel_index:496                if event[channel_index] in channels:497                    new_score[itrack].append(event)498            else:499                new_score[itrack].append(event)500        itrack += 1501    return new_score502 503def play_score(score=None):504    r'''Converts the "score" to midi, and feeds it into 'aplaymidi -'505'''506    if score == None:507        return508    import subprocess509    pipe = subprocess.Popen(['aplaymidi','-'], stdin=subprocess.PIPE)510    if score_type(score) == 'opus':511        pipe.stdin.write(opus2midi(score))512    else:513        pipe.stdin.write(score2midi(score))514    pipe.stdin.close()515 516def timeshift(score=None, shift=None, start_time=None, from_time=0, tracks={0,1,2,3,4,5,6,7,8,10,12,13,14,15}):517    r'''Returns a "score" shifted in time by "shift" ticks, or shifted518so that the first event starts at "start_time" ticks.519 520If "from_time" is specified, only those events in the score521that begin after it are shifted. If "start_time" is less than522"from_time" (or "shift" is negative), then the intermediate523notes are deleted, though patch-change events are preserved.524 525If "tracks" are specified, then only those tracks get shifted.526"tracks" can be a list, tuple or set; it gets converted to set527internally.528 529It is deprecated to specify both "shift" and "start_time".530If this does happen, timeshift() will print a warning to531stderr and ignore the "shift" argument.532 533If "shift" is negative and sufficiently large that it would534leave some event with a negative tick-value, then the score535is shifted so that the first event occurs at time 0. This536also occurs if "start_time" is negative, and is also the537default if neither "shift" nor "start_time" are specified.538'''539    #_warn('tracks='+str(tracks))540    if score == None or len(score) < 2:541        return [1000, [],]542    new_score = [score[0],]543    my_type = score_type(score)544    if my_type == '':545        return new_score546    if my_type == 'opus':547        _warn("timeshift: opus format is not supported\n")548        # _clean_up_scores()  6.2; doesn't exist! what was it supposed to do?549        return new_score550    if not (shift == None) and not (start_time == None):551        _warn("timeshift: shift and start_time specified: ignoring shift\n")552        shift = None553    if shift == None:554        if (start_time == None) or (start_time < 0):555            start_time = 0556        # shift = start_time - from_time557 558    i = 1   # ignore first element (ticks)559    tracks = set(tracks)  # defend against tuples and lists560    earliest = 1000000000561    if not (start_time == None) or shift < 0:  # first find the earliest event562        while i < len(score):563            if len(tracks) and not ((i-1) in tracks):564                i += 1565                continue566            for event in score[i]:567                 if event[1] < from_time:568                     continue  # just inspect the to_be_shifted events569                 if event[1] < earliest:570                     earliest = event[1]571            i += 1572    if earliest > 999999999:573        earliest = 0574    if shift == None:575        shift = start_time - earliest576    elif (earliest + shift) < 0:577        start_time = 0578        shift = 0 - earliest579 580    i = 1   # ignore first element (ticks)581    while i < len(score):582        if len(tracks) == 0 or not ((i-1) in tracks):  # 3.8583            new_score.append(score[i])584            i += 1585            continue586        new_track = []587        for event in score[i]:588            new_event = list(event)589            #if new_event[1] == 0 and shift > 0 and new_event[0] != 'note':590            #    pass591            #elif new_event[1] >= from_time:592            if new_event[1] >= from_time:593                # 4.1 must not rightshift set_tempo594                if new_event[0] != 'set_tempo' or shift<0:595                    new_event[1] += shift596            elif (shift < 0) and (new_event[1] >= (from_time+shift)):597                continue598            new_track.append(new_event)599        if len(new_track) > 0:600            new_score.append(new_track)601        i += 1602    _clean_up_warnings()603    return new_score604 605def segment(score=None, start_time=None, end_time=None, start=0, end=100000000,606 tracks={0,1,2,3,4,5,6,7,8,10,11,12,13,14,15}):607    r'''Returns a "score" which is a segment of the one supplied608as the argument, beginning at "start_time" ticks and ending609at "end_time" ticks (or at the end if "end_time" is not supplied).610If the set "tracks" is specified, only those tracks will611be returned.612'''613    if score == None or len(score) < 2:614        return [1000, [],]615    if start_time == None:  # as of 4.2 start_time is recommended616        start_time = start  # start is legacy usage617    if end_time == None:    # likewise618        end_time = end619    new_score = [score[0],]620    my_type = score_type(score)621    if my_type == '':622        return new_score623    if my_type == 'opus':624        # more difficult (disconnecting note_on's from their note_off's)...625        _warn("segment: opus format is not supported\n")626        _clean_up_warnings()627        return new_score628    i = 1   # ignore first element (ticks); we count in ticks anyway629    tracks = set(tracks)  # defend against tuples and lists630    while i < len(score):631        if len(tracks) and not ((i-1) in tracks):632            i += 1633            continue634        new_track = []635        channel2cc_num  = {}     # most recent controller change before start636        channel2cc_val  = {}637        channel2cc_time = {}638        channel2patch_num  = {}  # keep most recent patch change before start639        channel2patch_time = {}640        set_tempo_num  = 500000 # most recent tempo change before start 6.3641        set_tempo_time = 0642        earliest_note_time = end_time643        for event in score[i]:644            if event[0] == 'control_change':  # 6.5645                cc_time = channel2cc_time.get(event[2]) or 0646                if (event[1] <= start_time) and (event[1] >= cc_time):647                    channel2cc_num[event[2]]  = event[3]648                    channel2cc_val[event[2]]  = event[4]649                    channel2cc_time[event[2]] = event[1]650            elif event[0] == 'patch_change':651                patch_time = channel2patch_time.get(event[2]) or 0652                if (event[1]<=start_time) and (event[1] >= patch_time):  # 2.0653                    channel2patch_num[event[2]]  = event[3]654                    channel2patch_time[event[2]] = event[1]655            elif event[0] == 'set_tempo':656                if (event[1]<=start_time) and (event[1]>=set_tempo_time): #6.4657                    set_tempo_num  = event[2]658                    set_tempo_time = event[1]659            if (event[1] >= start_time) and (event[1] <= end_time):660                new_track.append(event)661                if (event[0] == 'note') and (event[1] < earliest_note_time):662                    earliest_note_time = event[1]663        if len(new_track) > 0:664            new_track.append(['set_tempo', start_time, set_tempo_num])665            for c in channel2patch_num:666                new_track.append(['patch_change',start_time,c,channel2patch_num[c]],)667            for c in channel2cc_num:   # 6.5668                new_track.append(['control_change',start_time,c,channel2cc_num[c],channel2cc_val[c]])669            new_score.append(new_track)670        i += 1671    _clean_up_warnings()672    return new_score673 674def score_type(opus_or_score=None):675    r'''Returns a string, either 'opus' or 'score' or ''676'''677    if opus_or_score == None or str(type(opus_or_score)).find('list')<0 or len(opus_or_score) < 2:678        return ''679    i = 1   # ignore first element680    while i < len(opus_or_score):681        for event in opus_or_score[i]:682            if event[0] == 'note':683                return 'score'684            elif event[0] == 'note_on':685                return 'opus'686        i += 1687    return ''688 689def concatenate_scores(scores):690    r'''Concatenates a list of scores into one score.691If the scores differ in their "ticks" parameter,692they will all get converted to millisecond-tick format.693'''694    # the deepcopys are needed if the input_score's are refs to the same obj695    # e.g. if invoked by midisox's repeat()696    input_scores = _consistentise_ticks(scores)  # 3.7697    output_score = copy.deepcopy(input_scores[0])698    for input_score in input_scores[1:]:699        output_stats = score2stats(output_score)700        delta_ticks = output_stats['nticks']701        itrack = 1702        while itrack < len(input_score):703            if itrack >= len(output_score): # new output track if doesn't exist704                output_score.append([])705            for event in input_score[itrack]:706                output_score[itrack].append(copy.deepcopy(event))707                output_score[itrack][-1][1] += delta_ticks708            itrack += 1709    return output_score710 711def merge_scores(scores):712    r'''Merges a list of scores into one score.  A merged score comprises713all of the tracks from all of the input scores; un-merging is possible714by selecting just some of the tracks.  If the scores differ in their715"ticks" parameter, they will all get converted to millisecond-tick716format.  merge_scores attempts to resolve channel-conflicts,717but there are of course only 15 available channels...718'''719    input_scores = _consistentise_ticks(scores)  # 3.6720    output_score = [1000]721    channels_so_far = set()722    all_channels = {0,1,2,3,4,5,6,7,8,10,11,12,13,14,15}723    global Event2channelindex724    for input_score in input_scores:725        new_channels = set(score2stats(input_score).get('channels_total', []))726        new_channels.discard(9)  # 2.8 cha9 must remain cha9 (in GM)727        for channel in channels_so_far & new_channels:728            # consistently choose lowest avaiable, to ease testing729            free_channels = list(all_channels - (channels_so_far|new_channels))730            if len(free_channels) > 0:731                free_channels.sort()732                free_channel = free_channels[0]733            else:734                free_channel = None735                break736            itrack = 1737            while itrack < len(input_score):738                for input_event in input_score[itrack]:739                    channel_index=Event2channelindex.get(input_event[0],False)740                    if channel_index and input_event[channel_index]==channel:741                        input_event[channel_index] = free_channel742                itrack += 1743            channels_so_far.add(free_channel)744 745        channels_so_far |= new_channels746        output_score.extend(input_score[1:])747    return output_score748 749def _ticks(event):750    return event[1]751def mix_opus_tracks(input_tracks):   # 5.5752    r'''Mixes an array of tracks into one track.  A mixed track753cannot be un-mixed.  It is assumed that the tracks share the same754ticks parameter and the same tempo.755Mixing score-tracks is trivial (just insert all events into one array).756Mixing opus-tracks is only slightly harder, but it's common enough757that a dedicated function is useful.758'''759    output_score = [1000, []]760    for input_track in input_tracks:   # 5.8761        input_score = opus2score([1000, input_track])762        for event in input_score[1]:763            output_score[1].append(event)764    output_score[1].sort(key=_ticks) 765    output_opus = score2opus(output_score)766    return output_opus[1]767 768def mix_scores(scores):769    r'''Mixes a list of scores into one one-track score.770A mixed score cannot be un-mixed.  Hopefully the scores771have no undesirable channel-conflicts between them.772If the scores differ in their "ticks" parameter,773they will all get converted to millisecond-tick format.774'''775    input_scores = _consistentise_ticks(scores)  # 3.6776    output_score = [1000, []]777    for input_score in input_scores:778        for input_track in input_score[1:]:779            output_score[1].extend(input_track)780    return output_score781 782def score2stats(opus_or_score=None):783    r'''Returns a dict of some basic stats about the score, like784bank_select (list of tuples (msb,lsb)),785channels_by_track (list of lists), channels_total (set),786general_midi_mode (list),787ntracks, nticks, patch_changes_by_track (list of dicts),788num_notes_by_channel (list of numbers),789patch_changes_total (set),790percussion (dict histogram of channel 9 events),791pitches (dict histogram of pitches on channels other than 9),792pitch_range_by_track (list, by track, of two-member-tuples),793pitch_range_sum (sum over tracks of the pitch_ranges),794'''795    bank_select_msb = -1796    bank_select_lsb = -1797    bank_select = []798    channels_by_track = []799    channels_total    = set([])800    general_midi_mode = []801    num_notes_by_channel = dict([])802    patches_used_by_track  = []803    patches_used_total     = set([])804    patch_changes_by_track = []805    patch_changes_total    = set([])806    percussion = dict([]) # histogram of channel 9 "pitches"807    pitches    = dict([]) # histogram of pitch-occurrences channels 0-8,10-15808    pitch_range_sum = 0   # u pitch-ranges of each track809    pitch_range_by_track = []810    is_a_score = True811    if opus_or_score == None:812        return {'bank_select':[], 'channels_by_track':[], 'channels_total':[],813         'general_midi_mode':[], 'ntracks':0, 'nticks':0,814         'num_notes_by_channel':dict([]),815         'patch_changes_by_track':[], 'patch_changes_total':[],816         'percussion':{}, 'pitches':{}, 'pitch_range_by_track':[],817         'ticks_per_quarter':0, 'pitch_range_sum':0}818    ticks_per_quarter = opus_or_score[0]819    i = 1   # ignore first element, which is ticks820    nticks = 0821    while i < len(opus_or_score):822        highest_pitch = 0823        lowest_pitch = 128824        channels_this_track = set([])825        patch_changes_this_track = dict({})826        for event in opus_or_score[i]:827            if event[0] == 'note':828                num_notes_by_channel[event[3]] = num_notes_by_channel.get(event[3],0) + 1829                if event[3] == 9:830                    percussion[event[4]] = percussion.get(event[4],0) + 1831                else:832                    pitches[event[4]]    = pitches.get(event[4],0) + 1833                    if event[4] > highest_pitch:834                        highest_pitch = event[4]835                    if event[4] < lowest_pitch:836                        lowest_pitch = event[4]837                channels_this_track.add(event[3])838                channels_total.add(event[3])839                finish_time = event[1] + event[2]840                if finish_time > nticks:841                    nticks = finish_time842            elif event[0] == 'note_off' or (event[0] == 'note_on' and event[4] == 0):  # 4.8843                finish_time = event[1]844                if finish_time > nticks:845                    nticks = finish_time846            elif event[0] == 'note_on':847                is_a_score = False848                num_notes_by_channel[event[2]] = num_notes_by_channel.get(event[2],0) + 1849                if event[2] == 9:850                    percussion[event[3]] = percussion.get(event[3],0) + 1851                else:852                    pitches[event[3]]    = pitches.get(event[3],0) + 1853                    if event[3] > highest_pitch:854                        highest_pitch = event[3]855                    if event[3] < lowest_pitch:856                        lowest_pitch = event[3]857                channels_this_track.add(event[2])858                channels_total.add(event[2])859            elif event[0] == 'patch_change':860                patch_changes_this_track[event[2]] = event[3]861                patch_changes_total.add(event[3])862            elif event[0] == 'control_change':863                if event[3] == 0:  # bank select MSB864                    bank_select_msb = event[4]865                elif event[3] == 32:  # bank select LSB866                    bank_select_lsb = event[4]867                if bank_select_msb >= 0 and bank_select_lsb >= 0:868                    bank_select.append((bank_select_msb,bank_select_lsb))869                    bank_select_msb = -1870                    bank_select_lsb = -1871            elif event[0] == 'sysex_f0':872                if _sysex2midimode.get(event[2], -1) >= 0:873                    general_midi_mode.append(_sysex2midimode.get(event[2]))874            if is_a_score:875                if event[1] > nticks:876                    nticks = event[1]877            else:878                nticks += event[1]879        if lowest_pitch == 128:880            lowest_pitch = 0881        channels_by_track.append(channels_this_track)882        patch_changes_by_track.append(patch_changes_this_track)883        pitch_range_by_track.append((lowest_pitch,highest_pitch))884        pitch_range_sum += (highest_pitch-lowest_pitch)885        i += 1886 887    return {'bank_select':bank_select,888            'channels_by_track':channels_by_track,889            'channels_total':channels_total,890            'general_midi_mode':general_midi_mode,891            'ntracks':len(opus_or_score)-1,892            'nticks':nticks,893            'num_notes_by_channel':num_notes_by_channel,894            'patch_changes_by_track':patch_changes_by_track,895            'patch_changes_total':patch_changes_total,896            'percussion':percussion,897            'pitches':pitches,898            'pitch_range_by_track':pitch_range_by_track,899            'pitch_range_sum':pitch_range_sum,900            'ticks_per_quarter':ticks_per_quarter}901 902#----------------------------- Event stuff --------------------------903 904_sysex2midimode = {905    "\x7E\x7F\x09\x01\xF7": 1,906    "\x7E\x7F\x09\x02\xF7": 0,907    "\x7E\x7F\x09\x03\xF7": 2,908}909 910# Some public-access tuples:911MIDI_events = tuple('''note_off note_on key_after_touch912control_change patch_change channel_after_touch913pitch_wheel_change'''.split())914 915Text_events = tuple('''text_event copyright_text_event916track_name instrument_name lyric marker cue_point text_event_08917text_event_09 text_event_0a text_event_0b text_event_0c918text_event_0d text_event_0e text_event_0f'''.split())919 920Nontext_meta_events = tuple('''end_track set_tempo921smpte_offset time_signature key_signature sequencer_specific922raw_meta_event sysex_f0 sysex_f7 song_position song_select923tune_request'''.split())924# unsupported: raw_data925 926# Actually, 'tune_request' is is F-series event, not strictly a meta-event...927Meta_events = Text_events + Nontext_meta_events928All_events  = MIDI_events + Meta_events929 930# And three dictionaries:931Number2patch = {   # General MIDI patch numbers:9320:'Acoustic Grand',9331:'Bright Acoustic',9342:'Electric Grand',9353:'Honky-Tonk',9364:'Electric Piano 1',9375:'Electric Piano 2',9386:'Harpsichord',9397:'Clav',9408:'Celesta',9419:'Glockenspiel',94210:'Music Box',94311:'Vibraphone',94412:'Marimba',94513:'Xylophone',94614:'Tubular Bells',94715:'Dulcimer',94816:'Drawbar Organ',94917:'Percussive Organ',95018:'Rock Organ',95119:'Church Organ',95220:'Reed Organ',95321:'Accordion',95422:'Harmonica',95523:'Tango Accordion',95624:'Acoustic Guitar(nylon)',95725:'Acoustic Guitar(steel)',95826:'Electric Guitar(jazz)',95927:'Electric Guitar(clean)',96028:'Electric Guitar(muted)',96129:'Overdriven Guitar',96230:'Distortion Guitar',96331:'Guitar Harmonics',96432:'Acoustic Bass',96533:'Electric Bass(finger)',96634:'Electric Bass(pick)',96735:'Fretless Bass',96836:'Slap Bass 1',96937:'Slap Bass 2',97038:'Synth Bass 1',97139:'Synth Bass 2',97240:'Violin',97341:'Viola',97442:'Cello',97543:'Contrabass',97644:'Tremolo Strings',97745:'Pizzicato Strings',97846:'Orchestral Harp',97947:'Timpani',98048:'String Ensemble 1',98149:'String Ensemble 2',98250:'SynthStrings 1',98351:'SynthStrings 2',98452:'Choir Aahs',98553:'Voice Oohs',98654:'Synth Voice',98755:'Orchestra Hit',98856:'Trumpet',98957:'Trombone',99058:'Tuba',99159:'Muted Trumpet',99260:'French Horn',99361:'Brass Section',99462:'SynthBrass 1',99563:'SynthBrass 2',99664:'Soprano Sax',99765:'Alto Sax',99866:'Tenor Sax',99967:'Baritone Sax',100068:'Oboe',100169:'English Horn',100270:'Bassoon',100371:'Clarinet',100472:'Piccolo',100573:'Flute',100674:'Recorder',100775:'Pan Flute',100876:'Blown Bottle',100977:'Skakuhachi',101078:'Whistle',101179:'Ocarina',101280:'Lead 1 (square)',101381:'Lead 2 (sawtooth)',101482:'Lead 3 (calliope)',101583:'Lead 4 (chiff)',101684:'Lead 5 (charang)',101785:'Lead 6 (voice)',101886:'Lead 7 (fifths)',101987:'Lead 8 (bass+lead)',102088:'Pad 1 (new age)',102189:'Pad 2 (warm)',102290:'Pad 3 (polysynth)',102391:'Pad 4 (choir)',102492:'Pad 5 (bowed)',102593:'Pad 6 (metallic)',102694:'Pad 7 (halo)',102795:'Pad 8 (sweep)',102896:'FX 1 (rain)',102997:'FX 2 (soundtrack)',103098:'FX 3 (crystal)',103199:'FX 4 (atmosphere)',1032100:'FX 5 (brightness)',1033101:'FX 6 (goblins)',1034102:'FX 7 (echoes)',1035103:'FX 8 (sci-fi)',1036104:'Sitar',1037105:'Banjo',1038106:'Shamisen',1039107:'Koto',1040108:'Kalimba',1041109:'Bagpipe',1042110:'Fiddle',1043111:'Shanai',1044112:'Tinkle Bell',1045113:'Agogo',1046114:'Steel Drums',1047115:'Woodblock',1048116:'Taiko Drum',1049117:'Melodic Tom',1050118:'Synth Drum',1051119:'Reverse Cymbal',1052120:'Guitar Fret Noise',1053121:'Breath Noise',1054122:'Seashore',1055123:'Bird Tweet',1056124:'Telephone Ring',1057125:'Helicopter',1058126:'Applause',1059127:'Gunshot',1060}1061Notenum2percussion = {   # General MIDI Percussion (on Channel 9):106235:'Acoustic Bass Drum',106336:'Bass Drum 1',106437:'Side Stick',106538:'Acoustic Snare',106639:'Hand Clap',106740:'Electric Snare',106841:'Low Floor Tom',106942:'Closed Hi-Hat',107043:'High Floor Tom',107144:'Pedal Hi-Hat',107245:'Low Tom',107346:'Open Hi-Hat',107447:'Low-Mid Tom',107548:'Hi-Mid Tom',107649:'Crash Cymbal 1',107750:'High Tom',107851:'Ride Cymbal 1',107952:'Chinese Cymbal',108053:'Ride Bell',108154:'Tambourine',108255:'Splash Cymbal',108356:'Cowbell',108457:'Crash Cymbal 2',108558:'Vibraslap',108659:'Ride Cymbal 2',108760:'Hi Bongo',108861:'Low Bongo',108962:'Mute Hi Conga',109063:'Open Hi Conga',109164:'Low Conga',109265:'High Timbale',109366:'Low Timbale',109467:'High Agogo',109568:'Low Agogo',109669:'Cabasa',109770:'Maracas',109871:'Short Whistle',109972:'Long Whistle',110073:'Short Guiro',110174:'Long Guiro',110275:'Claves',110376:'Hi Wood Block',110477:'Low Wood Block',110578:'Mute Cuica',110679:'Open Cuica',110780:'Mute Triangle',110881:'Open Triangle',1109}1110 1111Event2channelindex = { 'note':3, 'note_off':2, 'note_on':2,1112 'key_after_touch':2, 'control_change':2, 'patch_change':2,1113 'channel_after_touch':2, 'pitch_wheel_change':21114}1115 1116################################################################1117# The code below this line is full of frightening things, all to1118# do with the actual encoding and decoding of binary MIDI data.1119 1120def _twobytes2int(byte_a):1121    r'''decode a 16 bit quantity from two bytes,'''1122    return (byte_a[1] | (byte_a[0] << 8))1123 1124def _int2twobytes(int_16bit):1125    r'''encode a 16 bit quantity into two bytes,'''1126    return bytes([(int_16bit>>8) & 0xFF, int_16bit & 0xFF])1127 1128def _read_14_bit(byte_a):1129    r'''decode a 14 bit quantity from two bytes,'''1130    return (byte_a[0] | (byte_a[1] << 7))1131 1132def _write_14_bit(int_14bit):1133    r'''encode a 14 bit quantity into two bytes,'''1134    return bytes([int_14bit & 0x7F, (int_14bit>>7) & 0x7F])1135 1136def _ber_compressed_int(integer):1137    r'''BER compressed integer (not an ASN.1 BER, see perlpacktut for1138details).  Its bytes represent an unsigned integer in base 128,1139most significant digit first, with as few digits as possible.1140Bit eight (the high bit) is set on each byte except the last.1141'''1142    ber = bytearray(b'')1143    seven_bits = 0x7F & integer1144    ber.insert(0, seven_bits)  # XXX surely should convert to a char ?1145    integer >>= 71146    while integer > 0:1147        seven_bits = 0x7F & integer1148        ber.insert(0, 0x80|seven_bits)  # XXX surely should convert to a char ?1149        integer >>= 71150    return ber1151 1152def _unshift_ber_int(ba):1153    r'''Given a bytearray, returns a tuple of (the ber-integer at the1154start, and the remainder of the bytearray).1155'''1156    if not len(ba):   # 6.71157        _warn('_unshift_ber_int: no integer found')1158        return ((0, b""))1159    byte = ba.pop(0)1160    integer = 01161    while True:1162        integer += (byte & 0x7F)1163        if not (byte & 0x80):1164            return ((integer, ba))1165        if not len(ba):1166            _warn('_unshift_ber_int: no end-of-integer found')1167            return ((0, ba))1168        byte = ba.pop(0)1169        integer <<= 71170 1171def _clean_up_warnings():  # 5.41172    # Call this before returning from any publicly callable function1173    # whenever there's a possibility that a warning might have been printed1174    # by the function, or by any private functions it might have called.1175    global _previous_times1176    global _previous_warning1177    if _previous_times > 1:1178        # E:1176, 0: invalid syntax (<string>, line 1176) (syntax-error) ???1179        # print('  previous message repeated '+str(_previous_times)+' times', file=sys.stderr)1180        # 6.71181        sys.stderr.write('  previous message repeated {0} times\n'.format(_previous_times))1182    elif _previous_times > 0:1183        sys.stderr.write('  previous message repeated\n')1184    _previous_times = 01185    _previous_warning = ''1186 1187def _warn(s=''):1188    global _previous_times1189    global _previous_warning1190    if s == _previous_warning:  # 5.41191        _previous_times = _previous_times + 11192    else:1193        _clean_up_warnings()1194        sys.stderr.write(str(s)+"\n")1195        _previous_warning = s1196 1197def _some_text_event(which_kind=0x01, text=b'some_text'):1198    if str(type(text)).find("'str'") >= 0:   # 6.4 test for back-compatibility1199        data = bytes(text, encoding='ISO-8859-1')1200    else:

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