CoolFace
Apppublic

Chickaboo/Advanced-MIDI-Renderer

sourceHugging Facecc-by-nc-4.0updated 4mo agoView on Hugging Face
1likes
midi_to_colab_audio.py3637 linesDownload Raw Back to root
1r'''#===================================================================================================================2#3# MIDI to Colab AUdio Python Module4#5# Converts any MIDI file to raw audio which is compatible 6# with Google Colab or HUgging Face Gradio7#8# Version 2.09#10# Includes full source code of MIDI and pyfluidsynth11# 12# Original source code for all modules was retrieved on 07/31/202513#14# Project Los Angeles15# Tegridy Code 202516#17#===================================================================================================================18#19# Critical dependencies20#21# pip install numpy22# sudo apt install fluidsynth23#24#===================================================================================================================25# 26# Example usage:27#28# from midi_to_colab_audio import midi_to_colab_audio29# from IPython.display import display, Audio30#31# raw_audio = midi_to_colab_audio('/content/input.mid')32#33# display(Audio(raw_audio, rate=16000, normalize=False))34#35#===================================================================================================================36#! /usr/bin/python337# unsupported 20091104 ...38#     ['set_sequence_number', dtime, sequence]39#     ['raw_data', dtime, raw]40 41# 20150914   jimbo1qaz   MIDI.py str/bytes bug report42# I found a MIDI file which had Shift-JIS titles. When midi.py decodes it as43# latin-1, it produces a string which cannot even be accessed without raising44# a UnicodeDecodeError.  Maybe, when converting raw byte strings from MIDI,45# you should keep them as bytes, not improperly decode them.  However, this46# would change the API.  (ie: text = a "string" ? of 0 or more bytes).  It47# could break compatiblity, but there's not much else you can do to fix the bug48# https://en.wikipedia.org/wiki/Shift_JIS49 50This module offers functions:  concatenate_scores(), grep(),51merge_scores(), mix_scores(), midi2opus(), midi2score(), opus2midi(),52opus2score(), play_score(), score2midi(), score2opus(), score2stats(),53score_type(), segment(), timeshift() and to_millisecs(),54where "midi" means the MIDI-file bytes (as can be put in a .mid file,55or piped into aplaymidi), and "opus" and "score" are list-structures56as inspired by Sean Burke's MIDI-Perl CPAN module.57 58Warning: Version 6.4 is not necessarily backward-compatible with59previous versions, in that text-data is now bytes, not strings.60This reflects the fact that many MIDI files have text data in61encodings other that ISO-8859-1, for example in Shift-JIS.62 63Download MIDI.py from   http://www.pjb.com.au/midi/free/MIDI.py64and put it in your PYTHONPATH.  MIDI.py depends on Python3.65 66There is also a call-compatible translation into Lua of this67module: see http://www.pjb.com.au/comp/lua/MIDI.html68 69Backup web site: https://peterbillam.gitlab.io/miditools/70 71The "opus" is a direct translation of the midi-file-events, where72the times are delta-times, in ticks, since the previous event.73 74The "score" is more human-centric; it uses absolute times, and75combines the separate note_on and note_off events into one "note"76event, with a duration:77 ['note', start_time, duration, channel, note, velocity] # in a "score"78 79  EVENTS (in an "opus" structure)80     ['note_off', dtime, channel, note, velocity]       # in an "opus"81     ['note_on', dtime, channel, note, velocity]        # in an "opus"82     ['key_after_touch', dtime, channel, note, velocity]83     ['control_change', dtime, channel, controller(0-127), value(0-127)]84     ['patch_change', dtime, channel, patch]85     ['channel_after_touch', dtime, channel, velocity]86     ['pitch_wheel_change', dtime, channel, pitch_wheel]87     ['text_event', dtime, text]88     ['copyright_text_event', dtime, text]89     ['track_name', dtime, text]90     ['instrument_name', dtime, text]91     ['lyric', dtime, text]92     ['marker', dtime, text]93     ['cue_point', dtime, text]94     ['text_event_08', dtime, text]95     ['text_event_09', dtime, text]96     ['text_event_0a', dtime, text]97     ['text_event_0b', dtime, text]98     ['text_event_0c', dtime, text]99     ['text_event_0d', dtime, text]100     ['text_event_0e', dtime, text]101     ['text_event_0f', dtime, text]102     ['end_track', dtime]103     ['set_tempo', dtime, tempo]104     ['smpte_offset', dtime, hr, mn, se, fr, ff]105     ['time_signature', dtime, nn, dd, cc, bb]106     ['key_signature', dtime, sf, mi]107     ['sequencer_specific', dtime, raw]108     ['raw_meta_event', dtime, command(0-255), raw]109     ['sysex_f0', dtime, raw]110     ['sysex_f7', dtime, raw]111     ['song_position', dtime, song_pos]112     ['song_select', dtime, song_number]113     ['tune_request', dtime]114 115  DATA TYPES116     channel = a value 0 to 15117     controller = 0 to 127 (see http://www.pjb.com.au/muscript/gm.html#cc )118     dtime = time measured in "ticks", 0 to 268435455119     velocity = a value 0 (soft) to 127 (loud)120     note = a value 0 to 127  (middle-C is 60)121     patch = 0 to 127 (see http://www.pjb.com.au/muscript/gm.html )122     pitch_wheel = a value -8192 to 8191 (0x1FFF)123     raw = bytes, of length 0 or more  (for sysex events see below)124     sequence_number = a value 0 to 65,535 (0xFFFF)125     song_pos = a value 0 to 16,383 (0x3FFF)126     song_number = a value 0 to 127127     tempo = microseconds per crochet (quarter-note), 0 to 16777215128     text = bytes, of length 0 or more129     ticks = the number of ticks per crochet (quarter-note)130 131   In sysex_f0 events, the raw data must not start with a \xF0 byte,132   since this gets added automatically;133   but it must end with an explicit \xF7 byte!134   In the very unlikely case that you ever need to split sysex data135   into one sysex_f0 followed by one or more sysex_f7s, then only the136   last of those sysex_f7 events must end with the explicit \xF7 byte137   (again, the raw data of individual sysex_f7 events must not start138   with any \xF7 byte, since this gets added automatically).139 140   Since version 6.4, text data is in bytes, not in a ISO-8859-1 string.141 142 143  GOING THROUGH A SCORE WITHIN A PYTHON PROGRAM144    channels = {2,3,5,8,13}145    itrack = 1   # skip 1st element which is ticks146    while itrack < len(score):147        for event in score[itrack]:148            if event[0] == 'note':   # for example,149                pass  # do something to all notes150            # or, to work on events in only particular channels...151            channel_index = MIDI.Event2channelindex.get(event[0], False)152            if channel_index and (event[channel_index] in channels):153                pass  # do something to channels 2,3,5,8 and 13154        itrack += 1155 156'''157 158import sys, struct, copy159# sys.stdout = os.fdopen(sys.stdout.fileno(), 'wb')160Version = '6.7'161VersionDate = '20201120'162# 20201120 6.7 call to bytest() removed, and protect _unshift_ber_int163# 20160702 6.6 to_millisecs() now handles set_tempo across multiple Tracks164# 20150921 6.5 segment restores controllers as well as patch and tempo165# 20150914 6.4 text data is bytes or bytearray, not ISO-8859-1 strings166# 20150628 6.3 absent any set_tempo, default is 120bpm (see MIDI file spec 1.1)167# 20150101 6.2 all text events can be 8-bit; let user get the right encoding168# 20141231 6.1 fix _some_text_event; sequencer_specific data can be 8-bit169# 20141230 6.0 synth_specific data can be 8-bit170# 20120504 5.9 add the contents of mid_opus_tracks()171# 20120208 5.8 fix num_notes_by_channel() ; should be a dict172# 20120129 5.7 _encode handles empty tracks; score2stats num_notes_by_channel173# 20111111 5.6 fix patch 45 and 46 in Number2patch, should be Harp174# 20110129 5.5 add mix_opus_tracks() and event2alsaseq()175# 20110126 5.4 "previous message repeated N times" to save space on stderr176# 20110125 5.2 opus2score terminates unended notes at the end of the track177# 20110124 5.1 the warnings in midi2opus display track_num178# 21110122 5.0 if garbage, midi2opus returns the opus so far179# 21110119 4.9 non-ascii chars stripped out of the text_events180# 21110110 4.8 note_on with velocity=0 treated as a note-off181# 21110108 4.6 unknown F-series event correctly eats just one byte182# 21011010 4.2 segment() uses start_time, end_time named params183# 21011005 4.1 timeshift() must not pad the set_tempo command184# 21011003 4.0 pitch2note_event must be chapitch2note_event185# 21010918 3.9 set_sequence_number supported, FWIW186# 20100913 3.7 many small bugfixes; passes all tests187# 20100910 3.6 concatenate_scores enforce ticks=1000, just like merge_scores188# 20100908 3.5 minor bugs fixed in score2stats189# 20091104 3.4 tune_request now supported190# 20091104 3.3 fixed bug in decoding song_position and song_select191# 20091104 3.2 unsupported: set_sequence_number tune_request raw_data192# 20091101 3.1 document how to traverse a score within Python193# 20091021 3.0 fixed bug in score2stats detecting GM-mode = 0194# 20091020 2.9 score2stats reports GM-mode and bank msb,lsb events195# 20091019 2.8 in merge_scores, channel 9 must remain channel 9 (in GM)196# 20091018 2.7 handles empty tracks gracefully197# 20091015 2.6 grep() selects channels198# 20091010 2.5 merge_scores reassigns channels to avoid conflicts199# 20091010 2.4 fixed bug in to_millisecs which now only does opusses200# 20091010 2.3 score2stats returns channels & patch_changes, by_track & total201# 20091010 2.2 score2stats() returns also pitches and percussion dicts202# 20091010 2.1 bugs: >= not > in segment, to notice patch_change at time 0203# 20091010 2.0 bugs: spurious pop(0) ( in _decode sysex204# 20091008 1.9 bugs: ISO decoding in sysex; str( not int( in note-off warning205# 20091008 1.8 add concatenate_scores()206# 20091006 1.7 score2stats() measures nticks and ticks_per_quarter207# 20091004 1.6 first mix_scores() and merge_scores()208# 20090424 1.5 timeshift() bugfix: earliest only sees events after from_time209# 20090330 1.4 timeshift() has also a from_time argument210# 20090322 1.3 timeshift() has also a start_time argument211# 20090319 1.2 add segment() and timeshift()212# 20090301 1.1 add to_millisecs()213 214_previous_warning = ''  # 5.4215_previous_times = 0     # 5.4216#------------------------------- Encoding stuff --------------------------217 218def opus2midi(opus=[]):219    r'''The argument is a list: the first item in the list is the "ticks"220parameter, the others are the tracks. Each track is a list221of midi-events, and each event is itself a list; see above.222opus2midi() returns a bytestring of the MIDI, which can then be223written either to a file opened in binary mode (mode='wb'),224or to stdout by means of:   sys.stdout.buffer.write()225 226my_opus = [227    96, 228    [   # track 0:229        ['patch_change', 0, 1, 8],   # and these are the events...230        ['note_on',   5, 1, 25, 96],231        ['note_off', 96, 1, 25, 0],232        ['note_on',   0, 1, 29, 96],233        ['note_off', 96, 1, 29, 0],234    ],   # end of track 0235]236my_midi = opus2midi(my_opus)237sys.stdout.buffer.write(my_midi)238'''239    if len(opus) < 2:240        opus=[1000, [],]241    tracks = copy.deepcopy(opus)242    ticks = int(tracks.pop(0))243    ntracks = len(tracks)244    if ntracks == 1:245        format = 0246    else:247        format = 1248 249    my_midi = b"MThd\x00\x00\x00\x06"+struct.pack('>HHH',format,ntracks,ticks)250    for track in tracks:251        events = _encode(track)252        my_midi += b'MTrk' + struct.pack('>I',len(events)) + events253    _clean_up_warnings()254    return my_midi255 256 257def score2opus(score=None):258    r'''259The argument is a list: the first item in the list is the "ticks"260parameter, the others are the tracks. Each track is a list261of score-events, and each event is itself a list.  A score-event262is similar to an opus-event (see above), except that in a score:263 1) the times are expressed as an absolute number of ticks264    from the track's start time265 2) the pairs of 'note_on' and 'note_off' events in an "opus"266    are abstracted into a single 'note' event in a "score":267    ['note', start_time, duration, channel, pitch, velocity]268score2opus() returns a list specifying the equivalent "opus".269 270my_score = [271    96,272    [   # track 0:273        ['patch_change', 0, 1, 8],274        ['note', 5, 96, 1, 25, 96],275        ['note', 101, 96, 1, 29, 96]276    ],   # end of track 0277]278my_opus = score2opus(my_score)279'''280    if len(score) < 2:281        score=[1000, [],]282    tracks = copy.deepcopy(score)283    ticks = int(tracks.pop(0))284    opus_tracks = []285    for scoretrack in tracks:286        time2events = dict([])287        for scoreevent in scoretrack:288            if scoreevent[0] == 'note':289                note_on_event = ['note_on',scoreevent[1],290                 scoreevent[3],scoreevent[4],scoreevent[5]]291                note_off_event = ['note_off',scoreevent[1]+scoreevent[2],292                 scoreevent[3],scoreevent[4],scoreevent[5]]293                if time2events.get(note_on_event[1]):294                   time2events[note_on_event[1]].append(note_on_event)295                else:296                   time2events[note_on_event[1]] = [note_on_event,]297                if time2events.get(note_off_event[1]):298                   time2events[note_off_event[1]].append(note_off_event)299                else:300                   time2events[note_off_event[1]] = [note_off_event,]301                continue302            if time2events.get(scoreevent[1]):303               time2events[scoreevent[1]].append(scoreevent)304            else:305               time2events[scoreevent[1]] = [scoreevent,]306 307        sorted_times = []  # list of keys308        for k in time2events.keys():309            sorted_times.append(k)310        sorted_times.sort()311 312        sorted_events = []  # once-flattened list of values sorted by key313        for time in sorted_times:314            sorted_events.extend(time2events[time])315 316        abs_time = 0317        for event in sorted_events:  # convert abs times => delta times318            delta_time = event[1] - abs_time319            abs_time = event[1]320            event[1] = delta_time321        opus_tracks.append(sorted_events)322    opus_tracks.insert(0,ticks)323    _clean_up_warnings()324    return opus_tracks325 326def score2midi(score=None):327    r'''328Translates a "score" into MIDI, using score2opus() then opus2midi()329'''330    return opus2midi(score2opus(score))331 332#--------------------------- Decoding stuff ------------------------333 334def midi2opus(midi=b''):335    r'''Translates MIDI into a "opus".  For a description of the336"opus" format, see opus2midi()337'''338    my_midi=bytearray(midi)339    if len(my_midi) < 4:340        _clean_up_warnings()341        return [1000,[],]342    id = bytes(my_midi[0:4])343    if id != b'MThd':344        _warn("midi2opus: midi starts with "+str(id)+" instead of 'MThd'")345        _clean_up_warnings()346        return [1000,[],]347    [length, format, tracks_expected, ticks] = struct.unpack(348     '>IHHH', bytes(my_midi[4:14]))349    if length != 6:350        _warn("midi2opus: midi header length was "+str(length)+" instead of 6")351        _clean_up_warnings()352        return [1000,[],]353    my_opus = [ticks,]354    my_midi = my_midi[14:]355    track_num = 1   # 5.1356    while len(my_midi) >= 8:357        track_type   = bytes(my_midi[0:4])358        if track_type != b'MTrk':359            _warn('midi2opus: Warning: track #'+str(track_num)+' type is '+str(track_type)+" instead of b'MTrk'")360        [track_length] = struct.unpack('>I', my_midi[4:8])361        my_midi = my_midi[8:]362        if track_length > len(my_midi):363            _warn('midi2opus: track #'+str(track_num)+' length '+str(track_length)+' is too large')364            _clean_up_warnings()365            return my_opus   # 5.0366        my_midi_track = my_midi[0:track_length]367        my_track = _decode(my_midi_track)368        my_opus.append(my_track)369        my_midi = my_midi[track_length:]370        track_num += 1   # 5.1371    _clean_up_warnings()372    return my_opus373 374def opus2score(opus=[]):375    r'''For a description of the "opus" and "score" formats,376see opus2midi() and score2opus().377'''378    if len(opus) < 2:379        _clean_up_warnings()380        return [1000,[],]381    tracks = copy.deepcopy(opus)  # couple of slices probably quicker...382    ticks = int(tracks.pop(0))383    score = [ticks,]384    for opus_track in tracks:385        ticks_so_far = 0386        score_track = []387        chapitch2note_on_events = dict([])   # 4.0388        for opus_event in opus_track:389            ticks_so_far += opus_event[1]390            if opus_event[0] == 'note_off' or (opus_event[0] == 'note_on' and opus_event[4] == 0):  # 4.8391                cha = opus_event[2]392                pitch = opus_event[3]393                key = cha*128 + pitch394                if chapitch2note_on_events.get(key):395                    new_event = chapitch2note_on_events[key].pop(0)396                    new_event[2] = ticks_so_far - new_event[1]397                    score_track.append(new_event)398                elif pitch > 127:399                    pass #_warn('opus2score: note_off with no note_on, bad pitch='+str(pitch))400                else:401                    pass #_warn('opus2score: note_off with no note_on cha='+str(cha)+' pitch='+str(pitch))402            elif opus_event[0] == 'note_on':403                cha = opus_event[2]404                pitch = opus_event[3]405                key = cha*128 + pitch406                new_event = ['note',ticks_so_far,0,cha,pitch, opus_event[4]]407                if chapitch2note_on_events.get(key):408                    chapitch2note_on_events[key].append(new_event)409                else:410                    chapitch2note_on_events[key] = [new_event,]411            else:412                opus_event[1] = ticks_so_far413                score_track.append(opus_event)414        # check for unterminated notes (Oisín) -- 5.2415        for chapitch in chapitch2note_on_events:416            note_on_events = chapitch2note_on_events[chapitch]417            for new_e in note_on_events:418                new_e[2] = ticks_so_far - new_e[1]419                score_track.append(new_e)420                pass #_warn("opus2score: note_on with no note_off cha="+str(new_e[3])+' pitch='+str(new_e[4])+'; adding note_off at end')421        score.append(score_track)422    _clean_up_warnings()423    return score424 425def midi2score(midi=b''):426    r'''427Translates MIDI into a "score", using midi2opus() then opus2score()428'''429    return opus2score(midi2opus(midi))430 431def midi2ms_score(midi=b''):432    r'''433Translates MIDI into a "score" with one beat per second and one434tick per millisecond, using midi2opus() then to_millisecs()435then opus2score()436'''437    return opus2score(to_millisecs(midi2opus(midi)))438 439#------------------------ Other Transformations ---------------------440 441def to_millisecs(old_opus=None):442    r'''Recallibrates all the times in an "opus" to use one beat443per second and one tick per millisecond.  This makes it444hard to retrieve any information about beats or barlines,445but it does make it easy to mix different scores together.446'''447    if old_opus == None:448        return [1000,[],]449    try:450        old_tpq  = int(old_opus[0])451    except IndexError:   # 5.0452        _warn('to_millisecs: the opus '+str(type(old_opus))+' has no elements')453        return [1000,[],]454    new_opus = [1000,]455    # 6.7 first go through building a table of set_tempos by absolute-tick456    ticks2tempo = {}457    itrack = 1458    while itrack < len(old_opus):459        ticks_so_far = 0460        for old_event in old_opus[itrack]:461            if old_event[0] == 'note':462                raise TypeError('to_millisecs needs an opus, not a score')463            ticks_so_far += old_event[1]464            if old_event[0] == 'set_tempo':465                ticks2tempo[ticks_so_far] = old_event[2]466        itrack += 1467    # then get the sorted-array of their keys468    tempo_ticks = []  # list of keys469    for k in ticks2tempo.keys():470        tempo_ticks.append(k)471    tempo_ticks.sort()472    # then go through converting to millisec, testing if the next473    # set_tempo lies before the next track-event, and using it if so.474    itrack = 1475    while itrack < len(old_opus):476        ms_per_old_tick = 500.0 / old_tpq  # float: will round later 6.3477        i_tempo_ticks = 0478        ticks_so_far = 0479        ms_so_far = 0.0480        previous_ms_so_far = 0.0481        new_track = [['set_tempo',0,1000000],]  # new "crochet" is 1 sec482        for old_event in old_opus[itrack]:483            # detect if ticks2tempo has something before this event484            # 20160702 if ticks2tempo is at the same time, leave it485            event_delta_ticks = old_event[1]486            if (i_tempo_ticks < len(tempo_ticks) and487              tempo_ticks[i_tempo_ticks] < (ticks_so_far + old_event[1])):488                delta_ticks = tempo_ticks[i_tempo_ticks] - ticks_so_far489                ms_so_far += (ms_per_old_tick * delta_ticks)490                ticks_so_far = tempo_ticks[i_tempo_ticks]491                ms_per_old_tick = ticks2tempo[ticks_so_far] / (1000.0*old_tpq)492                i_tempo_ticks += 1493                event_delta_ticks -= delta_ticks494            new_event = copy.deepcopy(old_event)  # now handle the new event495            ms_so_far += (ms_per_old_tick * old_event[1])496            new_event[1] = round(ms_so_far - previous_ms_so_far)497            if old_event[0] != 'set_tempo':498                previous_ms_so_far = ms_so_far499                new_track.append(new_event)500            ticks_so_far += event_delta_ticks501        new_opus.append(new_track)502        itrack += 1503    _clean_up_warnings()504    return new_opus505 506def event2alsaseq(event=None):   # 5.5507    r'''Converts an event into the format needed by the alsaseq module,508http://pp.com.mx/python/alsaseq509The type of track (opus or score) is autodetected.510'''511    pass512 513def grep(score=None, channels=None):514    r'''Returns a "score" containing only the channels specified515'''516    if score == None:517        return [1000,[],]518    ticks = score[0]519    new_score = [ticks,]520    if channels == None:521        return new_score522    channels = set(channels)523    global Event2channelindex524    itrack = 1525    while itrack < len(score):526        new_score.append([])527        for event in score[itrack]:528            channel_index = Event2channelindex.get(event[0], False)529            if channel_index:530                if event[channel_index] in channels:531                    new_score[itrack].append(event)532            else:533                new_score[itrack].append(event)534        itrack += 1535    return new_score536 537def play_score(score=None):538    r'''Converts the "score" to midi, and feeds it into 'aplaymidi -'539'''540    if score == None:541        return542    import subprocess543    pipe = subprocess.Popen(['aplaymidi','-'], stdin=subprocess.PIPE)544    if score_type(score) == 'opus':545        pipe.stdin.write(opus2midi(score))546    else:547        pipe.stdin.write(score2midi(score))548    pipe.stdin.close()549 550def 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}):551    r'''Returns a "score" shifted in time by "shift" ticks, or shifted552so that the first event starts at "start_time" ticks.553 554If "from_time" is specified, only those events in the score555that begin after it are shifted. If "start_time" is less than556"from_time" (or "shift" is negative), then the intermediate557notes are deleted, though patch-change events are preserved.558 559If "tracks" are specified, then only those tracks get shifted.560"tracks" can be a list, tuple or set; it gets converted to set561internally.562 563It is deprecated to specify both "shift" and "start_time".564If this does happen, timeshift() will print a warning to565stderr and ignore the "shift" argument.566 567If "shift" is negative and sufficiently large that it would568leave some event with a negative tick-value, then the score569is shifted so that the first event occurs at time 0. This570also occurs if "start_time" is negative, and is also the571default if neither "shift" nor "start_time" are specified.572'''573    #_warn('tracks='+str(tracks))574    if score == None or len(score) < 2:575        return [1000, [],]576    new_score = [score[0],]577    my_type = score_type(score)578    if my_type == '':579        return new_score580    if my_type == 'opus':581        _warn("timeshift: opus format is not supported\n")582        # _clean_up_scores()  6.2; doesn't exist! what was it supposed to do?583        return new_score584    if not (shift == None) and not (start_time == None):585        _warn("timeshift: shift and start_time specified: ignoring shift\n")586        shift = None587    if shift == None:588        if (start_time == None) or (start_time < 0):589            start_time = 0590        # shift = start_time - from_time591 592    i = 1   # ignore first element (ticks)593    tracks = set(tracks)  # defend against tuples and lists594    earliest = 1000000000595    if not (start_time == None) or shift < 0:  # first find the earliest event596        while i < len(score):597            if len(tracks) and not ((i-1) in tracks):598                i += 1599                continue600            for event in score[i]:601                 if event[1] < from_time:602                     continue  # just inspect the to_be_shifted events603                 if event[1] < earliest:604                     earliest = event[1]605            i += 1606    if earliest > 999999999:607        earliest = 0608    if shift == None:609        shift = start_time - earliest610    elif (earliest + shift) < 0:611        start_time = 0612        shift = 0 - earliest613 614    i = 1   # ignore first element (ticks)615    while i < len(score):616        if len(tracks) == 0 or not ((i-1) in tracks):  # 3.8617            new_score.append(score[i])618            i += 1619            continue620        new_track = []621        for event in score[i]:622            new_event = list(event)623            #if new_event[1] == 0 and shift > 0 and new_event[0] != 'note':624            #    pass625            #elif new_event[1] >= from_time:626            if new_event[1] >= from_time:627                # 4.1 must not rightshift set_tempo628                if new_event[0] != 'set_tempo' or shift<0:629                    new_event[1] += shift630            elif (shift < 0) and (new_event[1] >= (from_time+shift)):631                continue632            new_track.append(new_event)633        if len(new_track) > 0:634            new_score.append(new_track)635        i += 1636    _clean_up_warnings()637    return new_score638 639def segment(score=None, start_time=None, end_time=None, start=0, end=100000000,640 tracks={0,1,2,3,4,5,6,7,8,10,11,12,13,14,15}):641    r'''Returns a "score" which is a segment of the one supplied642as the argument, beginning at "start_time" ticks and ending643at "end_time" ticks (or at the end if "end_time" is not supplied).644If the set "tracks" is specified, only those tracks will645be returned.646'''647    if score == None or len(score) < 2:648        return [1000, [],]649    if start_time == None:  # as of 4.2 start_time is recommended650        start_time = start  # start is legacy usage651    if end_time == None:    # likewise652        end_time = end653    new_score = [score[0],]654    my_type = score_type(score)655    if my_type == '':656        return new_score657    if my_type == 'opus':658        # more difficult (disconnecting note_on's from their note_off's)...659        _warn("segment: opus format is not supported\n")660        _clean_up_warnings()661        return new_score662    i = 1   # ignore first element (ticks); we count in ticks anyway663    tracks = set(tracks)  # defend against tuples and lists664    while i < len(score):665        if len(tracks) and not ((i-1) in tracks):666            i += 1667            continue668        new_track = []669        channel2cc_num  = {}     # most recent controller change before start670        channel2cc_val  = {}671        channel2cc_time = {}672        channel2patch_num  = {}  # keep most recent patch change before start673        channel2patch_time = {}674        set_tempo_num  = 500000 # most recent tempo change before start 6.3675        set_tempo_time = 0676        earliest_note_time = end_time677        for event in score[i]:678            if event[0] == 'control_change':  # 6.5679                cc_time = channel2cc_time.get(event[2]) or 0680                if (event[1] <= start_time) and (event[1] >= cc_time):681                    channel2cc_num[event[2]]  = event[3]682                    channel2cc_val[event[2]]  = event[4]683                    channel2cc_time[event[2]] = event[1]684            elif event[0] == 'patch_change':685                patch_time = channel2patch_time.get(event[2]) or 0686                if (event[1]<=start_time) and (event[1] >= patch_time):  # 2.0687                    channel2patch_num[event[2]]  = event[3]688                    channel2patch_time[event[2]] = event[1]689            elif event[0] == 'set_tempo':690                if (event[1]<=start_time) and (event[1]>=set_tempo_time): #6.4691                    set_tempo_num  = event[2]692                    set_tempo_time = event[1]693            if (event[1] >= start_time) and (event[1] <= end_time):694                new_track.append(event)695                if (event[0] == 'note') and (event[1] < earliest_note_time):696                    earliest_note_time = event[1]697        if len(new_track) > 0:698            new_track.append(['set_tempo', start_time, set_tempo_num])699            for c in channel2patch_num:700                new_track.append(['patch_change',start_time,c,channel2patch_num[c]],)701            for c in channel2cc_num:   # 6.5702                new_track.append(['control_change',start_time,c,channel2cc_num[c],channel2cc_val[c]])703            new_score.append(new_track)704        i += 1705    _clean_up_warnings()706    return new_score707 708def score_type(opus_or_score=None):709    r'''Returns a string, either 'opus' or 'score' or ''710'''711    if opus_or_score == None or str(type(opus_or_score)).find('list')<0 or len(opus_or_score) < 2:712        return ''713    i = 1   # ignore first element714    while i < len(opus_or_score):715        for event in opus_or_score[i]:716            if event[0] == 'note':717                return 'score'718            elif event[0] == 'note_on':719                return 'opus'720        i += 1721    return ''722 723def concatenate_scores(scores):724    r'''Concatenates a list of scores into one score.725If the scores differ in their "ticks" parameter,726they will all get converted to millisecond-tick format.727'''728    # the deepcopys are needed if the input_score's are refs to the same obj729    # e.g. if invoked by midisox's repeat()730    input_scores = _consistentise_ticks(scores)  # 3.7731    output_score = copy.deepcopy(input_scores[0])732    for input_score in input_scores[1:]:733        output_stats = score2stats(output_score)734        delta_ticks = output_stats['nticks']735        itrack = 1736        while itrack < len(input_score):737            if itrack >= len(output_score): # new output track if doesn't exist738                output_score.append([])739            for event in input_score[itrack]:740                output_score[itrack].append(copy.deepcopy(event))741                output_score[itrack][-1][1] += delta_ticks742            itrack += 1743    return output_score744 745def merge_scores(scores):746    r'''Merges a list of scores into one score.  A merged score comprises747all of the tracks from all of the input scores; un-merging is possible748by selecting just some of the tracks.  If the scores differ in their749"ticks" parameter, they will all get converted to millisecond-tick750format.  merge_scores attempts to resolve channel-conflicts,751but there are of course only 15 available channels...752'''753    input_scores = _consistentise_ticks(scores)  # 3.6754    output_score = [1000]755    channels_so_far = set()756    all_channels = {0,1,2,3,4,5,6,7,8,10,11,12,13,14,15}757    global Event2channelindex758    for input_score in input_scores:759        new_channels = set(score2stats(input_score).get('channels_total', []))760        new_channels.discard(9)  # 2.8 cha9 must remain cha9 (in GM)761        for channel in channels_so_far & new_channels:762            # consistently choose lowest avaiable, to ease testing763            free_channels = list(all_channels - (channels_so_far|new_channels))764            if len(free_channels) > 0:765                free_channels.sort()766                free_channel = free_channels[0]767            else:768                free_channel = None769                break770            itrack = 1771            while itrack < len(input_score):772                for input_event in input_score[itrack]:773                    channel_index=Event2channelindex.get(input_event[0],False)774                    if channel_index and input_event[channel_index]==channel:775                        input_event[channel_index] = free_channel776                itrack += 1777            channels_so_far.add(free_channel)778 779        channels_so_far |= new_channels780        output_score.extend(input_score[1:])781    return output_score782 783def _ticks(event):784    return event[1]785def mix_opus_tracks(input_tracks):   # 5.5786    r'''Mixes an array of tracks into one track.  A mixed track787cannot be un-mixed.  It is assumed that the tracks share the same788ticks parameter and the same tempo.789Mixing score-tracks is trivial (just insert all events into one array).790Mixing opus-tracks is only slightly harder, but it's common enough791that a dedicated function is useful.792'''793    output_score = [1000, []]794    for input_track in input_tracks:   # 5.8795        input_score = opus2score([1000, input_track])796        for event in input_score[1]:797            output_score[1].append(event)798    output_score[1].sort(key=_ticks) 799    output_opus = score2opus(output_score)800    return output_opus[1]801 802def mix_scores(scores):803    r'''Mixes a list of scores into one one-track score.804A mixed score cannot be un-mixed.  Hopefully the scores805have no undesirable channel-conflicts between them.806If the scores differ in their "ticks" parameter,807they will all get converted to millisecond-tick format.808'''809    input_scores = _consistentise_ticks(scores)  # 3.6810    output_score = [1000, []]811    for input_score in input_scores:812        for input_track in input_score[1:]:813            output_score[1].extend(input_track)814    return output_score815 816def score2stats(opus_or_score=None):817    r'''Returns a dict of some basic stats about the score, like818bank_select (list of tuples (msb,lsb)),819channels_by_track (list of lists), channels_total (set),820general_midi_mode (list),821ntracks, nticks, patch_changes_by_track (list of dicts),822num_notes_by_channel (list of numbers),823patch_changes_total (set),824percussion (dict histogram of channel 9 events),825pitches (dict histogram of pitches on channels other than 9),826pitch_range_by_track (list, by track, of two-member-tuples),827pitch_range_sum (sum over tracks of the pitch_ranges),828'''829    bank_select_msb = -1830    bank_select_lsb = -1831    bank_select = []832    channels_by_track = []833    channels_total    = set([])834    general_midi_mode = []835    num_notes_by_channel = dict([])836    patches_used_by_track  = []837    patches_used_total     = set([])838    patch_changes_by_track = []839    patch_changes_total    = set([])840    percussion = dict([]) # histogram of channel 9 "pitches"841    pitches    = dict([]) # histogram of pitch-occurrences channels 0-8,10-15842    pitch_range_sum = 0   # u pitch-ranges of each track843    pitch_range_by_track = []844    is_a_score = True845    if opus_or_score == None:846        return {'bank_select':[], 'channels_by_track':[], 'channels_total':[],847         'general_midi_mode':[], 'ntracks':0, 'nticks':0,848         'num_notes_by_channel':dict([]),849         'patch_changes_by_track':[], 'patch_changes_total':[],850         'percussion':{}, 'pitches':{}, 'pitch_range_by_track':[],851         'ticks_per_quarter':0, 'pitch_range_sum':0}852    ticks_per_quarter = opus_or_score[0]853    i = 1   # ignore first element, which is ticks854    nticks = 0855    while i < len(opus_or_score):856        highest_pitch = 0857        lowest_pitch = 128858        channels_this_track = set([])859        patch_changes_this_track = dict({})860        for event in opus_or_score[i]:861            if event[0] == 'note':862                num_notes_by_channel[event[3]] = num_notes_by_channel.get(event[3],0) + 1863                if event[3] == 9:864                    percussion[event[4]] = percussion.get(event[4],0) + 1865                else:866                    pitches[event[4]]    = pitches.get(event[4],0) + 1867                    if event[4] > highest_pitch:868                        highest_pitch = event[4]869                    if event[4] < lowest_pitch:870                        lowest_pitch = event[4]871                channels_this_track.add(event[3])872                channels_total.add(event[3])873                finish_time = event[1] + event[2]874                if finish_time > nticks:875                    nticks = finish_time876            elif event[0] == 'note_off' or (event[0] == 'note_on' and event[4] == 0):  # 4.8877                finish_time = event[1]878                if finish_time > nticks:879                    nticks = finish_time880            elif event[0] == 'note_on':881                is_a_score = False882                num_notes_by_channel[event[2]] = num_notes_by_channel.get(event[2],0) + 1883                if event[2] == 9:884                    percussion[event[3]] = percussion.get(event[3],0) + 1885                else:886                    pitches[event[3]]    = pitches.get(event[3],0) + 1887                    if event[3] > highest_pitch:888                        highest_pitch = event[3]889                    if event[3] < lowest_pitch:890                        lowest_pitch = event[3]891                channels_this_track.add(event[2])892                channels_total.add(event[2])893            elif event[0] == 'patch_change':894                patch_changes_this_track[event[2]] = event[3]895                patch_changes_total.add(event[3])896            elif event[0] == 'control_change':897                if event[3] == 0:  # bank select MSB898                    bank_select_msb = event[4]899                elif event[3] == 32:  # bank select LSB900                    bank_select_lsb = event[4]901                if bank_select_msb >= 0 and bank_select_lsb >= 0:902                    bank_select.append((bank_select_msb,bank_select_lsb))903                    bank_select_msb = -1904                    bank_select_lsb = -1905            elif event[0] == 'sysex_f0':906                if _sysex2midimode.get(event[2], -1) >= 0:907                    general_midi_mode.append(_sysex2midimode.get(event[2]))908            if is_a_score:909                if event[1] > nticks:910                    nticks = event[1]911            else:912                nticks += event[1]913        if lowest_pitch == 128:914            lowest_pitch = 0915        channels_by_track.append(channels_this_track)916        patch_changes_by_track.append(patch_changes_this_track)917        pitch_range_by_track.append((lowest_pitch,highest_pitch))918        pitch_range_sum += (highest_pitch-lowest_pitch)919        i += 1920 921    return {'bank_select':bank_select,922            'channels_by_track':channels_by_track,923            'channels_total':channels_total,924            'general_midi_mode':general_midi_mode,925            'ntracks':len(opus_or_score)-1,926            'nticks':nticks,927            'num_notes_by_channel':num_notes_by_channel,928            'patch_changes_by_track':patch_changes_by_track,929            'patch_changes_total':patch_changes_total,930            'percussion':percussion,931            'pitches':pitches,932            'pitch_range_by_track':pitch_range_by_track,933            'pitch_range_sum':pitch_range_sum,934            'ticks_per_quarter':ticks_per_quarter}935 936#----------------------------- Event stuff --------------------------937 938_sysex2midimode = {939    "\x7E\x7F\x09\x01\xF7": 1,940    "\x7E\x7F\x09\x02\xF7": 0,941    "\x7E\x7F\x09\x03\xF7": 2,942}943 944# Some public-access tuples:945MIDI_events = tuple('''note_off note_on key_after_touch946control_change patch_change channel_after_touch947pitch_wheel_change'''.split())948 949Text_events = tuple('''text_event copyright_text_event950track_name instrument_name lyric marker cue_point text_event_08951text_event_09 text_event_0a text_event_0b text_event_0c952text_event_0d text_event_0e text_event_0f'''.split())953 954Nontext_meta_events = tuple('''end_track set_tempo955smpte_offset time_signature key_signature sequencer_specific956raw_meta_event sysex_f0 sysex_f7 song_position song_select957tune_request'''.split())958# unsupported: raw_data959 960# Actually, 'tune_request' is is F-series event, not strictly a meta-event...961Meta_events = Text_events + Nontext_meta_events962All_events  = MIDI_events + Meta_events963 964# And three dictionaries:965Number2patch = {   # General MIDI patch numbers:9660:'Acoustic Grand',9671:'Bright Acoustic',9682:'Electric Grand',9693:'Honky-Tonk',9704:'Electric Piano 1',9715:'Electric Piano 2',9726:'Harpsichord',9737:'Clav',9748:'Celesta',9759:'Glockenspiel',97610:'Music Box',97711:'Vibraphone',97812:'Marimba',97913:'Xylophone',98014:'Tubular Bells',98115:'Dulcimer',98216:'Drawbar Organ',98317:'Percussive Organ',98418:'Rock Organ',98519:'Church Organ',98620:'Reed Organ',98721:'Accordion',98822:'Harmonica',98923:'Tango Accordion',99024:'Acoustic Guitar(nylon)',99125:'Acoustic Guitar(steel)',99226:'Electric Guitar(jazz)',99327:'Electric Guitar(clean)',99428:'Electric Guitar(muted)',99529:'Overdriven Guitar',99630:'Distortion Guitar',99731:'Guitar Harmonics',99832:'Acoustic Bass',99933:'Electric Bass(finger)',100034:'Electric Bass(pick)',100135:'Fretless Bass',100236:'Slap Bass 1',100337:'Slap Bass 2',100438:'Synth Bass 1',100539:'Synth Bass 2',100640:'Violin',100741:'Viola',100842:'Cello',100943:'Contrabass',101044:'Tremolo Strings',101145:'Pizzicato Strings',101246:'Orchestral Harp',101347:'Timpani',101448:'String Ensemble 1',101549:'String Ensemble 2',101650:'SynthStrings 1',101751:'SynthStrings 2',101852:'Choir Aahs',101953:'Voice Oohs',102054:'Synth Voice',102155:'Orchestra Hit',102256:'Trumpet',102357:'Trombone',102458:'Tuba',102559:'Muted Trumpet',102660:'French Horn',102761:'Brass Section',102862:'SynthBrass 1',102963:'SynthBrass 2',103064:'Soprano Sax',103165:'Alto Sax',103266:'Tenor Sax',103367:'Baritone Sax',103468:'Oboe',103569:'English Horn',103670:'Bassoon',103771:'Clarinet',103872:'Piccolo',103973:'Flute',104074:'Recorder',104175:'Pan Flute',104276:'Blown Bottle',104377:'Skakuhachi',104478:'Whistle',104579:'Ocarina',104680:'Lead 1 (square)',104781:'Lead 2 (sawtooth)',104882:'Lead 3 (calliope)',104983:'Lead 4 (chiff)',105084:'Lead 5 (charang)',105185:'Lead 6 (voice)',105286:'Lead 7 (fifths)',105387:'Lead 8 (bass+lead)',105488:'Pad 1 (new age)',105589:'Pad 2 (warm)',105690:'Pad 3 (polysynth)',105791:'Pad 4 (choir)',105892:'Pad 5 (bowed)',105993:'Pad 6 (metallic)',106094:'Pad 7 (halo)',106195:'Pad 8 (sweep)',106296:'FX 1 (rain)',106397:'FX 2 (soundtrack)',106498:'FX 3 (crystal)',106599:'FX 4 (atmosphere)',1066100:'FX 5 (brightness)',1067101:'FX 6 (goblins)',1068102:'FX 7 (echoes)',1069103:'FX 8 (sci-fi)',1070104:'Sitar',1071105:'Banjo',1072106:'Shamisen',1073107:'Koto',1074108:'Kalimba',1075109:'Bagpipe',1076110:'Fiddle',1077111:'Shanai',1078112:'Tinkle Bell',1079113:'Agogo',1080114:'Steel Drums',1081115:'Woodblock',1082116:'Taiko Drum',1083117:'Melodic Tom',1084118:'Synth Drum',1085119:'Reverse Cymbal',1086120:'Guitar Fret Noise',1087121:'Breath Noise',1088122:'Seashore',1089123:'Bird Tweet',1090124:'Telephone Ring',1091125:'Helicopter',1092126:'Applause',1093127:'Gunshot',1094}1095Notenum2percussion = {   # General MIDI Percussion (on Channel 9):109635:'Acoustic Bass Drum',109736:'Bass Drum 1',109837:'Side Stick',109938:'Acoustic Snare',110039:'Hand Clap',110140:'Electric Snare',110241:'Low Floor Tom',110342:'Closed Hi-Hat',110443:'High Floor Tom',110544:'Pedal Hi-Hat',110645:'Low Tom',110746:'Open Hi-Hat',110847:'Low-Mid Tom',110948:'Hi-Mid Tom',111049:'Crash Cymbal 1',111150:'High Tom',111251:'Ride Cymbal 1',111352:'Chinese Cymbal',111453:'Ride Bell',111554:'Tambourine',111655:'Splash Cymbal',111756:'Cowbell',111857:'Crash Cymbal 2',111958:'Vibraslap',112059:'Ride Cymbal 2',112160:'Hi Bongo',112261:'Low Bongo',112362:'Mute Hi Conga',112463:'Open Hi Conga',112564:'Low Conga',112665:'High Timbale',112766:'Low Timbale',112867:'High Agogo',112968:'Low Agogo',113069:'Cabasa',113170:'Maracas',113271:'Short Whistle',113372:'Long Whistle',113473:'Short Guiro',113574:'Long Guiro',113675:'Claves',113776:'Hi Wood Block',113877:'Low Wood Block',113978:'Mute Cuica',114079:'Open Cuica',114180:'Mute Triangle',114281:'Open Triangle',1143}1144 1145Event2channelindex = { 'note':3, 'note_off':2, 'note_on':2,1146 'key_after_touch':2, 'control_change':2, 'patch_change':2,1147 'channel_after_touch':2, 'pitch_wheel_change':21148}1149 1150################################################################1151# The code below this line is full of frightening things, all to1152# do with the actual encoding and decoding of binary MIDI data.1153 1154def _twobytes2int(byte_a):1155    r'''decode a 16 bit quantity from two bytes,'''1156    return (byte_a[1] | (byte_a[0] << 8))1157 1158def _int2twobytes(int_16bit):1159    r'''encode a 16 bit quantity into two bytes,'''1160    return bytes([(int_16bit>>8) & 0xFF, int_16bit & 0xFF])1161 1162def _read_14_bit(byte_a):1163    r'''decode a 14 bit quantity from two bytes,'''1164    return (byte_a[0] | (byte_a[1] << 7))1165 1166def _write_14_bit(int_14bit):1167    r'''encode a 14 bit quantity into two bytes,'''1168    return bytes([int_14bit & 0x7F, (int_14bit>>7) & 0x7F])1169 1170def _ber_compressed_int(integer):1171    r'''BER compressed integer (not an ASN.1 BER, see perlpacktut for1172details).  Its bytes represent an unsigned integer in base 128,1173most significant digit first, with as few digits as possible.1174Bit eight (the high bit) is set on each byte except the last.1175'''1176    ber = bytearray(b'')1177    seven_bits = 0x7F & integer1178    ber.insert(0, seven_bits)  # XXX surely should convert to a char ?1179    integer >>= 71180    while integer > 0:1181        seven_bits = 0x7F & integer1182        ber.insert(0, 0x80|seven_bits)  # XXX surely should convert to a char ?1183        integer >>= 71184    return ber1185 1186def _unshift_ber_int(ba):1187    r'''Given a bytearray, returns a tuple of (the ber-integer at the1188start, and the remainder of the bytearray).1189'''1190    if not len(ba):   # 6.71191        _warn('_unshift_ber_int: no integer found')1192        return ((0, b""))1193    byte = ba.pop(0)1194    integer = 01195    while True:1196        integer += (byte & 0x7F)1197        if not (byte & 0x80):1198            return ((integer, ba))1199        if not len(ba):1200            _warn('_unshift_ber_int: no end-of-integer found')

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