native_midi_mac.c
上传用户:nini_0081
上传日期:2022-07-21
资源大小:2628k
文件大小:18k
- /*
- native_midi_mac: Native Midi support on MacOS for the SDL_mixer library
- Copyright (C) 2001 Max Horn
- This library is free software; you can redistribute it and/or
- modify it under the terms of the GNU Library General Public
- License as published by the Free Software Foundation; either
- version 2 of the License, or (at your option) any later version.
- This library is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- Library General Public License for more details.
- You should have received a copy of the GNU Library General Public
- License along with this library; if not, write to the Free
- Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
- Max Horn
- max@quendi.de
- */
- #include "SDL_config.h"
- #include "SDL_endian.h"
- #if __MACOS__ /*|| __MACOSX__ */
- #include "native_midi.h"
- #include "native_midi_common.h"
- #if __MACOSX__
- #include <QuickTime/QuickTimeMusic.h>
- #else
- #include <QuickTimeMusic.h>
- #endif
- #include <assert.h>
- #include <stdlib.h>
- #include <string.h>
- /* Native Midi song */
- struct _NativeMidiSong
- {
- Uint32 *tuneSequence;
- Uint32 *tuneHeader;
- };
- enum
- {
- /* number of (32-bit) long words in a note request event */
- kNoteRequestEventLength = ((sizeof(NoteRequest)/sizeof(long)) + 2),
- /* number of (32-bit) long words in a marker event */
- kMarkerEventLength = 1,
- /* number of (32-bit) long words in a general event, minus its data */
- kGeneralEventLength = 2
- };
- #define ERROR_BUF_SIZE 256
- #define BUFFER_INCREMENT 5000
- #define REST_IF_NECESSARY() do {
- int timeDiff = eventPos->time - lastEventTime;
- if(timeDiff)
- {
- timeDiff = (int)(timeDiff*tick);
- qtma_StuffRestEvent(*tunePos, timeDiff);
- tunePos++;
- lastEventTime = eventPos->time;
- }
- } while(0)
- static Uint32 *BuildTuneSequence(MIDIEvent *evntlist, int ppqn, int part_poly_max[32], int part_to_inst[32], int *numParts);
- static Uint32 *BuildTuneHeader(int part_poly_max[32], int part_to_inst[32], int numParts);
- /* The global TunePlayer instance */
- static TunePlayer gTunePlayer = NULL;
- static int gInstaceCount = 0;
- static Uint32 *gCurrentTuneSequence = NULL;
- static char gErrorBuffer[ERROR_BUF_SIZE] = "";
- /* Check whether QuickTime is available */
- int native_midi_detect()
- {
- /* TODO */
- return 1;
- }
- NativeMidiSong *native_midi_loadsong(const char *midifile)
- {
- NativeMidiSong *song = NULL;
- MIDIEvent *evntlist = NULL;
- int part_to_inst[32];
- int part_poly_max[32];
- int numParts = 0;
- Uint16 ppqn;
- SDL_RWops *rw;
- /* Init the arrays */
- memset(part_poly_max,0,sizeof(part_poly_max));
- memset(part_to_inst,-1,sizeof(part_to_inst));
-
- /* Attempt to load the midi file */
- rw = SDL_RWFromFile(midifile, "rb");
- if (rw) {
- evntlist = CreateMIDIEventList(rw, &ppqn);
- SDL_RWclose(rw);
- if (!evntlist)
- goto bail;
- }
- /* Allocate memory for the song struct */
- song = malloc(sizeof(NativeMidiSong));
- if (!song)
- goto bail;
- /* Build a tune sequence from the event list */
- song->tuneSequence = BuildTuneSequence(evntlist, ppqn, part_poly_max, part_to_inst, &numParts);
- if(!song->tuneSequence)
- goto bail;
- /* Now build a tune header from the data we collect above, create
- all parts as needed and assign them the correct instrument.
- */
- song->tuneHeader = BuildTuneHeader(part_poly_max, part_to_inst, numParts);
- if(!song->tuneHeader)
- goto bail;
-
- /* Increment the instance count */
- gInstaceCount++;
- if (gTunePlayer == NULL)
- gTunePlayer = OpenDefaultComponent(kTunePlayerComponentType, 0);
- /* Finally, free the event list */
- FreeMIDIEventList(evntlist);
-
- return song;
-
- bail:
- if (evntlist)
- FreeMIDIEventList(evntlist);
-
- if (song)
- {
- if(song->tuneSequence)
- free(song->tuneSequence);
-
- if(song->tuneHeader)
- DisposePtr((Ptr)song->tuneHeader);
- free(song);
- }
-
- return NULL;
- }
- NativeMidiSong *native_midi_loadsong_RW(SDL_RWops *rw)
- {
- NativeMidiSong *song = NULL;
- MIDIEvent *evntlist = NULL;
- int part_to_inst[32];
- int part_poly_max[32];
- int numParts = 0;
- Uint16 ppqn;
- /* Init the arrays */
- memset(part_poly_max,0,sizeof(part_poly_max));
- memset(part_to_inst,-1,sizeof(part_to_inst));
-
- /* Attempt to load the midi file */
- evntlist = CreateMIDIEventList(rw, &ppqn);
- if (!evntlist)
- goto bail;
- /* Allocate memory for the song struct */
- song = malloc(sizeof(NativeMidiSong));
- if (!song)
- goto bail;
- /* Build a tune sequence from the event list */
- song->tuneSequence = BuildTuneSequence(evntlist, ppqn, part_poly_max, part_to_inst, &numParts);
- if(!song->tuneSequence)
- goto bail;
- /* Now build a tune header from the data we collect above, create
- all parts as needed and assign them the correct instrument.
- */
- song->tuneHeader = BuildTuneHeader(part_poly_max, part_to_inst, numParts);
- if(!song->tuneHeader)
- goto bail;
-
- /* Increment the instance count */
- gInstaceCount++;
- if (gTunePlayer == NULL)
- gTunePlayer = OpenDefaultComponent(kTunePlayerComponentType, 0);
- /* Finally, free the event list */
- FreeMIDIEventList(evntlist);
-
- return song;
-
- bail:
- if (evntlist)
- FreeMIDIEventList(evntlist);
-
- if (song)
- {
- if(song->tuneSequence)
- free(song->tuneSequence);
-
- if(song->tuneHeader)
- DisposePtr((Ptr)song->tuneHeader);
- free(song);
- }
-
- return NULL;
- }
- void native_midi_freesong(NativeMidiSong *song)
- {
- if(!song || !song->tuneSequence)
- return;
- /* If this is the currently playing song, stop it now */
- if (song->tuneSequence == gCurrentTuneSequence)
- native_midi_stop();
-
- /* Finally, free the data storage */
- free(song->tuneSequence);
- DisposePtr((Ptr)song->tuneHeader);
- free(song);
- /* Increment the instance count */
- gInstaceCount--;
- if ((gTunePlayer != NULL) && (gInstaceCount == 0))
- {
- CloseComponent(gTunePlayer);
- gTunePlayer = NULL;
- }
- }
- void native_midi_start(NativeMidiSong *song)
- {
- UInt32 queueFlags = 0;
- ComponentResult tpError;
-
- assert (gTunePlayer != NULL);
-
- SDL_PauseAudio(1);
- SDL_UnlockAudio();
-
- /* First, stop the currently playing music */
- native_midi_stop();
-
- /* Set up the queue flags */
- queueFlags = kTuneStartNow;
- /* Set the time scale (units per second), we want milliseconds */
- tpError = TuneSetTimeScale(gTunePlayer, 1000);
- if (tpError != noErr)
- {
- strncpy (gErrorBuffer, "MIDI error during TuneSetTimeScale", ERROR_BUF_SIZE);
- goto done;
- }
- /* Set the header, to tell what instruments are used */
- tpError = TuneSetHeader(gTunePlayer, (UInt32 *)song->tuneHeader);
- if (tpError != noErr)
- {
- strncpy (gErrorBuffer, "MIDI error during TuneSetHeader", ERROR_BUF_SIZE);
- goto done;
- }
-
- /* Have it allocate whatever resources are needed */
- tpError = TunePreroll(gTunePlayer);
- if (tpError != noErr)
- {
- strncpy (gErrorBuffer, "MIDI error during TunePreroll", ERROR_BUF_SIZE);
- goto done;
- }
- /* We want to play at normal volume */
- tpError = TuneSetVolume(gTunePlayer, 0x00010000);
- if (tpError != noErr)
- {
- strncpy (gErrorBuffer, "MIDI error during TuneSetVolume", ERROR_BUF_SIZE);
- goto done;
- }
-
- /* Finally, start playing the full song */
- gCurrentTuneSequence = song->tuneSequence;
- tpError = TuneQueue(gTunePlayer, (UInt32 *)song->tuneSequence, 0x00010000, 0, 0xFFFFFFFF, queueFlags, NULL, 0);
- if (tpError != noErr)
- {
- strncpy (gErrorBuffer, "MIDI error during TuneQueue", ERROR_BUF_SIZE);
- goto done;
- }
-
- done:
- SDL_LockAudio();
- SDL_PauseAudio(0);
- }
- void native_midi_stop()
- {
- if (gTunePlayer == NULL)
- return;
- /* Stop music */
- TuneStop(gTunePlayer, 0);
-
- /* Deallocate all instruments */
- TuneUnroll(gTunePlayer);
- }
- int native_midi_active()
- {
- if (gTunePlayer != NULL)
- {
- TuneStatus ts;
- TuneGetStatus(gTunePlayer,&ts);
- return ts.queueTime != 0;
- }
- else
- return 0;
- }
- void native_midi_setvolume(int volume)
- {
- if (gTunePlayer == NULL)
- return;
- /* QTMA olume may range from 0.0 to 1.0 (in 16.16 fixed point encoding) */
- TuneSetVolume(gTunePlayer, (0x00010000 * volume)/SDL_MIX_MAXVOLUME);
- }
- const char *native_midi_error(void)
- {
- return gErrorBuffer;
- }
- Uint32 *BuildTuneSequence(MIDIEvent *evntlist, int ppqn, int part_poly_max[32], int part_to_inst[32], int *numParts)
- {
- int part_poly[32];
- int channel_to_part[16];
-
- int channel_pan[16];
- int channel_vol[16];
- int channel_pitch_bend[16];
-
- int lastEventTime = 0;
- int tempo = 500000;
- double Ippqn = 1.0 / (1000*ppqn);
- double tick = tempo * Ippqn;
- MIDIEvent *eventPos = evntlist;
- MIDIEvent *noteOffPos;
- Uint32 *tunePos, *endPos;
- Uint32 *tuneSequence;
- size_t tuneSize;
-
- /* allocate space for the tune header */
- tuneSize = 5000;
- tuneSequence = (Uint32 *)malloc(tuneSize * sizeof(Uint32));
- if (tuneSequence == NULL)
- return NULL;
-
- /* Set starting position in our tune memory */
- tunePos = tuneSequence;
- endPos = tuneSequence + tuneSize;
- /* Initialise the arrays */
- memset(part_poly,0,sizeof(part_poly));
-
- memset(channel_to_part,-1,sizeof(channel_to_part));
- memset(channel_pan,-1,sizeof(channel_pan));
- memset(channel_vol,-1,sizeof(channel_vol));
- memset(channel_pitch_bend,-1,sizeof(channel_pitch_bend));
-
- *numParts = 0;
-
- /*
- * Now the major work - iterate over all GM events,
- * and turn them into QuickTime Music format.
- * At the same time, calculate the max. polyphony for each part,
- * and also the part->instrument mapping.
- */
- while(eventPos)
- {
- int status = (eventPos->status&0xF0)>>4;
- int channel = eventPos->status&0x0F;
- int part = channel_to_part[channel];
- int velocity, pitch;
- int value, controller;
- int bend;
- int newInst;
-
- /* Check if we are running low on space... */
- if((tunePos+16) > endPos)
- {
- /* Resize our data storage. */
- Uint32 *oldTuneSequence = tuneSequence;
- tuneSize += BUFFER_INCREMENT;
- tuneSequence = (Uint32 *)realloc(tuneSequence, tuneSize * sizeof(Uint32));
- if(oldTuneSequence != tuneSequence)
- tunePos += tuneSequence - oldTuneSequence;
- endPos = tuneSequence + tuneSize;
- }
-
- switch (status)
- {
- case MIDI_STATUS_NOTE_OFF:
- assert(part>=0 && part<=31);
- /* Keep track of the polyphony of the current part */
- part_poly[part]--;
- break;
- case MIDI_STATUS_NOTE_ON:
- if (part < 0)
- {
- /* If no part is specified yet, we default to the first instrument, which
- is piano (or the first drum kit if we are on the drum channel)
- */
- int newInst;
-
- if (channel == 9)
- newInst = kFirstDrumkit + 1; /* the first drum kit is the "no drum" kit! */
- else
- newInst = kFirstGMInstrument;
- part = channel_to_part[channel] = *numParts;
- part_to_inst[(*numParts)++] = newInst;
- }
- /* TODO - add support for more than 32 parts using eXtended QTMA events */
- assert(part<=31);
-
- /* Decode pitch & velocity */
- pitch = eventPos->data[0];
- velocity = eventPos->data[1];
-
- if (velocity == 0)
- {
- /* was a NOTE OFF in disguise, so we decrement the polyphony */
- part_poly[part]--;
- }
- else
- {
- /* Keep track of the polyphony of the current part */
- int foo = ++part_poly[part];
- if (part_poly_max[part] < foo)
- part_poly_max[part] = foo;
- /* Now scan forward to find the matching NOTE OFF event */
- for(noteOffPos = eventPos; noteOffPos; noteOffPos = noteOffPos->next)
- {
- if ((noteOffPos->status&0xF0)>>4 == MIDI_STATUS_NOTE_OFF
- && channel == (eventPos->status&0x0F)
- && pitch == noteOffPos->data[0])
- break;
- /* NOTE ON with velocity == 0 is the same as a NOTE OFF */
- if ((noteOffPos->status&0xF0)>>4 == MIDI_STATUS_NOTE_ON
- && channel == (eventPos->status&0x0F)
- && pitch == noteOffPos->data[0]
- && 0 == noteOffPos->data[1])
- break;
- }
-
- /* Did we find a note off? Should always be the case, but who knows... */
- if (noteOffPos)
- {
- /* We found a NOTE OFF, now calculate the note duration */
- int duration = (int)((noteOffPos->time - eventPos->time)*tick);
-
- REST_IF_NECESSARY();
- /* Now we need to check if we get along with a normal Note Event, or if we need an extended one... */
- if (duration < 2048 && pitch>=32 && pitch<=95 && velocity>=0 && velocity<=127)
- {
- qtma_StuffNoteEvent(*tunePos, part, pitch, velocity, duration);
- tunePos++;
- }
- else
- {
- qtma_StuffXNoteEvent(*tunePos, *(tunePos+1), part, pitch, velocity, duration);
- tunePos+=2;
- }
- }
- }
- break;
- case MIDI_STATUS_AFTERTOUCH:
- /* NYI - use kControllerAfterTouch. But how are the parameters to be mapped? */
- break;
- case MIDI_STATUS_CONTROLLER:
- controller = eventPos->data[0];
- value = eventPos->data[1];
- switch(controller)
- {
- case 0: /* bank change - igore for now */
- break;
- case kControllerVolume:
- if(channel_vol[channel] != value<<8)
- {
- channel_vol[channel] = value<<8;
- if(part>=0 && part<=31)
- {
- REST_IF_NECESSARY();
- qtma_StuffControlEvent(*tunePos, part, kControllerVolume, channel_vol[channel]);
- tunePos++;
- }
- }
- break;
- case kControllerPan:
- if(channel_pan[channel] != (value << 1) + 256)
- {
- channel_pan[channel] = (value << 1) + 256;
- if(part>=0 && part<=31)
- {
- REST_IF_NECESSARY();
- qtma_StuffControlEvent(*tunePos, part, kControllerPan, channel_pan[channel]);
- tunePos++;
- }
- }
- break;
- default:
- /* No other controllers implemented yet */;
- break;
- }
-
- break;
- case MIDI_STATUS_PROG_CHANGE:
- /* Instrument changed */
- newInst = eventPos->data[0];
-
- /* Channel 9 (the 10th channel) is different, it indicates a drum kit */
- if (channel == 9)
- newInst += kFirstDrumkit;
- else
- newInst += kFirstGMInstrument;
- /* Only if the instrument for this channel *really* changed, add a new part. */
- if(newInst != part_to_inst[part])
- {
- /* TODO maybe make use of kGeneralEventPartChange here,
- to help QT reuse note channels?
- */
- part = channel_to_part[channel] = *numParts;
- part_to_inst[(*numParts)++] = newInst;
- if(channel_vol[channel] >= 0)
- {
- REST_IF_NECESSARY();
- qtma_StuffControlEvent(*tunePos, part, kControllerVolume, channel_vol[channel]);
- tunePos++;
- }
- if(channel_pan[channel] >= 0)
- {
- REST_IF_NECESSARY();
- qtma_StuffControlEvent(*tunePos, part, kControllerPan, channel_pan[channel]);
- tunePos++;
- }
- if(channel_pitch_bend[channel] >= 0)
- {
- REST_IF_NECESSARY();
- qtma_StuffControlEvent(*tunePos, part, kControllerPitchBend, channel_pitch_bend[channel]);
- tunePos++;
- }
- }
- break;
- case MIDI_STATUS_PRESSURE:
- /* NYI */
- break;
- case MIDI_STATUS_PITCH_WHEEL:
- /* In the midi spec, 0x2000 = center, 0x0000 = - 2 semitones, 0x3FFF = +2 semitones
- but for QTMA, we specify it as a 8.8 fixed point of semitones
- TODO: detect "pitch bend range changes" & honor them!
- */
- bend = (eventPos->data[0] & 0x7f) | ((eventPos->data[1] & 0x7f) << 7);
-
- /* "Center" the bend */
- bend -= 0x2000;
-
- /* Move it to our format: */
- bend <<= 4;
-
- /* If it turns out the pitch bend didn't change, stop here */
- if(channel_pitch_bend[channel] == bend)
- break;
-
- channel_pitch_bend[channel] = bend;
- if(part>=0 && part<=31)
- {
- /* Stuff a control event */
- REST_IF_NECESSARY();
- qtma_StuffControlEvent(*tunePos, part, kControllerPitchBend, bend);
- tunePos++;
- }
- break;
- case MIDI_STATUS_SYSEX:
- if (eventPos->status == 0xFF && eventPos->data[0] == 0x51) /* Tempo change */
- {
- tempo = (eventPos->extraData[0] << 16) +
- (eventPos->extraData[1] << 8) +
- eventPos->extraData[2];
-
- tick = tempo * Ippqn;
- }
- break;
- }
-
- /* on to the next event */
- eventPos = eventPos->next;
- }
-
- /* Finally, place an end marker */
- *tunePos = kEndMarkerValue;
-
- return tuneSequence;
- }
- Uint32 *BuildTuneHeader(int part_poly_max[32], int part_to_inst[32], int numParts)
- {
- Uint32 *myHeader;
- Uint32 *myPos1, *myPos2; /* pointers to the head and tail long words of a music event */
- NoteRequest *myNoteRequest;
- NoteAllocator myNoteAllocator; /* for the NAStuffToneDescription call */
- ComponentResult myErr = noErr;
- int part;
- myHeader = NULL;
- myNoteAllocator = NULL;
- /*
- * Open up the Note Allocator
- */
- myNoteAllocator = OpenDefaultComponent(kNoteAllocatorComponentType,0);
- if (myNoteAllocator == NULL)
- goto bail;
-
- /*
- * Allocate space for the tune header
- */
- myHeader = (Uint32 *)
- NewPtrClear((numParts * kNoteRequestEventLength + kMarkerEventLength) * sizeof(Uint32));
- if (myHeader == NULL)
- goto bail;
-
- myPos1 = myHeader;
-
- /*
- * Loop over all parts
- */
- for(part = 0; part < numParts; ++part)
- {
- /*
- * Stuff request for the instrument with the given polyphony
- */
- myPos2 = myPos1 + (kNoteRequestEventLength - 1); /* last longword of general event */
- qtma_StuffGeneralEvent(*myPos1, *myPos2, part, kGeneralEventNoteRequest, kNoteRequestEventLength);
- myNoteRequest = (NoteRequest *)(myPos1 + 1);
- myNoteRequest->info.flags = 0;
- /* I'm told by the Apple people that the Quicktime types were poorly designed and it was
- * too late to change them. On little endian, the BigEndian(Short|Fixed) types are structs
- * while on big endian they are primitive types. Furthermore, Quicktime failed to
- * provide setter and getter functions. To get this to work, we need to case the
- * code for the two possible situations.
- * My assumption is that the right-side value was always expected to be BigEndian
- * as it was written way before the Universal Binary transition. So in the little endian
- * case, OSSwap is used.
- */
- #if SDL_BYTEORDER == SDL_LIL_ENDIAN
- myNoteRequest->info.polyphony.bigEndianValue = OSSwapHostToBigInt16(part_poly_max[part]);
- myNoteRequest->info.typicalPolyphony.bigEndianValue = OSSwapHostToBigInt32(0x00010000);
- #else
- myNoteRequest->info.polyphony = part_poly_max[part];
- myNoteRequest->info.typicalPolyphony = 0x00010000;
- #endif
- myErr = NAStuffToneDescription(myNoteAllocator,part_to_inst[part],&myNoteRequest->tone);
- if (myErr != noErr)
- goto bail;
-
- /* move pointer to beginning of next event */
- myPos1 += kNoteRequestEventLength;
- }
- *myPos1 = kEndMarkerValue; /* end of sequence marker */
- bail:
- if(myNoteAllocator)
- CloseComponent(myNoteAllocator);
- /* if we encountered an error, dispose of the storage we allocated and return NULL */
- if (myErr != noErr) {
- DisposePtr((Ptr)myHeader);
- myHeader = NULL;
- }
- return myHeader;
- }
- #endif /* MacOS native MIDI support */