/*---------------------------------------------------------------------------* Project: MIDI sequencer application for AX synthesizer File: seq.c Copyright (C)1998-2006 Nintendo All Rights Reserved. These coded instructions, statements, and computer programs contain proprietary information of Nintendo of America Inc. and/or Nintendo Company Ltd., and are protected by Federal copyright law. They may not be disclosed to third parties or copied or duplicated in any form, in whole or in part, without the prior written consent of Nintendo. $Log: seq.c,v $ Revision 1.6.4.1 2007/09/13 04:07:16 aka For SDK3.1 patch1. Revision 1.7 2007/08/08 02:43:07 aka Added __init flag. Revision 1.6 2006/12/27 04:17:23 aka Revised comments. Revision 1.5 2006/12/27 02:51:14 aka Revised comments. Revision 1.4 2006/11/21 04:10:55 aka Removed assertion of the zero buffer. Revision 1.3 2006/01/31 06:35:21 aka Changed arguments of SEQAddSequence(). Revision 1.2 2005/11/08 01:43:00 aka Changed suiting to Revolution's audio spec. Revision 1.1.1.1 2005/05/12 02:15:50 yasuh-to Imported from dolphin tree. 5 2003/03/05 3:39p Akagi Fixed timing of calling controller callback. 4 2002/06/14 1:35p Billyjack - fix for tempo... used to be a tiny bit slow 3 2001/08/16 12:26p Billyjack Added zeroBuffer offset to API. 2 2001/05/11 4:17p Billyjack Fixed problem with songs ending and set state to SEQ_STATE_RUN. 1 2001/05/09 1:14p Billyjack Created $NoKeywords: $ *---------------------------------------------------------------------------*/ #include #include /*---------------------------------------------------------------------------* Parse MIDI chunk name. *---------------------------------------------------------------------------*/ #define __SEQChunkName(a, b, c, d)( \ ((a & 0xff) << 24) | \ ((b & 0xff) << 16) | \ ((c & 0xff) << 8) | \ (d & 0xff)) /*---------------------------------------------------------------------------* Table for MIDI event length in bytes. *---------------------------------------------------------------------------*/ static u8 __SEQMidiEventLength[] = { 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // 0x80 - 0x8F 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // 0x90 - 0x9F 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // 0xA0 - 0xAF 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // 0xB0 - 0xBF 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0xC0 - 0xCF 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0xD0 - 0xDF 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // 0xE0 - 0xEF 0, 0, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 // 0xF0 - 0xFF }; /*---------------------------------------------------------------------------* List of sequences to run. *---------------------------------------------------------------------------*/ static SEQSEQUENCE *__SEQSequenceList; /*---------------------------------------------------------------------------* Internal variables for the mixer. *---------------------------------------------------------------------------*/ static BOOL __init = FALSE; /*---------------------------------------------------------------------------* Push the specified sequence into the sequence list. *---------------------------------------------------------------------------*/ static void __SEQPushSequenceList(SEQSEQUENCE *sequence) { int old; old = OSDisableInterrupts(); if (__SEQSequenceList) sequence->next = __SEQSequenceList; else sequence->next = NULL; __SEQSequenceList = sequence; OSRestoreInterrupts(old); } /*---------------------------------------------------------------------------* Remove the specified sequence from list. *---------------------------------------------------------------------------*/ static void __SEQRemoveSequenceFromList(SEQSEQUENCE *sequence) { int old; SEQSEQUENCE *thisSequence; old = OSDisableInterrupts(); thisSequence = __SEQSequenceList; __SEQSequenceList = NULL; while (thisSequence) { SEQSEQUENCE *next = thisSequence->next; if (thisSequence != sequence) __SEQPushSequenceList(thisSequence); thisSequence = next; } OSRestoreInterrupts(old); } /*---------------------------------------------------------------------------* Get MIDI variable length integer. *---------------------------------------------------------------------------*/ static u32 __SEQGetIntTrack(SEQTRACK *track) { u32 value; ASSERT(track); value = (u32)(*track->current & 0x7f); while (*track->current & 0x80) { track->current++; value = (value << 7) + (*track->current & 0x7f); } track->current++; return value; } /*---------------------------------------------------------------------------* System exclusive events. *---------------------------------------------------------------------------*/ static void __SEQHandleSysExEvent(SEQTRACK *track) { u32 length; ASSERT(track); length = __SEQGetIntTrack(track); track->current += length; } /*---------------------------------------------------------------------------* Set the ticks per frame based on beats per second. *---------------------------------------------------------------------------*/ static void __SEQSetTicksPerFrame(SEQTRACK *track, f32 bps) { SEQSEQUENCE *sequence; ASSERT(track); sequence = (SEQSEQUENCE*)track->sequence; track->beatsPerSec = bps; track->ticksPerFrame = (u32)(0x00010000 * ( (f32)AX_IN_SAMPLES_PER_FRAME / ( // 96 samples / frame (f32)AX_IN_SAMPLES_PER_SEC / // 32000Hz bps / sequence->timeFormat))); } /*---------------------------------------------------------------------------* Handle tempo meta events. *---------------------------------------------------------------------------*/ static void __SEQTempoMetaEvent(SEQTRACK *track) { u32 data; f32 beatsPerSec; data = (u32)(*track->current); track->current++; data = (data << 8) + (u32)(*track->current); track->current++; data = (data << 8) + (u32)(*track->current); track->current++; beatsPerSec = 1000000.0f / data; __SEQSetTicksPerFrame(track, beatsPerSec); } /*---------------------------------------------------------------------------* Rewind the track to starting position. *---------------------------------------------------------------------------*/ static void __SEQTrackEnd(SEQTRACK *track) { SEQSEQUENCE *sequence; ASSERT(track); sequence = track->sequence; sequence->tracksRunning--; track->state = SEQ_STATE_STOP; if (sequence->tracksRunning == 0) sequence->end = 1; } /*---------------------------------------------------------------------------* Handle meta events. *---------------------------------------------------------------------------*/ static void __SEQHandleMetaEvent(SEQTRACK *track) { u8 type; u32 length; ASSERT(track); type = *track->current; track->current++; switch (type) { case 47: // End of track. __SEQTrackEnd(track); break; case 81: // Tempo length = __SEQGetIntTrack(track); __SEQTempoMetaEvent(track); break; default: length = __SEQGetIntTrack(track); track->current += length; break; } } /*---------------------------------------------------------------------------* Handle MIDI events to pass to the synth. *---------------------------------------------------------------------------*/ static void __SEQHandleSynthEvent(SYNSYNTH *synth, SEQTRACK *track) { u8 ch[3]; u32 bytes; bytes = __SEQMidiEventLength[track->status - 0x80]; ch[0] = track->status; switch (bytes) { case 0: break; case 1: ch[1] = *track->current; track->current++; break; case 2: ch[1] = *track->current; track->current++; ch[2] = *track->current; track->current++; break; } // Perform controller callback if any. if ((ch[0] & 0xf0) == 0xb0) { SEQCALLBACK callback = ((SEQSEQUENCE*)(track->sequence))->callback[ch[1]]; if (callback) (*callback)(track, ch[1]); } SYNMidiInput(synth, ch); } /*---------------------------------------------------------------------------* Run the next event on the MIDI stream. *---------------------------------------------------------------------------*/ static void __SEQRunEvent(SYNSYNTH *synth, SEQTRACK *track) { u8 event; ASSERT(synth); ASSERT(track); event = *track->current; if (event >= 0x80) { track->status = event; track->current++; } switch (track->status) { case 0xf0: // System exclusive. case 0xf7: // Special system exclusive. __SEQHandleSysExEvent(track); break; case 0xff: // Meta events. __SEQHandleMetaEvent(track); break; default: // Send the event to the synth. __SEQHandleSynthEvent(synth, track); break; } if (track->current >= track->end) __SEQTrackEnd(track); } /*---------------------------------------------------------------------------* Initialize tracks. *---------------------------------------------------------------------------*/ static void __SEQInitTracks(SEQSEQUENCE *sequence, u8 *read, int tracks) { int i; u8 *p; i = 0; p = read; while (tracks) { while(1) { u32 chunk; u32 bytes; chunk = *(u32*)p; p += 4; bytes = *(u32*)p; p += 4; if (chunk == __SEQChunkName('M', 'T', 'r', 'k')) { SEQTRACK *track = &sequence->track[i]; track->sequence = sequence; track->start = p; track->end = p + bytes; track->current = p; track->defaultTicksPerFrame = (u32)(0x00010000 * ( (f32)AX_IN_SAMPLES_PER_FRAME / ( // 96 samples / frame 60.0f / 120.0f * (f32)AX_IN_SAMPLES_PER_SEC / // 32000Hz sequence->timeFormat))); track->state = SEQ_STATE_STOP; p += bytes; break; } p += bytes; } tracks--; i++; } } /*---------------------------------------------------------------------------* Reads header from MIDI stream. *---------------------------------------------------------------------------*/ static void __SEQReadHeader(SEQSEQUENCE *sequence, u8 *midiStream) { u8 *read; u32 bytes; u32 fileType; read = midiStream; ASSERTMSG( *(u32*)read == __SEQChunkName('M', 'T', 'h', 'd'), "!!!midiStream is not a valid MIDI file\n!!!" ); read += 4; bytes = *(u32*)read; read += 4; fileType = *(u16*)(read); read += 2; sequence->nTracks = *(u16*)(read); read += 2; sequence->timeFormat = *(s16*)(read); read += 2; ASSERTMSG( sequence->timeFormat >= 0, "!!!SEQ does not support SMPTE time!!!\n" ); bytes -= 6; read += bytes; // Load up tracks according to file type. switch (fileType) { case 0: // Type 0 only play first track. sequence->nTracks = 1; __SEQInitTracks(sequence, read, 1); break; case 1: ASSERTMSG( sequence->nTracks < SEQ_MAX_TRACKS, "exceeded SEQ_MAX_TRACKS, please increase SEQ_MAX_TRACKS\n" ); __SEQInitTracks(sequence, read, sequence->nTracks); break; default: ASSERTMSG(0, "!!!Invalid MIDI file type\n!!!"); break; } sequence->tracksRunning = sequence->nTracks; } /*---------------------------------------------------------------------------* Exposed API functions. *---------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------* Initialize the sequencer. *---------------------------------------------------------------------------*/ void SEQInit(void) { if (__init) { return; } __SEQSequenceList = NULL; __init = TRUE; } /*---------------------------------------------------------------------------* Quit the sequencer gracefully. *---------------------------------------------------------------------------*/ void SEQQuit(void) { __SEQSequenceList = NULL; __init = FALSE; } /*---------------------------------------------------------------------------* Run one audio frame's worth of events. *---------------------------------------------------------------------------*/ void SEQRunAudioFrame(void) { SEQSEQUENCE *sequence = __SEQSequenceList; if (!__init) { return; } // Go through all the sequences. while (sequence) { if ((sequence->state == SEQ_STATE_RUN) || (sequence->state == SEQ_STATE_RUNLOOPED)) { u32 i; for (i = 0; i < sequence->nTracks; i++) { SEQTRACK *track = &sequence->track[i]; if ((track->state == SEQ_STATE_RUN) || (track->state == SEQ_STATE_RUNLOOPED)) { u32 ticks = track->ticksPerFrame; if (track->delay > ticks) { track->delay -= ticks; } else { while (ticks >= track->delay) { ticks -= track->delay; __SEQRunEvent(&sequence->synth, track); if (track->state == SEQ_STATE_STOP) break; track->delay = __SEQGetIntTrack(track) << 16; } track->delay -= ticks; } } } } if (sequence->end) { if (sequence->state == SEQ_STATE_RUNLOOPED) { SEQSetState(sequence, SEQ_STATE_STOP); SEQSetState(sequence, SEQ_STATE_RUNLOOPED); } else { SEQSetState(sequence, SEQ_STATE_STOP); } } sequence = sequence->next; } } /*---------------------------------------------------------------------------* Initialize and add the specified sequence to sequence list. *---------------------------------------------------------------------------*/ void SEQAddSequence( SEQSEQUENCE *sequence, // User allocated SEQSEQUENCE. u8 *midiStream, // Pointer to MIDI stream. u8 *wavetable, // Pointer to wave table. u8 *samples, // Pointer to samples. u8 *zerobuffer, // Pointer to zero buffer. u32 priorityVoiceAlloc, // Priority for allocating notes. u32 priorityNoteOn, // Priority for notes that are on. u32 priorityNoteRelease // Priority for notes in release stage. ) { int i; ASSERT(sequence); ASSERT(midiStream); ASSERT(wavetable); ASSERT(samples); //ASSERT(zerobuffer); ASSERT((priorityVoiceAlloc < 32) && (priorityVoiceAlloc > 0)); ASSERT((priorityNoteOn < 32) && (priorityNoteOn > 0)); ASSERT((priorityNoteRelease < 32) && (priorityNoteRelease > 0)); // Initialize the synth. SYNInitSynth( &sequence->synth, wavetable, samples, zerobuffer, priorityVoiceAlloc, priorityNoteOn, priorityNoteRelease ); // Initialize data members. sequence->state = SEQ_STATE_STOP; // 0 controller callbacks. for (i = 0; i < 128; i++) { sequence->callback[i] = NULL; } // Read the MIDI file header. __SEQReadHeader(sequence, midiStream); // Put it in a list of sequences to service. __SEQPushSequenceList(sequence); } /*---------------------------------------------------------------------------* Remove the specified sequence from sequence list. *---------------------------------------------------------------------------*/ void SEQRemoveSequence(SEQSEQUENCE *sequence) { ASSERT(sequence); // Remove from list of running sequences. __SEQRemoveSequenceFromList(sequence); // Shut down the synth for the sequence. SYNQuitSynth(&sequence->synth); } /*---------------------------------------------------------------------------* Register callback for controller event. *---------------------------------------------------------------------------*/ void SEQRegisterControllerCallback( SEQSEQUENCE *sequence, // User initialized SEQSEQUENCE. u8 controller, // MIDI controller SEQCALLBACK callback // Callback function ) { ASSERT(sequence); ASSERT(controller < 128); ASSERT(callback); sequence->callback[controller] = callback; } /*---------------------------------------------------------------------------* Set specified sequence to specified state. *---------------------------------------------------------------------------*/ void SEQSetState(SEQSEQUENCE *sequence, u32 state) { int i; ASSERT(sequence); switch (state) { case SEQ_STATE_RUN: case SEQ_STATE_RUNLOOPED: // If the previous state was SEQ_STATE_STOP start the tracks from // the beginning. if (sequence->state == SEQ_STATE_STOP) { int old = OSDisableInterrupts(); for (i = 0; i < sequence->nTracks; i++) { SEQTRACK *track = &sequence->track[i]; track->current = track->start; track->ticksPerFrame = track->defaultTicksPerFrame; track->delay = __SEQGetIntTrack(track) << 16; track->state = SEQ_STATE_RUN; } sequence->tracksRunning = sequence->nTracks; OSRestoreInterrupts(old); } sequence->end = 0; break; // We also to silence all sounds from the synth. case SEQ_STATE_STOP: case SEQ_STATE_PAUSE: for (i = 0; i < 16; i++) { int old; u8 ch[3]; old = OSDisableInterrupts(); ch[0] = (u8)(0xb0 | i); ch[1] = 0x7b; ch[2] = 0; SYNMidiInput(&sequence->synth, ch); OSRestoreInterrupts(old); } break; } sequence->state = state; } /*---------------------------------------------------------------------------* Get current state of sequence. *---------------------------------------------------------------------------*/ u32 SEQGetState(SEQSEQUENCE *sequence) { ASSERT(sequence); return sequence->state; } /*---------------------------------------------------------------------------* Set specified track(s) or specified sequence to specified BPM. *---------------------------------------------------------------------------*/ void SEQSetTempo(SEQSEQUENCE *sequence, u32 trackIndex, f32 bpm) { ASSERT(sequence); ASSERT((trackIndex < sequence->nTracks) || (trackIndex == SEQ_ALL_TRACKS)); if (trackIndex == SEQ_ALL_TRACKS) { int i; for (i = 0; i < sequence->nTracks; i++) __SEQSetTicksPerFrame(&sequence->track[i], bpm / 60); } else { __SEQSetTicksPerFrame(&sequence->track[trackIndex], bpm / 60); } } /*---------------------------------------------------------------------------* Get current tempo for specified track of specified sequencer. *---------------------------------------------------------------------------*/ f32 SEQGetTempo(SEQSEQUENCE *sequence, u32 trackIndex) { ASSERT(sequence); ASSERT(trackIndex < sequence->nTracks); return sequence->track[trackIndex].beatsPerSec * 60; } /*---------------------------------------------------------------------------* Set specified sequence to specified volume 0x00000001 = 0.1dB. *---------------------------------------------------------------------------*/ void SEQSetVolume(SEQSEQUENCE *sequence, s32 dB) { ASSERT(sequence); SYNSetMasterVolume(&sequence->synth, dB); } /*---------------------------------------------------------------------------* Get current volume for specified sequence. *---------------------------------------------------------------------------*/ s32 SEQGetVolume(SEQSEQUENCE *sequence) { ASSERT(sequence); return SYNGetMasterVolume(&sequence->synth); }