CoolFace
Apppublic

Chickaboo/Advanced-MIDI-Renderer

sourceHugging Facecc-by-nc-4.0updated 4mo agoView on Hugging Face
1likes
TMIDIX.py19254 linesDownload Raw Back to root
1#! /usr/bin/python32 3r'''4###############################################################################5#6#	Tegridy MIDI X Module (TMIDI X / tee-midi eks)7#8#   NOTE: TMIDI X Module starts after the partial MIDI.py module @ line 14619#10#	Based upon MIDI.py module v.6.7. by Peter Billam / pjb.com.au11#12#	Project Los Angeles13#14#	Tegridy Code 202615#16#   https://github.com/Tegridy-Code/Project-Los-Angeles17#18###################################################################################19#20#   Copyright 2026 Project Los Angeles / Tegridy Code21#22#   Licensed under the Apache License, Version 2.0 (the "License");23#   you may not use this file except in compliance with the License.24#   You may obtain a copy of the License at25#26#   http://www.apache.org/licenses/LICENSE-2.027#28#   Unless required by applicable law or agreed to in writing, software29#   distributed under the License is distributed on an "AS IS" BASIS,30#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.31#   See the License for the specific language governing permissions and32#   limitations under the License.33#34###################################################################################35#36#	PARTIAL MIDI.py Module v.6.7. by Peter Billam37#   Please see TMIDI 2.3/tegridy-tools repo for full MIDI.py module code38# 39#   Or you can always download the latest full version from:40#41#   https://pjb.com.au/42#   https://peterbillam.gitlab.io/miditools/43#	44#	Copyright 2020 Peter Billam45#46###################################################################################47'''48 49###################################################################################50 51__version__ = "26.5.19" # TMIDIX version52 53###################################################################################54 55print('=' * 70)56print('TMIDIX Python module')57print('Version:', __version__)58print('=' * 70)59print('Loading module...')60 61###################################################################################62 63import sys, struct, copy64 65###################################################################################66 67Version = '6.7'68VersionDate = '20201120'69 70###################################################################################71 72_previous_warning = ''  # 5.473_previous_times = 0     # 5.474_no_warning = False75 76###################################################################################77 78def set_no_warning(value: bool):79    global _no_warning80    _no_warning = value81    82###################################################################################83 84#------------------------------- Encoding stuff --------------------------85 86def opus2midi(opus=[], text_encoding='ISO-8859-1'):87    r'''The argument is a list: the first item in the list is the "ticks"88parameter, the others are the tracks. Each track is a list89of midi-events, and each event is itself a list; see above.90opus2midi() returns a bytestring of the MIDI, which can then be91written either to a file opened in binary mode (mode='wb'),92or to stdout by means of:   sys.stdout.buffer.write()93 94my_opus = [95    96, 96    [   # track 0:97        ['patch_change', 0, 1, 8],   # and these are the events...98        ['note_on',   5, 1, 25, 96],99        ['note_off', 96, 1, 25, 0],100        ['note_on',   0, 1, 29, 96],101        ['note_off', 96, 1, 29, 0],102    ],   # end of track 0103]104my_midi = opus2midi(my_opus)105sys.stdout.buffer.write(my_midi)106'''107    if len(opus) < 2:108        opus=[1000, [],]109    tracks = copy.deepcopy(opus)110    ticks = int(tracks.pop(0))111    ntracks = len(tracks)112    if ntracks == 1:113        format = 0114    else:115        format = 1116 117    my_midi = b"MThd\x00\x00\x00\x06"+struct.pack('>HHH',format,ntracks,ticks)118    for track in tracks:119        events = _encode(track, text_encoding=text_encoding)120        my_midi += b'MTrk' + struct.pack('>I',len(events)) + events121    _clean_up_warnings()122    return my_midi123 124 125def score2opus(score=None, text_encoding='ISO-8859-1'):126    r'''127The argument is a list: the first item in the list is the "ticks"128parameter, the others are the tracks. Each track is a list129of score-events, and each event is itself a list.  A score-event130is similar to an opus-event (see above), except that in a score:131 1) the times are expressed as an absolute number of ticks132    from the track's start time133 2) the pairs of 'note_on' and 'note_off' events in an "opus"134    are abstracted into a single 'note' event in a "score":135    ['note', start_time, duration, channel, pitch, velocity]136score2opus() returns a list specifying the equivalent "opus".137 138my_score = [139    96,140    [   # track 0:141        ['patch_change', 0, 1, 8],142        ['note', 5, 96, 1, 25, 96],143        ['note', 101, 96, 1, 29, 96]144    ],   # end of track 0145]146my_opus = score2opus(my_score)147'''148    if len(score) < 2:149        score=[1000, [],]150    tracks = copy.deepcopy(score)151    ticks = int(tracks.pop(0))152    opus_tracks = []153    for scoretrack in tracks:154        time2events = dict([])155        for scoreevent in scoretrack:156            if scoreevent[0] == 'note':157                note_on_event = ['note_on',scoreevent[1],158                 scoreevent[3],scoreevent[4],scoreevent[5]]159                note_off_event = ['note_off',scoreevent[1]+scoreevent[2],160                 scoreevent[3],scoreevent[4],scoreevent[5]]161                if time2events.get(note_on_event[1]):162                   time2events[note_on_event[1]].append(note_on_event)163                else:164                   time2events[note_on_event[1]] = [note_on_event,]165                if time2events.get(note_off_event[1]):166                   time2events[note_off_event[1]].append(note_off_event)167                else:168                   time2events[note_off_event[1]] = [note_off_event,]169                continue170            if time2events.get(scoreevent[1]):171               time2events[scoreevent[1]].append(scoreevent)172            else:173               time2events[scoreevent[1]] = [scoreevent,]174 175        sorted_times = []  # list of keys176        for k in time2events.keys():177            sorted_times.append(k)178        sorted_times.sort()179 180        sorted_events = []  # once-flattened list of values sorted by key181        for time in sorted_times:182            sorted_events.extend(time2events[time])183 184        abs_time = 0185        for event in sorted_events:  # convert abs times => delta times186            delta_time = event[1] - abs_time187            abs_time = event[1]188            event[1] = delta_time189        opus_tracks.append(sorted_events)190    opus_tracks.insert(0,ticks)191    _clean_up_warnings()192    return opus_tracks193 194def score2midi(score=None, text_encoding='ISO-8859-1'):195    r'''196Translates a "score" into MIDI, using score2opus() then opus2midi()197'''198    return opus2midi(score2opus(score, text_encoding), text_encoding)199 200#--------------------------- Decoding stuff ------------------------201 202def midi2opus(midi=b'', do_not_check_MIDI_signature=False):203    r'''Translates MIDI into a "opus".  For a description of the204"opus" format, see opus2midi()205'''206    my_midi=bytearray(midi)207    if len(my_midi) < 4:208        _clean_up_warnings()209        return [1000,[],]210    id = bytes(my_midi[0:4])211    if id != b'MThd':212        _warn("midi2opus: midi starts with "+str(id)+" instead of 'MThd'")213        _clean_up_warnings()214        if do_not_check_MIDI_signature == False:215          return [1000,[],]216    [length, format, tracks_expected, ticks] = struct.unpack(217     '>IHHH', bytes(my_midi[4:14]))218    if length != 6:219        _warn("midi2opus: midi header length was "+str(length)+" instead of 6")220        _clean_up_warnings()221        return [1000,[],]222    my_opus = [ticks,]223    my_midi = my_midi[14:]224    track_num = 1   # 5.1225    while len(my_midi) >= 8:226        track_type   = bytes(my_midi[0:4])227        if track_type != b'MTrk':228            #_warn('midi2opus: Warning: track #'+str(track_num)+' type is '+str(track_type)+" instead of b'MTrk'")229            pass230        [track_length] = struct.unpack('>I', my_midi[4:8])231        my_midi = my_midi[8:]232        if track_length > len(my_midi):233            _warn('midi2opus: track #'+str(track_num)+' length '+str(track_length)+' is too large')234            _clean_up_warnings()235            return my_opus   # 5.0236        my_midi_track = my_midi[0:track_length]237        my_track = _decode(my_midi_track)238        my_opus.append(my_track)239        my_midi = my_midi[track_length:]240        track_num += 1   # 5.1241    _clean_up_warnings()242    return my_opus243 244def opus2score(opus=[]):245    r'''For a description of the "opus" and "score" formats,246see opus2midi() and score2opus().247'''248    if len(opus) < 2:249        _clean_up_warnings()250        return [1000,[],]251    tracks = copy.deepcopy(opus)  # couple of slices probably quicker...252    ticks = int(tracks.pop(0))253    score = [ticks,]254    for opus_track in tracks:255        ticks_so_far = 0256        score_track = []257        chapitch2note_on_events = dict([])   # 4.0258        for opus_event in opus_track:259            ticks_so_far += opus_event[1]260            if opus_event[0] == 'note_off' or (opus_event[0] == 'note_on' and opus_event[4] == 0):  # 4.8261                cha = opus_event[2]262                pitch = opus_event[3]263                key = cha*128 + pitch264                if chapitch2note_on_events.get(key):265                    new_event = chapitch2note_on_events[key].pop(0)266                    new_event[2] = ticks_so_far - new_event[1]267                    score_track.append(new_event)268                elif pitch > 127:269                    pass #_warn('opus2score: note_off with no note_on, bad pitch='+str(pitch))270                else:271                    pass #_warn('opus2score: note_off with no note_on cha='+str(cha)+' pitch='+str(pitch))272            elif opus_event[0] == 'note_on':273                cha = opus_event[2]274                pitch = opus_event[3]275                key = cha*128 + pitch276                new_event = ['note',ticks_so_far,0,cha,pitch, opus_event[4]]277                if chapitch2note_on_events.get(key):278                    chapitch2note_on_events[key].append(new_event)279                else:280                    chapitch2note_on_events[key] = [new_event,]281            else:282                opus_event[1] = ticks_so_far283                score_track.append(opus_event)284        # check for unterminated notes (Oisín) -- 5.2285        for chapitch in chapitch2note_on_events:286            note_on_events = chapitch2note_on_events[chapitch]287            for new_e in note_on_events:288                new_e[2] = ticks_so_far - new_e[1]289                score_track.append(new_e)290                pass #_warn("opus2score: note_on with no note_off cha="+str(new_e[3])+' pitch='+str(new_e[4])+'; adding note_off at end')291        score.append(score_track)292    _clean_up_warnings()293    return score294 295def midi2score(midi=b'', do_not_check_MIDI_signature=False):296    r'''297Translates MIDI into a "score", using midi2opus() then opus2score()298'''299    return opus2score(midi2opus(midi, do_not_check_MIDI_signature))300 301def midi2ms_score(midi=b'', do_not_check_MIDI_signature=False):302    r'''303Translates MIDI into a "score" with one beat per second and one304tick per millisecond, using midi2opus() then to_millisecs()305then opus2score()306'''307    return opus2score(to_millisecs(midi2opus(midi, do_not_check_MIDI_signature)))308 309def midi2single_track_ms_score(midi_path_or_bytes, 310                                recalculate_channels = False, 311                                pass_old_timings_events= False, 312                                verbose = False, 313                                do_not_check_MIDI_signature=False314                                ):315    r'''316Translates MIDI into a single track "score" with 16 instruments and one beat per second and one317tick per millisecond318'''319 320    if type(midi_path_or_bytes) == bytes:321      midi_data = midi_path_or_bytes322 323    elif type(midi_path_or_bytes) == str:324      midi_data = open(midi_path_or_bytes, 'rb').read() 325 326    score = midi2score(midi_data, do_not_check_MIDI_signature)327 328    if recalculate_channels:329 330      events_matrixes = []331 332      itrack = 1333      events_matrixes_channels = []334      while itrack < len(score):335          events_matrix = []336          for event in score[itrack]:337              if event[0] == 'note' and event[3] != 9:338                event[3] = (16 * (itrack-1)) + event[3]339                if event[3] not in events_matrixes_channels:340                  events_matrixes_channels.append(event[3])341 342              events_matrix.append(event)343          events_matrixes.append(events_matrix)344          itrack += 1345 346      events_matrix1 = []347      for e in events_matrixes:348        events_matrix1.extend(e)349 350      if verbose:351        if len(events_matrixes_channels) > 16:352          print('MIDI has', len(events_matrixes_channels), 'instruments!', len(events_matrixes_channels) - 16, 'instrument(s) will be removed!')353 354      for e in events_matrix1:355        if e[0] == 'note' and e[3] != 9:356          if e[3] in events_matrixes_channels[:15]:357            if events_matrixes_channels[:15].index(e[3]) < 9:358              e[3] = events_matrixes_channels[:15].index(e[3])359            else:360              e[3] = events_matrixes_channels[:15].index(e[3])+1361          else:362            events_matrix1.remove(e)363        364        if e[0] in ['patch_change', 'control_change', 'channel_after_touch', 'key_after_touch', 'pitch_wheel_change'] and e[2] != 9:365          if e[2] in [e % 16 for e in events_matrixes_channels[:15]]:366            if [e % 16 for e in events_matrixes_channels[:15]].index(e[2]) < 9:367              e[2] = [e % 16 for e in events_matrixes_channels[:15]].index(e[2])368            else:369              e[2] = [e % 16 for e in events_matrixes_channels[:15]].index(e[2])+1370          else:371            events_matrix1.remove(e)372    373    else:374      events_matrix1 = []375      itrack = 1376     377      while itrack < len(score):378          for event in score[itrack]:379            events_matrix1.append(event)380          itrack += 1    381 382    opus = score2opus([score[0], events_matrix1])383    ms_score = opus2score(to_millisecs(opus, pass_old_timings_events=pass_old_timings_events))384 385    return ms_score386 387#------------------------ Other Transformations ---------------------388 389def to_millisecs(old_opus=None, desired_time_in_ms=1, pass_old_timings_events = False):390    r'''Recallibrates all the times in an "opus" to use one beat391per second and one tick per millisecond.  This makes it392hard to retrieve any information about beats or barlines,393but it does make it easy to mix different scores together.394'''395    if old_opus == None:396        return [1000 * desired_time_in_ms,[],]397    try:398        old_tpq  = int(old_opus[0])399    except IndexError:   # 5.0400        _warn('to_millisecs: the opus '+str(type(old_opus))+' has no elements')401        return [1000 * desired_time_in_ms,[],]402    new_opus = [1000 * desired_time_in_ms,]403    # 6.7 first go through building a table of set_tempos by absolute-tick404    ticks2tempo = {}405    itrack = 1406    while itrack < len(old_opus):407        ticks_so_far = 0408        for old_event in old_opus[itrack]:409            if old_event[0] == 'note':410                raise TypeError('to_millisecs needs an opus, not a score')411            ticks_so_far += old_event[1]412            if old_event[0] == 'set_tempo':413                ticks2tempo[ticks_so_far] = old_event[2]414        itrack += 1415    # then get the sorted-array of their keys416    tempo_ticks = []  # list of keys417    for k in ticks2tempo.keys():418        tempo_ticks.append(k)419    tempo_ticks.sort()420    # then go through converting to millisec, testing if the next421    # set_tempo lies before the next track-event, and using it if so.422    itrack = 1423    while itrack < len(old_opus):424        ms_per_old_tick = 400 / old_tpq  # float: will round later 6.3425        i_tempo_ticks = 0426        ticks_so_far = 0427        ms_so_far = 0.0428        previous_ms_so_far = 0.0429 430        if pass_old_timings_events:431          new_track = [['set_tempo',0,1000000 * desired_time_in_ms],['old_tpq', 0, old_tpq]]  # new "crochet" is 1 sec432        else:433          new_track = [['set_tempo',0,1000000 * desired_time_in_ms],]  # new "crochet" is 1 sec434        for old_event in old_opus[itrack]:435            # detect if ticks2tempo has something before this event436            # 20160702 if ticks2tempo is at the same time, leave it437            event_delta_ticks = old_event[1] * desired_time_in_ms438            if (i_tempo_ticks < len(tempo_ticks) and439              tempo_ticks[i_tempo_ticks] < (ticks_so_far + old_event[1]) * desired_time_in_ms):440                delta_ticks = tempo_ticks[i_tempo_ticks] - ticks_so_far441                ms_so_far += (ms_per_old_tick * delta_ticks * desired_time_in_ms)442                ticks_so_far = tempo_ticks[i_tempo_ticks]443                ms_per_old_tick = ticks2tempo[ticks_so_far] / (1000.0*old_tpq * desired_time_in_ms)444                i_tempo_ticks += 1445                event_delta_ticks -= delta_ticks446            new_event = copy.deepcopy(old_event)  # now handle the new event447            ms_so_far += (ms_per_old_tick * old_event[1] * desired_time_in_ms)448            new_event[1] = round(ms_so_far - previous_ms_so_far)449 450            if pass_old_timings_events:451              if old_event[0] != 'set_tempo':452                  previous_ms_so_far = ms_so_far453                  new_track.append(new_event)454              else:455                  new_event[0] = 'old_set_tempo'456                  previous_ms_so_far = ms_so_far457                  new_track.append(new_event)458            else:459              if old_event[0] != 'set_tempo':460                  previous_ms_so_far = ms_so_far461                  new_track.append(new_event)462            ticks_so_far += event_delta_ticks463        new_opus.append(new_track)464        itrack += 1465    _clean_up_warnings()466    return new_opus467 468def event2alsaseq(event=None):   # 5.5469    r'''Converts an event into the format needed by the alsaseq module,470http://pp.com.mx/python/alsaseq471The type of track (opus or score) is autodetected.472'''473    pass474 475def grep(score=None, channels=None):476    r'''Returns a "score" containing only the channels specified477'''478    if score == None:479        return [1000,[],]480    ticks = score[0]481    new_score = [ticks,]482    if channels == None:483        return new_score484    channels = set(channels)485    global Event2channelindex486    itrack = 1487    while itrack < len(score):488        new_score.append([])489        for event in score[itrack]:490            channel_index = Event2channelindex.get(event[0], False)491            if channel_index:492                if event[channel_index] in channels:493                    new_score[itrack].append(event)494            else:495                new_score[itrack].append(event)496        itrack += 1497    return new_score498 499def score2stats(opus_or_score=None):500    r'''Returns a dict of some basic stats about the score, like501bank_select (list of tuples (msb,lsb)),502channels_by_track (list of lists), channels_total (set),503general_midi_mode (list),504ntracks, nticks, patch_changes_by_track (list of dicts),505num_notes_by_channel (list of numbers),506patch_changes_total (set),507percussion (dict histogram of channel 9 events),508pitches (dict histogram of pitches on channels other than 9),509pitch_range_by_track (list, by track, of two-member-tuples),510pitch_range_sum (sum over tracks of the pitch_ranges),511'''512    bank_select_msb = -1513    bank_select_lsb = -1514    bank_select = []515    channels_by_track = []516    channels_total    = set([])517    general_midi_mode = []518    num_notes_by_channel = dict([])519    patches_used_by_track  = []520    patches_used_total     = set([])521    patch_changes_by_track = []522    patch_changes_total    = set([])523    percussion = dict([]) # histogram of channel 9 "pitches"524    pitches    = dict([]) # histogram of pitch-occurrences channels 0-8,10-15525    pitch_range_sum = 0   # u pitch-ranges of each track526    pitch_range_by_track = []527    is_a_score = True528    if opus_or_score == None:529        return {'bank_select':[], 'channels_by_track':[], 'channels_total':[],530         'general_midi_mode':[], 'ntracks':0, 'nticks':0,531         'num_notes_by_channel':dict([]),532         'patch_changes_by_track':[], 'patch_changes_total':[],533         'percussion':{}, 'pitches':{}, 'pitch_range_by_track':[],534         'ticks_per_quarter':0, 'pitch_range_sum':0}535    ticks_per_quarter = opus_or_score[0]536    i = 1   # ignore first element, which is ticks537    nticks = 0538    while i < len(opus_or_score):539        highest_pitch = 0540        lowest_pitch = 128541        channels_this_track = set([])542        patch_changes_this_track = dict({})543        for event in opus_or_score[i]:544            if event[0] == 'note':545                num_notes_by_channel[event[3]] = num_notes_by_channel.get(event[3],0) + 1546                if event[3] == 9:547                    percussion[event[4]] = percussion.get(event[4],0) + 1548                else:549                    pitches[event[4]]    = pitches.get(event[4],0) + 1550                    if event[4] > highest_pitch:551                        highest_pitch = event[4]552                    if event[4] < lowest_pitch:553                        lowest_pitch = event[4]554                channels_this_track.add(event[3])555                channels_total.add(event[3])556                finish_time = event[1] + event[2]557                if finish_time > nticks:558                    nticks = finish_time559            elif event[0] == 'note_off' or (event[0] == 'note_on' and event[4] == 0):  # 4.8560                finish_time = event[1]561                if finish_time > nticks:562                    nticks = finish_time563            elif event[0] == 'note_on':564                is_a_score = False565                num_notes_by_channel[event[2]] = num_notes_by_channel.get(event[2],0) + 1566                if event[2] == 9:567                    percussion[event[3]] = percussion.get(event[3],0) + 1568                else:569                    pitches[event[3]]    = pitches.get(event[3],0) + 1570                    if event[3] > highest_pitch:571                        highest_pitch = event[3]572                    if event[3] < lowest_pitch:573                        lowest_pitch = event[3]574                channels_this_track.add(event[2])575                channels_total.add(event[2])576            elif event[0] == 'patch_change':577                patch_changes_this_track[event[2]] = event[3]578                patch_changes_total.add(event[3])579            elif event[0] == 'control_change':580                if event[3] == 0:  # bank select MSB581                    bank_select_msb = event[4]582                elif event[3] == 32:  # bank select LSB583                    bank_select_lsb = event[4]584                if bank_select_msb >= 0 and bank_select_lsb >= 0:585                    bank_select.append((bank_select_msb,bank_select_lsb))586                    bank_select_msb = -1587                    bank_select_lsb = -1588            elif event[0] == 'sysex_f0':589                if _sysex2midimode.get(event[2], -1) >= 0:590                    general_midi_mode.append(_sysex2midimode.get(event[2]))591            if is_a_score:592                if event[1] > nticks:593                    nticks = event[1]594            else:595                nticks += event[1]596        if lowest_pitch == 128:597            lowest_pitch = 0598        channels_by_track.append(channels_this_track)599        patch_changes_by_track.append(patch_changes_this_track)600        pitch_range_by_track.append((lowest_pitch,highest_pitch))601        pitch_range_sum += (highest_pitch-lowest_pitch)602        i += 1603 604    return {'bank_select':bank_select,605            'channels_by_track':channels_by_track,606            'channels_total':channels_total,607            'general_midi_mode':general_midi_mode,608            'ntracks':len(opus_or_score)-1,609            'nticks':nticks,610            'num_notes_by_channel':num_notes_by_channel,611            'patch_changes_by_track':patch_changes_by_track,612            'patch_changes_total':patch_changes_total,613            'percussion':percussion,614            'pitches':pitches,615            'pitch_range_by_track':pitch_range_by_track,616            'pitch_range_sum':pitch_range_sum,617            'ticks_per_quarter':ticks_per_quarter}618 619#----------------------------- Event stuff --------------------------620 621_sysex2midimode = {622    "\x7E\x7F\x09\x01\xF7": 1,623    "\x7E\x7F\x09\x02\xF7": 0,624    "\x7E\x7F\x09\x03\xF7": 2,625}626 627# Some public-access tuples:628MIDI_events = tuple('''note_off note_on key_after_touch629control_change patch_change channel_after_touch630pitch_wheel_change'''.split())631 632Text_events = tuple('''text_event copyright_text_event633track_name instrument_name lyric marker cue_point text_event_08634text_event_09 text_event_0a text_event_0b text_event_0c635text_event_0d text_event_0e text_event_0f'''.split())636 637Nontext_meta_events = tuple('''end_track set_tempo638smpte_offset time_signature key_signature sequencer_specific639raw_meta_event sysex_f0 sysex_f7 song_position song_select640tune_request'''.split())641# unsupported: raw_data642 643# Actually, 'tune_request' is is F-series event, not strictly a meta-event...644Meta_events = Text_events + Nontext_meta_events645All_events  = MIDI_events + Meta_events646 647# And three dictionaries:648Number2patch = {   # General MIDI patch numbers:6490:'Acoustic Grand',6501:'Bright Acoustic',6512:'Electric Grand',6523:'Honky-Tonk',6534:'Electric Piano 1',6545:'Electric Piano 2',6556:'Harpsichord',6567:'Clav',6578:'Celesta',6589:'Glockenspiel',65910:'Music Box',66011:'Vibraphone',66112:'Marimba',66213:'Xylophone',66314:'Tubular Bells',66415:'Dulcimer',66516:'Drawbar Organ',66617:'Percussive Organ',66718:'Rock Organ',66819:'Church Organ',66920:'Reed Organ',67021:'Accordion',67122:'Harmonica',67223:'Tango Accordion',67324:'Acoustic Guitar(nylon)',67425:'Acoustic Guitar(steel)',67526:'Electric Guitar(jazz)',67627:'Electric Guitar(clean)',67728:'Electric Guitar(muted)',67829:'Overdriven Guitar',67930:'Distortion Guitar',68031:'Guitar Harmonics',68132:'Acoustic Bass',68233:'Electric Bass(finger)',68334:'Electric Bass(pick)',68435:'Fretless Bass',68536:'Slap Bass 1',68637:'Slap Bass 2',68738:'Synth Bass 1',68839:'Synth Bass 2',68940:'Violin',69041:'Viola',69142:'Cello',69243:'Contrabass',69344:'Tremolo Strings',69445:'Pizzicato Strings',69546:'Orchestral Harp',69647:'Timpani',69748:'String Ensemble 1',69849:'String Ensemble 2',69950:'SynthStrings 1',70051:'SynthStrings 2',70152:'Choir Aahs',70253:'Voice Oohs',70354:'Synth Voice',70455:'Orchestra Hit',70556:'Trumpet',70657:'Trombone',70758:'Tuba',70859:'Muted Trumpet',70960:'French Horn',71061:'Brass Section',71162:'SynthBrass 1',71263:'SynthBrass 2',71364:'Soprano Sax',71465:'Alto Sax',71566:'Tenor Sax',71667:'Baritone Sax',71768:'Oboe',71869:'English Horn',71970:'Bassoon',72071:'Clarinet',72172:'Piccolo',72273:'Flute',72374:'Recorder',72475:'Pan Flute',72576:'Blown Bottle',72677:'Skakuhachi',72778:'Whistle',72879:'Ocarina',72980:'Lead 1 (square)',73081:'Lead 2 (sawtooth)',73182:'Lead 3 (calliope)',73283:'Lead 4 (chiff)',73384:'Lead 5 (charang)',73485:'Lead 6 (voice)',73586:'Lead 7 (fifths)',73687:'Lead 8 (bass+lead)',73788:'Pad 1 (new age)',73889:'Pad 2 (warm)',73990:'Pad 3 (polysynth)',74091:'Pad 4 (choir)',74192:'Pad 5 (bowed)',74293:'Pad 6 (metallic)',74394:'Pad 7 (halo)',74495:'Pad 8 (sweep)',74596:'FX 1 (rain)',74697:'FX 2 (soundtrack)',74798:'FX 3 (crystal)',74899:'FX 4 (atmosphere)',749100:'FX 5 (brightness)',750101:'FX 6 (goblins)',751102:'FX 7 (echoes)',752103:'FX 8 (sci-fi)',753104:'Sitar',754105:'Banjo',755106:'Shamisen',756107:'Koto',757108:'Kalimba',758109:'Bagpipe',759110:'Fiddle',760111:'Shanai',761112:'Tinkle Bell',762113:'Agogo',763114:'Steel Drums',764115:'Woodblock',765116:'Taiko Drum',766117:'Melodic Tom',767118:'Synth Drum',768119:'Reverse Cymbal',769120:'Guitar Fret Noise',770121:'Breath Noise',771122:'Seashore',772123:'Bird Tweet',773124:'Telephone Ring',774125:'Helicopter',775126:'Applause',776127:'Gunshot',777}778Notenum2percussion = {   # General MIDI Percussion (on Channel 9):77935:'Acoustic Bass Drum',78036:'Bass Drum 1',78137:'Side Stick',78238:'Acoustic Snare',78339:'Hand Clap',78440:'Electric Snare',78541:'Low Floor Tom',78642:'Closed Hi-Hat',78743:'High Floor Tom',78844:'Pedal Hi-Hat',78945:'Low Tom',79046:'Open Hi-Hat',79147:'Low-Mid Tom',79248:'Hi-Mid Tom',79349:'Crash Cymbal 1',79450:'High Tom',79551:'Ride Cymbal 1',79652:'Chinese Cymbal',79753:'Ride Bell',79854:'Tambourine',79955:'Splash Cymbal',80056:'Cowbell',80157:'Crash Cymbal 2',80258:'Vibraslap',80359:'Ride Cymbal 2',80460:'Hi Bongo',80561:'Low Bongo',80662:'Mute Hi Conga',80763:'Open Hi Conga',80864:'Low Conga',80965:'High Timbale',81066:'Low Timbale',81167:'High Agogo',81268:'Low Agogo',81369:'Cabasa',81470:'Maracas',81571:'Short Whistle',81672:'Long Whistle',81773:'Short Guiro',81874:'Long Guiro',81975:'Claves',82076:'Hi Wood Block',82177:'Low Wood Block',82278:'Mute Cuica',82379:'Open Cuica',82480:'Mute Triangle',82581:'Open Triangle',826}827 828Event2channelindex = { 'note':3, 'note_off':2, 'note_on':2,829 'key_after_touch':2, 'control_change':2, 'patch_change':2,830 'channel_after_touch':2, 'pitch_wheel_change':2831}832 833################################################################834# The code below this line is full of frightening things, all to835# do with the actual encoding and decoding of binary MIDI data.836 837def _twobytes2int(byte_a):838    r'''decode a 16 bit quantity from two bytes,'''839    return (byte_a[1] | (byte_a[0] << 8))840 841def _int2twobytes(int_16bit):842    r'''encode a 16 bit quantity into two bytes,'''843    return bytes([(int_16bit>>8) & 0xFF, int_16bit & 0xFF])844 845def _read_14_bit(byte_a):846    r'''decode a 14 bit quantity from two bytes,'''847    return (byte_a[0] | (byte_a[1] << 7))848 849def _write_14_bit(int_14bit):850    r'''encode a 14 bit quantity into two bytes,'''851    return bytes([int_14bit & 0x7F, (int_14bit>>7) & 0x7F])852 853def _ber_compressed_int(integer):854    r'''BER compressed integer (not an ASN.1 BER, see perlpacktut for855details).  Its bytes represent an unsigned integer in base 128,856most significant digit first, with as few digits as possible.857Bit eight (the high bit) is set on each byte except the last.858'''859    ber = bytearray(b'')860    seven_bits = 0x7F & integer861    ber.insert(0, seven_bits)  # XXX surely should convert to a char ?862    integer >>= 7863    while integer > 0:864        seven_bits = 0x7F & integer865        ber.insert(0, 0x80|seven_bits)  # XXX surely should convert to a char ?866        integer >>= 7867    return ber868 869def _unshift_ber_int(ba):870    r'''Given a bytearray, returns a tuple of (the ber-integer at the871start, and the remainder of the bytearray).872'''873    if not len(ba):  # 6.7874        _warn('_unshift_ber_int: no integer found')875        return ((0, b""))876    byte = ba[0]877    ba = ba[1:]878    integer = 0879    while True:880        integer += (byte & 0x7F)881        if not (byte & 0x80):882            return ((integer, ba))883        if not len(ba):884            _warn('_unshift_ber_int: no end-of-integer found')885            return ((0, ba))886        byte = ba[0]887        ba = ba[1:]888        integer <<= 7889 890 891def _clean_up_warnings():  # 5.4892    # Call this before returning from any publicly callable function893    # whenever there's a possibility that a warning might have been printed894    # by the function, or by any private functions it might have called.895    if _no_warning:896        return897    global _previous_times898    global _previous_warning899    if _previous_times > 1:900        # E:1176, 0: invalid syntax (<string>, line 1176) (syntax-error) ???901        # print('  previous message repeated '+str(_previous_times)+' times', file=sys.stderr)902        # 6.7903        sys.stderr.write('  previous message repeated {0} times\n'.format(_previous_times))904    elif _previous_times > 0:905        sys.stderr.write('  previous message repeated\n')906    _previous_times = 0907    _previous_warning = ''908 909 910def _warn(s=''):911    if _no_warning:912        return913    global _previous_times914    global _previous_warning915    if s == _previous_warning:  # 5.4916        _previous_times = _previous_times + 1917    else:918        _clean_up_warnings()919        sys.stderr.write(str(s) + "\n")920        _previous_warning = s921 922 923def _some_text_event(which_kind=0x01, text=b'some_text', text_encoding='ISO-8859-1'):924    if str(type(text)).find("'str'") >= 0:  # 6.4 test for back-compatibility925        data = bytes(text, encoding=text_encoding)926    else:927        data = bytes(text)928    return b'\xFF' + bytes((which_kind,)) + _ber_compressed_int(len(data)) + data929 930 931def _consistentise_ticks(scores):  # 3.6932    # used by mix_scores, merge_scores, concatenate_scores933    if len(scores) == 1:934        return copy.deepcopy(scores)935    are_consistent = True936    ticks = scores[0][0]937    iscore = 1938    while iscore < len(scores):939        if scores[iscore][0] != ticks:940            are_consistent = False941            break942        iscore += 1943    if are_consistent:944        return copy.deepcopy(scores)945    new_scores = []946    iscore = 0947    while iscore < len(scores):948        score = scores[iscore]949        new_scores.append(opus2score(to_millisecs(score2opus(score))))950        iscore += 1951    return new_scores952 953 954###########################################################################955def _decode(trackdata=b'', exclude=None, include=None,956            event_callback=None, exclusive_event_callback=None, no_eot_magic=False):957    r'''Decodes MIDI track data into an opus-style list of events.958The options:959  'exclude' is a list of event types which will be ignored SHOULD BE A SET960  'include' (and no exclude), makes exclude a list961       of all possible events, /minus/ what include specifies962  'event_callback' is a coderef963  'exclusive_event_callback' is a coderef964'''965    trackdata = bytearray(trackdata)966    if exclude == None:967        exclude = []968    if include == None:969        include = []970    if include and not exclude:971        exclude = All_events972    include = set(include)973    exclude = set(exclude)974 975    # Pointer = 0;  not used here; we eat through the bytearray instead.976    event_code = -1;  # used for running status977    event_count = 0;978    events = []979 980    while (len(trackdata)):981        # loop while there's anything to analyze ...982        eot = False  # When True, the event registrar aborts this loop983        event_count += 1984 985        E = []986        # E for events - we'll feed it to the event registrar at the end.987 988        # Slice off the delta time code, and analyze it989        [time, trackdata] = _unshift_ber_int(trackdata)990 991        # Now let's see what we can make of the command992        first_byte = trackdata[0] & 0xFF993        trackdata = trackdata[1:]994        if (first_byte < 0xF0):  # It's a MIDI event995            if (first_byte & 0x80):996                event_code = first_byte997            else:998                # It wants running status; use last event_code value999                trackdata.insert(0, first_byte)1000                if (event_code == -1):1001                    _warn("Running status not set; Aborting track.")1002                    return []1003 1004            command = event_code & 0xF01005            channel = event_code & 0x0F1006 1007            if (command == 0xF6):  # 0-byte argument1008                pass1009            elif (command == 0xC0 or command == 0xD0):  # 1-byte argument1010                parameter = trackdata[0]  # could be B1011                trackdata = trackdata[1:]1012            else:  # 2-byte argument could be BB or 14-bit1013                parameter = (trackdata[0], trackdata[1])1014                trackdata = trackdata[2:]1015 1016            #################################################################1017            # MIDI events1018 1019            if (command == 0x80):1020                if 'note_off' in exclude:1021                    continue1022                E = ['note_off', time, channel, parameter[0], parameter[1]]1023            elif (command == 0x90):1024                if 'note_on' in exclude:1025                    continue1026                E = ['note_on', time, channel, parameter[0], parameter[1]]1027            elif (command == 0xA0):1028                if 'key_after_touch' in exclude:1029                    continue1030                E = ['key_after_touch', time, channel, parameter[0], parameter[1]]1031            elif (command == 0xB0):1032                if 'control_change' in exclude:1033                    continue1034                E = ['control_change', time, channel, parameter[0], parameter[1]]1035            elif (command == 0xC0):1036                if 'patch_change' in exclude:1037                    continue1038                E = ['patch_change', time, channel, parameter]1039            elif (command == 0xD0):1040                if 'channel_after_touch' in exclude:1041                    continue1042                E = ['channel_after_touch', time, channel, parameter]1043            elif (command == 0xE0):1044                if 'pitch_wheel_change' in exclude:1045                    continue1046                E = ['pitch_wheel_change', time, channel,1047                     _read_14_bit(parameter) - 0x2000]1048            else:1049                _warn("Shouldn't get here; command=" + hex(command))1050 1051        elif (first_byte == 0xFF):  # It's a Meta-Event! ##################1052            # [command, length, remainder] =1053            #    unpack("xCwa*", substr(trackdata, $Pointer, 6));1054            # Pointer += 6 - len(remainder);1055            #    # Move past JUST the length-encoded.1056            command = trackdata[0] & 0xFF1057            trackdata = trackdata[1:]1058            [length, trackdata] = _unshift_ber_int(trackdata)1059            if (command == 0x00):1060                if (length == 2):1061                    E = ['set_sequence_number', time, _twobytes2int(trackdata)]1062                else:1063                    _warn('set_sequence_number: length must be 2, not ' + str(length))1064                    E = ['set_sequence_number', time, 0]1065 1066            elif command >= 0x01 and command <= 0x0f:  # Text events1067                # 6.2 take it in bytes; let the user get the right encoding.1068                # text_str = trackdata[0:length].decode('ascii','ignore')1069                # text_str = trackdata[0:length].decode('ISO-8859-1')1070                # 6.4 take it in bytes; let the user get the right encoding.1071                text_data = bytes(trackdata[0:length])  # 6.41072                # Defined text events1073                if (command == 0x01):1074                    E = ['text_event', time, text_data]1075                elif (command == 0x02):1076                    E = ['copyright_text_event', time, text_data]1077                elif (command == 0x03):1078                    E = ['track_name', time, text_data]1079                elif (command == 0x04):1080                    E = ['instrument_name', time, text_data]1081                elif (command == 0x05):1082                    E = ['lyric', time, text_data]1083                elif (command == 0x06):1084                    E = ['marker', time, text_data]1085                elif (command == 0x07):1086                    E = ['cue_point', time, text_data]1087                # Reserved but apparently unassigned text events1088                elif (command == 0x08):1089                    E = ['text_event_08', time, text_data]1090                elif (command == 0x09):1091                    E = ['text_event_09', time, text_data]1092                elif (command == 0x0a):1093                    E = ['text_event_0a', time, text_data]1094                elif (command == 0x0b):1095                    E = ['text_event_0b', time, text_data]1096                elif (command == 0x0c):1097                    E = ['text_event_0c', time, text_data]1098                elif (command == 0x0d):1099                    E = ['text_event_0d', time, text_data]1100                elif (command == 0x0e):1101                    E = ['text_event_0e', time, text_data]1102                elif (command == 0x0f):1103                    E = ['text_event_0f', time, text_data]1104 1105            # Now the sticky events -------------------------------------1106            elif (command == 0x2F):1107                E = ['end_track', time]1108                # The code for handling this, oddly, comes LATER,1109                # in the event registrar.1110            elif (command == 0x51):  # DTime, Microseconds/Crochet1111                if length != 3:1112                    _warn('set_tempo event, but length=' + str(length))1113                E = ['set_tempo', time,1114                     struct.unpack(">I", b'\x00' + trackdata[0:3])[0]]1115            elif (command == 0x54):1116                if length != 5:  # DTime, HR, MN, SE, FR, FF1117                    _warn('smpte_offset event, but length=' + str(length))1118                E = ['smpte_offset', time] + list(struct.unpack(">BBBBB", trackdata[0:5]))1119            elif (command == 0x58):1120                if length != 4:  # DTime, NN, DD, CC, BB1121                    _warn('time_signature event, but length=' + str(length))1122                E = ['time_signature', time] + list(trackdata[0:4])1123            elif (command == 0x59):1124                if length != 2:  # DTime, SF(signed), MI1125                    _warn('key_signature event, but length=' + str(length))1126                E = ['key_signature', time] + list(struct.unpack(">bB", trackdata[0:2]))1127            elif (command == 0x7F):  # 6.41128                E = ['sequencer_specific', time, bytes(trackdata[0:length])]1129            else:1130                E = ['raw_meta_event', time, command,1131                     bytes(trackdata[0:length])]  # 6.01132                # "[uninterpretable meta-event command of length length]"1133                # DTime, Command, Binary Data1134                # It's uninterpretable; record it as raw_data.1135 1136            # Pointer += length; #  Now move Pointer1137            trackdata = trackdata[length:]1138 1139        ######################################################################1140        elif (first_byte == 0xF0 or first_byte == 0xF7):1141            # Note that sysexes in MIDI /files/ are different than sysexes1142            # in MIDI transmissions!! The vast majority of system exclusive1143            # messages will just use the F0 format. For instance, the1144            # transmitted message F0 43 12 00 07 F7 would be stored in a1145            # MIDI file as F0 05 43 12 00 07 F7. As mentioned above, it is1146            # required to include the F7 at the end so that the reader of the1147            # MIDI file knows that it has read the entire message. (But the F71148            # is omitted if this is a non-final block in a multiblock sysex;1149            # but the F7 (if there) is counted in the message's declared1150            # length, so we don't have to think about it anyway.)1151            # command = trackdata.pop(0)1152            [length, trackdata] = _unshift_ber_int(trackdata)1153            if first_byte == 0xF0:1154                # 20091008 added ISO-8859-1 to get an 8-bit str1155                # 6.4 return bytes instead1156                E = ['sysex_f0', time, bytes(trackdata[0:length])]1157            else:1158                E = ['sysex_f7', time, bytes(trackdata[0:length])]1159            trackdata = trackdata[length:]1160 1161        ######################################################################1162        # Now, the MIDI file spec says:1163        #  <track data> = <MTrk event>+1164        #  <MTrk event> = <delta-time> <event>1165        #  <event> = <MIDI event> | <sysex event> | <meta-event>1166        # I know that, on the wire, <MIDI event> can include note_on,1167        # note_off, and all the other 8x to Ex events, AND Fx events1168        # other than F0, F7, and FF -- namely, <song position msg>,1169        # <song select msg>, and <tune request>.1170        #1171        # Whether these can occur in MIDI files is not clear specified1172        # from the MIDI file spec.  So, I'm going to assume that1173        # they CAN, in practice, occur.  I don't know whether it's1174        # proper for you to actually emit these into a MIDI file.1175 1176        elif (first_byte == 0xF2):  # DTime, Beats1177            #  <song position msg> ::=     F2 <data pair>1178            E = ['song_position', time, _read_14_bit(trackdata[:2])]1179            trackdata = trackdata[2:]1180 1181        elif (first_byte == 0xF3):  # <song select msg> ::= F3 <data singlet>1182            # E = ['song_select', time, struct.unpack('>B',trackdata.pop(0))[0]]1183            E = ['song_select', time, trackdata[0]]1184            trackdata = trackdata[1:]1185            # DTime, Thing (what?! song number?  whatever ...)1186 1187        elif (first_byte == 0xF6):  # DTime1188            E = ['tune_request', time]1189            # What would a tune request be doing in a MIDI /file/?1190 1191            #########################################################1192            # ADD MORE META-EVENTS HERE.  TODO:1193            # f1 -- MTC Quarter Frame Message. One data byte follows1194            #     the Status; it's the time code value, from 0 to 127.1195            # f8 -- MIDI clock.    no data.1196            # fa -- MIDI start.    no data.1197            # fb -- MIDI continue. no data.1198            # fc -- MIDI stop.     no data.1199            # fe -- Active sense.  no data.1200            # f4 f5 f9 fd -- unallocated

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