📄 native_midi_mac.c
字号:
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 */
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -