/*******************************Module*Header*********************************\ * Module Name: mmseq.c * * MultiMedia Systems MIDI Sequencer DLL * * Created: 4/10/90 * Author: Greg Simons * * History: * * Copyright (c) 1985-1998 Microsoft Corporation * \******************************************************************************/ #define UNICODE //MMSYSTEM #define MMNOSOUND - Sound support #define MMNOWAVE - Waveform support #define MMNOAUX - Auxiliary output support #define MMNOJOY - Joystick support //MMDDK #define NOWAVEDEV - Waveform support #define NOAUXDEV - Auxiliary output support #define NOJOYDEV - Joystick support #include #include #include #include #include "mmsys.h" #include "list.h" #include "mmseqi.h" #include "mciseq.h" static ListHandle seqListHandle; // Debug macro which checks sequencer structure signature. #ifdef DEBUG #define DEBUG_SIG 0x45427947 #define ValidateNPSEQ(npSeq) ((npSeq)->dwDebug == DEBUG_SIG) #endif #define SeqSetTempo(npSeq, dwTempo) ((npSeq)->tempo = (dwTempo)) // usec per tick /* Setup strucure to handle meta event properly at real time. Since meta events can update time-critical internal variables **and optionally ** be buffered and send out as well, we will defer from reading them until real time. */ #define SetUpMetaEvent(npTrack) ((npTrack)->shortMIDIData.byteMsg.status = METAEVENT) /**************************** PRIVATE FUNCTIONS *************************/ PUBLIC VOID NEAR PASCAL SkipBytes(NPTRACK npTrack, LONG length) // skips "length" bytes in the given track. { LONG i = 0; while (i < length) { GetByte(npTrack); if ((!npTrack->blockedOn) || (npTrack->endOfTrack)) i++; else break; } npTrack->dwBytesLeftToSkip = length - i; // remember for next time return; } /**********************************************************/ PRIVATE DWORD NEAR PASCAL GetMotorola24(NPTRACK npTrack) // reads integer in 24 bit motorola format from the given track { WORD w; w = (WORD)GetByte(npTrack) << 8; w += GetByte(npTrack); return ((DWORD)w << 8) + GetByte(npTrack); } PRIVATE DWORD NEAR PASCAL MStoTicks(NPSEQ npSeq, DWORD dwMs) /* Convert milliseconds into ticks (some unit of time) in the given file. If it's a ppqn file, this conversion totally depends on the tempo map (which tells what tempo changes happen at what times). */ { NPTEMPOMAPELEMENT npFrontTME; NPTEMPOMAPELEMENT npBehindTME; DWORD dwElapsedMs; DWORD dwElapsedTicks; DWORD dwTotalTicks; npBehindTME = NULL; // behind tempo map item: starts null // Find the last element that's before the time passed in npFrontTME = (NPTEMPOMAPELEMENT) List_Get_First(npSeq->tempoMapList); while ((npFrontTME) && (npFrontTME->dwMs <= dwMs)) { npBehindTME = npFrontTME; npFrontTME = (NPTEMPOMAPELEMENT) List_Get_Next(npSeq->tempoMapList, npFrontTME); } if (!npBehindTME) return (DWORD)-1L; //fail bad -- list was empty, or no dwMs = 0 item // Great, we found it. Now just extrapolate, and return result. dwElapsedMs = dwMs - npBehindTME->dwMs; //compute dwet = dwems * 1000 / dwtempo // (ticks from last tempo chg to here) dwElapsedTicks = muldiv32(dwElapsedMs, 1000, npBehindTME->dwTempo); // ticks from beginning of file to here dwTotalTicks = npBehindTME->dwTicks + dwElapsedTicks; return dwTotalTicks; } PRIVATE DWORD NEAR PASCAL TickstoMS(NPSEQ npSeq, DWORD dwTicks) /* Convert ticks (some unit of time) into milliseconds in the given file. If it's a ppqn file, this conversion totally depends on the tempo map (which tells what tempo changes happen at what times). */ { NPTEMPOMAPELEMENT npFrontTME; NPTEMPOMAPELEMENT npBehindTME; DWORD dwRet; DWORD dwElapsedTicks; npBehindTME = NULL; // Find the last element that's before the ticks passed in npFrontTME = (NPTEMPOMAPELEMENT) List_Get_First(npSeq->tempoMapList); while ((npFrontTME) && (npFrontTME->dwTicks <= dwTicks)) { npBehindTME = npFrontTME; npFrontTME = (NPTEMPOMAPELEMENT) List_Get_Next(npSeq->tempoMapList, npFrontTME); } if (!npBehindTME) return (DWORD)-1L; //fail bad -- list was empty, or didn't have tick = 0 item // Great, found it! Now extrapolate and return result. dwElapsedTicks = dwTicks - npBehindTME->dwTicks; dwRet = npBehindTME->dwMs + muldiv32(dwElapsedTicks, npBehindTME->dwTempo, 1000); // (((dwTicks - npBehindTME->dwTicks) * npBehindTME->dwTempo) // / 1000); // remember, tempo in microseconds per tick return dwRet; } PRIVATE BOOL NEAR PASCAL AddTempoMapItem(NPSEQ npSeq, DWORD dwTempo, DWORD dwTicks) /* given a tempo change to dwTempo, happening at time dwTicks, in sequence npSeq, allocate a tempo map element, and put it at the end of the list. Return false iff memory alloc error. */ { NPTEMPOMAPELEMENT npNewTME; NPTEMPOMAPELEMENT npLastTME; NPTEMPOMAPELEMENT npTestTME; DWORD dwElapsedTicks; npLastTME = NULL; // Find last tempo map element npTestTME = (NPTEMPOMAPELEMENT) List_Get_First(npSeq->tempoMapList); while (npTestTME) { npLastTME = npTestTME; npTestTME = (NPTEMPOMAPELEMENT) List_Get_Next(npSeq->tempoMapList, npTestTME); } // Allocate new element if (!(npNewTME = (NPTEMPOMAPELEMENT) List_Allocate(npSeq->tempoMapList))) return FALSE; // failure List_Attach_Tail(npSeq->tempoMapList, (NPSTR) npNewTME); npNewTME->dwTicks = dwTicks; // these fields are always the same npNewTME->dwTempo = dwTempo; // ms field depends on last element if (!npLastTME) // if list was empty npNewTME->dwMs = 0; else // base new element data on last element { dwElapsedTicks = dwTicks - npLastTME->dwTicks; npNewTME->dwMs = npLastTME->dwMs + ((npLastTME->dwTempo * dwElapsedTicks) / 1000); } return TRUE; // success } PRIVATE VOID NEAR PASCAL SetBit(BitVector128 *bvPtr, UINT wIndex, BOOL On) /* Affect "index" bit of filter pointed to by "bvPtr." If On then set the bit, else clear it. */ { UINT mask; mask = 1 << (wIndex & 0x000F); wIndex >>= 4; if (On) bvPtr->filter[wIndex] |= mask; // set the bit else bvPtr->filter[wIndex] &= (~mask); // clear the bit } PRIVATE BOOL NEAR PASCAL GetBit(BitVector128 *bvPtr, int index) /* If the bit indicated by "index" is set, return true, else return false */ { UINT mask; mask = 1 << (index & 0x000F); index >>= 4; return (bvPtr->filter[index] & mask); // returns true iff bit set } PRIVATE VOID NEAR PASCAL AllNotesOff(NPSEQ npSeq, HMIDIOUT hMIDIPort) // Sends a note off for every key and every channel that is on, // according to npSeq->keyOnBitVect { ShortMIDI myShortMIDIData; UINT channel; UINT key; if (hMIDIPort) for(channel = 0; channel < 16; channel++) { // sustain pedal off for all channels myShortMIDIData.byteMsg.status= (BYTE) (0xB0 + channel); myShortMIDIData.byteMsg.byte2 = (BYTE) 0x40; myShortMIDIData.byteMsg.byte3 = 0x0; midiOutShortMsg(hMIDIPort, myShortMIDIData.wordMsg); // now do note offs myShortMIDIData.byteMsg.status= (BYTE) (0x80 + channel); myShortMIDIData.byteMsg.byte3 = 0x40; // release velocity for(key = 0; key < 128; key++) { if (GetBit(&npSeq->keyOnBitVect[channel], key)) // is key "on" ? { myShortMIDIData.byteMsg.byte2 = (BYTE) key; // turn it off // doubly, for layered synths (2 on STOP 2 off) midiOutShortMsg(hMIDIPort, myShortMIDIData.wordMsg); // remember that it's off SetBit(&npSeq->keyOnBitVect[channel], key, FALSE); } } } } PRIVATE NPSEQ NEAR PASCAL InitASeq(LPMIDISEQOPENDESC lpOpen, int divisionType, int resolution) // Create a sequence by allocating a sequence data structure for it. // Put it in the sequence list. { NPSEQ npSeqNew; ListHandle hListTrack; ListHandle hTempoMapList; int buff; if (!seqListHandle) { seqListHandle = List_Create((LONG)sizeof(SEQ), 0L); if (seqListHandle == NULLLIST) return(NULL); } // allocate the sequence structure npSeqNew = pSEQ(List_Allocate(seqListHandle)); if (!npSeqNew) return(NULL); // create the sequence's track list hListTrack = List_Create((LONG) sizeof(TRACK), 0L); if (hListTrack == NULLLIST) { List_Deallocate(seqListHandle, (NPSTR) npSeqNew); return(NULL); } // create the sequence's tempo map list hTempoMapList = List_Create((LONG) sizeof(TempoMapElement), 0L); if (hTempoMapList == NULLLIST) { List_Deallocate(seqListHandle, (NPSTR) npSeqNew); List_Destroy(hListTrack); return(NULL); } // set these sequencer fields to default values _fmemset(npSeqNew, 0, sizeof(SEQ)); npSeqNew->divType = divisionType; npSeqNew->resolution = resolution; npSeqNew->slaveOf = SEQ_SYNC_FILE; npSeqNew->seekTicks = NotInUse; // set these sequencer fields to specific values already derived npSeqNew->trackList = hListTrack; npSeqNew->tempoMapList = hTempoMapList; npSeqNew->hStream = lpOpen->hStream; npSeqNew->fwFlags = LEGALFILE; // assume good till proven otherwise for (buff = 0; buff < NUMSYSEXHDRS + 1; buff++) { npSeqNew->longMIDI[buff].midihdr.lpData = (LPSTR) &npSeqNew->longMIDI[buff].data; // resolve data ptr // make buffer refer to seq so can find owner on callback npSeqNew->longMIDI[buff].midihdr.dwUser = (DWORD_PTR)(LPVOID)npSeqNew; npSeqNew->longMIDI[buff].midihdr.dwFlags |= MHDR_DONE; //just set done bit } // initialize internal filter on meta events to ignore all but // tempo, time signature, smpte offset, and end of track meta events. SetBit(&npSeqNew->intMetaFilter, TEMPOCHANGE, TRUE); // accept int tempo changes SetBit(&npSeqNew->intMetaFilter, ENDOFTRACK, TRUE); // accept int end of track SetBit(&npSeqNew->intMetaFilter, SMPTEOFFSET, TRUE); // accept int SMPTE offset SetBit(&npSeqNew->intMetaFilter, TIMESIG, TRUE); // accept int time sig SetBit(&npSeqNew->intMetaFilter, SEQSTAMP, TRUE); // put sequence in global list of all sequences List_Attach_Tail(seqListHandle, (NPSTR) npSeqNew); return npSeqNew; } PRIVATE DWORD NEAR PASCAL InitTempo(int divType, int resolution) { DWORD ticksPerMinute; DWORD tempo; // set tempo to correct default (120 bpm or 24, 25, 30 fps). switch (divType) { case SEQ_DIV_PPQN: ticksPerMinute = (DWORD) DefaultTempo * resolution; break; case SEQ_DIV_SMPTE_24: ticksPerMinute = ((DWORD) (24 * 60)) * resolution; // 24 frames per second break; case SEQ_DIV_SMPTE_25: ticksPerMinute = ((DWORD) (25 * 60)) * resolution; break; case SEQ_DIV_SMPTE_30: case SEQ_DIV_SMPTE_30DROP: ticksPerMinute = ((DWORD) (30 * 60)) * resolution; break; } tempo = USecPerMinute / ticksPerMinute; return(tempo); } PRIVATE BOOL NEAR PASCAL SetUpToPlay(NPSEQ npSeq) /* After the sequence has been initialized and "connected" to the streamer, this function should be called. It scans the file to create a tempo map, set up for the patch-cache message, and determine the length of the file. (Actually, it just set's this process in motion, and much of the important code is in the blocking/unblocking logic.) Returns false only if there's a fatal error (e.g. memory alloc error), else true. */ { BOOL tempoChange; // set tempo to 120bpm or normal SMPTE frame rate //npSeq->tempo = InitTempo(npSeq->divType, npSeq->resolution); SeqSetTempo(npSeq, InitTempo(npSeq->divType, npSeq->resolution)); if (!(AddTempoMapItem(npSeq, npSeq->tempo, 0L))) return FALSE; if (npSeq->slaveOf != SEQ_SYNC_FILE) tempoChange = FALSE; else tempoChange = TRUE; SetBit(&npSeq->intMetaFilter,TEMPOCHANGE, tempoChange); ResetToBeginning(npSeq); // this is considered reset 1 SetBlockedTracksTo(npSeq, on_input, in_rewind_1); // 'mature' the input block state /* In state code, goes on to reset, scan early metas, build tempo map, and reset again, set tempo to 120bpm or normal SMPTE frame rate fill in the tracks (search to song pointer value) and then sets "ready to play." Iff npSeq->playing, then plays the sequence. */ return TRUE; } PRIVATE VOID NEAR PASCAL Destroy(NPSEQ npSeq) { int buff; Stop(npSeq); // among other things, this cancels any pending callbacks List_Destroy(npSeq->trackList); //destroys track data List_Destroy(npSeq->tempoMapList); //destroys tempo map if (npSeq->npTrkArr) LocalFree((HANDLE)npSeq->npTrkArr); // free track array // (ptr == handle since lmem_fixed) if (npSeq->hMIDIOut) // should have already been closed -- but just in case for (buff = 0; buff < NUMSYSEXHDRS; buff++) midiOutUnprepareHeader(npSeq->hMIDIOut, (LPMIDIHDR) &npSeq->longMIDI[buff].midihdr, sizeof(npSeq->longMIDI[buff].midihdr)); List_Deallocate(seqListHandle, (NPSTR) npSeq); // deallocate memory } PRIVATE int NEAR PASCAL MIDILength(BYTE status) /* returns length of various MIDI messages */ { if (status & 0x80) // status byte since ms bit set { switch (status & 0xf0) // look at ms nibble { case 0x80: // note on case 0x90: // note off case 0xA0: // key aftertouch case 0xB0: // cntl change or channel mode case 0xE0: return 3; // pitch bend case 0xC0: // pgm change case 0xD0: return 2; // channel pressure case 0xF0: // system { switch (status & 0x0F) // look at ls nibble { // "system common" case 0x0: return SysExCode; // sysex: variable size case 0x1: // 2 MTC Q-Frame case 0x3: return 2; // 2 Song Select case 0x2: return 3; // 3 Song Pos Ptr case 0x4: // 0 undefined case 0x5: return 0; // 0 undefined case 0x6: // 1 tune request case 0x7: return 1; // 1 end of sysex (not really a message) // "system real-time" case 0x8: // 1 timing clock case 0xA: // 1 start case 0xB: // 1 continue case 0xC: // 1 stop case 0xE: return 1; // 1 active sensing case 0x9: // 0 undefined case 0xD: return 0; // 0 undefined /* 0xFF is really system reset, but is used as a meta event header in MIDI files. */ case 0xF: return(MetaEventCode); } // case ls }// sytem messages } // case ms } // if status // else return 0; // 0 undefined not a status byte } // MIDILength PRIVATE LONG NEAR PASCAL GetVarLen(NPTRACK npTrack) // returns next variable length qty in track { // will have to account for end of track here (perhaps change GetByte) int count = 1; BYTE c; LONG delta; if ((delta = GetByte(npTrack)) & 0x80) /* gets the next delta */ { delta &= 0x7f; do { delta = (delta << 7) + ((c = GetByte(npTrack)) & 0x7f); count++; } while (c & 0x80); } if (count > 4) /* 4 byte max on deltas */ { dprintf1(("BOGUS DELTA !!!!")); return 0x7fffffff; } else return delta; } PRIVATE VOID NEAR PASCAL SkipEvent(BYTE status, NPTRACK npTrack) // skips event in track, based on status byte passed in. { LONG length; if ((status == METAEVENT) || (status == SYSEX) || (status == SYSEXF7)) length = GetVarLen(npTrack); else length = MIDILength(status) -1 ;// -1 becuase already read status if ((!npTrack->blockedOn) && (length)) { SkipBytes(npTrack, length); if (npTrack->blockedOn) npTrack->blockedOn = in_SkipBytes_ScanEM; } return; } PRIVATE VOID NEAR PASCAL FlushMidi(HMIDIOUT hMidiOut, LongMIDI * pBuf) { if (pBuf->midihdr.dwBufferLength) { midiOutLongMsg(hMidiOut, &pBuf->midihdr, sizeof(MIDIHDR)); pBuf->midihdr.dwBufferLength = 0; } } PRIVATE VOID NEAR PASCAL SetData(HMIDIOUT hMidiOut, LongMIDI * pBuf, ShortMIDI Data, int length) { if (LONGBUFFSIZE < pBuf->midihdr.dwBufferLength + length) { FlushMidi(hMidiOut, pBuf); } pBuf->data[pBuf->midihdr.dwBufferLength++] = Data.byteMsg.status; if (length > 1) { pBuf->data[pBuf->midihdr.dwBufferLength++] = Data.byteMsg.byte2; if (length > 2) { pBuf->data[pBuf->midihdr.dwBufferLength++] = Data.byteMsg.byte3; } } } PRIVATE VOID NEAR PASCAL SendMIDI(NPSEQ npSeq, NPTRACK npTrack) { ShortMIDI myShortMIDIData; int length; BYTE status; BYTE channel; BYTE key; BYTE velocity; BOOL setBit; myShortMIDIData = npTrack->shortMIDIData; if ((length = MIDILength(myShortMIDIData.byteMsg.status)) <= 3) { if (npSeq->hMIDIOut) { //send short MIDI message //maintain note on/off structure status = (BYTE)((myShortMIDIData.byteMsg.status) & 0xF0); if ((status == 0x80) || (status == 0x90)) // note on or off { channel = (BYTE)((myShortMIDIData.byteMsg.status) & 0x0F); key = myShortMIDIData.byteMsg.byte2; velocity = myShortMIDIData.byteMsg.byte3; // // Only play channels 1 to 12 for marked files // if ((npSeq->fwFlags & GENERALMSMIDI) && channel >= 12) { return; } if ((status == 0x90) && (velocity != 0)) // note on { setBit = TRUE; if (GetBit(&npSeq->keyOnBitVect[channel], key)) // are we hitting a key that's ALREADY "on" ? { // if so, turn it OFF myShortMIDIData.byteMsg.status &= 0xEF; //9x->8x SetData(npSeq->hMIDIOut, &npSeq->longMIDI[NUMSYSEXHDRS], myShortMIDIData, length); // midiOutShortMsg(npSeq->hMIDIOut, myShortMIDIData.wordMsg); myShortMIDIData.byteMsg.status |= 0x10; //8x->9x } } else setBit = FALSE; SetBit(&npSeq->keyOnBitVect[channel], key, setBit); } SetData(npSeq->hMIDIOut, &npSeq->longMIDI[NUMSYSEXHDRS], myShortMIDIData, length); // midiOutShortMsg(npSeq->hMIDIOut, myShortMIDIData.wordMsg); } } } PRIVATE VOID NEAR PASCAL SubtractAllTracks(NPSEQ npSeq, LONG subValue) // subtract subvalue from every track { NPTRACK npTrack; if (subValue) // ignore if zero { npTrack = (NPTRACK) List_Get_First(npSeq->trackList); while (npTrack) /* subtract this delta from all others */ { if (npTrack->delta != TrackEmpty) npTrack->delta -= subValue; npTrack = (NPTRACK) List_Get_Next(npSeq->trackList, npTrack); } } } PRIVATE VOID NEAR PASCAL SetUpSysEx(NPTRACK npTrack, BYTE status) /* Handle similar to Metas (don't prebuffer, since several tracks could have sysex, and we only have 2 buffers */ { npTrack->shortMIDIData.byteMsg.status = status; npTrack->sysExRemLength = GetVarLen(npTrack); } PRIVATE VOID NEAR PASCAL GetShortMIDIData(NPTRACK npTrack, BYTE status, int length) { npTrack->shortMIDIData.byteMsg.status = status; if (length >= 2) { npTrack->shortMIDIData.byteMsg.byte2 = GetByte(npTrack); if (length == 3) npTrack->shortMIDIData.byteMsg.byte3 = GetByte(npTrack); } } PRIVATE BYTE NEAR PASCAL GetStatus(NPTRACK npTrack) // returns correct status byte, taking running status fully into account. { BYTE firstByte; BYTE status; if ((firstByte = LookByte(npTrack)) & 0x80) // status byte?? { firstByte = GetByte(npTrack); // actually get it if status if ((firstByte >= 0xF0) && (firstByte <= 0xF7)) // sysex or sys common? npTrack->lastStatus = 0; // cancel running status else if (firstByte < 0xF0) // only use channel messages npTrack->lastStatus = firstByte; // else save it for running status status = firstByte; // return this as status byte regardless } else // 1st byte wasn't a status byte { if (npTrack->lastStatus & 0x80) // there was prev. running status status = npTrack->lastStatus; // return previous status else status = 0; // error } return status; } PRIVATE VOID NEAR PASCAL FillInEvent(NPTRACK npTrack) { BYTE status; if (!npTrack->blockedOn) { status = GetStatus(npTrack); if (!npTrack->blockedOn) { int length; if ((length = MIDILength(status)) <= 3) GetShortMIDIData(npTrack, status, length); else if ((status == SYSEX) || (status == SYSEXF7)) // set up for sysEx SetUpSysEx(npTrack, status); else if (status == METAEVENT) // set up for meta event SetUpMetaEvent(npTrack); else { dprintf1(("Bogus long message encountered!!!")); } } } } PRIVATE UINT NEAR PASCAL SetTempo(NPSEQ npSeq, DWORD dwUserTempo) // tempo passed in from user. Convert from beats per minute or frames // per second to internal format (microseconds per tick) { DWORD dwTempo; if (!dwUserTempo) // zero is an illegal tempo! return MCIERR_OUTOFRANGE; if (npSeq->divType == SEQ_DIV_PPQN) dwTempo = USecPerMinute / (dwUserTempo * npSeq->resolution); else dwTempo = USecPerSecond / (dwUserTempo * npSeq->resolution); if (!dwTempo) dwTempo = 1; // at least 1 usec per tick! This is spec'ed max tempo SeqSetTempo(npSeq, dwTempo); if (npSeq->wTimerID) { DestroyTimer(npSeq); // recompute everything from current position npSeq->nextExactTime = timeGetTime(); // // Bug fix - make everything happen on the timer thread instead // of calling TimerIntRoutine which can get deadlocked. // SetTimerCallback(npSeq, MINPERIOD, npSeq->dwTimerParam); } return MIDISEQERR_NOERROR; } /**************************** PUBLIC FUNCTIONS *************************/ /**************************************************************************** * * @doc INTERNAL SEQUENCER * * @api DWORD | midiSeqMessage | Single entry point for Sequencer * * @parm HMIDISEQ | hMIDISeq | Handle to MIDI Sequence * * @parm UINT | wMessage | The requested action to be performed. * * @parm DWORD | dwParam1 | Data for this message. * * @parm DWORD | dwParam2 | Data for this message. * * @rdesc Sequencer error code (see mmseq.h). * ***************************************************************************/ PUBLIC DWORD_PTR FAR PASCAL midiSeqMessage( HMIDISEQ hMIDISeq, UINT wMessage, DWORD_PTR dwParam1, DWORD_PTR dwParam2) { if (wMessage == SEQ_OPEN) return CreateSequence((LPMIDISEQOPENDESC)dwParam1, (LPHMIDISEQ)dwParam2); if (!hMIDISeq) return MIDISEQERR_INVALSEQHANDLE; switch (wMessage) { case SEQ_CLOSE: Destroy(pSEQ(hMIDISeq)); break; case SEQ_PLAY: return Play(pSEQ(hMIDISeq), (DWORD)dwParam1); case SEQ_RESET: // set song pointer to beginning of the sequence; return midiSeqMessage(hMIDISeq, SEQ_SETSONGPTR, 0L, 0L); case SEQ_SETSYNCMASTER: switch ((WORD)dwParam1) { case SEQ_SYNC_NOTHING: pSEQ(hMIDISeq)->masterOf = LOWORD(dwParam1); break; case SEQ_SYNC_MIDI: // not yet implemented... case SEQ_SYNC_SMPTE: return MIDISEQERR_INVALPARM; case SEQ_SYNC_OFFSET: // in both master and slave (same) pSEQ(hMIDISeq)->smpteOffset = *((LPMMTIME) dwParam2); break; default: return MIDISEQERR_INVALPARM; } break; case SEQ_SETSYNCSLAVE: // what we should slave to switch ((WORD)dwParam1) { case SEQ_SYNC_NOTHING: // don't accept internal tempo changes; SetBit(&pSEQ(hMIDISeq)->intMetaFilter, TEMPOCHANGE, FALSE); pSEQ(hMIDISeq)->slaveOf = LOWORD(dwParam1); break; case SEQ_SYNC_FILE: // accept internal tempo changes; SetBit(&pSEQ(hMIDISeq)->intMetaFilter, TEMPOCHANGE, TRUE); pSEQ(hMIDISeq)->slaveOf = LOWORD(dwParam1); break; case SEQ_SYNC_SMPTE: // not yet implemented... case SEQ_SYNC_MIDI: return MIDISEQERR_INVALPARM; case SEQ_SYNC_OFFSET: // in both master and slave (same) pSEQ(hMIDISeq)->smpteOffset = *((LPMMTIME)dwParam2); break; default: return MIDISEQERR_INVALPARM; } break; case SEQ_MSTOTICKS: // given an ms value, convert it to ticks *((DWORD FAR *)dwParam2) = MStoTicks(pSEQ(hMIDISeq), (DWORD)dwParam1); break; case SEQ_TICKSTOMS: // given a tick value, convert it to ms *((DWORD FAR *)dwParam2) = TickstoMS(pSEQ(hMIDISeq), (DWORD)dwParam1); break; case SEQ_SETTEMPO: return SetTempo(pSEQ(hMIDISeq), (DWORD)dwParam1); case SEQ_SETSONGPTR: // remember it in case blocked; if (pSEQ(hMIDISeq)->divType == SEQ_DIV_PPQN) // div 4 16th->1/4 note pSEQ(hMIDISeq)->seekTicks = (DWORD)((dwParam1 * pSEQ(hMIDISeq)->resolution) / 4); else pSEQ(hMIDISeq)->seekTicks = (DWORD)dwParam1 * pSEQ(hMIDISeq)->resolution; // frames SeekTicks(pSEQ(hMIDISeq)); break; case SEQ_SEEKTICKS: pSEQ(hMIDISeq)->wCBMessage = wMessage; // remember message type // No break; case SEQ_SYNCSEEKTICKS: // finer resolution than song ptr command; pSEQ(hMIDISeq)->seekTicks = (DWORD)dwParam1; SeekTicks(pSEQ(hMIDISeq)); break; case SEQ_SETUPTOPLAY: if (!(SetUpToPlay(pSEQ(hMIDISeq)))) { Destroy(pSEQ(hMIDISeq)); return MIDISEQERR_NOMEM; } break; case SEQ_STOP: Stop(pSEQ(hMIDISeq)); break; case SEQ_TRACKDATA: if (!dwParam1) return MIDISEQERR_INVALPARM; else return NewTrackData(pSEQ(hMIDISeq), (LPMIDISEQHDR)dwParam1); case SEQ_GETINFO: if (!dwParam1) return MIDISEQERR_INVALPARM; else return GetInfo(pSEQ(hMIDISeq), (LPMIDISEQINFO) dwParam1); case SEQ_SETPORT: { UINT wRet; if (MMSYSERR_NOERROR != (wRet = midiOutOpen(&pSEQ(hMIDISeq)->hMIDIOut, (DWORD)dwParam1, (DWORD_PTR)MIDICallback, 0L, CALLBACK_FUNCTION))) return wRet; if (MMSYSERR_NOERROR != (wRet = SendPatchCache(pSEQ(hMIDISeq), TRUE))) { midiOutClose(pSEQ(hMIDISeq)->hMIDIOut); pSEQ(hMIDISeq)->hMIDIOut = NULL; return wRet; } for (wRet = 0; wRet < NUMSYSEXHDRS + 1; wRet++) { midiOutPrepareHeader(pSEQ(hMIDISeq)->hMIDIOut, (LPMIDIHDR)&pSEQ(hMIDISeq)->longMIDI[wRet].midihdr, sizeof(pSEQ(hMIDISeq)->longMIDI[wRet].midihdr)); pSEQ(hMIDISeq)->longMIDI[wRet].midihdr.dwFlags |= MHDR_DONE; } break; } case SEQ_SETPORTOFF: if (pSEQ(hMIDISeq)->hMIDIOut) { UINT wHeader; HMIDIOUT hTempMIDIOut; for (wHeader = 0; wHeader < NUMSYSEXHDRS + 1; wHeader++) midiOutUnprepareHeader(pSEQ(hMIDISeq)->hMIDIOut, (LPMIDIHDR)&pSEQ(hMIDISeq)->longMIDI[wHeader].midihdr, sizeof(pSEQ(hMIDISeq)->longMIDI[wHeader].midihdr)); hTempMIDIOut = pSEQ(hMIDISeq)->hMIDIOut; pSEQ(hMIDISeq)->hMIDIOut = NULL; // avoid notes during "notesoff" if ((BOOL)dwParam1) AllNotesOff(pSEQ(hMIDISeq), hTempMIDIOut); midiOutClose(hTempMIDIOut); } break; case SEQ_QUERYGENMIDI: return pSEQ(hMIDISeq)->fwFlags & GENERALMSMIDI; case SEQ_QUERYHMIDI: return (DWORD_PTR)pSEQ(hMIDISeq)->hMIDIOut; default: return MIDISEQERR_INVALMSG; } return MIDISEQERR_NOERROR; } /**********************************************************/ PRIVATE VOID NEAR PASCAL SeekTicks(NPSEQ npSeq) /* Used for song pointer and seek ticks (same, but finer res.) command. */ { if (npSeq->playing) // not a good idea to seek while playing! Stop(npSeq); if (npSeq->currentTick >= npSeq->seekTicks) // = because may have already // played current notes { // seeking behind: must reset first npSeq->readyToPlay = FALSE; ResetToBeginning(npSeq); // tell streamer to start over // tell blocking logic what operation we're in SetBlockedTracksTo(npSeq, on_input, in_Seek_Tick); } else // seeking ahead in the file { if (GetNextEvent(npSeq) == NoErr) // if there's a valid event set up // send ALL events in the file up through time = seekTicks SendAllEventsB4(npSeq, (npSeq->seekTicks - npSeq->currentTick), MODE_SEEK_TICKS); if ((AllTracksUnblocked(npSeq)) && ((npSeq->currentTick + npSeq->nextEventTrack->delta) >= npSeq->seekTicks)) // Did we complete the operation?? { npSeq->seekTicks = NotInUse; // signify -- got there if (npSeq->wCBMessage == SEQ_SEEKTICKS) NotifyCallback(npSeq->hStream); } else npSeq->readyToPlay = FALSE; // didn't get there -- protect from play } } PUBLIC UINT NEAR PASCAL GetInfo(NPSEQ npSeq, LPMIDISEQINFO lpInfo) /* Used to fulfill seqInfo command. Fills in seq info structure passed in */ { // fill in the lpInfo structure lpInfo->wDivType = (WORD)npSeq->divType; lpInfo->wResolution = (WORD)npSeq->resolution; lpInfo->dwLength = npSeq->length; lpInfo->bPlaying = npSeq->playing; lpInfo->bSeeking = !(npSeq->seekTicks == NotInUse); lpInfo->bReadyToPlay = npSeq->readyToPlay; lpInfo->dwCurrentTick = npSeq->currentTick; lpInfo->dwPlayTo = npSeq->playTo; lpInfo->dwTempo = npSeq->tempo; // lpInfo->bTSNum = (BYTE) npSeq->timeSignature.numerator; // lpInfo->bTSDenom = (BYTE) npSeq->timeSignature.denominator; // lpInfo->wNumTracks = npSeq->wNumTrks; // lpInfo->hPort = npSeq->hMIDIOut; lpInfo->mmSmpteOffset = npSeq->smpteOffset; lpInfo->wInSync = npSeq->slaveOf; lpInfo->wOutSync = npSeq->masterOf; lpInfo->bLegalFile = (BYTE)(npSeq->fwFlags & LEGALFILE); if (List_Get_First(npSeq->tempoMapList)) lpInfo->tempoMapExists = TRUE; else lpInfo->tempoMapExists = FALSE; return MIDISEQERR_NOERROR; } PUBLIC UINT NEAR PASCAL CreateSequence(LPMIDISEQOPENDESC lpOpen, LPHMIDISEQ lphMIDISeq) // Given a structure holding MIDI file header info, allocate and initialize // all internal structures to play this file. Return the allocated // structure in lphMIDISeq. { WORD wTracks; int division; int divType; int resolution; NPTRACK npTrackCur; NPSEQ npSeq; BOOL trackAllocError; WORD iTrkNum; *lphMIDISeq = NULL; // initially set up for error return if (lpOpen->dwLen < 6) // header must be at least 6 bytes return MIDISEQERR_INVALPARM; wTracks = GETMOTWORD(lpOpen->lpMIDIFileHdr + sizeof(WORD)); if (wTracks > MAXTRACKS) // protect from random wTracks return MIDISEQERR_INVALPARM; division = (int)GETMOTWORD(lpOpen->lpMIDIFileHdr + 2 * sizeof(WORD)); if (!(division & 0x8000)) // check division type: smpte or ppqn { divType = SEQ_DIV_PPQN; resolution = division; // ticks per q-note } else // SMPTE { divType = -(division >> 8); /* this will be -24, -25, -29 or -30 for each different SMPTE frame rate. Negate to make positive */ resolution = (division & 0x00FF); } // allocate actual seq struct npSeq = InitASeq(lpOpen, divType, resolution); if (!npSeq) return MIDISEQERR_NOMEM; trackAllocError = FALSE; // allocate track array npSeq->npTrkArr = (NPTRACKARRAY) LocalAlloc(LMEM_FIXED, sizeof(NPTRACK) * wTracks); npSeq->wNumTrks = wTracks; if (!npSeq->npTrkArr) trackAllocError = TRUE; if (!trackAllocError) for (iTrkNum = 0; iTrkNum < wTracks; iTrkNum++) { if (!(npTrackCur = (NPTRACK) List_Allocate(npSeq->trackList))) { trackAllocError = TRUE; break; } // set trk array entry npSeq->npTrkArr->trkArr[iTrkNum] = npTrackCur; List_Attach_Tail(npSeq->trackList, (NPSTR) npTrackCur); if (npSeq->firstTrack == (NPTRACK) NULL) npSeq->firstTrack = npTrackCur; //1st track is special for metas npTrackCur->inPort.hdrList = NULL; npTrackCur->length = 0; npTrackCur->blockedOn = not_blocked; npTrackCur->dwCallback = (DWORD_PTR)lpOpen->dwCallback; npTrackCur->dwInstance = (DWORD_PTR)lpOpen->dwInstance; npTrackCur->sysExRemLength = 0; npTrackCur->iTrackNum = iTrkNum; } if (trackAllocError) { Destroy(npSeq); // dealloc seq related memory... return MIDISEQERR_NOMEM; } *lphMIDISeq = hSEQ(npSeq); /* Make what lphMIDISeq points to, point to sequence. */ return MIDISEQERR_NOERROR; } PUBLIC VOID NEAR PASCAL SetBlockedTracksTo(NPSEQ npSeq, int fromState, int toState) /* Set all tracks that are blocked with a given state, to a new state */ { NPTRACK npTrack; npTrack = (NPTRACK) List_Get_First(npSeq->trackList); while (npTrack) { if (npTrack->blockedOn == fromState) npTrack->blockedOn = toState; npTrack = (NPTRACK) List_Get_Next(npSeq->trackList, npTrack); } } PUBLIC VOID NEAR PASCAL ResetToBeginning(NPSEQ npSeq) /* set all globals and streams to play from beginning */ { NPTRACK npTrack; npSeq->currentTick = 0; npSeq->nextExactTime = timeGetTime(); npTrack = (NPTRACK) List_Get_First(npSeq->trackList); while (npTrack) { npTrack->delta = 0; npTrack->shortMIDIData.wordMsg = 0; npTrack->endOfTrack = FALSE; RewindToStart(npSeq, npTrack); /* reset stream to beginning of track (this basically entails freeing the buffers and setting a low-level block) */ npTrack = (NPTRACK) List_Get_Next(npSeq->trackList, npTrack); } } PUBLIC UINT NEAR PASCAL Play(NPSEQ npSeq, DWORD dwPlayTo) /* play the sequence */ { npSeq->wCBMessage = SEQ_PLAY; if (dwPlayTo == PLAYTOEND) // default is play to end dwPlayTo = npSeq->length; if (npSeq->currentTick > npSeq->length) // illegal position in file return MIDISEQERR_ERROR; else if ((npSeq->playing) && (npSeq->playTo == dwPlayTo)) return MIDISEQERR_NOERROR; //do nothing, this play redundant else { if (npSeq->playing) Stop(npSeq); // stop it before playing again npSeq->playing = TRUE; npSeq->nextExactTime = timeGetTime(); // start time of reference npSeq->playTo = dwPlayTo; // set limit if (!npSeq->bSetPeriod) { timeBeginPeriod(MINPERIOD); npSeq->bSetPeriod = TRUE; } if (npSeq->readyToPlay) // // Bug fix - make everything happen on the timer thread instead // of calling TimerIntRoutine which can get deadlocked. // return SetTimerCallback(npSeq, MINPERIOD, 0); else return MIDISEQERR_NOERROR; /* don't worry--if doesn't play here, it will start playing from state code in case where npSeq->playing == true (but may mask timer error). */ } } /**********************************************************/ PUBLIC VOID NEAR PASCAL Stop(NPSEQ npSeq) /* stop the sequence */ { DestroyTimer(npSeq); if (npSeq->bSetPeriod) // only reset it once! { timeEndPeriod(MINPERIOD); npSeq->bSetPeriod = FALSE; } npSeq->playing = FALSE; AllNotesOff(npSeq, npSeq->hMIDIOut); } PUBLIC BOOL NEAR PASCAL HandleMetaEvent(NPSEQ npSeq, NPTRACK npTrack, UINT wMode) // called at time ready to send!!! /* Look at the meta event currently being pointed to in this track. Act on it accordingly. Ignore all except tempo change, end of track, time signature, and smpte offset. Returns false only if tempo map allocation failed. wMode: MODE_SEEK_TICKS, MODE_PLAYING, MODE_SCANEM: passed in by caller to indicate tempo map allocation, and how to mature blocking status. Caution: wState must be FALSE when called at interrupt time! (This variable causes a tempo map element to be added to the tempo map list every time a tempo change meta event is encountered.) */ { BYTE metaIDByte; int bytesRead; LONG length; DWORD tempTempo; MMTIME tempMM = {0, 0}; TimeSigType tempTimeSig; BYTE Manufacturer[3]; // it is assumed at this point that the leading 0xFF status byte // has been read. metaIDByte = GetByte(npTrack); length = GetVarLen(npTrack); bytesRead = 0; if (GetBit(&npSeq->intMetaFilter, metaIDByte) && (!npTrack->blockedOn)) /* only consider meta events that you've allowed to pass */ { switch (metaIDByte) { case ENDOFTRACK: // end of track npTrack->endOfTrack = TRUE; break; // (read 0 bytes) case TEMPOCHANGE: // tempo change if (npTrack == npSeq->firstTrack) { tempTempo = GetMotorola24(npTrack); bytesRead = 3; if (npTrack->blockedOn == not_blocked) { //npSeq->tempo = tempTempo / npSeq->resolution; SeqSetTempo(npSeq, tempTempo / npSeq->resolution); if (wMode == MODE_SCANEM) if (!(AddTempoMapItem(npSeq, npSeq->tempo, npTrack->length))) return FALSE; // memory alloc failure ! } } break; case SMPTEOFFSET: // SMPTE Offset if (npTrack == npSeq->firstTrack) { tempMM.u.smpte.hour = GetByte(npTrack); tempMM.u.smpte.min = GetByte(npTrack); tempMM.u.smpte.sec = GetByte(npTrack); tempMM.u.smpte.frame = GetByte(npTrack); //tempSMPTEOff.fractionalFrame = GetByte(npTrack); // add later? bytesRead = 4; if (npTrack->blockedOn == not_blocked) npSeq->smpteOffset = tempMM; } break; case TIMESIG: // time signature // spec doesn't say, but probably only use if on track 1. tempTimeSig.numerator = GetByte(npTrack); tempTimeSig.denominator = GetByte(npTrack); tempTimeSig.midiClocksMetro = GetByte(npTrack); tempTimeSig.thirtySecondQuarter = GetByte(npTrack); bytesRead = 4; if (npTrack->blockedOn == not_blocked) npSeq->timeSignature = tempTimeSig; break; case SEQSTAMP: // General MS midi stamp if ((length < 3) || npTrack->delta) break; for (; bytesRead < 3;) Manufacturer[bytesRead++] = GetByte(npTrack); if (!Manufacturer[0] && !Manufacturer[1] && (Manufacturer[2] == 0x41)) npSeq->fwFlags |= GENERALMSMIDI; break; } // end switch } // if metaFilter if (!npTrack->blockedOn) { SkipBytes(npTrack, length - bytesRead);// skip unexpected bytes (as per spec) if (npTrack->blockedOn) switch (wMode) // mature blocking status { case MODE_SEEK_TICKS: npTrack->blockedOn = in_SkipBytes_Seek; break; case MODE_PLAYING: case MODE_SILENT: npTrack->blockedOn = in_SkipBytes_Play; break; case MODE_SCANEM: npTrack->blockedOn = in_SkipBytes_ScanEM; break; } } return TRUE; } PRIVATE BOOL NEAR PASCAL LegalMIDIFileStatus(BYTE status) { if (status < 0x80) return FALSE; switch (status) { // legal case 0xf0: sysex excape case 0xf1: case 0xf2: case 0xf3: case 0xf4: case 0xf5: case 0xf6: // legal case 0xf7: no f0 sysex excape case 0xf8: case 0xf9: case 0xfA: case 0xfB: case 0xfC: case 0xfD: case 0xfE: // legal case 0xfF: meta escape return FALSE; break; default: return TRUE; // all other cases are legal status bytes } } PUBLIC BOOL NEAR PASCAL ScanEarlyMetas(NPSEQ npSeq, NPTRACK npTrack, DWORD dwUntil) /* Scan each track for meta events that affect the initialization of data such as tempo, time sig, key sig, SMPTE offset... If track passed in null, start at beginning of seq, else start with the track passed in. Warning: the track parameter is for reentrancy in case of blocking. This function should be called with track NULL first, else ListGetNext will not function properly. This function assumes that all sequence tracks have been rewound. Returns false only on memory allocation error. */ { BYTE status; BYTE patch; BYTE chan; BYTE key; BOOL bTempoMap; LONG lOldDelta; #define BASEDRUMCHAN 15 #define EXTENDDRUMCHAN 9 #define NOTEON 0X90 // determine if need to create a tempo map if (npSeq->divType == SEQ_DIV_PPQN) bTempoMap = TRUE; else bTempoMap = FALSE; if (!npTrack) // if track passed in null, get the first one { npTrack = (NPTRACK) List_Get_First(npSeq->trackList); npTrack->lastStatus = 0; // start with null running status npTrack->length = 0; } do { do { MarkLocation(npTrack); // remember current location lOldDelta = npTrack->delta; // remember last delta FillInDelta(npTrack); // ***TBD ck illegal delta if (npTrack->blockedOn) // abort on block break; if ((npTrack->delta + npTrack->length) < dwUntil) { status = GetStatus(npTrack); chan = (BYTE)(status & 0x0F); if (npTrack->blockedOn) break; // check illegal status if (!LegalMIDIFileStatus(status)) //error { npSeq->fwFlags &= ~LEGALFILE; return TRUE; } else if (status == METAEVENT) { // these actions will set the sequencer globals if (!(HandleMetaEvent(npSeq, npTrack, MODE_SCANEM))) return FALSE; // blew a tempo memory alloc } else if ((status & 0xF0) == PROGRAMCHANGE) { patch = GetByte(npTrack); if ((patch < 128) && (!npTrack->blockedOn)) npSeq->patchArray[patch] |= (1 << chan); } else if ( ((status & 0xF0) == NOTEON) && ((chan == BASEDRUMCHAN) || (chan == EXTENDDRUMCHAN)) ) { key = GetByte(npTrack); if ((key < 128) && (!npTrack->blockedOn)) { npSeq->drumKeyArray[key] |= (1 << chan); GetByte(npTrack); // toss velocity byte } } else SkipEvent(status, npTrack); //skip bytes block set within } if ((npTrack->blockedOn == not_blocked) && (!npTrack->endOfTrack)) { /* NB: eot avoids adding last delta (which can be large, but isn't really a part of the sequence) */ npTrack->length += npTrack->delta; //add in this delta npTrack->delta = 0; // zero it out (emulates playing it) } } while ((npTrack->blockedOn == not_blocked) && (!npTrack->endOfTrack) && (npTrack->length < dwUntil)); if (npTrack->blockedOn == not_blocked) { if (npTrack->length > npSeq->length) npSeq->length = npTrack->length; // seq length is longest track if (NULL != (npTrack = (NPTRACK) List_Get_Next(npSeq->trackList, npTrack))) { npTrack->lastStatus = 0; // start with null running status npTrack->length = 0; } } } // get the next track while (npTrack && (npTrack->blockedOn == not_blocked)); // now reset location and mature the block status if blocked on input // (note: doesn't affect skip bytes status, which is set at lower level) if (npTrack && (npTrack->blockedOn == on_input)) { ResetLocation(npTrack); // restore last saved location npTrack->delta = lOldDelta; // "undo" any change to delta npTrack->blockedOn = in_ScanEarlyMetas; } return TRUE; } PUBLIC UINT NEAR PASCAL TimerIntRoutine(NPSEQ npSeq, LONG elapsedTick) /* This routine does everything that should be done at this time (usually sending notes) and sets up the timer to wake us up the next time something should happen. Interface: elapsedTick is set by the caller to tell how much time has elapsed since this fn was last called. (For ppqn files, a tick is 1 ppqn in 960 ppqn format. For SMPTE files, a tick is some fraction of a frame. */ { FileStatus fStatus = NoErr; BOOL loop; LONG delta; LONG wakeupInterval; DWORD dTime; //delta in ms. 'till next event int mode; #ifdef WIN32 EnterCrit(); #endif // WIN32 if (npSeq->bTimerEntered) { dprintf1(("TI REENTERED!!!!!!")); #ifdef WIN32 LeaveCrit(); #endif // WIN32 return 0; } npSeq->bTimerEntered = TRUE; // compute whether we're behind so we know whether to sound note-ons. wakeupInterval = (DWORD)npSeq->nextExactTime - timeGetTime(); if (npSeq->playing) { do { loop = FALSE; /* "ElapsedTick is set by whoever sets up timer callback ALL TIMING IS IN TERMS OF Ticks!!! (except for timer API) */ if (wakeupInterval > -100) // send all notes not more than mode = MODE_PLAYING; // 0.1 seconds behind else mode = MODE_SILENT; fStatus = SendAllEventsB4(npSeq, elapsedTick, mode); if (fStatus == NoErr) { delta = npSeq->nextEventTrack->delta; // get delta 'till next event elapsedTick = delta; // for next time if (delta) dTime = muldiv32(delta, npSeq->tempo, 1000); else dTime = 0; npSeq->nextExactTime += dTime; /* nextExactTime is a global that is always looking at next event. Remember, tempo is in u-sec per tick (+500 for rounding) */ wakeupInterval = (DWORD)npSeq->nextExactTime - timeGetTime(); if (wakeupInterval > (LONG)MINPERIOD) // buffer to prevent reentrancy { #ifdef DEBUG if (wakeupInterval > 60000) { // 1 minute dprintf2(("MCISEQ: Setting HUGE TIMER INTERVAL!!!")); } #endif // // we are going to program a event, clear bTimerEntered // just in case it goes off before we get out of this // function. // npSeq->bTimerEntered = FALSE; if (SetTimerCallback(npSeq, (UINT) wakeupInterval, elapsedTick) == MIDISEQERR_TIMER) { #ifndef WIN32 // Win 16 effectively releases the critical section // more easily than NT. The flag could have been // reset since it was cleared above #ifdef DEBUG npSeq->bTimerEntered = FALSE; #endif #endif Stop(npSeq); seqCallback(npSeq->firstTrack, MIDISEQ_DONEPLAY, 0, 0); dprintf1(("MCISEQ: TIMER ERROR!!!")); #ifdef WIN32 LeaveCrit(); #endif // WIN32 return MIDISEQERR_TIMER; } } else { loop = TRUE; // already time to fire next note! // while ((DWORD)npSeq->nextExactTime // > timeGetTime()); // busy wait 'till then } } //if (fStatus == NoErr) else if ((fStatus == AllTracksEmpty) || (fStatus == AtLimit)) { if (npSeq->wCBMessage == SEQ_PLAY) NotifyCallback(npSeq->hStream); Stop(npSeq); seqCallback(npSeq->firstTrack, MIDISEQ_DONEPLAY, 0, 0); dprintf1(("MCISEQ: At Limit or EOF")); } else { dprintf1(("MCISEQ: QUIT!!! fStatus = %x", fStatus)); } } while (loop); // if enough time elapsed to fire next note already. } // if npSeq->playing FlushMidi(npSeq->hMIDIOut, &npSeq->longMIDI[NUMSYSEXHDRS]); npSeq->bTimerEntered = FALSE; #ifdef WIN32 LeaveCrit(); #endif // WIN32 return MIDISEQERR_NOERROR; } PUBLIC FileStatus NEAR PASCAL SendAllEventsB4(NPSEQ npSeq, LONG elapsedTick, int mode) /* send all events in the MIDI stream that occur before current tick, where currentTick is elapsed ticks since last called. This function is called both to play notes, and to scan forward to a song pointer position. The mode parameter reflects this state. */ { LONG residualDelta; // residual holds how much of elapsed // tick has been accounted for FileStatus fStatus = GetNextEvent(npSeq); WORD wAdj; BYTE status; DWORD dwNextTick; if (npSeq->bSending) // prevent reentrancy return NoErr; npSeq->bSending = TRUE; // set entered flag #ifdef DEBUG if (mode == MODE_SEEK_TICKS) { dprintf2(("ENTERING SEND ALL EVENTS")); } #endif if (elapsedTick > 0) residualDelta = elapsedTick; else residualDelta = 0; if (mode == MODE_SEEK_TICKS) // hack for song ptr -- don't send unless before eT wAdj = 1; else wAdj = 0; while ((fStatus == NoErr) && (residualDelta >= (npSeq->nextEventTrack->delta + wAdj)) && (!npSeq->bSendingSysEx)) // can't process any other msgs during sysex /* send all events within delta */ { if (mode == MODE_PLAYING) // if playing, are we at the end yet yet? { // compute temp var dwNextTick = npSeq->currentTick + npSeq->nextEventTrack->delta; // if not play to end, don't play the last note if ((dwNextTick > npSeq->playTo) || ((npSeq->playTo < npSeq->length) && (dwNextTick == npSeq->playTo))) { fStatus = AtLimit; // have reached play limit user requested SubtractAllTracks(npSeq, (npSeq->playTo - npSeq->currentTick)); npSeq->currentTick = npSeq->playTo; // set to limit break; // leave while loop } } status = npSeq->nextEventTrack->shortMIDIData.byteMsg.status; if (status == METAEVENT) { MarkLocation(npSeq->nextEventTrack); // remember current location // note that these are "handled" even within song ptr traversal HandleMetaEvent(npSeq, npSeq->nextEventTrack, mode); if (npSeq->nextEventTrack->blockedOn == on_input) { // nb: not affected if blocked in skip bytes!! ResetLocation(npSeq->nextEventTrack); // reset location if (mode == MODE_SEEK_TICKS) npSeq->nextEventTrack->blockedOn = in_Seek_Meta; else npSeq->nextEventTrack->blockedOn = in_Normal_Meta; } } else if ((status == SYSEX) || (status == SYSEXF7)) { SendSysEx(npSeq); if (npSeq->bSendingSysEx) fStatus = InSysEx; } // send all but note-ons unless playing and note is current else if (((mode == MODE_PLAYING) && (npSeq->nextEventTrack->delta >= 0)) || ( ! (((status & 0xF0) == 0x90) && // note on with vel != 0 (npSeq->nextEventTrack->shortMIDIData.byteMsg.byte3)) )) SendMIDI(npSeq, npSeq->nextEventTrack); if ((npSeq->nextEventTrack->blockedOn == not_blocked) || (npSeq->bSendingSysEx) || (npSeq->nextEventTrack->blockedOn == in_SkipBytes_Play) || (npSeq->nextEventTrack->blockedOn == in_SkipBytes_Seek) || (npSeq->nextEventTrack->blockedOn == in_SkipBytes_ScanEM)) { // account for time spent only if it was sent // ...and only if dealing with a fresh delta if (npSeq->nextEventTrack->delta > 0) { residualDelta -= npSeq->nextEventTrack->delta; npSeq->currentTick += npSeq->nextEventTrack->delta; // account for delta SubtractAllTracks(npSeq, npSeq->nextEventTrack->delta); } } if ((npSeq->nextEventTrack->blockedOn == not_blocked) && (!npSeq->nextEventTrack->endOfTrack) && (!npSeq->bSendingSysEx)) //fill in the next event from stream FillInNextTrack(npSeq->nextEventTrack); if (npSeq->nextEventTrack->blockedOn == on_input) //mature block { if (mode == MODE_SEEK_TICKS) // set blocked status depending on mode npSeq->nextEventTrack->blockedOn = in_Seek_Tick; else npSeq->nextEventTrack->blockedOn = between_msg_out; } if (!npSeq->bSendingSysEx) // Make nextEventTrack point to next track to play if (((fStatus = GetNextEvent(npSeq)) == NoErr) && (npSeq->nextEventTrack->endOfTrack)) npSeq->readyToPlay = FALSE; } if ((fStatus == NoErr) && AllTracksUnblocked(npSeq)) { npSeq->currentTick += residualDelta; SubtractAllTracks(npSeq, residualDelta); //account for rest of delta } #ifdef DEBUG if (mode == MODE_SEEK_TICKS) { dprintf2(("LEAVING SEND ALL EVENTS")); } #endif npSeq->bSending = FALSE; // reset entered flag return fStatus; } PUBLIC FileStatus NEAR PASCAL GetNextEvent(NPSEQ npSeq) /* scan all track queues for the next event, and put the next one to occur in "nextEvent." Remember that each track can use its own running status (we'll fill in all status). */ #define MAXDELTA 0x7FFFFFFF { NPTRACK npTrack; NPTRACK npTrackMin = NULL; LONG minDelta = MAXDELTA; /* larger than any possible delta */ BOOL foundBlocked = FALSE; npTrack = (NPTRACK) List_Get_First(npSeq->trackList); while (npTrack) /* find smallest delta */ { if ((!npTrack->endOfTrack) && (npTrack->delta < minDelta)) // note that "ties" go to earliest track { if (npTrack->blockedOn) foundBlocked = TRUE; else { minDelta = npTrack->delta; npTrackMin = npTrack; } } npTrack = (NPTRACK) List_Get_Next(npSeq->trackList, npTrack); } npSeq->nextEventTrack = npTrackMin; if (npTrackMin == NULL) if (foundBlocked) return OnlyBlockedTracks; else return AllTracksEmpty; else return NoErr; } PUBLIC VOID NEAR PASCAL FillInNextTrack(NPTRACK npTrack) /* given a pointer to a track structure, fill it in with next event data (delta time and data) received from mciseq streamer. */ { LONG lOldDelta; MarkLocation(npTrack); // remember where you started in case get blocked lOldDelta = npTrack->delta; // remember this delta in case block FillInDelta(npTrack); FillInEvent(npTrack); if (npTrack->blockedOn) { ResetLocation(npTrack); // blocked -- hence rewind npTrack->delta = lOldDelta; // restore old delta } } /**********************************************************/ PUBLIC VOID NEAR PASCAL FillInDelta(NPTRACK npTrack) // fills in delta of track passed in { npTrack->delta += GetVarLen(npTrack); } PUBLIC UINT NEAR PASCAL SendPatchCache(NPSEQ npSeq, BOOL cache) { UINT cacheOrNot; UINT wRet; #define BANK 0 #define DRUMPATCH 0 if (!npSeq->hMIDIOut) // do nothing if no midiport return MIDISEQERR_NOERROR; if (cache) cacheOrNot = MIDI_CACHE_BESTFIT; else cacheOrNot = MIDI_UNCACHE; wRet = midiOutCachePatches(npSeq->hMIDIOut, BANK, // send it (LPPATCHARRAY) &npSeq->patchArray, cacheOrNot); if (!wRet) wRet = midiOutCacheDrumPatches(npSeq->hMIDIOut, DRUMPATCH, // send it (LPKEYARRAY) &npSeq->drumKeyArray, cacheOrNot); return wRet == MMSYSERR_NOTSUPPORTED ? 0 : wRet; } PUBLIC VOID NEAR PASCAL SendSysEx(NPSEQ npSeq) // get a sysex buffer, fill it, and send it off // keep doing this until done (!sysexremlength), or blocked on input, // or blocked on output. { NPLONGMIDI myBuffer; // temp variable to reduce address computation time DWORD sysExRemLength = npSeq->nextEventTrack->sysExRemLength; int max, bytesSent; npSeq->bSendingSysEx = TRUE; dprintf2(("Entering SendSysEx")); while ((sysExRemLength) && (!npSeq->nextEventTrack->blockedOn)) { if (!(myBuffer = GetSysExBuffer(npSeq))) break; // can't get a buffer bytesSent = 0; // init buffer data index // if status byte was F0, make that 1st byte of message // (remember, it could be F7, which shouldn't be sent) if (npSeq->nextEventTrack->shortMIDIData.byteMsg.status == SYSEX) { dprintf3(("Packing Sysex Byte")); myBuffer->data[bytesSent++] = SYSEX; sysExRemLength++; // semi-hack to account for extra byte //by convention, clear f0 status after f0 has been sent npSeq->nextEventTrack->shortMIDIData.byteMsg.status = 0; } max = min(LONGBUFFSIZE, (int)sysExRemLength); // max bytes for this buff // fill buffer -- note that i will reflect # of valid bytes in buff do myBuffer->data[bytesSent] = GetByte(npSeq->nextEventTrack); while ((!npSeq->nextEventTrack->blockedOn) && (++bytesSent < max)); // account for bytes sent sysExRemLength -= bytesSent; // send buffer myBuffer->midihdr.dwBufferLength = bytesSent; dprintf3(("SENDing SendSysEx")); if (npSeq->hMIDIOut) midiOutLongMsg(npSeq->hMIDIOut, &myBuffer->midihdr, sizeof(MIDIHDR)); } if (sysExRemLength) // not done -- must be blocked { //blocked on in or out? if (npSeq->nextEventTrack->blockedOn) { //blocked on in npSeq->nextEventTrack->blockedOn = in_SysEx; dprintf3(("Sysex blocked on INPUT")); } else { //blocked on out npSeq->bSysExBlock = TRUE; dprintf3(("Sysex blocked on OUTPUT")); } } else // done { npSeq->bSendingSysEx = FALSE; dprintf4(("Sysex Legally Finished")); } npSeq->nextEventTrack->sysExRemLength = sysExRemLength; // restore } /* // BOGUS void DoSyncPrep(NPSEQ npSeq) { //a bunch of sync prep will go here, such as: if ((npSeq->slaveOf != SEQ_SYNC_NOTHING) && (npSeq->slaveOf != SEQ_SYNC_MIDICLOCK) && (npSeq->division == SEQ_DIV_PPQN)) addTempoMap(npSeq); else destroyTempoMap(npSeq); if (npSeq->masterOf != SEQ_SYNC_NOTHING) addSyncOut(npSeq); else destroySyncOut(npSeq); } */