native_midi/native_midi_mac.c
author Sam Lantinga <slouken@libsdl.org>
Fri, 29 Jan 2016 12:44:13 -0800
changeset 718 fb0562cc1559
parent 625 1d489d8ec2e0
child 779 a2b494c054d5
permissions -rw-r--r--
Added Mix_OpenAudioDevice() so you can specify the audio device to open
slouken@106
     1
/*
slouken@518
     2
  native_midi_mac:  Native Midi support on MacOS for the SDL_mixer library
slouken@518
     3
  Copyright (C) 2001  Max Horn <max@quendi.de>
slouken@106
     4
slouken@518
     5
  This software is provided 'as-is', without any express or implied
slouken@518
     6
  warranty.  In no event will the authors be held liable for any damages
slouken@518
     7
  arising from the use of this software.
slouken@106
     8
slouken@518
     9
  Permission is granted to anyone to use this software for any purpose,
slouken@518
    10
  including commercial applications, and to alter it and redistribute it
slouken@518
    11
  freely, subject to the following restrictions:
slouken@106
    12
slouken@518
    13
  1. The origin of this software must not be misrepresented; you must not
slouken@518
    14
     claim that you wrote the original software. If you use this software
slouken@518
    15
     in a product, an acknowledgment in the product documentation would be
slouken@518
    16
     appreciated but is not required.
slouken@518
    17
  2. Altered source versions must be plainly marked as such, and must not be
slouken@518
    18
     misrepresented as being the original software.
slouken@518
    19
  3. This notice may not be removed or altered from any source distribution.
slouken@106
    20
*/
slouken@294
    21
#include "SDL_config.h"
slouken@294
    22
#include "SDL_endian.h"
slouken@718
    23
#include "../mixer.h"
slouken@106
    24
icculus@458
    25
#if __MACOS__ /*|| __MACOSX__ */
slouken@106
    26
slouken@106
    27
#include "native_midi.h"
slouken@106
    28
#include "native_midi_common.h"
slouken@106
    29
slouken@294
    30
#if __MACOSX__
slouken@110
    31
#include <QuickTime/QuickTimeMusic.h>
slouken@110
    32
#else
slouken@106
    33
#include <QuickTimeMusic.h>
slouken@110
    34
#endif
slouken@106
    35
slouken@106
    36
#include <assert.h>
slouken@106
    37
#include <stdlib.h>
slouken@106
    38
#include <string.h>
slouken@106
    39
slouken@106
    40
slouken@106
    41
/* Native Midi song */
slouken@106
    42
struct _NativeMidiSong
slouken@106
    43
{
slouken@617
    44
    Uint32      *tuneSequence;
slouken@617
    45
    Uint32      *tuneHeader;
slouken@106
    46
};
slouken@106
    47
slouken@106
    48
enum
slouken@106
    49
{
slouken@617
    50
    /* number of (32-bit) long words in a note request event */
slouken@617
    51
    kNoteRequestEventLength = ((sizeof(NoteRequest)/sizeof(long)) + 2),
slouken@106
    52
slouken@617
    53
    /* number of (32-bit) long words in a marker event */
slouken@617
    54
    kMarkerEventLength  = 1,
slouken@106
    55
slouken@617
    56
    /* number of (32-bit) long words in a general event, minus its data */
slouken@617
    57
    kGeneralEventLength = 2
slouken@106
    58
};
slouken@106
    59
slouken@617
    60
#define ERROR_BUF_SIZE          256
slouken@617
    61
#define BUFFER_INCREMENT        5000
slouken@106
    62
slouken@617
    63
#define REST_IF_NECESSARY() do {\
slouken@617
    64
            int timeDiff = eventPos->time - lastEventTime;  \
slouken@617
    65
            if(timeDiff)    \
slouken@617
    66
            {   \
slouken@617
    67
                timeDiff = (int)(timeDiff*tick);    \
slouken@617
    68
                qtma_StuffRestEvent(*tunePos, timeDiff);    \
slouken@617
    69
                tunePos++;  \
slouken@617
    70
                lastEventTime = eventPos->time; \
slouken@617
    71
            }   \
slouken@617
    72
        } while(0)
slouken@106
    73
slouken@106
    74
slouken@106
    75
static Uint32 *BuildTuneSequence(MIDIEvent *evntlist, int ppqn, int part_poly_max[32], int part_to_inst[32], int *numParts);
slouken@106
    76
static Uint32 *BuildTuneHeader(int part_poly_max[32], int part_to_inst[32], int numParts);
slouken@106
    77
slouken@106
    78
/* The global TunePlayer instance */
slouken@617
    79
static TunePlayer   gTunePlayer = NULL;
slouken@617
    80
static int          gInstaceCount = 0;
slouken@617
    81
static Uint32       *gCurrentTuneSequence = NULL;
slouken@617
    82
static char         gErrorBuffer[ERROR_BUF_SIZE] = "";
slouken@106
    83
slouken@106
    84
slouken@106
    85
/* Check whether QuickTime is available */
slouken@106
    86
int native_midi_detect()
slouken@106
    87
{
slouken@617
    88
    /* TODO */
slouken@617
    89
    return 1;
slouken@106
    90
}
slouken@106
    91
slouken@625
    92
NativeMidiSong *native_midi_loadsong_RW(SDL_RWops *src, int freesrc)
patmandin@269
    93
{
slouken@617
    94
    NativeMidiSong  *song = NULL;
slouken@617
    95
    MIDIEvent       *evntlist = NULL;
slouken@617
    96
    int             part_to_inst[32];
slouken@617
    97
    int             part_poly_max[32];
slouken@617
    98
    int             numParts = 0;
slouken@617
    99
    Uint16          ppqn;
patmandin@274
   100
slouken@617
   101
    /* Init the arrays */
slouken@617
   102
    memset(part_poly_max,0,sizeof(part_poly_max));
slouken@617
   103
    memset(part_to_inst,-1,sizeof(part_to_inst));
patmandin@274
   104
slouken@617
   105
    /* Attempt to load the midi file */
slouken@625
   106
    evntlist = CreateMIDIEventList(src, &ppqn);
slouken@617
   107
    if (!evntlist)
slouken@617
   108
        goto bail;
patmandin@274
   109
slouken@617
   110
    /* Allocate memory for the song struct */
slouken@617
   111
    song = malloc(sizeof(NativeMidiSong));
slouken@617
   112
    if (!song)
slouken@617
   113
        goto bail;
patmandin@274
   114
slouken@617
   115
    /* Build a tune sequence from the event list */
slouken@617
   116
    song->tuneSequence = BuildTuneSequence(evntlist, ppqn, part_poly_max, part_to_inst, &numParts);
slouken@617
   117
    if(!song->tuneSequence)
slouken@617
   118
        goto bail;
patmandin@274
   119
slouken@617
   120
    /* Now build a tune header from the data we collect above, create
slouken@617
   121
       all parts as needed and assign them the correct instrument.
slouken@617
   122
    */
slouken@617
   123
    song->tuneHeader = BuildTuneHeader(part_poly_max, part_to_inst, numParts);
slouken@617
   124
    if(!song->tuneHeader)
slouken@617
   125
        goto bail;
slouken@521
   126
slouken@617
   127
    /* Increment the instance count */
slouken@617
   128
    gInstaceCount++;
slouken@617
   129
    if (gTunePlayer == NULL)
slouken@617
   130
        gTunePlayer = OpenDefaultComponent(kTunePlayerComponentType, 0);
slouken@617
   131
slouken@617
   132
    /* Finally, free the event list */
slouken@617
   133
    FreeMIDIEventList(evntlist);
slouken@617
   134
slouken@617
   135
    if (freerw) {
slouken@617
   136
        SDL_RWclose(rw);
slouken@617
   137
    }
slouken@617
   138
    return song;
slouken@617
   139
patmandin@274
   140
bail:
slouken@617
   141
    if (evntlist)
slouken@617
   142
        FreeMIDIEventList(evntlist);
patmandin@274
   143
slouken@617
   144
    if (song)
slouken@617
   145
    {
slouken@617
   146
        if(song->tuneSequence)
slouken@617
   147
            free(song->tuneSequence);
slouken@617
   148
slouken@617
   149
        if(song->tuneHeader)
slouken@617
   150
            DisposePtr((Ptr)song->tuneHeader);
slouken@617
   151
slouken@617
   152
        free(song);
slouken@617
   153
    }
slouken@617
   154
    return NULL;
patmandin@269
   155
}
patmandin@269
   156
slouken@106
   157
void native_midi_freesong(NativeMidiSong *song)
slouken@106
   158
{
slouken@617
   159
    if(!song || !song->tuneSequence)
slouken@617
   160
        return;
slouken@106
   161
slouken@617
   162
    /* If this is the currently playing song, stop it now */
slouken@617
   163
    if (song->tuneSequence == gCurrentTuneSequence)
slouken@617
   164
        native_midi_stop();
slouken@106
   165
slouken@617
   166
    /* Finally, free the data storage */
slouken@617
   167
    free(song->tuneSequence);
slouken@617
   168
    DisposePtr((Ptr)song->tuneHeader);
slouken@617
   169
    free(song);
slouken@617
   170
slouken@617
   171
    /* Increment the instance count */
slouken@617
   172
    gInstaceCount--;
slouken@617
   173
    if ((gTunePlayer != NULL) && (gInstaceCount == 0))
slouken@617
   174
    {
slouken@617
   175
        CloseComponent(gTunePlayer);
slouken@617
   176
        gTunePlayer = NULL;
slouken@617
   177
    }
slouken@106
   178
}
slouken@106
   179
slouken@533
   180
void native_midi_start(NativeMidiSong *song, int loops)
slouken@106
   181
{
slouken@617
   182
    UInt32      queueFlags = 0;
slouken@617
   183
    ComponentResult tpError;
slouken@533
   184
slouken@617
   185
    assert (gTunePlayer != NULL);
slouken@106
   186
slouken@617
   187
    /* FIXME: is this code even used anymore? */
slouken@617
   188
    assert (loops == 0);
slouken@106
   189
slouken@617
   190
    SDL_PauseAudio(1);
slouken@718
   191
    Mix_UnlockAudio();
slouken@106
   192
slouken@617
   193
    /* First, stop the currently playing music */
slouken@617
   194
    native_midi_stop();
slouken@617
   195
slouken@617
   196
    /* Set up the queue flags */
slouken@617
   197
    queueFlags = kTuneStartNow;
slouken@617
   198
slouken@617
   199
    /* Set the time scale (units per second), we want milliseconds */
slouken@617
   200
    tpError = TuneSetTimeScale(gTunePlayer, 1000);
slouken@617
   201
    if (tpError != noErr)
slouken@617
   202
    {
slouken@617
   203
        strncpy (gErrorBuffer, "MIDI error during TuneSetTimeScale", ERROR_BUF_SIZE);
slouken@617
   204
        goto done;
slouken@617
   205
    }
slouken@617
   206
slouken@617
   207
    /* Set the header, to tell what instruments are used */
slouken@617
   208
    tpError = TuneSetHeader(gTunePlayer, (UInt32 *)song->tuneHeader);
slouken@617
   209
    if (tpError != noErr)
slouken@617
   210
    {
slouken@617
   211
        strncpy (gErrorBuffer, "MIDI error during TuneSetHeader", ERROR_BUF_SIZE);
slouken@617
   212
        goto done;
slouken@617
   213
    }
slouken@617
   214
slouken@617
   215
    /* Have it allocate whatever resources are needed */
slouken@617
   216
    tpError = TunePreroll(gTunePlayer);
slouken@617
   217
    if (tpError != noErr)
slouken@617
   218
    {
slouken@617
   219
        strncpy (gErrorBuffer, "MIDI error during TunePreroll", ERROR_BUF_SIZE);
slouken@617
   220
        goto done;
slouken@617
   221
    }
slouken@617
   222
slouken@617
   223
    /* We want to play at normal volume */
slouken@617
   224
    tpError = TuneSetVolume(gTunePlayer, 0x00010000);
slouken@617
   225
    if (tpError != noErr)
slouken@617
   226
    {
slouken@617
   227
        strncpy (gErrorBuffer, "MIDI error during TuneSetVolume", ERROR_BUF_SIZE);
slouken@617
   228
        goto done;
slouken@617
   229
    }
slouken@617
   230
slouken@617
   231
    /* Finally, start playing the full song */
slouken@617
   232
    gCurrentTuneSequence = song->tuneSequence;
slouken@617
   233
    tpError = TuneQueue(gTunePlayer, (UInt32 *)song->tuneSequence, 0x00010000, 0, 0xFFFFFFFF, queueFlags, NULL, 0);
slouken@617
   234
    if (tpError != noErr)
slouken@617
   235
    {
slouken@617
   236
        strncpy (gErrorBuffer, "MIDI error during TuneQueue", ERROR_BUF_SIZE);
slouken@617
   237
        goto done;
slouken@617
   238
    }
slouken@617
   239
slouken@223
   240
done:
slouken@718
   241
    Mix_LockAudio();
slouken@617
   242
    SDL_PauseAudio(0);
slouken@106
   243
}
slouken@106
   244
slouken@106
   245
void native_midi_stop()
slouken@106
   246
{
slouken@617
   247
    if (gTunePlayer == NULL)
slouken@617
   248
        return;
slouken@106
   249
slouken@617
   250
    /* Stop music */
slouken@617
   251
    TuneStop(gTunePlayer, 0);
slouken@617
   252
slouken@617
   253
    /* Deallocate all instruments */
slouken@617
   254
    TuneUnroll(gTunePlayer);
slouken@106
   255
}
slouken@106
   256
slouken@106
   257
int native_midi_active()
slouken@106
   258
{
slouken@617
   259
    if (gTunePlayer != NULL)
slouken@617
   260
    {
slouken@617
   261
        TuneStatus  ts;
slouken@106
   262
slouken@617
   263
        TuneGetStatus(gTunePlayer,&ts);
slouken@617
   264
        return ts.queueTime != 0;
slouken@617
   265
    }
slouken@617
   266
    else
slouken@617
   267
        return 0;
slouken@106
   268
}
slouken@106
   269
slouken@106
   270
void native_midi_setvolume(int volume)
slouken@106
   271
{
slouken@617
   272
    if (gTunePlayer == NULL)
slouken@617
   273
        return;
slouken@106
   274
slouken@617
   275
    /* QTMA olume may range from 0.0 to 1.0 (in 16.16 fixed point encoding) */
slouken@617
   276
    TuneSetVolume(gTunePlayer, (0x00010000 * volume)/SDL_MIX_MAXVOLUME);
slouken@106
   277
}
slouken@106
   278
slouken@400
   279
const char *native_midi_error(void)
slouken@106
   280
{
slouken@617
   281
    return gErrorBuffer;
slouken@106
   282
}
slouken@106
   283
slouken@106
   284
Uint32 *BuildTuneSequence(MIDIEvent *evntlist, int ppqn, int part_poly_max[32], int part_to_inst[32], int *numParts)
slouken@106
   285
{
slouken@617
   286
    int         part_poly[32];
slouken@617
   287
    int         channel_to_part[16];
slouken@106
   288
slouken@617
   289
    int         channel_pan[16];
slouken@617
   290
    int         channel_vol[16];
slouken@617
   291
    int         channel_pitch_bend[16];
slouken@617
   292
slouken@617
   293
    int         lastEventTime = 0;
slouken@617
   294
    int         tempo = 500000;
slouken@617
   295
    double      Ippqn = 1.0 / (1000*ppqn);
slouken@617
   296
    double      tick = tempo * Ippqn;
slouken@617
   297
    MIDIEvent   *eventPos = evntlist;
slouken@617
   298
    MIDIEvent   *noteOffPos;
slouken@617
   299
    Uint32      *tunePos, *endPos;
slouken@617
   300
    Uint32      *tuneSequence;
slouken@617
   301
    size_t      tuneSize;
slouken@617
   302
slouken@617
   303
    /* allocate space for the tune header */
slouken@617
   304
    tuneSize = 5000;
slouken@617
   305
    tuneSequence = (Uint32 *)malloc(tuneSize * sizeof(Uint32));
slouken@617
   306
    if (tuneSequence == NULL)
slouken@617
   307
        return NULL;
slouken@617
   308
slouken@617
   309
    /* Set starting position in our tune memory */
slouken@617
   310
    tunePos = tuneSequence;
slouken@617
   311
    endPos = tuneSequence + tuneSize;
slouken@617
   312
slouken@617
   313
    /* Initialise the arrays */
slouken@617
   314
    memset(part_poly,0,sizeof(part_poly));
slouken@617
   315
slouken@617
   316
    memset(channel_to_part,-1,sizeof(channel_to_part));
slouken@617
   317
    memset(channel_pan,-1,sizeof(channel_pan));
slouken@617
   318
    memset(channel_vol,-1,sizeof(channel_vol));
slouken@617
   319
    memset(channel_pitch_bend,-1,sizeof(channel_pitch_bend));
slouken@617
   320
slouken@617
   321
    *numParts = 0;
slouken@617
   322
slouken@617
   323
    /*
slouken@617
   324
     * Now the major work - iterate over all GM events,
slouken@617
   325
     * and turn them into QuickTime Music format.
slouken@617
   326
     * At the same time, calculate the max. polyphony for each part,
slouken@617
   327
     * and also the part->instrument mapping.
slouken@617
   328
     */
slouken@617
   329
    while(eventPos)
slouken@617
   330
    {
slouken@617
   331
        int status = (eventPos->status&0xF0)>>4;
slouken@617
   332
        int channel = eventPos->status&0x0F;
slouken@617
   333
        int part = channel_to_part[channel];
slouken@106
   334
        int velocity, pitch;
slouken@106
   335
        int value, controller;
slouken@106
   336
        int bend;
slouken@106
   337
        int newInst;
slouken@106
   338
slouken@617
   339
        /* Check if we are running low on space... */
slouken@617
   340
        if((tunePos+16) > endPos)
slouken@617
   341
        {
slouken@617
   342
            /* Resize our data storage. */
slouken@617
   343
            Uint32      *oldTuneSequence = tuneSequence;
slouken@106
   344
slouken@617
   345
            tuneSize += BUFFER_INCREMENT;
slouken@617
   346
            tuneSequence = (Uint32 *)realloc(tuneSequence, tuneSize * sizeof(Uint32));
slouken@617
   347
            if(oldTuneSequence != tuneSequence)
slouken@617
   348
                tunePos += tuneSequence - oldTuneSequence;
slouken@617
   349
            endPos = tuneSequence + tuneSize;
slouken@617
   350
        }
slouken@106
   351
slouken@617
   352
        switch (status)
slouken@617
   353
        {
slouken@617
   354
        case MIDI_STATUS_NOTE_OFF:
slouken@617
   355
            assert(part>=0 && part<=31);
slouken@106
   356
slouken@617
   357
            /* Keep track of the polyphony of the current part */
slouken@617
   358
            part_poly[part]--;
slouken@617
   359
            break;
slouken@617
   360
        case MIDI_STATUS_NOTE_ON:
slouken@617
   361
            if (part < 0)
slouken@617
   362
            {
slouken@617
   363
                /* If no part is specified yet, we default to the first instrument, which
slouken@617
   364
                   is piano (or the first drum kit if we are on the drum channel)
slouken@617
   365
                */
slouken@617
   366
                int newInst;
slouken@106
   367
slouken@617
   368
                if (channel == 9)
slouken@617
   369
                    newInst = kFirstDrumkit + 1;        /* the first drum kit is the "no drum" kit! */
slouken@617
   370
                else
slouken@617
   371
                    newInst = kFirstGMInstrument;
slouken@617
   372
                part = channel_to_part[channel] = *numParts;
slouken@617
   373
                part_to_inst[(*numParts)++] = newInst;
slouken@617
   374
            }
slouken@617
   375
            /* TODO - add support for more than 32 parts using eXtended QTMA events */
slouken@617
   376
            assert(part<=31);
slouken@617
   377
slouken@617
   378
            /* Decode pitch & velocity */
slouken@617
   379
            pitch = eventPos->data[0];
slouken@617
   380
            velocity = eventPos->data[1];
slouken@617
   381
slouken@617
   382
            if (velocity == 0)
slouken@617
   383
            {
slouken@617
   384
                /* was a NOTE OFF in disguise, so we decrement the polyphony */
slouken@617
   385
                part_poly[part]--;
slouken@617
   386
            }
slouken@617
   387
            else
slouken@617
   388
            {
slouken@617
   389
                /* Keep track of the polyphony of the current part */
slouken@617
   390
                int foo = ++part_poly[part];
slouken@617
   391
                if (part_poly_max[part] < foo)
slouken@617
   392
                    part_poly_max[part] = foo;
slouken@617
   393
slouken@617
   394
                /* Now scan forward to find the matching NOTE OFF event */
slouken@617
   395
                for(noteOffPos = eventPos; noteOffPos; noteOffPos = noteOffPos->next)
slouken@617
   396
                {
slouken@617
   397
                    if ((noteOffPos->status&0xF0)>>4 == MIDI_STATUS_NOTE_OFF
slouken@617
   398
                        && channel == (eventPos->status&0x0F)
slouken@617
   399
                        && pitch == noteOffPos->data[0])
slouken@617
   400
                        break;
slouken@617
   401
                    /* NOTE ON with velocity == 0 is the same as a NOTE OFF */
slouken@617
   402
                    if ((noteOffPos->status&0xF0)>>4 == MIDI_STATUS_NOTE_ON
slouken@617
   403
                        && channel == (eventPos->status&0x0F)
slouken@617
   404
                        && pitch == noteOffPos->data[0]
slouken@617
   405
                        && 0 == noteOffPos->data[1])
slouken@617
   406
                        break;
slouken@617
   407
                }
slouken@617
   408
slouken@617
   409
                /* Did we find a note off? Should always be the case, but who knows... */
slouken@617
   410
                if (noteOffPos)
slouken@617
   411
                {
slouken@617
   412
                    /* We found a NOTE OFF, now calculate the note duration */
slouken@617
   413
                    int duration = (int)((noteOffPos->time - eventPos->time)*tick);
slouken@617
   414
slouken@617
   415
                    REST_IF_NECESSARY();
slouken@617
   416
                    /* Now we need to check if we get along with a normal Note Event, or if we need an extended one... */
slouken@617
   417
                    if (duration < 2048 && pitch>=32 && pitch<=95 && velocity>=0 && velocity<=127)
slouken@617
   418
                    {
slouken@617
   419
                        qtma_StuffNoteEvent(*tunePos, part, pitch, velocity, duration);
slouken@617
   420
                        tunePos++;
slouken@617
   421
                    }
slouken@617
   422
                    else
slouken@617
   423
                    {
slouken@617
   424
                        qtma_StuffXNoteEvent(*tunePos, *(tunePos+1), part, pitch, velocity, duration);
slouken@617
   425
                        tunePos+=2;
slouken@617
   426
                    }
slouken@617
   427
                }
slouken@617
   428
            }
slouken@617
   429
            break;
slouken@617
   430
        case MIDI_STATUS_AFTERTOUCH:
slouken@617
   431
            /* NYI - use kControllerAfterTouch. But how are the parameters to be mapped? */
slouken@617
   432
            break;
slouken@617
   433
        case MIDI_STATUS_CONTROLLER:
slouken@617
   434
            controller = eventPos->data[0];
slouken@617
   435
            value = eventPos->data[1];
slouken@617
   436
slouken@617
   437
            switch(controller)
slouken@617
   438
            {
slouken@617
   439
            case 0: /* bank change - igore for now */
slouken@617
   440
                break;
slouken@617
   441
            case kControllerVolume:
slouken@617
   442
                if(channel_vol[channel] != value<<8)
slouken@617
   443
                {
slouken@617
   444
                    channel_vol[channel] = value<<8;
slouken@617
   445
                    if(part>=0 && part<=31)
slouken@617
   446
                    {
slouken@617
   447
                        REST_IF_NECESSARY();
slouken@617
   448
                        qtma_StuffControlEvent(*tunePos, part, kControllerVolume, channel_vol[channel]);
slouken@617
   449
                        tunePos++;
slouken@617
   450
                    }
slouken@617
   451
                }
slouken@617
   452
                break;
slouken@617
   453
            case kControllerPan:
slouken@617
   454
                if(channel_pan[channel] != (value << 1) + 256)
slouken@617
   455
                {
slouken@617
   456
                    channel_pan[channel] = (value << 1) + 256;
slouken@617
   457
                    if(part>=0 && part<=31)
slouken@617
   458
                    {
slouken@617
   459
                        REST_IF_NECESSARY();
slouken@617
   460
                        qtma_StuffControlEvent(*tunePos, part, kControllerPan, channel_pan[channel]);
slouken@617
   461
                        tunePos++;
slouken@617
   462
                    }
slouken@617
   463
                }
slouken@617
   464
                break;
slouken@617
   465
            default:
slouken@617
   466
                /* No other controllers implemented yet */;
slouken@617
   467
                break;
slouken@617
   468
            }
slouken@617
   469
slouken@617
   470
            break;
slouken@617
   471
        case MIDI_STATUS_PROG_CHANGE:
slouken@617
   472
            /* Instrument changed */
slouken@617
   473
            newInst = eventPos->data[0];
slouken@617
   474
slouken@617
   475
            /* Channel 9 (the 10th channel) is different, it indicates a drum kit */
slouken@617
   476
            if (channel == 9)
slouken@617
   477
                newInst += kFirstDrumkit;
slouken@617
   478
            else
slouken@617
   479
                newInst += kFirstGMInstrument;
slouken@617
   480
            /* Only if the instrument for this channel *really* changed, add a new part. */
slouken@617
   481
            if(newInst != part_to_inst[part])
slouken@617
   482
            {
slouken@617
   483
                /* TODO maybe make use of kGeneralEventPartChange here,
slouken@617
   484
                   to help QT reuse note channels?
slouken@617
   485
                */
slouken@617
   486
                part = channel_to_part[channel] = *numParts;
slouken@617
   487
                part_to_inst[(*numParts)++] = newInst;
slouken@617
   488
slouken@617
   489
                if(channel_vol[channel] >= 0)
slouken@617
   490
                {
slouken@617
   491
                    REST_IF_NECESSARY();
slouken@617
   492
                    qtma_StuffControlEvent(*tunePos, part, kControllerVolume, channel_vol[channel]);
slouken@617
   493
                    tunePos++;
slouken@617
   494
                }
slouken@617
   495
                if(channel_pan[channel] >= 0)
slouken@617
   496
                {
slouken@617
   497
                    REST_IF_NECESSARY();
slouken@617
   498
                    qtma_StuffControlEvent(*tunePos, part, kControllerPan, channel_pan[channel]);
slouken@617
   499
                    tunePos++;
slouken@617
   500
                }
slouken@617
   501
                if(channel_pitch_bend[channel] >= 0)
slouken@617
   502
                {
slouken@617
   503
                    REST_IF_NECESSARY();
slouken@617
   504
                    qtma_StuffControlEvent(*tunePos, part, kControllerPitchBend, channel_pitch_bend[channel]);
slouken@617
   505
                    tunePos++;
slouken@617
   506
                }
slouken@617
   507
            }
slouken@617
   508
            break;
slouken@617
   509
        case MIDI_STATUS_PRESSURE:
slouken@617
   510
            /* NYI */
slouken@617
   511
            break;
slouken@617
   512
        case MIDI_STATUS_PITCH_WHEEL:
slouken@617
   513
            /* In the midi spec, 0x2000 = center, 0x0000 = - 2 semitones, 0x3FFF = +2 semitones
slouken@617
   514
               but for QTMA, we specify it as a 8.8 fixed point of semitones
slouken@617
   515
               TODO: detect "pitch bend range changes" & honor them!
slouken@617
   516
            */
slouken@617
   517
            bend = (eventPos->data[0] & 0x7f) | ((eventPos->data[1] & 0x7f) << 7);
slouken@617
   518
slouken@617
   519
            /* "Center" the bend */
slouken@617
   520
            bend -= 0x2000;
slouken@617
   521
slouken@617
   522
            /* Move it to our format: */
slouken@617
   523
            bend <<= 4;
slouken@617
   524
slouken@617
   525
            /* If it turns out the pitch bend didn't change, stop here */
slouken@617
   526
            if(channel_pitch_bend[channel] == bend)
slouken@617
   527
                break;
slouken@617
   528
slouken@617
   529
            channel_pitch_bend[channel] = bend;
slouken@617
   530
            if(part>=0 && part<=31)
slouken@617
   531
            {
slouken@617
   532
                /* Stuff a control event */
slouken@617
   533
                REST_IF_NECESSARY();
slouken@617
   534
                qtma_StuffControlEvent(*tunePos, part, kControllerPitchBend, bend);
slouken@617
   535
                tunePos++;
slouken@617
   536
            }
slouken@617
   537
            break;
slouken@617
   538
        case MIDI_STATUS_SYSEX:
slouken@617
   539
            if (eventPos->status == 0xFF && eventPos->data[0] == 0x51) /* Tempo change */
slouken@617
   540
            {
slouken@617
   541
                tempo = (eventPos->extraData[0] << 16) +
slouken@617
   542
                    (eventPos->extraData[1] << 8) +
slouken@617
   543
                    eventPos->extraData[2];
slouken@617
   544
slouken@617
   545
                tick = tempo * Ippqn;
slouken@617
   546
            }
slouken@617
   547
            break;
slouken@617
   548
        }
slouken@617
   549
slouken@617
   550
        /* on to the next event */
slouken@617
   551
        eventPos = eventPos->next;
slouken@617
   552
    }
slouken@617
   553
slouken@617
   554
    /* Finally, place an end marker */
slouken@617
   555
    *tunePos = kEndMarkerValue;
slouken@617
   556
slouken@617
   557
    return tuneSequence;
slouken@106
   558
}
slouken@106
   559
slouken@106
   560
Uint32 *BuildTuneHeader(int part_poly_max[32], int part_to_inst[32], int numParts)
slouken@106
   561
{
slouken@617
   562
    Uint32          *myHeader;
slouken@617
   563
    Uint32          *myPos1, *myPos2;       /* pointers to the head and tail long words of a music event */
slouken@617
   564
    NoteRequest     *myNoteRequest;
slouken@617
   565
    NoteAllocator   myNoteAllocator;        /* for the NAStuffToneDescription call */
slouken@617
   566
    ComponentResult myErr = noErr;
slouken@617
   567
    int             part;
slouken@106
   568
slouken@617
   569
    myHeader = NULL;
slouken@617
   570
    myNoteAllocator = NULL;
slouken@106
   571
slouken@617
   572
    /*
slouken@617
   573
     * Open up the Note Allocator
slouken@617
   574
     */
slouken@617
   575
    myNoteAllocator = OpenDefaultComponent(kNoteAllocatorComponentType,0);
slouken@617
   576
    if (myNoteAllocator == NULL)
slouken@617
   577
        goto bail;
slouken@617
   578
slouken@617
   579
    /*
slouken@617
   580
     * Allocate space for the tune header
slouken@617
   581
     */
slouken@617
   582
    myHeader = (Uint32 *)
slouken@617
   583
            NewPtrClear((numParts * kNoteRequestEventLength + kMarkerEventLength) * sizeof(Uint32));
slouken@617
   584
    if (myHeader == NULL)
slouken@617
   585
        goto bail;
slouken@617
   586
slouken@617
   587
    myPos1 = myHeader;
slouken@617
   588
slouken@617
   589
    /*
slouken@617
   590
     * Loop over all parts
slouken@617
   591
     */
slouken@617
   592
    for(part = 0; part < numParts; ++part)
slouken@617
   593
    {
slouken@617
   594
        /*
slouken@617
   595
         * Stuff request for the instrument with the given polyphony
slouken@617
   596
         */
slouken@617
   597
        myPos2 = myPos1 + (kNoteRequestEventLength - 1); /* last longword of general event */
slouken@617
   598
        qtma_StuffGeneralEvent(*myPos1, *myPos2, part, kGeneralEventNoteRequest, kNoteRequestEventLength);
slouken@617
   599
        myNoteRequest = (NoteRequest *)(myPos1 + 1);
slouken@617
   600
        myNoteRequest->info.flags = 0;
slouken@617
   601
        /* I'm told by the Apple people that the Quicktime types were poorly designed and it was
slouken@617
   602
         * too late to change them. On little endian, the BigEndian(Short|Fixed) types are structs
slouken@617
   603
         * while on big endian they are primitive types. Furthermore, Quicktime failed to
slouken@617
   604
         * provide setter and getter functions. To get this to work, we need to case the
slouken@617
   605
         * code for the two possible situations.
slouken@617
   606
         * My assumption is that the right-side value was always expected to be BigEndian
slouken@617
   607
         * as it was written way before the Universal Binary transition. So in the little endian
slouken@617
   608
         * case, OSSwap is used.
slouken@617
   609
         */
slouken@294
   610
#if SDL_BYTEORDER == SDL_LIL_ENDIAN
slouken@617
   611
        myNoteRequest->info.polyphony.bigEndianValue = OSSwapHostToBigInt16(part_poly_max[part]);
slouken@617
   612
        myNoteRequest->info.typicalPolyphony.bigEndianValue = OSSwapHostToBigInt32(0x00010000);
icculus@261
   613
#else
slouken@617
   614
        myNoteRequest->info.polyphony = part_poly_max[part];
slouken@617
   615
        myNoteRequest->info.typicalPolyphony = 0x00010000;
icculus@261
   616
#endif
slouken@617
   617
        myErr = NAStuffToneDescription(myNoteAllocator,part_to_inst[part],&myNoteRequest->tone);
slouken@617
   618
        if (myErr != noErr)
slouken@617
   619
            goto bail;
slouken@106
   620
slouken@617
   621
        /* move pointer to beginning of next event */
slouken@617
   622
        myPos1 += kNoteRequestEventLength;
slouken@617
   623
    }
slouken@617
   624
slouken@617
   625
    *myPos1 = kEndMarkerValue;      /* end of sequence marker */
slouken@106
   626
slouken@106
   627
slouken@106
   628
bail:
slouken@617
   629
    if(myNoteAllocator)
slouken@617
   630
        CloseComponent(myNoteAllocator);
slouken@106
   631
slouken@617
   632
    /* if we encountered an error, dispose of the storage we allocated and return NULL */
slouken@617
   633
    if (myErr != noErr) {
slouken@617
   634
        DisposePtr((Ptr)myHeader);
slouken@617
   635
        myHeader = NULL;
slouken@617
   636
    }
slouken@106
   637
slouken@617
   638
    return myHeader;
slouken@106
   639
}
slouken@106
   640
slouken@106
   641
#endif /* MacOS native MIDI support */