/*---------------------------------------------------------------------------* Project: TwlSDK - SND - demos - seq File: seq.c Copyright 2007-2008 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. $Date:: 2008-09-18#$ $Rev: 8573 $ $Author: okubata_ryoma $ *---------------------------------------------------------------------------*/ #include "seq.h" #include "smfdefine.h" /* Status constants. */ #define SEQ_STATE_PLAY 0x01 /* Playing */ #define SEQ_STATE_END 0x02 /* End of playback (end of track) */ #define SEQ_STATE_LOOP 0x04 /* Loop flag */ #define SEQ_STATE_MUTE 0x10 /* Mute */ /* Default tempo = 120 BPM (microseconds) */ #define SEQ_DEFAULT_TEMPO 500000 /* System exclusive buffer size (ignores the message when over size) */ #define SEQ_SYSX_BUFFER_SIZE 64 static u8 SeqReadVariableData(const u8 *data, u32 *result); static u8 SeqReadSMFHeader(SeqHandle * block, const u8 *data); static u8 SeqExecMetaEvent(SeqHandle * block); static u16 ReverseEndian16(u16 data); static u32 ReverseEndian32(u32 data); typedef struct tSeqSystemBlock { void (*callback) (const u8 *); u32 clock_interval; /* Time interval that calls SeqMain. Units in microseconds. */ } SeqSystemBlock; SeqSystemBlock seq_sys; /* Data byte number of each MIDI event */ static const u8 seq_midi_byte_size[8] = { /* 8x,9x,Ax,Bx,Cx,Dx,Ex,Fx*/ 2, 2, 2, 2, 1, 1, 2, 0 }; /* System exclusive buffer */ static u8 seqSysxBuffer[SEQ_SYSX_BUFFER_SIZE]; /* Saves the error code at time of an error */ static u8 seqErrorCode; /*---------------------------------------------------------------------------* Name: SeqInit Description: Initializes the sequence system. Arguments: clock_interval: Time interval that calls SeqMain. Units in microseconds. callback: Configures the function called when a MIDI event occurs. Returns: None. *---------------------------------------------------------------------------*/ void SeqInit(u32 clock_interval, void (*callback) (const u8 *)) { seq_sys.callback = callback; // Saves the callback function seq_sys.clock_interval = clock_interval; // Saves the call interval } /*---------------------------------------------------------------------------* Name: SeqMain Description: The main routine of the sequencer. Arguments: handle: Song handle that executes a process Returns: TRUE if an error occurs. *---------------------------------------------------------------------------*/ BOOL SeqMain(SeqHandle * handle) { u8 read_size; u8 current_event; u8 f_exist_status; /* Whether there are MIDI event instructions (whether currently in running status) */ /* Returns as-is when not in a playback state */ if (!(handle->state & SEQ_STATE_PLAY)) { return FALSE; } /* Progresses 1 callback's worth of time */ handle->time_control += handle->time_per_callback; while (handle->time_control > handle->tempo) { /* Process for each tick */ /* Subtracts delta time */ if (handle->delta_time > 0) { handle->delta_time--; } while (handle->delta_time == 0) { /* Delta time reached 0, so an event is executed */ handle->current_ptr += handle->next_delta_bytesize; /* current_ptr is the start of a MIDI message */ if (*handle->current_ptr >= 0x80) { /* Running status is configured because of starting from an instruction */ current_event = handle->running_status = *handle->current_ptr; f_exist_status = 1; } else { /* Running status is used because of starting from a value */ if (handle->running_status < 0x80) { seqErrorCode = SEQ_ERROR_WRONG_TRACK; return TRUE; /* Error */ } current_event = handle->running_status; f_exist_status = 0; } if (*handle->current_ptr == SMF_META) { /* The meta event is an operation within the sequencer */ read_size = SeqExecMetaEvent(handle); if (handle->state & SEQ_STATE_END) { /* END OF TRACK: Finished playing a song to the end. */ (void)SeqStop(handle); handle->state &= ~SEQ_STATE_END; return FALSE; } else if (handle->state & SEQ_STATE_LOOP) { /* LOOP END: Return to the starting position of a loop. */ handle->current_ptr = handle->loop_begin; handle->state &= ~SEQ_STATE_LOOP; } else { /* Proceed to the next event */ handle->current_ptr += read_size; } } else /* Other than meta-event */ { if (*handle->current_ptr == SMF_SYSX) { /* System exclusive */ if (SEQ_SYSX_BUFFER_SIZE > handle->current_ptr[1] + 1) { u8 i; /* Copy to buffer */ seqSysxBuffer[0] = handle->current_ptr[0]; for (i = 1; i <= handle->current_ptr[1]; i++) { seqSysxBuffer[i] = handle->current_ptr[i + 1]; } /* Send the MIDI event to the callback */ seq_sys.callback(seqSysxBuffer); } /* Proceed to the next event */ handle->current_ptr += handle->current_ptr[1] + 2; } else { /* Short MIDI event */ /* Send the MIDI event to the callback */ seq_sys.callback(handle->current_ptr); /* Proceed to the next event */ handle->current_ptr += seq_midi_byte_size[(current_event >> 4) - 8] + f_exist_status; } } /* Read the delta time of the next event */ handle->next_delta_bytesize = SeqReadVariableData(handle->current_ptr, &handle->delta_time); if (handle->next_delta_bytesize == 0) { return TRUE; /* Error */ } } /* Advance tick */ handle->total_tick++; /* Advance time */ handle->time_control -= handle->tempo; } return FALSE; } /*---------------------------------------------------------------------------* Name: SeqExecMetaEvent Description: Meta-event process routine. Arguments: handle: Song handle that executes a process Returns: Meta-event byte size. *---------------------------------------------------------------------------*/ static u8 SeqExecMetaEvent(SeqHandle * handle) { switch (handle->current_ptr[1]) { case SMF_META_MARKER: /* Marker */ if (handle->current_ptr[2] != 1) { break; } /* NITRO Composer compatible loop functionality */ if (handle->current_ptr[3] == '[') { /* Loop start position */ handle->loop_begin = handle->current_ptr + handle->current_ptr[2] + 3; } else if (handle->current_ptr[3] == ']') { /* Loop end position */ if (handle->loop_begin != NULL) { handle->state |= SEQ_STATE_LOOP; } } break; case SMF_META_ENDOFTRACK: /* End of track */ handle->state |= SEQ_STATE_END; break; case SMF_META_TEMPO: /* Set tempo */ handle->tempo = handle->current_ptr[3]; handle->tempo <<= 8; handle->tempo += handle->current_ptr[4]; handle->tempo <<= 8; handle->tempo += handle->current_ptr[5]; break; default: break; } return (u8)(handle->current_ptr[2] + 3); } /*---------------------------------------------------------------------------* Name: SeqPlay Description: Starts SMF data playback. Arguments: handle: Song handle that executes a process smfdata: SMF data Returns: Meta-event byte size. *---------------------------------------------------------------------------*/ BOOL SeqPlay(SeqHandle * handle, const u8 *smfdata) { u8 read_size; static const u8 mtrk[] = "MTrk"; u8 i; /* Reads the SMF header chunk */ read_size = SeqReadSMFHeader(handle, smfdata); if (read_size == 0) return TRUE; /* Error */ handle->current_ptr = smfdata + read_size; /* Reads track chunk header */ /* 'MTrk' check */ for (i = 0; i < 4; i++) { if (handle->current_ptr[i] != mtrk[i]) { seqErrorCode = SEQ_ERROR_WRONG_TRACK; /* Error */ return TRUE; } } handle->current_ptr += sizeof(u8) * 4; /* Chunk size */ handle->chunk_size = ReverseEndian32(*(u32 *)handle->current_ptr); handle->current_ptr += sizeof(u32); /* Track head */ handle->track_begin = handle->current_ptr; /* Initial settings */ handle->tempo = SEQ_DEFAULT_TEMPO; handle->time_control = 0; handle->time_per_callback = handle->division * seq_sys.clock_interval; handle->total_tick = 0; handle->running_status = 0x00; handle->loop_begin = NULL; /* Status variable settings */ handle->state = SEQ_STATE_PLAY; /* Reads the initial delta time */ handle->next_delta_bytesize = SeqReadVariableData(handle->current_ptr, &handle->delta_time); if (handle->next_delta_bytesize == 0) { seqErrorCode = SEQ_ERROR_WRONG_TRACK; return TRUE; /* Error */ } return FALSE; } /*---------------------------------------------------------------------------* Name: SeqStop Description: Stops SMF data playback. Arguments: handle: Song handle that executes a process Returns: None. *---------------------------------------------------------------------------*/ void SeqStop(SeqHandle * handle) { handle->current_ptr = handle->track_begin; /* Status variable settings */ handle->state &= ~SEQ_STATE_PLAY; } /*---------------------------------------------------------------------------* Name: SeqPause Description: Pauses SMF data playback. Arguments: handle: Song handle that executes a process Returns: None. *---------------------------------------------------------------------------*/ void SeqPause(SeqHandle * handle) { /* Status variable settings */ handle->state &= ~SEQ_STATE_PLAY; } /*---------------------------------------------------------------------------* Name: SeqReadSMFHeader Description: Reads the SMF header chunk. Arguments: handle: Song handle that executes a process data: SMF data string Returns: Number of bytes read. Normally 14. *---------------------------------------------------------------------------*/ static u8 SeqReadSMFHeader(SeqHandle * handle, const u8 *data) { static const u8 mthd[] = "MThd"; u8 i; const u8 *ptr = data; /* All data that is fixed in a format other than 0 is considered an error. */ /* 'MThd' check */ for (i = 0; i < 4; i++) { if (ptr[i] != mthd[i]) { seqErrorCode = SEQ_ERROR_WRONG_HEADER; /* Error */ return 0; } } ptr += sizeof(u8) * 4; /* The length of the header chunk data is 6 bytes */ if (ReverseEndian32(*(u32 *)ptr) != 6) { seqErrorCode = SEQ_ERROR_WRONG_HEADER; /* Error */ return 0; } ptr += sizeof(u32); /* Only handles format 0 */ if (*(u16 *)ptr != SMF_FORMAT0) { seqErrorCode = SEQ_ERROR_NOT_FORMAT0; /* Error */ return 0; } ptr += sizeof(u16); /* Error if there is other than 1 track */ if (ReverseEndian16(*(u16 *)ptr) != 1) { seqErrorCode = SEQ_ERROR_WRONG_HEADER; /* Error */ return 0; } ptr += sizeof(u16); /* Saves the resolution */ handle->division = ReverseEndian16(*(u16 *)ptr); if (handle->division >= 0x8000) { seqErrorCode = SEQ_ERROR_DIVISION_TIMECODE; /* Error */ return 0; } ptr += sizeof(u16); return (u8)(ptr - data); } /*---------------------------------------------------------------------------* Name: SeqReadVariableData Description: Routine for reading variable length data. Arguments: data: Variable length SMFMIDI data string result: Area for storing the read numerical value Returns: Number of bytes read. The maximum is 4 bytes in SMF, so anything above that is considered an error and returns 0. *---------------------------------------------------------------------------*/ static u8 SeqReadVariableData(const u8 *data, u32 *result) { u8 count = 0; *result = 0; while (*data & 0x80) { /* Continuous bytes */ *result = (*result << 7) + (*data & 0x7f); count++; if (count == 4) { /* The 4th byte is not the final byte, so this is an error */ seqErrorCode = SEQ_ERROR_WRONG_DELTA_TIME; return 0; } data++; } /* Final byte */ *result = (*result << 7) + *data; count++; return count; } /*---------------------------------------------------------------------------* Name: ReverseEndian16 Description: Endian conversion (2 bytes). Arguments: data: The data to convert Returns: Endian converted data. *---------------------------------------------------------------------------*/ static u16 ReverseEndian16(u16 data) { return (u16)(((data & 0x00ff) << 8) | ((data & 0xff00) >> 8)); } /*---------------------------------------------------------------------------* Name: ReverseEndian32 Description: Endian conversion (4 bytes). Arguments: data: The data to convert Returns: Endian converted data. *---------------------------------------------------------------------------*/ static u32 ReverseEndian32(u32 data) { return (((data >> 24) & 0x000000ff) | ((data >> 8) & 0x0000ff00) | ((data << 8) & 0x00ff0000) | ((data << 24) & 0xff000000)); } /*---------------------------------------------------------------------------*/ u8 SeqGetErrorCode(void) { return seqErrorCode; }