This file is indexed.

/usr/bin/mma-mnx is in mma 15.12-1.

This file is owned by root:root, with mode 0o755.

The actual contents of the file can be viewed below.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
#! /usr/bin/python

# Extract midi solos for including into mma scripts using MIDINote.
# Bob van der Poel, Jan/09

# You need to run this with python2. Probably will not get converted since
# the usage of this program is mostly replaced my the midiInc command in MMA.
# bvdp, Feb/2014.

import os, sys, math, time, getopt

# Some constants/globals

voiceNames=[
    'Piano1', 'Piano2','Piano3',
    'Honky-TonkPiano', 'RhodesPiano', 'EPiano',
    'HarpsiChord', 'Clavinet', 'Celesta',
    'Glockenspiel', 'MusicBox', 'Vibraphone',
    'Marimba', 'Xylophone', 'TubularBells', 'Santur',
    'Organ1', 'Organ2', 'Organ3', 'ChurchOrgan',
    'ReedOrgan', 'Accordion', 'Harmonica',
    'Bandoneon', 'NylonGuitar', 'SteelGuitar',
    'JazzGuitar', 'CleanGuitar', 'MutedGuitar',
    'OverDriveGuitar', 'DistortonGuitar',
    'GuitarHarmonics', 'AcousticBass',
    'FingeredBass', 'PickedBass', 'FretlessBass',
    'SlapBass1', 'SlapBass2', 'SynthBass1',
    'SynthBass2', 'Violin', 'Viola', 'Cello',
    'ContraBass', 'TremoloStrings',
    'PizzicatoString', 'OrchestralHarp', 'Timpani',
    'Strings', 'SlowStrings', 'SynthStrings1',
    'SynthStrings2', 'ChoirAahs', 'VoiceOohs',
    'SynthVox', 'OrchestraHit', 'Trumpet',
    'Trombone', 'Tuba', 'MutedTrumpet', 'FrenchHorn',
    'BrassSection', 'SynthBrass1', 'SynthBrass2',
    'SopranoSax', 'AltoSax', 'TenorSax',
    'BaritoneSax', 'Oboe', 'EnglishHorn', 'Bassoon',
    'Clarinet', 'Piccolo', 'Flute', 'Recorder',
    'PanFlute', 'BottleBlow', 'Shakuhachi',
    'Whistle', 'Ocarina', 'SquareWave', 'SawWave',
    'SynCalliope', 'ChifferLead', 'Charang',
    'SoloVoice', '5thSawWave', 'Bass&Lead',
    'Fantasia', 'WarmPad', 'PolySynth', 'SpaceVoice',
    'BowedGlass', 'MetalPad', 'HaloPad', 'SweepPad',
    'IceRain', 'SoundTrack', 'Crystal', 'Atmosphere',
    'Brightness', 'Goblins', 'EchoDrops',
    'StarTheme', 'Sitar', 'Banjo', 'Shamisen',
    'Koto', 'Kalimba', 'BagPipe', 'Fiddle', 'Shanai',
    'TinkleBell', 'AgogoBells', 'SteelDrums',
    'WoodBlock', 'TaikoDrum', 'MelodicTom1',
    'SynthDrum', 'ReverseCymbal', 'GuitarFretNoise',
    'BreathNoise', 'SeaShore', 'BirdTweet',
    'TelephoneRing', 'HelicopterBlade',
    'Applause/Noise', 'GunShot' ]

# Controller names. Tables are borrowed from:
#     http://www.midi.org/about-midi/table3.shtml

ctrlNames = {
    0:'Bank', 1:'Modulation', 2:'Breath',
    3:'Ctrl3', 4:'Foot', 5:'Portamento',
    6:'Data', 7:'Volume', 8:'Balance',
    9:'Ctrl9', 10:'Pan', 11:'Expression',
    12:'Effect1', 13:'Effect2', 14:'Ctrl14',
    15:'Ctrl15', 16:'General1', 17:'General2',
    18:'General3', 19:'General4', 20:'Ctrl20',
    21:'Ctrl21', 22:'Ctrl22', 23:'Ctrl23',
    24:'Ctrl24', 25:'Ctrl25', 26:'Ctrl26',
    27:'Ctrl27', 28:'Ctrl28', 29:'Ctrl29',
    30:'Ctrl30', 31:'Ctrl31', 32:'BankLSB',
    33:'ModulationLSB', 34:'BreathLSB', 35:'Ctrl35',
    36:'FootLSB', 37:'PortamentoLSB', 38:'DataLSB',
    39:'VolumeLSB', 40:'BalanceLSB', 41:'Ctrl41',
    42:'PanLSB', 43:'ExpressionLSB', 44:'Effect1LSB',
    45:'Effect2LSB', 46:'Ctrl46', 47:'Ctrl47',
    48:'General1LSB', 49:'General2LSB', 50:'General3LSB',
    51:'General4LSB', 52:'Ctrl52', 53:'Ctrl53',
    54:'Ctrl54', 55:'Ctrl55', 56:'Ctrl56',
    57:'Ctrl57', 58:'Ctrl58', 59:'Ctrl59',
    60:'Ctrl60', 61:'Ctrl61', 62:'Ctrl62',
    63:'Ctrl63', 64:'Sustain', 65:'Portamento',
    66:'Sostenuto', 67:'SoftPedal', 68:'Legato',
    69:'Hold2', 70:'Variation', 71:'Resonance',
    72:'ReleaseTime', 73:'AttackTime', 74:'Brightness',
    75:'DecayTime', 76:'VibratoRate', 77:'VibratoDepth',
    78:'VibratoDelay', 79:'Ctrl79', 80:'General5',
    81:'General6', 82:'General7', 83:'General8',
    84:'PortamentoCtrl', 85:'Ctrl85', 86:'Ctrl86',
    87:'Ctrl87', 88:'Ctrl88', 89:'Ctrl89',
    90:'Ctrl90', 91:'Reverb', 92:'Tremolo',
    93:'Chorus', 94:'Detune', 95:'Phaser',
    96:'DataInc', 97:'DataDec', 98:'NonRegLSB',
    99:'NonRegMSB', 100:'RegParLSB', 101:'RegParMSB',
    102:'Ctrl102', 103:'Ctrl103', 104:'Ctrl104',
    105:'Ctrl105', 106:'Ctrl106', 107:'Ctrl107',
    108:'Ctrl108', 109:'Ctrl109', 110:'Ctrl110',
    111:'Ctrl111', 112:'Ctrl112', 113:'Ctrl113',
    114:'Ctrl114', 115:'Ctrl115', 116:'Ctrl116',
    117:'Ctrl117', 118:'Ctrl118', 119:'Ctrl119',
    # pseudo controllers, also called channel mode messages
    120:'AllSoundsOff', 121:'ResetAll', 122:'LocalCtrl',
    123:'AllNotesOff', 124:'OmniOff', 125:'OmniOn',
    126:'PolyOff', 127:'PolyOn' }

# This is our midi file reader. Sucked out of ys2mma

class MF:

    def __init__(self, filename):

        """ The filename is assumed to be a valid midi file and is read and
            parsed. The events found are separated into channels and inserted
            into events{}. The stucture of events() is:
                 events[ch]     - a entry for each channel
                 events[ch][ev] - entry for each offset 

            we also create a few helpful variables:
                 voices[] - the last voice found for each channel, defaults to 0
                 metanames[] - set for ??/
                 tempo       - pulled out of header info

            NOTE: All events are read/parsed but only the limited set
                  needed are saved ... meta events, etc. are just dumped.
        """

        self.events        = {}
        self.voices        = [ 0 ] * 16   # last voice found for each track
        self.metanames     = []
        self.tempo         = 120          # default tempo if none found in file
 
        for c in range(0,16):   # create an empty entry for each possible channel
            self.events[c]=[]

        # read in file

        try:
            inpath = file(filename, "rb")
        except:
            error("Unable to open MIDI file %s for reading" % filename)

        self.midifile=inpath.read()
        inpath.close()
        self.offset = 0

        hd = self.chars(4) # Ensure this is valid header
        if hd != 'MThd':
            error("Expecting 'HThd', %s not a standard midi file." % filename)

        # the next chunk is a 32bit length (always 6), format (0 or 1),
        # number of tracks, beat division

        a = self.m32i() 
        if a != 6:
            error("Expecting a 32 bit value of 6 in header")

        format=self.m16i()
        if format not in (0,1):
            error("MIDI file format %s not recognized" % format)

        ntracks=self.m16i()
        self.beatDivision=self.m16i()

        # Finished the header, now we do each track

        for tr in range(ntracks):
            tm=0    # this is the offset pointer, incremented as we parse event times
            hdr = self.chars(4)
            if hdr != 'MTrk':
                error("Malformed MIDI file in track header")
            trlen = self.m32i()     # track length (we don't use it)

            lastevent = None

            """ Parse the midi file. We have to parse off each event, even
                though many will just be thrown away. You can't just skip around
                in a midi file :)
            """

            while 1:
                tm += self.mvarlen()        # adjust total offset by delta

                ev=self.m8b()               # command byte

                # if the command byte is < 0x80 then we are doing running status
                if ev < 0x80:
                    if not lastevent:
                        error("Illegal running status in %s at $%06x"  % (filename, self.offset))
                    self.offset -= 1
                    ev=lastevent

                sValue = ev>>4         # Shift MSBs to get a 4 bit value for the command
                channel = ev & 0x0f    # and the channel number (0..15)
                evptr = self.events[channel]

                if sValue == 0x8:           # note off event
                    evptr.append([tm, 'NoteOff', self.m8b(), self.m8b() ])

                elif sValue == 0x9:         # note on event
                    note=self.m8b()
                    vel=self.m8b()
                    # this needs to be conditional FIXME!
                    if vel == 0:
                        evptr.append([tm, 'NoteOff', note, vel])
                    else:
                        evptr.append([tm, 'NoteOn', note, vel])

                elif sValue == 0xa:         # key pressure
                    self.m8b()  # grab and ignore key pressure
                    self.m8b()

                elif sValue == 0xb:         # control change
                    evptr.append([tm, 'Controller', self.m8b(), self.m8b() ])
                    
                elif sValue == 0xc:         # program change
                    v = self.m8b()
                    evptr.append( [tm, 'ProgChange', v])
                    self.voices[channel] = v

                elif sValue == 0xd:         # channel pressure
                    evptr.append( [tm, 'ChannelPressure', self.m8b() ])

                elif sValue == 0xe:         # pitch blend
                    evptr.append([tm, 'PitchBend',  self.m8b()+(self.m8b()*128) ])

                elif sValue == 0xf:      # system events
                    if ev == 0xff:       # meta events
                        a=self.m8b()
                        stuff=self.chars(self.mvarlen())
 
                        if a == 0x2f: # end of track
                            evptr.append([tm, "EOT", stuff])
                            break
  
                        elif a == 0x58: # time sig
                            self.BperB = ord(stuff[0])
                            self.timedenom = 2 ** ord(stuff[1])

                        else:       # ignore anything else
                            pass

                    elif ev == 0xf0:    # system exclusive
                        self.offset += self.mvarlen()
 
                    elif ev == 0xf2:    # song position pointer, 2 bytes
                        self.offset += 2

                    elif ev == 0xf3:    # song select, 1 byte
                        self.offset += 1

                    else:       # all others are single byte commands
                        pass

                if ev >= 0x80 and ev <= 0xef:
                    lastevent = ev

        self.metanames.append( [tm, "EOF"])


    def mvarlen(self):
        """ Convert variable length midi value to int. """

        x=0L
        for i in range(4):

            try:
                byte=ord(self.midifile[self.offset])
                self.offset += 1
            except:
                error("Invalid MIDI file include (varlen->int).")

            if byte < 0x80:
                x = ( x << 7 ) + byte
                break
            else:
                x = ( x << 7 ) + ( byte & 0x7f )

        return int(x)

    # Class routines to grab bits of the midi

    def chars(self, count):
        """ Return 'count' chars from file (updates global pointer). """

        bytes=self.midifile[self.offset:self.offset+count]
        self.offset+=count
        return bytes


    def m8b(self):
        """ Get 1 byte/8 bit value (updates global pointer). """

        try:
            byte = self.midifile[self.offset]
            self.offset += 1
        except:
            error("Invalid MIDI file include (byte, offset=%s)." % self.offset)

        return ord(byte)


    def m32i(self):
        """ Get 4 byte/32 bit integer. """

        x = 0L
        for i in range(4):
            try:
                byte = self.midifile[self.offset]
                self.offset += 1
            except:
                error("Invalid MIDI file include (i32->int, offset=%s)." % self.offset)
            x = (x << 8) + ord(byte)

        return int(x)


    def m16i(self):
        """ Get 2 byte/16 bit integer. """

        x = 0L
        for i in range(2):
            try:
                byte = self.midifile[self.offset]
                self.offset += 1
            except:
                error("Invalid MIDI file include (i16->int, offset=%s)." % self.offset)
            x = (x << 8) + ord(byte)

        return int(x)


#####################################################
# Routines for errors, usage, etc.

def error(msg):
    print "ERROR: %s" % msg
    sys.exit(1)

def usage():
    print "mma-mnx.py - Extracts note events from MIDI files"
    print "             for future inclusion into MMA files"
    print "             using MidiNote (mnx==MidiNote eXtract)."
    print "usage: mma-mnx.py INFILE [options]"
    print "Options:"
    print "   -h this screen"
    print "   -v display version"
    print "   -c CHANNEL channel to extract data"

    sys.exit(1)

def showversion():
    print "0.1"
    sys.exit(0)

def doOpts():
    """ Parse off the command line options. """

    global channel, infile

    try:
        opts, args = getopt.gnu_getopt(sys.argv[1:], "hvc:", [])
    except getopt.GetoptError:
        usage()
    for o, a in opts:

        if o == "-v":
            showversion()

        elif o == "-h":
            usage()

        elif o == "-c":
            channel = int(a)

        else:
            usage()

    if len(args) != 1:
        usage()

    infile = args[0]  


def chReport(mf):
    """ Report number of channels, events. """

    for a in range(0,16):
        if len(mf.events[a]):
            print "  Channel %02s  Total Events: %04s  Voice: %s" % \
                (a+1, len(mf.events[a]), voiceNames[mf.voices[a]])
    sys.exit(0)

def chDump(mf):
    """ Make up a list of recognized events for MMA midinote. """

    events = mf.events[channel-1]
    ons=[]
    quarters=mf.beatDivision  # beats per quarter note

    print "// Note data for %s. Offsets=Ticks  Duration=Ticks" % (infile)

    # Go though track and change all offsets to MMA's 192 BperQ

    if quarters != 192:
        print "// MIDI file tick/beat of %s differs from MMA's 192." \
           " Will try to compensate" % quarters

        adjust = 192. / quarters
        for i in xrange(len(events)):
            events[i][0] = int(events[i][0] * adjust)
        quarters = 192

    beatBar = mf.BperB * quarters   # beats per bar
    bprint=-1     # last bar number printed

    for i,a in enumerate(events):
        thisbar = int(a[0]/beatBar)

        # Determine current bar number

        if thisbar > bprint:
            print "// %s" % thisbar
            bprint=thisbar

        if a[1] == 'NoteOn':
            start=a[0]
            end=-1
            note=a[2]
            for aa in events[i:]:
                if aa[1]=='NoteOff' and aa[2]==note:
                    end=aa[0]
                    break
            if end == -1:
                end = start+1
            dur = end-start

            print "Note %d %d %d %d" % (a[0], note, a[3], dur )

        elif a[1] == 'PitchBend':   # pitch bend
            # NOTE, we subtract 8191 from the real value to keep
            # our values -8191 to +8192.
            print "PB %s %d " % (a[0], a[2]-8191)

        elif a[1] == 'ChannelPressure':   # channel aftertouch
            print "CHaT %s %s" % (a[0], a[2])

        elif a[1] == 'Controller':   # controller
            print "Ctrl %s %s %s" % (a[0], ctrlNames[a[2]], a[3])



########################################################
######  Start of mainline code.


channel = 0     # midi channel to extract 1..16
infile = ''     # name of the midi file

doOpts()        # get command line options/filename

mididata=MF(infile)

if not mididata.BperB:
    error("Expecting file to have valid time signature.")

if channel == 0:
    chReport(mididata)
else:
    chDump(mididata)

# eof