diff --git a/samples/dreamroq/dreamroqlib.c b/samples/dreamroq/dreamroqlib.c new file mode 100644 index 0000000..4679fce --- /dev/null +++ b/samples/dreamroq/dreamroqlib.c @@ -0,0 +1,572 @@ +/* + * Dreamroq by Mike Melanson + * Updated by Josh Pearson to add audio support + * + * This is the main playback engine. + */ +/* + Name:Ian micheal + Date: 15/08/23 08:16 + Description: kos filesystem api port +*/ + +#include +#include +#include +#include +#include +#include // Include the KOS filesystem header +#include "dreamroqlib.h" + +#define RoQ_INFO 0x1001 +#define RoQ_QUAD_CODEBOOK 0x1002 +#define RoQ_QUAD_VQ 0x1011 +#define RoQ_SOUND_MONO 0x1020 +#define RoQ_SOUND_STEREO 0x1021 +#define RoQ_SIGNATURE 0x1084 + +#define CHUNK_HEADER_SIZE 8 + +#define LE_16(buf) (*buf | (*(buf+1) << 8)) +#define LE_32(buf) (*buf | (*(buf+1) << 8) | (*(buf+2) << 16) | (*(buf+3) << 24)) + +#define MAX_BUF_SIZE (64 * 1024) + + +#define ROQ_CODEBOOK_SIZE 256 + +struct roq_audio +{ + int pcm_samples; + int channels; + int position; + short snd_sqr_arr[260]; + unsigned char pcm_sample[MAX_BUF_SIZE]; +}roq_audio; + +typedef struct +{ + int width; + int height; + int mb_width; + int mb_height; + int mb_count; + + int current_frame; + unsigned short *frame[2]; + int stride; + int texture_height; + + unsigned short cb2x2[ROQ_CODEBOOK_SIZE][4]; + unsigned short cb4x4[ROQ_CODEBOOK_SIZE][16]; +} roq_state; + + + +static int roq_unpack_quad_codebook(unsigned char *buf, int size, int arg, + roq_state *state) +{ + int y[4]; + int yp, u, v; + int r, g, b; + int count2x2; + int count4x4; + int i, j; + unsigned short *v2x2; + unsigned short *v4x4; + + count2x2 = (arg >> 8) & 0xFF; + count4x4 = arg & 0xFF; + + if (!count2x2) + count2x2 = ROQ_CODEBOOK_SIZE; + /* 0x00 means 256 4x4 vectors iff there is enough space in the chunk + * after accounting for the 2x2 vectors */ + if (!count4x4 && count2x2 * 6 < size) + count4x4 = ROQ_CODEBOOK_SIZE; + + /* size sanity check */ + if ((count2x2 * 6 + count4x4 * 4) != size) + { + return ROQ_BAD_CODEBOOK; + } + + /* unpack the 2x2 vectors */ + for (i = 0; i < count2x2; i++) + { + /* unpack the YUV components from the bytestream */ + for (j = 0; j < 4; j++) + y[j] = *buf++; + u = *buf++; + v = *buf++; + + /* convert to RGB565 */ + for (j = 0; j < 4; j++) + { + yp = (y[j] - 16) * 1.164; + r = (yp + 1.596 * (v - 128)) / 8; + g = (yp - 0.813 * (v - 128) - 0.391 * (u - 128)) / 4; + b = (yp + 2.018 * (u - 128)) / 8; + + if (r < 0) r = 0; + if (r > 31) r = 31; + if (g < 0) g = 0; + if (g > 63) g = 63; + if (b < 0) b = 0; + if (b > 31) b = 31; + + state->cb2x2[i][j] = ( + (r << 11) | + (g << 5) | + (b << 0) ); + } + } + + /* unpack the 4x4 vectors */ + for (i = 0; i < count4x4; i++) + { + for (j = 0; j < 4; j++) + { + v2x2 = state->cb2x2[*buf++]; + v4x4 = state->cb4x4[i] + (j / 2) * 8 + (j % 2) * 2; + v4x4[0] = v2x2[0]; + v4x4[1] = v2x2[1]; + v4x4[4] = v2x2[2]; + v4x4[5] = v2x2[3]; + } + } + + return ROQ_SUCCESS; +} + +#define GET_BYTE(x) \ + if (index >= size) { \ + status = ROQ_BAD_VQ_STREAM; \ + x = 0; \ + } else { \ + x = buf[index++]; \ + } + +#define GET_MODE() \ + if (!mode_count) { \ + GET_BYTE(mode_lo); \ + GET_BYTE(mode_hi); \ + mode_set = (mode_hi << 8) | mode_lo; \ + mode_count = 16; \ + } \ + mode_count -= 2; \ + mode = (mode_set >> mode_count) & 0x03; + +static int roq_unpack_vq(unsigned char *buf, int size, unsigned int arg, + roq_state *state) +{ + int status = ROQ_SUCCESS; + int mb_x, mb_y; + int block; /* 8x8 blocks */ + int subblock; /* 4x4 blocks */ + int stride = state->stride; + int i; + + /* frame and pixel management */ + unsigned short *this_frame; + unsigned short *last_frame; + + int line_offset; + int mb_offset; + int block_offset; + int subblock_offset; + + unsigned short *this_ptr; + unsigned int *this_ptr32; + unsigned short *last_ptr; + /*unsigned int *last_ptr32;*/ + unsigned short *vector16; + unsigned int *vector32; + int stride32 = stride / 2; + + /* bytestream management */ + int index = 0; + int mode_set = 0; + int mode, mode_lo, mode_hi; + int mode_count = 0; + + /* vectors */ + int mx, my; + int motion_x, motion_y; + unsigned char data_byte; + + mx = (arg >> 8) & 0xFF; + my = arg & 0xFF; + + if (state->current_frame == 1) + { + state->current_frame = 0; + this_frame = state->frame[0]; + last_frame = state->frame[1]; + } + else + { + state->current_frame = 1; + this_frame = state->frame[1]; + last_frame = state->frame[0]; + } + + for (mb_y = 0; mb_y < state->mb_height && status == ROQ_SUCCESS; mb_y++) + { + line_offset = mb_y * 16 * stride; + for (mb_x = 0; mb_x < state->mb_width && status == ROQ_SUCCESS; mb_x++) + { + mb_offset = line_offset + mb_x * 16; + for (block = 0; block < 4 && status == ROQ_SUCCESS; block++) + { + block_offset = mb_offset + (block / 2 * 8 * stride) + (block % 2 * 8); + /* each 8x8 block gets a mode */ + GET_MODE(); + switch (mode) + { + case 0: /* MOT: skip */ + break; + + case 1: /* FCC: motion compensation */ + /* this needs to be done 16 bits at a time due to + * data alignment issues on the SH-4 */ + GET_BYTE(data_byte); + motion_x = 8 - (data_byte >> 4) - mx; + motion_y = 8 - (data_byte & 0xF) - my; + last_ptr = last_frame + block_offset + + (motion_y * stride) + motion_x; + this_ptr = this_frame + block_offset; + for (i = 0; i < 8; i++) + { + *this_ptr++ = *last_ptr++; + *this_ptr++ = *last_ptr++; + *this_ptr++ = *last_ptr++; + *this_ptr++ = *last_ptr++; + *this_ptr++ = *last_ptr++; + *this_ptr++ = *last_ptr++; + *this_ptr++ = *last_ptr++; + *this_ptr++ = *last_ptr++; + + last_ptr += stride - 8; + this_ptr += stride - 8; + } + break; + + case 2: /* SLD: upsample 4x4 vector */ + GET_BYTE(data_byte); + vector16 = state->cb4x4[data_byte]; + for (i = 0; i < 4*4; i++) + { + this_ptr = this_frame + block_offset + + (i / 4 * 2 * stride) + (i % 4 * 2); + this_ptr[0] = *vector16; + this_ptr[1] = *vector16; + this_ptr[stride+0] = *vector16; + this_ptr[stride+1] = *vector16; + vector16++; + } + break; + + case 3: /* CCC: subdivide into 4 subblocks */ + for (subblock = 0; subblock < 4; subblock++) + { + subblock_offset = block_offset + (subblock / 2 * 4 * stride) + (subblock % 2 * 4); + + GET_MODE(); + switch (mode) + { + case 0: /* MOT: skip */ + break; + + case 1: /* FCC: motion compensation */ + GET_BYTE(data_byte); + motion_x = 8 - (data_byte >> 4) - mx; + motion_y = 8 - (data_byte & 0xF) - my; + last_ptr = last_frame + subblock_offset + + (motion_y * stride) + motion_x; + this_ptr = this_frame + subblock_offset; + for (i = 0; i < 4; i++) + { + *this_ptr++ = *last_ptr++; + *this_ptr++ = *last_ptr++; + *this_ptr++ = *last_ptr++; + *this_ptr++ = *last_ptr++; + + last_ptr += stride - 4; + this_ptr += stride - 4; + } + break;; + case 2: /* SLD: use 4x4 vector from codebook */ + GET_BYTE(data_byte); + vector32 = (unsigned int*)state->cb4x4[data_byte]; + + this_ptr32 = (unsigned int*)this_frame; + this_ptr32 += subblock_offset / 2; + for (i = 0; i < 4; i++) + { + *this_ptr32++ = *vector32++; + *this_ptr32++ = *vector32++; + + + this_ptr32 += stride32 - 2; + } + break; + + case 3: /* CCC: subdivide into 4 subblocks */ + GET_BYTE(data_byte); + vector16 = state->cb2x2[data_byte]; + this_ptr = this_frame + subblock_offset; + + + this_ptr[0] = vector16[0]; + this_ptr[1] = vector16[1]; + this_ptr[stride+0] = vector16[2]; + this_ptr[stride+1] = vector16[3]; + + GET_BYTE(data_byte); + vector16 = state->cb2x2[data_byte]; + + + this_ptr[2] = vector16[0]; + this_ptr[3] = vector16[1]; + this_ptr[stride+2] = vector16[2]; + this_ptr[stride+3] = vector16[3]; + + this_ptr += stride * 2; + + GET_BYTE(data_byte); + vector16 = state->cb2x2[data_byte]; + + + this_ptr[0] = vector16[0]; + this_ptr[1] = vector16[1]; + this_ptr[stride+0] = vector16[2]; + this_ptr[stride+1] = vector16[3]; + + GET_BYTE(data_byte); + vector16 = state->cb2x2[data_byte]; + + + this_ptr[2] = vector16[0]; + this_ptr[3] = vector16[1]; + this_ptr[stride+2] = vector16[2]; + this_ptr[stride+3] = vector16[3]; + + break; + } + } + break; + } + } + } + } + + /* sanity check to see if the stream was fully consumed */ + if (status == ROQ_SUCCESS && index < size-2) + { + status = ROQ_BAD_VQ_STREAM; + } + + return status; +} +/* + Name: Ian micheal + Copyright: + Author: + Date: 15/08/23 19:24 + Description: ported from C normal file system to kos FS file system api because if this + + Info from TapamN One issue you might run into is slow file access over ethernet. + Using the C library stdio.h functions (fread, fwrite) can be much slower than using the KOS filesystem calls directly (fs_read, fs_write) when reading/writing large blocks. + With stdio, you get something like tens of KB/sec, while with KOS you can get over 1 MB/sec. Stdio might be faster when preforming many very small operations. + dcload-serial doesn't have this issue. +*/ + +int dreamroq_play(char *filename, int loop, render_callback render_cb, + audio_callback audio_cb, quit_callback quit_cb) +{ + file_t f; + ssize_t file_ret; + int framerate; + int chunk_id; + unsigned int chunk_size; + unsigned int chunk_arg; + roq_state state; + int status; + int initialized = 0; + unsigned char read_buffer[MAX_BUF_SIZE]; + int i, snd_left, snd_right; + + f = fs_open(filename, O_RDONLY); + if (f < 0) + return ROQ_FILE_OPEN_FAILURE; + + file_ret = fs_read(f, read_buffer, CHUNK_HEADER_SIZE); + if (file_ret != CHUNK_HEADER_SIZE) + { + fs_close(f); + printf("\nROQ_FILE_READ_FAILURE\n\n"); + return ROQ_FILE_READ_FAILURE; + } + framerate = LE_16(&read_buffer[6]); + printf("RoQ file plays at %d frames/sec\n", framerate); + + /* Initialize Audio SQRT Look-Up Table */ + for(i = 0; i < 128; i++) + { + roq_audio.snd_sqr_arr[i] = i * i; + roq_audio.snd_sqr_arr[i + 128] = -(i * i); + } + + +status = ROQ_SUCCESS; +while (1) +{ + if (quit_cb && quit_cb()) + break; + + file_ret = fs_read(f, read_buffer, CHUNK_HEADER_SIZE); + #ifdef FPSGRAPH + printf("r\n"); + #endif + if (file_ret < CHUNK_HEADER_SIZE) + { + if (file_ret == 0) // Indicates end of file + break; + else if (loop) + { + fs_seek(f, 8, SEEK_SET); + continue; + } + else + break; + } + chunk_id = LE_16(&read_buffer[0]); + chunk_size = LE_32(&read_buffer[2]); + chunk_arg = LE_16(&read_buffer[6]); + + if (chunk_size > MAX_BUF_SIZE) + { + fs_close(f); + return ROQ_CHUNK_TOO_LARGE; + } + + file_ret = fs_read(f, read_buffer, chunk_size); + if (file_ret != chunk_size) + { + status = ROQ_FILE_READ_FAILURE; + break; + } + + + switch(chunk_id) + { + case RoQ_INFO: + if (initialized) + continue; + + state.width = LE_16(&read_buffer[0]); + state.height = LE_16(&read_buffer[2]); + /* width and height each need to be divisible by 16 */ + if ((state.width & 0xF) || (state.height & 0xF)) + { + status = ROQ_INVALID_PIC_SIZE; + break; + } + state.mb_width = state.width / 16; + state.mb_height = state.height / 16; + state.mb_count = state.mb_width * state.mb_height; + if (state.width < 8 || state.width > 1024) + status = ROQ_INVALID_DIMENSION; + else + { + state.stride = 8; + while (state.stride < state.width) + state.stride <<= 1; + } + if (state.height < 8 || state.height > 1024) + status = ROQ_INVALID_DIMENSION; + else + { + state.texture_height = 8; + while (state.texture_height < state.height) + state.texture_height <<= 1; + } + printf(" RoQ_INFO: dimensions = %dx%d, %dx%d; %d mbs, texture = %dx%d\n", + state.width, state.height, state.mb_width, state.mb_height, + state.mb_count, state.stride, state.texture_height); + state.frame[0] = (unsigned short*)malloc(state.texture_height * state.stride * sizeof(unsigned short)); + state.frame[1] = (unsigned short*)malloc(state.texture_height * state.stride * sizeof(unsigned short)); + state.current_frame = 0; + if (!state.frame[0] || !state.frame[1]) + { + free (state.frame[0]); + free (state.frame[1]); + status = ROQ_NO_MEMORY; + break; + } + memset(state.frame[0], 0, state.texture_height * state.stride * sizeof(unsigned short)); + memset(state.frame[1], 0, state.texture_height * state.stride * sizeof(unsigned short)); + + /* set this flag so that this code is not executed again when + * looping */ + initialized = 1; + break; + + case RoQ_QUAD_CODEBOOK: + status = roq_unpack_quad_codebook(read_buffer, chunk_size, + chunk_arg, &state); + break; + + case RoQ_QUAD_VQ: + status = roq_unpack_vq(read_buffer, chunk_size, + chunk_arg, &state); + if (render_cb) + status = render_cb(state.frame[state.current_frame], + state.width, state.height, state.stride, state.texture_height); + break; + case RoQ_SOUND_MONO: + roq_audio.channels = 1; + roq_audio.pcm_samples = chunk_size*2; + snd_left = chunk_arg; + for(i = 0; i < chunk_size; i++) + { + snd_left += roq_audio.snd_sqr_arr[read_buffer[i]]; + roq_audio.pcm_sample[i * 2] = snd_left & 0xff; + roq_audio.pcm_sample[i * 2 + 1] = (snd_left & 0xff00) >> 8; + } + if (audio_cb) + status = audio_cb( roq_audio.pcm_sample, roq_audio.pcm_samples, + roq_audio.channels ); + break; + + case RoQ_SOUND_STEREO: + roq_audio.channels = 2; + roq_audio.pcm_samples = chunk_size*2; + snd_left = (chunk_arg & 0xFF00); + snd_right = (chunk_arg & 0xFF) << 8; + for(i = 0; i < chunk_size; i += 2) + { + snd_left += roq_audio.snd_sqr_arr[read_buffer[i]]; + snd_right += roq_audio.snd_sqr_arr[read_buffer[i+1]]; + roq_audio.pcm_sample[i * 2] = snd_left & 0xff; + roq_audio.pcm_sample[i * 2 + 1] = (snd_left & 0xff00) >> 8; + roq_audio.pcm_sample[i * 2 + 2] = snd_right & 0xff; + roq_audio.pcm_sample[i * 2 + 3] = (snd_right & 0xff00) >> 8; + } + if (audio_cb) + status = audio_cb( roq_audio.pcm_sample, roq_audio.pcm_samples, + roq_audio.channels ); + break; + + default: + break; + } +} + free(state.frame[0]); + free(state.frame[1]); + fs_close(f); + + return status; +} + diff --git a/samples/dreamroq/dreamroqlib.h b/samples/dreamroq/dreamroqlib.h new file mode 100644 index 0000000..09f7176 --- /dev/null +++ b/samples/dreamroq/dreamroqlib.h @@ -0,0 +1,37 @@ +/* + * Dreamroq by Mike Melanson + * + * This is the header file to be included in the programs wishing to + * use the Dreamroq playback engine. + */ + +#ifndef NEWROQ_H +#define NEWROQ_H + +#define ROQ_SUCCESS 0 +#define ROQ_FILE_OPEN_FAILURE 1 +#define ROQ_FILE_READ_FAILURE 2 +#define ROQ_CHUNK_TOO_LARGE 3 +#define ROQ_BAD_CODEBOOK 4 +#define ROQ_INVALID_PIC_SIZE 5 +#define ROQ_NO_MEMORY 6 +#define ROQ_BAD_VQ_STREAM 7 +#define ROQ_INVALID_DIMENSION 8 +#define ROQ_RENDER_PROBLEM 9 +#define ROQ_CLIENT_PROBLEM 10 + +/* The library calls this function when it has a frame ready for display. */ +typedef int (*render_callback)(unsigned short *buf, int width, int height, + int stride, int texture_height); + +/* The library calls this function when it has pcm samples ready for output. */ +typedef int (*audio_callback)(unsigned char *buf, int samples, int channels); + +/* The library calls this function to ask whether it should quit playback. + * Return non-zero if it's time to quite. */ +typedef int (*quit_callback)(); + +int dreamroq_play(char *filename, int loop, render_callback render_cb, + audio_callback audio_cb, quit_callback quit_cb); + +#endif /* NEWROQ_H */ diff --git a/samples/dreamroq/libdcmc/arm/aica_cmd_iface.h b/samples/dreamroq/libdcmc/arm/aica_cmd_iface.h new file mode 100644 index 0000000..4b34ad0 --- /dev/null +++ b/samples/dreamroq/libdcmc/arm/aica_cmd_iface.h @@ -0,0 +1,138 @@ +/* KallistiOS ##version## + + aica_cmd_iface.h + (c)2000-2002 Dan Potter + + Definitions for the SH-4/AICA interface. This file is meant to be + included from both the ARM and SH-4 sides of the fence. +*/ + +#ifndef __ARM_AICA_CMD_IFACE_H +#define __ARM_AICA_CMD_IFACE_H + +/* $Id: aica_cmd_iface.h,v 1.3 2002/06/13 05:52:35 bardtx Exp $ */ + +#ifndef __ARCH_TYPES_H +typedef unsigned long uint8; +typedef unsigned long uint32; +#endif + +/* Command queue; one of these for passing data from the SH-4 to the + AICA, and another for the other direction. If a command is written + to the queue and it is longer than the amount of space between the + head point and the queue size, the command will wrap around to + the beginning (i.e., queue commands _can_ be split up). */ +typedef struct aica_queue { + uint32 head; /* Insertion point offset (in bytes) */ + uint32 tail; /* Removal point offset (in bytes) */ + uint32 size; /* Queue size (in bytes) */ + uint32 valid; /* 1 if the queue structs are valid */ + uint32 process_ok; /* 1 if it's ok to process the data */ + uint32 data; /* Pointer to queue data buffer */ +} aica_queue_t; + +/* Command queue struct for commanding the AICA from the SH-4 */ +typedef struct aica_cmd { + uint32 size; /* Command data size in dwords */ + uint32 cmd; /* Command ID */ + uint32 timestamp; /* When to execute the command (0 == now) */ + uint32 cmd_id; /* Command ID, for cmd/response pairs, or channel id */ + uint32 misc[4]; /* Misc Parameters / Padding */ + uint8 cmd_data[0]; /* Command data */ +} aica_cmd_t; + +/* Maximum command size -- 256 dwords */ +#define AICA_CMD_MAX_SIZE 256 + +/* This is the cmd_data for AICA_CMD_CHAN. Make this 16 dwords long + for two aica bus queues. */ +typedef struct aica_channel { + uint32 cmd; /* Command ID */ + uint32 base; /* Sample base in RAM */ + uint32 type; /* (8/16bit/ADPCM) */ + uint32 length; /* Sample length */ + uint32 loop; /* Sample looping */ + uint32 loopstart; /* Sample loop start */ + uint32 loopend; /* Sample loop end */ + uint32 freq; /* Frequency */ + uint32 vol; /* Volume 0-255 */ + uint32 pan; /* Pan 0-255 */ + uint32 pos; /* Sample playback pos */ + uint32 pad[5]; /* Padding */ +} aica_channel_t; + +/* Declare an aica_cmd_t big enough to hold an aica_channel_t + using temp name T, aica_cmd_t name CMDR, and aica_channel_t name CHANR */ +#define AICA_CMDSTR_CHANNEL(T, CMDR, CHANR) \ + uint8 T[sizeof(aica_cmd_t) + sizeof(aica_channel_t)]; \ + aica_cmd_t * CMDR = (aica_cmd_t *)T; \ + aica_channel_t * CHANR = (aica_channel_t *)(CMDR->cmd_data); +#define AICA_CMDSTR_CHANNEL_SIZE ((sizeof(aica_cmd_t) + sizeof(aica_channel_t))/4) + +/* Command values (for aica_cmd_t) */ +#define AICA_CMD_NONE 0x00000000 /* No command (dummy packet) */ +#define AICA_CMD_PING 0x00000001 /* Check for signs of life */ +#define AICA_CMD_CHAN 0x00000002 /* Perform a wavetable action */ +#define AICA_CMD_SYNC_CLOCK 0x00000003 /* Reset the millisecond clock */ + +/* Response values (for aica_cmd_t) */ +#define AICA_RESP_NONE 0x00000000 +#define AICA_RESP_PONG 0x00000001 /* Response to CMD_PING */ +#define AICA_RESP_DBGPRINT 0x00000002 /* Entire payload is a null-terminated string */ + +/* Command values (for aica_channel_t commands) */ +#define AICA_CH_CMD_MASK 0x0000000f + +#define AICA_CH_CMD_NONE 0x00000000 +#define AICA_CH_CMD_START 0x00000001 +#define AICA_CH_CMD_STOP 0x00000002 +#define AICA_CH_CMD_UPDATE 0x00000003 + +/* Start values */ +#define AICA_CH_START_MASK 0x00300000 + +#define AICA_CH_START_DELAY 0x00100000 /* Set params, but delay key-on */ +#define AICA_CH_START_SYNC 0x00200000 /* Set key-on for all selected channels */ + +/* Update values */ +#define AICA_CH_UPDATE_MASK 0x000ff000 + +#define AICA_CH_UPDATE_SET_FREQ 0x00001000 /* frequency */ +#define AICA_CH_UPDATE_SET_VOL 0x00002000 /* volume */ +#define AICA_CH_UPDATE_SET_PAN 0x00004000 /* panning */ + +/* Sample types */ +#define AICA_SM_8BIT 1 +#define AICA_SM_16BIT 0 +#define AICA_SM_ADPCM 2 + + +/* This is where our SH-4/AICA comm variables go... */ + +/* 0x000000 - 0x010000 are reserved for the program */ + +/* Location of the SH-4 to AICA queue; commands from here will be + periodically processed by the AICA and then removed from the queue. */ +#define AICA_MEM_CMD_QUEUE 0x010000 /* 32K */ + +/* Location of the AICA to SH-4 queue; commands from here will be + periodically processed by the SH-4 and then removed from the queue. */ +#define AICA_MEM_RESP_QUEUE 0x018000 /* 32K */ + +/* This is the channel base, which holds status structs for all the + channels. This is READ-ONLY from the SH-4 side. */ +#define AICA_MEM_CHANNELS 0x020000 /* 64 * 16*4 = 4K */ + +/* The clock value (in milliseconds) */ +#define AICA_MEM_CLOCK 0x021000 /* 4 bytes */ + +/* 0x021004 - 0x030000 are reserved for future expansion */ + +/* Open ram for sample data */ +#define AICA_RAM_START 0x030000 +#define AICA_RAM_END 0x200000 + +/* Quick access to the AICA channels */ +#define AICA_CHANNEL(x) (AICA_MEM_CHANNELS + (x) * sizeof(aica_channel_t)) + +#endif /* __ARM_AICA_CMD_IFACE_H */ diff --git a/samples/dreamroq/libdcmc/dc_timer.h b/samples/dreamroq/libdcmc/dc_timer.h new file mode 100644 index 0000000..6df1ee9 --- /dev/null +++ b/samples/dreamroq/libdcmc/dc_timer.h @@ -0,0 +1,19 @@ +/* +** +** This File is a part of Dreamcast Media Center +** (C) Josh "PH3NOM" Pearson 2011 +** +*/ + +/* 'Public' Function Protocols */ + +#ifndef DCTIMER_H +#define DCTIMER_H + +/* Returns hardware time in miliseconds */ +int dc_get_time(); + +/* Regulate the Video Frame Rate */ +void frame_delay( float AVI_video_rate, float AVI_delay, int frameCounter ); + +#endif diff --git a/samples/dreamroq/libdcmc/snd_stream.c b/samples/dreamroq/libdcmc/snd_stream.c new file mode 100644 index 0000000..348c8ad --- /dev/null +++ b/samples/dreamroq/libdcmc/snd_stream.c @@ -0,0 +1,532 @@ +/* +** +** Josh 'PH3NOM' Pearson 2011 +** Notes: Had to modify the requested samples by soundstream_poll +** for easy integration with libROQ +*/ + +/* KallistiOS ##version## + + snd_stream.c + Copyright (c)2000,2001,2002,2003,2004 Dan Potter + Copyright (c)2002 Florian Schulze + + SH-4 support routines for SPU streaming sound driver +*/ +/* Missing headers Ian micheal 2020*/ +/* + Name: Ian micheal + Copyright: + Author: Ian micheal + Date: 12/08/23 05:17 + Description: kos 2.0 up port threading fix and wrappers and all warnings fixed +*/ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "arm/aica_cmd_iface.h" + + +/* + +This module uses a nice circularly queued data stream in SPU RAM, which is +looped by a program running in the SPU itself. + +Basically the poll routine checks to see if a certain minimum amount of +data is available to the SPU to be played, and if not, we ask the user +routine for more sound data and load it up. That's about it. + +This version is capable of playing back N streams at once, with the limit +being available CPU time and channels. + +*/ + +typedef struct filter { + TAILQ_ENTRY(filter) lent; + snd_stream_filter_t func; + void * data; +} filter_t; + +/* Each of these represents an active streaming channel */ +typedef struct strchan { + // Which AICA channels are we using? + int ch[2]; + + // The last write position in the playing buffer + int last_write_pos; // = 0 + int curbuffer; // = 0 + + // The buffer size allocated for this stream. + int buffer_size; // = 0x10000 + + // Stream data location in AICA RAM + uint32 spu_ram_sch[2]; + + // "Get data" callback; we'll call this any time we want to get + // another buffer of output data. + snd_stream_callback_t get_data; + + // Our list of filter callback functions for this stream + TAILQ_HEAD(filterlist, filter) filters; + + // Stereo/mono flag + int stereo; + + // Playback frequency + int frequency; + + /* Stream queueing is where we get everything ready to go but don't + actually start it playing until the signal (for music sync, etc) */ + int queueing; + + /* Have we been initialized yet? (and reserved a buffer, etc) */ + volatile int initted; +} strchan_t; + +// Our stream structs +static strchan_t streams[SND_STREAM_MAX] = { { { 0 } } }; + +// Separation buffers (for stereo) +int16 * sep_buffer[2] = { NULL, NULL }; + +/* the address of the sound ram from the SH4 side */ +#define SPU_RAM_BASE 0xa0800000 + +// Check an incoming handle +#define CHECK_HND(x) do { \ + assert( (x) >= 0 && (x) < SND_STREAM_MAX ); \ + assert( streams[(x)].initted ); \ +} while(0) + +/* Set "get data" callback */ +void snd_stream_set_callback(snd_stream_hnd_t hnd, snd_stream_callback_t cb) { + CHECK_HND(hnd); + streams[hnd].get_data = cb; +} + +void snd_stream_filter_add(snd_stream_hnd_t hnd, snd_stream_filter_t filtfunc, void * obj) { + filter_t * f; + + CHECK_HND(hnd); + + f = malloc(sizeof(filter_t)); + f->func = filtfunc; + f->data = obj; + TAILQ_INSERT_TAIL(&streams[hnd].filters, f, lent); +} + +void snd_stream_filter_remove(snd_stream_hnd_t hnd, snd_stream_filter_t filtfunc, void * obj) { + filter_t * f; + + CHECK_HND(hnd); + + TAILQ_FOREACH(f, &streams[hnd].filters, lent) { + if (f->func == filtfunc && f->data == obj) { + TAILQ_REMOVE(&streams[hnd].filters, f, lent); + free(f); + return; + } + } +} + +static void process_filters(snd_stream_hnd_t hnd, void **buffer, int *samplecnt) { + filter_t * f; + + TAILQ_FOREACH(f, &streams[hnd].filters, lent) { + f->func(hnd, f->data, streams[hnd].frequency, streams[hnd].stereo ? 2 : 1, buffer, samplecnt); + } +} + + +/* Performs stereo seperation for the two channels; this routine + has been optimized for the SH-4. */ +static void sep_data(void *buffer, int len, int stereo) { + register int16 *bufsrc, *bufdst; + register int x, y, cnt; + + if (stereo) { + bufsrc = (int16*)buffer; + bufdst = sep_buffer[0]; + x = 0; y = 0; cnt = len / 2; + do { + *bufdst = *bufsrc; + bufdst++; bufsrc+=2; cnt--; + } while (cnt > 0); + + bufsrc = (int16*)buffer; bufsrc++; + bufdst = sep_buffer[1]; + x = 1; y = 0; cnt = len / 2; + do { + *bufdst = *bufsrc; + bufdst++; bufsrc+=2; cnt--; + x+=2; y++; + } while (cnt > 0); + } else { + memcpy(sep_buffer[0], buffer, len); + memcpy(sep_buffer[1], buffer, len); + } +} + +/* Prefill buffers -- do this before calling start() */ +void snd_stream_prefill(snd_stream_hnd_t hnd) { + void *buf; + int got; + + CHECK_HND(hnd); + + if (!streams[hnd].get_data) return; + + /* Load first buffer */ + /* XXX Note: This will not work if the full data size is less than + buffer_size or buffer_size/2. */ + if (streams[hnd].stereo) + buf = streams[hnd].get_data(hnd, streams[hnd].buffer_size, &got); + else + buf = streams[hnd].get_data(hnd, streams[hnd].buffer_size/2, &got); + process_filters(hnd, &buf, &got); + sep_data(buf, (streams[hnd].buffer_size/2), streams[hnd].stereo); + spu_memload( + streams[hnd].spu_ram_sch[0] + (streams[hnd].buffer_size/2)*0, + (uint8*)sep_buffer[0], streams[hnd].buffer_size/2); + spu_memload( + streams[hnd].spu_ram_sch[1] + (streams[hnd].buffer_size/2)*0, + (uint8*)sep_buffer[1], streams[hnd].buffer_size/2); + + /* Load second buffer */ + if (streams[hnd].stereo) + buf = streams[hnd].get_data(hnd, streams[hnd].buffer_size, &got); + else + buf = streams[hnd].get_data(hnd, streams[hnd].buffer_size/2, &got); + process_filters(hnd, &buf, &got); + sep_data(buf, (streams[hnd].buffer_size/2), streams[hnd].stereo); + spu_memload( + streams[hnd].spu_ram_sch[0] + (streams[hnd].buffer_size/2)*1, + (uint8*)sep_buffer[0], streams[hnd].buffer_size/2); + spu_memload( + streams[hnd].spu_ram_sch[1] + (streams[hnd].buffer_size/2)*1, + (uint8*)sep_buffer[1], streams[hnd].buffer_size/2); + + /* Start with playing on buffer 0 */ + streams[hnd].last_write_pos = 0; + streams[hnd].curbuffer = 0; +} + +/* Initialize stream system */ +int snd_stream_init() { + /* Create stereo seperation buffers */ + if (!sep_buffer[0]) { + sep_buffer[0] = memalign(32, (SND_STREAM_BUFFER_MAX/2)); + sep_buffer[1] = memalign(32, (SND_STREAM_BUFFER_MAX/2)); + } + + /* Finish loading the stream driver */ + if (snd_init() < 0) { + dbglog(DBG_ERROR, "snd_stream_init(): snd_init() failed, giving up\n"); + return -1; + } + + return 0; +} + +snd_stream_hnd_t snd_stream_alloc(snd_stream_callback_t cb, int bufsize) { + int i, old; + snd_stream_hnd_t hnd; + + // Get an unused handle + hnd = -1; + old = irq_disable(); + for (i=0; icmd = AICA_CMD_CHAN; + cmd->timestamp = 0; + cmd->size = AICA_CMDSTR_CHANNEL_SIZE; + cmd->cmd_id = streams[hnd].ch[0]; + chan->cmd = AICA_CH_CMD_START | AICA_CH_START_DELAY; + chan->base = streams[hnd].spu_ram_sch[0]; + chan->type = AICA_SM_16BIT; + chan->length = (streams[hnd].buffer_size/2); + chan->loop = 1; + chan->loopstart = 0; + chan->loopend = (streams[hnd].buffer_size/2); + chan->freq = freq; + chan->vol = 255; + chan->pan = 0; + snd_sh4_to_aica(tmp, cmd->size); + + /* Channel 1 */ + cmd->cmd_id = streams[hnd].ch[1]; + chan->base = streams[hnd].spu_ram_sch[1]; + chan->pan = 255; + snd_sh4_to_aica(tmp, cmd->size); + + /* Start both channels simultaneously */ + cmd->cmd_id = (1 << streams[hnd].ch[0]) | + (1 << streams[hnd].ch[1]); + chan->cmd = AICA_CH_CMD_START | AICA_CH_START_SYNC; + snd_sh4_to_aica(tmp, cmd->size); + + /* Process the changes */ + if (!streams[hnd].queueing) + snd_sh4_to_aica_start(); +} + +/* Actually make it go (in queued mode) */ +void snd_stream_queue_go(snd_stream_hnd_t hnd) { + CHECK_HND(hnd); + snd_sh4_to_aica_start(); +} + +/* Stop streaming */ +void snd_stream_stop(snd_stream_hnd_t hnd) { + AICA_CMDSTR_CHANNEL(tmp, cmd, chan); + + CHECK_HND(hnd); + + if (!streams[hnd].get_data) return; + + /* Stop stream */ + /* Channel 0 */ + cmd->cmd = AICA_CMD_CHAN; + cmd->timestamp = 0; + cmd->size = AICA_CMDSTR_CHANNEL_SIZE; + cmd->cmd_id = streams[hnd].ch[0]; + chan->cmd = AICA_CH_CMD_STOP; + snd_sh4_to_aica(tmp, cmd->size); + + /* Channel 1 */ + cmd->cmd_id = streams[hnd].ch[1]; + snd_sh4_to_aica(tmp, AICA_CMDSTR_CHANNEL_SIZE); +} + +/* The DMA will chain to this to start the second DMA. */ +/* static uint32 dmadest, dmacnt; +static void dma_chain(ptr_t data) { + spu_dma_transfer(sep_buffer[1], dmadest, dmacnt, 0, NULL, 0); +} */ + +/* Poll streamer to load more data if necessary */ +int snd_stream_poll(snd_stream_hnd_t hnd) { + uint32 ch0pos, ch1pos; + // Ian micheal fixed all threading problems + /* int realbuffer; */ // Remove this line + int current_play_pos; + int needed_samples; + int got_samples; + void *data; + + CHECK_HND(hnd); + + if (!streams[hnd].get_data) return -1; + // Ian micheal fixed all threading problems + /* Get "real" buffer */ + ch0pos = g2_read_32(SPU_RAM_BASE + AICA_CHANNEL(streams[hnd].ch[0]) + offsetof(aica_channel_t, pos)); + ch1pos = g2_read_32(SPU_RAM_BASE + AICA_CHANNEL(streams[hnd].ch[1]) + offsetof(aica_channel_t, pos)); + + if (ch0pos >= (streams[hnd].buffer_size/2)) { + dbglog(DBG_ERROR, "snd_stream_poll: chan0(%d).pos = %ld (%08lx)\n", streams[hnd].ch[0], ch0pos, ch0pos); + return -1; + } + + current_play_pos = (ch0pos < ch1pos)?(ch0pos):(ch1pos); + + /* count just till the end of the buffer, so we don't have to + handle buffer wraps */ + if (streams[hnd].last_write_pos <= current_play_pos) + needed_samples = current_play_pos - streams[hnd].last_write_pos; + else + needed_samples = (streams[hnd].buffer_size/2) - streams[hnd].last_write_pos; + /* round it a little bit */ + needed_samples &= ~0x7ff; + /* printf("last_write_pos %6i, current_play_pos %6i, needed_samples %6i\n",last_write_pos,current_play_pos,needed_samples); */ + + //if (needed_samples > 0) { + if (needed_samples ==4096) { + if (streams[hnd].stereo) { + data = streams[hnd].get_data(hnd, needed_samples * 4, &got_samples); + process_filters(hnd, &data, &got_samples); + if (got_samples < needed_samples * 4) { + needed_samples = got_samples / 4; + if (needed_samples & 3) + needed_samples = (needed_samples + 4) & ~3; + } + } else { + data = streams[hnd].get_data(hnd, needed_samples * 2, &got_samples); + process_filters(hnd, &data, &got_samples); + if (got_samples < needed_samples * 2) { + needed_samples = got_samples / 2; + if (needed_samples & 1) + needed_samples = (needed_samples + 2) & ~1; + } + } + if (data == NULL) { + /* Fill the "other" buffer with zeros */ + spu_memset(streams[hnd].spu_ram_sch[0] + (streams[hnd].last_write_pos * 2), 0, needed_samples * 2); + spu_memset(streams[hnd].spu_ram_sch[1] + (streams[hnd].last_write_pos * 2), 0, needed_samples * 2); + return -3; + } + + sep_data(data, needed_samples * 2, streams[hnd].stereo); + spu_memload(streams[hnd].spu_ram_sch[0] + (streams[hnd].last_write_pos * 2), (uint8*)sep_buffer[0], needed_samples * 2); + spu_memload(streams[hnd].spu_ram_sch[1] + (streams[hnd].last_write_pos * 2), (uint8*)sep_buffer[1], needed_samples * 2); + + // Second DMA will get started by the chain handler + /* dcache_flush_range(sep_buffer[0], needed_samples*2); + dcache_flush_range(sep_buffer[1], needed_samples*2); + dmadest = spu_ram_sch2 + (last_write_pos * 2); + dmacnt = needed_samples * 2; + spu_dma_transfer(sep_buffer[0], spu_ram_sch1 + (last_write_pos * 2), needed_samples * 2, + 0, dma_chain, 0); */ + + streams[hnd].last_write_pos += needed_samples; + if (streams[hnd].last_write_pos >= (streams[hnd].buffer_size/2)) + streams[hnd].last_write_pos -= (streams[hnd].buffer_size/2); + } + return 0; +} + +/* Set the volume on the streaming channels */ +void snd_stream_volume(snd_stream_hnd_t hnd, int vol) { + AICA_CMDSTR_CHANNEL(tmp, cmd, chan); + + CHECK_HND(hnd); + + cmd->cmd = AICA_CMD_CHAN; + cmd->timestamp = 0; + cmd->size = AICA_CMDSTR_CHANNEL_SIZE; + cmd->cmd_id = streams[hnd].ch[0]; + chan->cmd = AICA_CH_CMD_UPDATE | AICA_CH_UPDATE_SET_VOL; + chan->vol = vol; + snd_sh4_to_aica(tmp, cmd->size); + + cmd->cmd_id = streams[hnd].ch[1]; + snd_sh4_to_aica(tmp, cmd->size); +} + + diff --git a/samples/dreamroq/libdcmc/snddrv.c b/samples/dreamroq/libdcmc/snddrv.c new file mode 100644 index 0000000..62ec293 --- /dev/null +++ b/samples/dreamroq/libdcmc/snddrv.c @@ -0,0 +1,142 @@ +/* +** +** (C) Josh 'PH3NOM' Pearson 2011 +** +*/ +/* +** To anyone looking at this code: +** +** This driver runs in its own thread on the sh4. +** +** When the AICA driver requests more samples, +** it will signal sndbuf_status=SNDDRV_STATUS_NEEDBUF +** and assign the number of requested samples to snddrv.pcm_needed. +** +** The decoders need to check sndbuf_status, +** when more samples are requested by the driver ** the decoders will loop +** decoding into pcm_buffer untill pcm_bytes==snddrv.pcm_needed +** at that point the decoder signals sndbuf_status=SNDDRV_STATUS_HAVEBUF +** +*/ + +#include +#include +#include +#include +#include +#include "snddrv.h" + +snd_stream_hnd_t shnd; +kthread_t * snddrv_thd; +static int snddrv_vol = 255; +snd_drv snddrv; + +/* Increase the Sound Driver volume */ +int snddrv_volume_up() { + + if( snddrv_vol <= 245 ) { + snddrv_vol += 10; + snd_stream_volume(shnd, snddrv_vol); + } + return snddrv_vol; +} + +/* Decrease the Sound Driver volume */ +int snddrv_volume_down() { + + if( snddrv_vol >= 10 ) { + snddrv_vol -= 10; + snd_stream_volume(shnd, snddrv_vol); + } + return snddrv_vol; +} + +/* Exit the Sound Driver */ +int snddrv_exit() { + + if( snddrv.drv_status != SNDDRV_STATUS_NULL ) { + snddrv.drv_status = SNDDRV_STATUS_DONE; + snddrv.buf_status = SNDDRV_STATUS_BUFEND; + + while( snddrv.drv_status != SNDDRV_STATUS_NULL ) + thd_pass(); + + + printf("SNDDRV: Exited\n"); + } + + memset( snddrv.pcm_buffer, 0, 65536+16384); + snddrv.pcm_bytes = 0; + snddrv.pcm_needed = 0; + + SNDDRV_FREE_STRUCT(); + + return snddrv.drv_status; +} + +/* Signal how many samples the AICA needs, then wait for the deocder to produce them */ +static void *snddrv_callback(snd_stream_hnd_t hnd, int len, int * actual) { + + /* Signal the Decoder thread how many more samples are needed */ + snddrv.pcm_needed = len; + snddrv.buf_status = SNDDRV_STATUS_NEEDBUF; + + /* Wait for the samples to be ready */ + while( snddrv.buf_status != SNDDRV_STATUS_HAVEBUF && snddrv.buf_status != SNDDRV_STATUS_BUFEND ) + thd_pass(); + + snddrv.pcm_ptr = snddrv.pcm_buffer; + snddrv.pcm_bytes = 0; + *actual = len; + + return snddrv.pcm_ptr; + +} + +static int snddrv_thread() { + + printf("SNDDRV: Rate - %i, Channels - %i\n", snddrv.rate, snddrv.channels); + + shnd = snd_stream_alloc(snddrv_callback, SND_STREAM_BUFFER_MAX/4); + + snd_stream_start(shnd, snddrv.rate, snddrv.channels-1); + snddrv.drv_status = SNDDRV_STATUS_STREAMING; + + while( snddrv.drv_status != SNDDRV_STATUS_DONE && snddrv.drv_status != SNDDRV_STATUS_ERROR ) { + + snd_stream_poll(shnd); + thd_sleep(20); + + } + snddrv.drv_status = SNDDRV_STATUS_NULL; + + snd_stream_destroy(shnd); + snd_stream_shutdown(); + + printf("SNDDRV: Finished\n"); + + return snddrv.drv_status; +} + +/* Start the AICA Sound Stream Thread */ +int snddrv_start( int rate, int chans ) { + + snddrv.rate = rate; + snddrv.channels = chans; + if( snddrv.channels > 2) { + printf("SNDDRV: ERROR - Exceeds maximum channels\n"); + return -1; + } + + printf("SNDDRV: Creating Driver Thread\n"); + + snddrv.drv_status = SNDDRV_STATUS_INITIALIZING; + + snd_stream_init(); + /*libdcmc/snddrv.c:136: warning: passing arg 1 of `thd_create' from incompatible pointer type */ //Ian micheal 2020 warning + snddrv_thd = thd_create(0, snddrv_thread, NULL ); + + return snddrv.drv_status; + +} + diff --git a/samples/dreamroq/libdcmc/snddrv.h b/samples/dreamroq/libdcmc/snddrv.h new file mode 100644 index 0000000..de07a65 --- /dev/null +++ b/samples/dreamroq/libdcmc/snddrv.h @@ -0,0 +1,110 @@ +/* +** +** This File is a part of Dreamcast Media Center +** (C) Josh "PH3NOM" Pearson 2011 +** +*/ + +#ifndef SNDDRV_H +#define SNDDRV_H + +/* Keep track of things from the Driver side */ +#define SNDDRV_STATUS_NULL 0x00 +#define SNDDRV_STATUS_INITIALIZING 0x01 +#define SNDDRV_STATUS_READY 0x02 +#define SNDDRV_STATUS_STREAMING 0x03 +#define SNDDRV_STATUS_DONE 0x04 +#define SNDDRV_STATUS_ERROR 0x05 + +/* Keep track of things from the Decoder side */ +#define SNDDEC_STATUS_NULL 0x00 +#define SNDDEC_STATUS_INITIALIZING 0x01 +#define SNDDEC_STATUS_READY 0x02 +#define SNDDEC_STATUS_STREAMING 0x03 +#define SNDDEC_STATUS_PAUSING 0x04 +#define SNDDEC_STATUS_PAUSED 0x05 +#define SNDDEC_STATUS_RESUMING 0x06 +#define SNDDEC_STATUS_DONE 0x07 +#define SNDDEC_STATUS_ERROR 0x08 + +/* Keep track of the buffer status from both sides*/ +#define SNDDRV_STATUS_NEEDBUF 0x00 +#define SNDDRV_STATUS_HAVEBUF 0x01 +#define SNDDRV_STATUS_BUFEND 0x02 + +/* This seems to be a good number for file seeking on compressed audio */ +#define SEEK_LEN 16384*48 + +/* SNDDRV (C) AICA Audio Driver */ +typedef struct _snd_drv { + int rate; + int channels; + int pcm_bytes; + int pcm_needed; + volatile int drv_status; + volatile int dec_status; + volatile int buf_status; + unsigned int pcm_buffer[65536+16384]; + unsigned int *pcm_ptr; +} snd_drv; + +extern snd_drv snddrv; + +#define SNDDRV_FREE_STRUCT() { \ + snddrv.rate = snddrv.channels = snddrv.drv_status = \ + snddrv.dec_status = snddrv.buf_status = 0; } + +typedef struct snddrv_song_info { + char *artist[128]; + char * title[128]; + char * track[128]; + char * album[128]; + char * genre[128]; + char *fname; + volatile int fpos; + volatile float spos; + int fsize; + float slen; +} snd_sinfo; + +#define SNDDRV_FREE_SINFO() { \ + sq_clr( snd_sinfo.artist, 128 ); \ + sq_clr( snd_sinfo.title, 128 ); \ + sq_clr( snd_sinfo.track, 128 ); \ + sq_clr( snd_sinfo.album, 128 ); \ + sq_clr( snd_sinfo.genre, 128 ); \ + snd_sinfo.fpos = snd_sinfo.spos = snd_sinfo.fsize = snd_sinfo.slen = 0; } + +#define min(a,b) ( (a) < (b) ? (a) : (b) ) + +#define MAX_CHANNELS 6 /* make this higher to support files with + more channels for LibFAAD */ + +/* MicroSoft channel definitions */ +#define SPEAKER_FRONT_LEFT 0x1 +#define SPEAKER_FRONT_RIGHT 0x2 +#define SPEAKER_FRONT_CENTER 0x4 +#define SPEAKER_LOW_FREQUENCY 0x8 +#define SPEAKER_BACK_LEFT 0x10 +#define SPEAKER_BACK_RIGHT 0x20 +#define SPEAKER_FRONT_LEFT_OF_CENTER 0x40 +#define SPEAKER_FRONT_RIGHT_OF_CENTER 0x80 +#define SPEAKER_BACK_CENTER 0x100 +#define SPEAKER_SIDE_LEFT 0x200 +#define SPEAKER_SIDE_RIGHT 0x400 +#define SPEAKER_TOP_CENTER 0x800 +#define SPEAKER_TOP_FRONT_LEFT 0x1000 +#define SPEAKER_TOP_FRONT_CENTER 0x2000 +#define SPEAKER_TOP_FRONT_RIGHT 0x4000 +#define SPEAKER_TOP_BACK_LEFT 0x8000 +#define SPEAKER_TOP_BACK_CENTER 0x10000 +#define SPEAKER_TOP_BACK_RIGHT 0x20000 +#define SPEAKER_RESERVED 0x80000000 + +/* SNDDRV Function Protocols */ +int snddrv_start( int rate, int chans ); +int snddrv_exit(); +int snddrv_volume_up(); +int snddrv_volume_down(); + +#endif diff --git a/samples/dreamroq/libdcmc/timer.c b/samples/dreamroq/libdcmc/timer.c new file mode 100644 index 0000000..fd1bb01 --- /dev/null +++ b/samples/dreamroq/libdcmc/timer.c @@ -0,0 +1,36 @@ +/* +** +** This file is a part of Dreamcast Media Center +** (C) Josh PH3NOM Pearson 2011 +** +*/ + +#include +#include +#include +#include +#include "dc_timer.h" + +/* Get current hardware timing using arch/timer.h */ +int dc_get_time() +{ + uint32 s, ms; + uint64 msec; + + timer_ms_gettime(&s, &ms); + msec = (((uint64)s) * ((uint64)1000)) + ((uint64)ms); + + return (int)msec; +} + +/* Regulate the Video Frame Rate */ +void frame_delay( float AVI_video_rate, float AVI_delay, int frameCounter ) +{ + float AVI_real_time = frameCounter / AVI_video_rate; + float CPU_real_time= ( ( (float)dc_get_time()- AVI_delay ) / 1000.0f ); + //printf("AVI_real_time: %f, CPU_real_time %f\n", AVI_real_time, CPU_real_time ); + while ( CPU_real_time < AVI_real_time ) { + CPU_real_time= ( ( (float)dc_get_time()- AVI_delay ) / 1000.0f ); + thd_pass(); + } +} diff --git a/samples/dreamroq/main.c b/samples/dreamroq/main.c new file mode 100644 index 0000000..7b278b5 --- /dev/null +++ b/samples/dreamroq/main.c @@ -0,0 +1,272 @@ +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "libdcmc/snddrv.h" +#include "dreamroqlib.h" +#include "libdcmc/dc_timer.h" + +#ifdef __DREAMCAST__ +extern uint8 romdisk[]; +KOS_INIT_ROMDISK(romdisk); +#define VIDEO_FILENAME "/rd/video.roq" +#else +#define VIDEO_FILENAME "../samples/dreamroq/romdisk/video.roq" +#endif + +maple_device_t *cont; +cont_state_t *state; + +/* Audio Global variables */ +#define PCM_BUF_SIZE 1024*1024 +static unsigned char *pcm_buf = NULL; +static int pcm_size = 0; +static int audio_init = 0; +static mutex_t * pcm_mut; + +/* Video Global variables */ +static int current_frame = 0; +static int graphics_initialized = 0; +static float video_delay = 0.0f; +static int frame = 0; +static const float VIDEO_RATE = 30.0f; /* Video FPS */ + +static GLint frameTexture[2]; +//static GLVertexKOS vertices[4]; + +// maybe should aling this better? +typedef struct { + float x, y, z; +} Vec3; + +typedef struct { + float u, v; +} UV; + +static Vec3 vertices[4]; +static UV uv[4]; + +static void snd_thd() +{ + do + { + /* Wait for AICA Driver to request some samples */ + while( snddrv.buf_status != SNDDRV_STATUS_NEEDBUF ) + thd_pass(); + + /* Wait for RoQ Decoder to produce enough samples */ + while( pcm_size < snddrv.pcm_needed ) + { + if( snddrv.dec_status == SNDDEC_STATUS_DONE ) + goto done; + thd_pass(); + } + + /* Copy the Requested PCM Samples to the AICA Driver */ + mutex_lock( pcm_mut ); + memcpy( snddrv.pcm_buffer, pcm_buf, snddrv.pcm_needed ); + + /* Shift the Remaining PCM Samples Back */ + pcm_size -= snddrv.pcm_needed; + memmove( pcm_buf, pcm_buf+snddrv.pcm_needed, pcm_size ); + mutex_unlock( pcm_mut ); + + /* Let the AICA Driver know the PCM samples are ready */ + snddrv.buf_status = SNDDRV_STATUS_HAVEBUF; + + } while( snddrv.dec_status == SNDDEC_STATUS_STREAMING ); + done: + snddrv.dec_status = SNDDEC_STATUS_NULL; +} + +static int renderGLdc_cb(unsigned short *buf, int width, int height, int stride, int texture_height) +{ + + /* send the video frame as a texture over to video RAM */ + //pvr_txr_load(buf, textures[current_frame], stride * texture_height * 2); + glBindTexture(GL_TEXTURE_2D, frameTexture[current_frame]); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 512, 512, 0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, buf); + + /* Delay the frame to match Frame Rate */ + frame_delay(VIDEO_RATE, video_delay, ++frame); + + glEnableClientState(GL_VERTEX_ARRAY); + glEnableClientState(GL_TEXTURE_COORD_ARRAY); + + glVertexPointer (3, GL_FLOAT, 0, vertices); + glTexCoordPointer (2, GL_FLOAT, 0, uv); + + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); + + glDisableClientState(GL_COLOR_ARRAY); + glDisableClientState(GL_VERTEX_ARRAY); + + + glKosSwapBuffers(); + + /* + if (current_frame) + current_frame = 0; + else + current_frame = 1; + */ + + return ROQ_SUCCESS; +} + +static int audio_cb( unsigned char *buf, int size, int channels) +{ + + + /* Copy the decoded PCM samples to our local PCM buffer */ + mutex_lock( pcm_mut ); + memcpy( pcm_buf+pcm_size, buf, size); + pcm_size += size; + mutex_unlock( pcm_mut ); + + return ROQ_SUCCESS; +} + +static int quit_cb() +{ + /* + state = (cont_state_t *)maple_dev_status(cont); + + if(state->buttons & CONT_START) + return 1; + else + return 0; + */ +} + +int main(int argc, char **argv) +{ + printf("--- DreamRoQ Player for Dreamcast\n"); + glKosInit(); + //cont = maple_enum_type(0, MAPLE_FUNC_CONTROLLER); + //state = (cont_state_t *)maple_dev_status(cont); + + if(!graphics_initialized) { + glClearColor(1.0f, 0.0f, 0.0f, 1.0f); // This Will Clear The Background Color To Black + glClearDepth(1.0); // Enables Clearing Of The Depth Buffer + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + //glDisable(GL_DEPTH_TEST); + //glEnable(GL_NORMALIZE); + + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); // Reset The Projection Matrix + glOrtho(0.0, 640.0, 0.0, 480.0, -1.0, 1.0); + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + + glDisable(GL_LIGHTING); + glEnable(GL_TEXTURE_2D); + glGenTextures(2, frameTexture); + glBindTexture(GL_TEXTURE_2D, frameTexture[0]); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 512, 512, 0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, NULL); + + glBindTexture(GL_TEXTURE_2D, frameTexture[1]); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 512, 512, 0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, NULL); + + video_delay = (float)dc_get_time(); + + float w = 512; + float h = 512; + int v = 0; + + vertices[v].x = 0; + vertices[v].y = 0; + vertices[v].z = 0; + uv[v].u = 0.0f; + uv[v].v = 1.0f; + v++; + + vertices[v].x = 0; + vertices[v].y = 480; + vertices[v].z = 0; + uv[v].u = 0.0f; + uv[v].v = 0.0f; + v++; + + vertices[v].x = 640; + vertices[v].y = 0; + vertices[v].z = 0; + uv[v].u = 1.0f; + uv[v].v = 1.0f; + v++; + + vertices[v].x = 640; + vertices[v].y = 480; + vertices[v].z = 0; + uv[v].u = 1.0f; + uv[v].v = 0.0f; + v++; + + GLfloat drawColor[4] = {1.0f, 1.0f, 1.0f, 1.0f}; + GLfloat emissionColor[4] = {0.0, 0.0, 0.0, 1.0f}; + glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, drawColor); + glMaterialfv(GL_FRONT, GL_SPECULAR, drawColor); + glMaterialfv(GL_FRONT, GL_EMISSION, emissionColor); + + graphics_initialized = 1; + } + + /* + if(!audio_init) { + // allocate PCM buffer + pcm_buf = malloc(PCM_BUF_SIZE); + if( pcm_buf == NULL ) + return ROQ_NO_MEMORY; + + // Start AICA Driver + // Audio rate, channel number + snddrv_start( 22050, 2); + snddrv.dec_status = SNDDEC_STATUS_STREAMING; + + // Create a thread to stream the samples to the AICA + thd_create(0, snd_thd, NULL); + + // Create a mutex to handle the double-threaded buffer + //pcm_mut = mutex_create(); + + audio_init = 1; + } + */ + + printf("--- Playing video using DreamRoQ\n"); + int status = dreamroq_play(VIDEO_FILENAME, 0, renderGLdc_cb, 0, 0); + printf("dreamroq_play() status = %d\n", status); + + /* + if(audio_init) { + snddrv.dec_status = SNDDEC_STATUS_DONE; // Singal audio thread to stop + while( snddrv.dec_status != SNDDEC_STATUS_NULL ) + thd_pass(); + free( pcm_buf ); + pcm_buf = NULL; + pcm_size = 0; + mutex_destroy(pcm_mut); // Destroy the PCM mutex + snddrv_exit(); // Exit the AICA Driver + } + + if(graphics_initialized) { + glDeleteTextures(2, frameTexture); + glEnable(GL_LIGHTING); + } + */ + return status; +} + diff --git a/samples/dreamroq/romdisk/video.roq b/samples/dreamroq/romdisk/video.roq new file mode 100644 index 0000000..d87f28f Binary files /dev/null and b/samples/dreamroq/romdisk/video.roq differ