cog/Frameworks/GME/vgmplay/chips/qsound.c

490 lines
12 KiB
C

/***************************************************************************
Capcom System QSound(tm)
========================
Driver by Paul Leaman (paul@vortexcomputing.demon.co.uk)
and Miguel Angel Horna (mahorna@teleline.es)
A 16 channel stereo sample player.
QSpace position is simulated by panning the sound in the stereo space.
Register
0 xxbb xx = unknown bb = start high address
1 ssss ssss = sample start address
2 pitch
3 unknown (always 0x8000)
4 loop offset from end address
5 end
6 master channel volume
7 not used
8 Balance (left=0x0110 centre=0x0120 right=0x0130)
9 unknown (most fixed samples use 0 for this register)
Many thanks to CAB (the author of Amuse), without whom this probably would
never have been finished.
If anybody has some information about this hardware, please send it to me
to mahorna@teleline.es or 432937@cepsz.unizar.es.
http://teleline.terra.es/personal/mahorna
***************************************************************************/
//#include "emu.h"
#include "mamedef.h"
#ifdef _DEBUG
#include <stdio.h>
#endif
#include <string.h>
#include <stdlib.h>
#include <math.h>
#include "qsound.h"
#ifndef NULL
#define NULL ((void *)0)
#endif
/*
Debug defines
*/
#define LOG_WAVE 0
#define VERBOSE 0
#define LOG(x) do { if (VERBOSE) logerror x; } while (0)
/* 8 bit source ROM samples */
typedef INT8 QSOUND_SRC_SAMPLE;
#define QSOUND_CLOCKDIV 166 /* Clock divider */
#define QSOUND_CHANNELS 16
typedef stream_sample_t QSOUND_SAMPLE;
struct QSOUND_CHANNEL
{
UINT32 bank; // bank
UINT32 address; // start/cur address
UINT16 loop; // loop address
UINT16 end; // end address
UINT32 freq; // frequency
UINT16 vol; // master volume
// work variables
UINT8 enabled; // key on / key off
int lvol; // left volume
int rvol; // right volume
UINT32 step_ptr; // current offset counter
UINT8 Muted;
};
typedef struct _qsound_state qsound_state;
struct _qsound_state
{
/* Private variables */
//sound_stream * stream; /* Audio stream */
struct QSOUND_CHANNEL channel[QSOUND_CHANNELS];
UINT16 data; /* register latch data */
QSOUND_SRC_SAMPLE *sample_rom; /* Q sound sample ROM */
UINT32 sample_rom_length;
int pan_table[33]; /* Pan volume table */
//FILE *fpRawDataL;
//FILE *fpRawDataR;
};
/*INLINE qsound_state *get_safe_token(device_t *device)
{
assert(device != NULL);
assert(device->type() == QSOUND);
return (qsound_state *)downcast<legacy_device_base *>(device)->token();
}*/
/* Function prototypes */
//static STREAM_UPDATE( qsound_update );
static void qsound_set_command(qsound_state *chip, UINT8 address, UINT16 data);
//static DEVICE_START( qsound )
int device_start_qsound(void **_info, int clock)
{
//qsound_state *chip = get_safe_token(device);
qsound_state *chip;
int i;
chip = (qsound_state *) calloc(1, sizeof(qsound_state));
*_info = (void *) chip;
//chip->sample_rom = (QSOUND_SRC_SAMPLE *)*device->region();
//chip->sample_rom_length = device->region()->bytes();
chip->sample_rom = NULL;
chip->sample_rom_length = 0x00;
/* Create pan table */
for (i=0; i<33; i++)
chip->pan_table[i]=(int)((256/sqrt(32.0)) * sqrt((double)i));
// init sound regs
memset(chip->channel, 0, sizeof(chip->channel));
// LOG(("Pan table\n"));
// for (i=0; i<33; i++)
// LOG(("%02x ", chip->pan_table[i]));
/* Allocate stream */
/*chip->stream = device->machine().sound().stream_alloc(
*device, 0, 2,
device->clock() / QSOUND_CLOCKDIV,
chip,
qsound_update );*/
/*if (LOG_WAVE)
{
chip->fpRawDataR=fopen("qsoundr.raw", "w+b");
chip->fpRawDataL=fopen("qsoundl.raw", "w+b");
}*/
/* state save */
/*for (i=0; i<QSOUND_CHANNELS; i++)
{
device->save_item(NAME(chip->channel[i].bank), i);
device->save_item(NAME(chip->channel[i].address), i);
device->save_item(NAME(chip->channel[i].pitch), i);
device->save_item(NAME(chip->channel[i].loop), i);
device->save_item(NAME(chip->channel[i].end), i);
device->save_item(NAME(chip->channel[i].vol), i);
device->save_item(NAME(chip->channel[i].pan), i);
device->save_item(NAME(chip->channel[i].key), i);
device->save_item(NAME(chip->channel[i].lvol), i);
device->save_item(NAME(chip->channel[i].rvol), i);
device->save_item(NAME(chip->channel[i].lastdt), i);
device->save_item(NAME(chip->channel[i].offset), i);
}*/
for (i = 0; i < QSOUND_CHANNELS; i ++)
chip->channel[i].Muted = 0x00;
return clock / QSOUND_CLOCKDIV;
}
//static DEVICE_STOP( qsound )
void device_stop_qsound(void *_info)
{
//qsound_state *chip = get_safe_token(device);
qsound_state *chip = (qsound_state *)_info;
/*if (chip->fpRawDataR)
{
fclose(chip->fpRawDataR);
}
chip->fpRawDataR = NULL;
if (chip->fpRawDataL)
{
fclose(chip->fpRawDataL);
}
chip->fpRawDataL = NULL;*/
free(chip->sample_rom); chip->sample_rom = NULL;
free(chip);
}
void device_reset_qsound(void *_info)
{
qsound_state *chip = (qsound_state *)_info;
int adr;
// init sound regs
memset(chip->channel, 0, sizeof(chip->channel));
for (adr = 0x7f; adr >= 0; adr--)
qsound_set_command(chip, adr, 0);
for (adr = 0x80; adr < 0x90; adr++)
qsound_set_command(chip, adr, 0x120);
return;
}
//WRITE8_DEVICE_HANDLER( qsound_w )
void qsound_w(void *_info, offs_t offset, UINT8 data)
{
//qsound_state *chip = get_safe_token(device);
qsound_state *chip = (qsound_state *)_info;
switch (offset)
{
case 0:
chip->data=(chip->data&0xff)|(data<<8);
break;
case 1:
chip->data=(chip->data&0xff00)|data;
break;
case 2:
qsound_set_command(chip, data, chip->data);
break;
default:
//logerror("%s: unexpected qsound write to offset %d == %02X\n", device->machine().describe_context(), offset, data);
#ifdef _DEBUG
logerror("QSound: unexpected qsound write to offset %d == %02X\n", offset, data);
#endif
break;
}
}
//READ8_DEVICE_HANDLER( qsound_r )
UINT8 qsound_r(void *chip, offs_t offset)
{
/* Port ready bit (0x80 if ready) */
return 0x80;
}
static void qsound_set_command(qsound_state *chip, UINT8 address, UINT16 data)
{
int ch = 0, reg = 0;
// direct sound reg
if (address < 0x80)
{
ch = address >> 3;
reg = address & 0x07;
}
// >= 0x80 is probably for the dsp?
else if (address < 0x90)
{
ch = address & 0x0F;
reg = 8;
}
else if (address >= 0xba && address < 0xca)
{
ch = address - 0xba;
reg=9;
}
else
{
/* Unknown registers */
ch = 99;
reg = 99;
}
switch (reg)
{
case 0:
// bank, high bits unknown
ch = (ch + 1) & 0x0f; /* strange ... */
chip->channel[ch].bank = (data & 0x7f) << 16; // Note: The most recent MAME doesn't do "& 0x7F"
#ifdef _DEBUG
if (data && !(data & 0x8000))
printf("QSound Ch %u: Bank = %04x\n",ch,data);
#endif
break;
case 1:
// start/cur address
chip->channel[ch].address = data;
break;
case 2:
// frequency
chip->channel[ch].freq = data;
// This was working with the old code, but breaks the songs with the new one.
// And I'm pretty sure the hardware won't do this. -Valley Bell
/*if (!data)
{
// key off
chip->channel[ch].enabled = 0;
}*/
break;
case 3:
#ifdef _DEBUG
if (chip->channel[ch].enabled && data != 0x8000)
printf("QSound Ch %u: KeyOn = %04x\n",ch,data);
#endif
// key on (does the value matter? it always writes 0x8000)
//chip->channel[ch].enabled = 1;
chip->channel[ch].enabled = (data & 0x8000) >> 15;
chip->channel[ch].step_ptr = 0;
break;
case 4:
// loop address
chip->channel[ch].loop = data;
break;
case 5:
// end address
chip->channel[ch].end = data;
break;
case 6:
// master volume
#ifdef _DEBUG
if (! chip->channel[ch].enabled && data)
printf("QSound update warning - please report!\n");
#endif
chip->channel[ch].vol = data;
break;
case 7:
// unused?
#ifdef MAME_DEBUG
popmessage("UNUSED QSOUND REG 7=%04x",data);
#endif
break;
case 8:
{
// panning (left=0x0110, centre=0x0120, right=0x0130)
// looks like it doesn't write other values than that
int pan = (data & 0x3f) - 0x10;
if (pan > 0x20)
pan = 0x20;
if (pan < 0)
pan = 0;
chip->channel[ch].rvol=chip->pan_table[pan];
chip->channel[ch].lvol=chip->pan_table[0x20 - pan];
}
break;
case 9:
// unknown
/*
#ifdef MAME_DEBUG
popmessage("QSOUND REG 9=%04x",data);
#endif
*/
break;
default:
//logerror("%s: write_data %02x = %04x\n", machine().describe_context(), address, data);
break;
}
//LOG(("QSOUND WRITE %02x CH%02d-R%02d =%04x\n", address, ch, reg, data));
}
//static STREAM_UPDATE( qsound_update )
void qsound_update(void *param, stream_sample_t **outputs, int samples)
{
qsound_state *chip = (qsound_state *)param;
int i,j;
UINT32 offset;
UINT32 advance;
INT8 sample;
struct QSOUND_CHANNEL *pC=&chip->channel[0];
memset( outputs[0], 0x00, samples * sizeof(*outputs[0]) );
memset( outputs[1], 0x00, samples * sizeof(*outputs[1]) );
if (! chip->sample_rom_length)
return;
for (i=0; i<QSOUND_CHANNELS; i++, pC++)
{
if (pC->enabled && ! pC->Muted)
{
QSOUND_SAMPLE *pOutL=outputs[0];
QSOUND_SAMPLE *pOutR=outputs[1];
for (j=samples-1; j>=0; j--)
{
advance = (pC->step_ptr >> 12);
pC->step_ptr &= 0xfff;
pC->step_ptr += pC->freq;
if (advance)
{
pC->address += advance;
if (pC->freq && pC->address >= pC->end)
{
if (pC->loop)
{
// Reached the end, restart the loop
pC->address -= pC->loop;
// Make sure we don't overflow (what does the real chip do in this case?)
if (pC->address >= pC->end)
pC->address = pC->end - pC->loop;
pC->address &= 0xffff;
}
else
{
// Reached the end of a non-looped sample
//pC->enabled = 0;
pC->address --; // ensure that old ripped VGMs still work
pC->step_ptr += 0x1000;
break;
}
}
}
offset = (pC->bank | pC->address) % chip->sample_rom_length;
sample = chip->sample_rom[offset];
*pOutL++ += ((sample * pC->lvol * pC->vol) >> 14);
*pOutR++ += ((sample * pC->rvol * pC->vol) >> 14);
}
}
}
/*if (chip->fpRawDataL)
fwrite(outputs[0], samples*sizeof(QSOUND_SAMPLE), 1, chip->fpRawDataL);
if (chip->fpRawDataR)
fwrite(outputs[1], samples*sizeof(QSOUND_SAMPLE), 1, chip->fpRawDataR);*/
}
void qsound_write_rom(void *_info, offs_t ROMSize, offs_t DataStart, offs_t DataLength,
const UINT8* ROMData)
{
qsound_state* info = (qsound_state *)_info;
if (info->sample_rom_length != ROMSize)
{
info->sample_rom = (QSOUND_SRC_SAMPLE*)realloc(info->sample_rom, ROMSize);
info->sample_rom_length = ROMSize;
memset(info->sample_rom, 0xFF, ROMSize);
}
if (DataStart > ROMSize)
return;
if (DataStart + DataLength > ROMSize)
DataLength = ROMSize - DataStart;
memcpy(info->sample_rom + DataStart, ROMData, DataLength);
return;
}
void qsound_set_mute_mask(void *_info, UINT32 MuteMask)
{
qsound_state* info = (qsound_state *)_info;
UINT8 CurChn;
for (CurChn = 0; CurChn < QSOUND_CHANNELS; CurChn ++)
info->channel[CurChn].Muted = (MuteMask >> CurChn) & 0x01;
return;
}
/**************************************************************************
* Generic get_info
**************************************************************************/
/*DEVICE_GET_INFO( qsound )
{
switch (state)
{
// --- the following bits of info are returned as 64-bit signed integers --- //
case DEVINFO_INT_TOKEN_BYTES: info->i = sizeof(qsound_state); break;
// --- the following bits of info are returned as pointers to data or functions --- //
case DEVINFO_FCT_START: info->start = DEVICE_START_NAME( qsound ); break;
case DEVINFO_FCT_STOP: info->stop = DEVICE_STOP_NAME( qsound ); break;
case DEVINFO_FCT_RESET: // Nothing // break;
// --- the following bits of info are returned as NULL-terminated strings --- //
case DEVINFO_STR_NAME: strcpy(info->s, "Q-Sound"); break;
case DEVINFO_STR_FAMILY: strcpy(info->s, "Capcom custom"); break;
case DEVINFO_STR_VERSION: strcpy(info->s, "1.0"); break;
case DEVINFO_STR_SOURCE_FILE: strcpy(info->s, __FILE__); break;
case DEVINFO_STR_CREDITS: strcpy(info->s, "Copyright Nicola Salmoria and the MAME Team"); break;
}
}*/
/**************** end of file ****************/
//DEFINE_LEGACY_SOUND_DEVICE(QSOUND, qsound);