921 lines
25 KiB
C
921 lines
25 KiB
C
/**************************************************************************************
|
|
* Game Boy sound emulation (c) Anthony Kruize (trandor@labyrinth.net.au)
|
|
*
|
|
* Anyways, sound on the Game Boy consists of 4 separate 'channels'
|
|
* Sound1 = Quadrangular waves with SWEEP and ENVELOPE functions (NR10,11,12,13,14)
|
|
* Sound2 = Quadrangular waves with ENVELOPE functions (NR21,22,23,24)
|
|
* Sound3 = Wave patterns from WaveRAM (NR30,31,32,33,34)
|
|
* Sound4 = White noise with an envelope (NR41,42,43,44)
|
|
*
|
|
* Each sound channel has 2 modes, namely ON and OFF... whoa
|
|
*
|
|
* These tend to be the two most important equations in
|
|
* converting between Hertz and GB frequency registers:
|
|
* (Sounds will have a 2.4% higher frequency on Super GB.)
|
|
* gb = 2048 - (131072 / Hz)
|
|
* Hz = 131072 / (2048 - gb)
|
|
*
|
|
* Changes:
|
|
*
|
|
* 10/2/2002 AK - Preliminary sound code.
|
|
* 13/2/2002 AK - Added a hack for mode 4, other fixes.
|
|
* 23/2/2002 AK - Use lookup tables, added sweep to mode 1. Re-wrote the square
|
|
* wave generation.
|
|
* 13/3/2002 AK - Added mode 3, better lookup tables, other adjustments.
|
|
* 15/3/2002 AK - Mode 4 can now change frequencies.
|
|
* 31/3/2002 AK - Accidently forgot to handle counter/consecutive for mode 1.
|
|
* 3/4/2002 AK - Mode 1 sweep can still occur if shift is 0. Don't let frequency
|
|
* go past the maximum allowed value. Fixed Mode 3 length table.
|
|
* Slight adjustment to Mode 4's period table generation.
|
|
* 5/4/2002 AK - Mode 4 is done correctly, using a polynomial counter instead
|
|
* of being a total hack.
|
|
* 6/4/2002 AK - Slight tweak to mode 3's frequency calculation.
|
|
* 13/4/2002 AK - Reset envelope value when sound is initialized.
|
|
* 21/4/2002 AK - Backed out the mode 3 frequency calculation change.
|
|
* Merged init functions into gameboy_sound_w().
|
|
* 14/5/2002 AK - Removed magic numbers in the fixed point math.
|
|
* 12/6/2002 AK - Merged SOUNDx structs into one SOUND struct.
|
|
* 26/10/2002 AK - Finally fixed channel 3!
|
|
*
|
|
***************************************************************************************/
|
|
|
|
#include "mamedef.h"
|
|
#include <stdlib.h> // for rand
|
|
#include <string.h> // for memset
|
|
//#include "emu.h"
|
|
#include "gb.h"
|
|
//#include "streams.h"
|
|
|
|
|
|
/***************************************************************************
|
|
CONSTANTS
|
|
***************************************************************************/
|
|
|
|
#define NR10 0x00
|
|
#define NR11 0x01
|
|
#define NR12 0x02
|
|
#define NR13 0x03
|
|
#define NR14 0x04
|
|
#define NR21 0x06
|
|
#define NR22 0x07
|
|
#define NR23 0x08
|
|
#define NR24 0x09
|
|
#define NR30 0x0A
|
|
#define NR31 0x0B
|
|
#define NR32 0x0C
|
|
#define NR33 0x0D
|
|
#define NR34 0x0E
|
|
#define NR41 0x10
|
|
#define NR42 0x11
|
|
#define NR43 0x12
|
|
#define NR44 0x13
|
|
#define NR50 0x14
|
|
#define NR51 0x15
|
|
#define NR52 0x16
|
|
#define AUD3W0 0x20
|
|
#define AUD3W1 0x21
|
|
#define AUD3W2 0x22
|
|
#define AUD3W3 0x23
|
|
#define AUD3W4 0x24
|
|
#define AUD3W5 0x25
|
|
#define AUD3W6 0x26
|
|
#define AUD3W7 0x27
|
|
#define AUD3W8 0x28
|
|
#define AUD3W9 0x29
|
|
#define AUD3WA 0x2A
|
|
#define AUD3WB 0x2B
|
|
#define AUD3WC 0x2C
|
|
#define AUD3WD 0x2D
|
|
#define AUD3WE 0x2E
|
|
#define AUD3WF 0x2F
|
|
|
|
#define LEFT 1
|
|
#define RIGHT 2
|
|
#define MAX_FREQUENCIES 2048
|
|
#define FIXED_POINT 16
|
|
|
|
/* Represents wave duties of 12.5%, 25%, 50% and 75% */
|
|
static const float wave_duty_table[4] = { 8.0f, 4.0f, 2.0f, 1.33333f };
|
|
|
|
|
|
/***************************************************************************
|
|
TYPE DEFINITIONS
|
|
***************************************************************************/
|
|
|
|
struct SOUND
|
|
{
|
|
/* Common */
|
|
UINT8 on;
|
|
UINT8 channel;
|
|
INT32 length;
|
|
INT32 pos;
|
|
//UINT32 pos;
|
|
UINT32 period;
|
|
INT32 count;
|
|
INT8 mode;
|
|
/* Mode 1, 2, 3 */
|
|
INT8 duty;
|
|
/* Mode 1, 2, 4 */
|
|
INT32 env_value;
|
|
INT8 env_direction;
|
|
INT32 env_length;
|
|
INT32 env_count;
|
|
INT8 signal;
|
|
/* Mode 1 */
|
|
UINT32 frequency;
|
|
INT32 swp_shift;
|
|
INT32 swp_direction;
|
|
INT32 swp_time;
|
|
INT32 swp_count;
|
|
/* Mode 3 */
|
|
INT8 level;
|
|
UINT8 offset;
|
|
UINT32 dutycount;
|
|
/* Mode 4 */
|
|
INT32 ply_step;
|
|
INT16 ply_value;
|
|
UINT8 Muted;
|
|
};
|
|
|
|
struct SOUNDC
|
|
{
|
|
UINT8 on;
|
|
UINT8 vol_left;
|
|
UINT8 vol_right;
|
|
UINT8 mode1_left;
|
|
UINT8 mode1_right;
|
|
UINT8 mode2_left;
|
|
UINT8 mode2_right;
|
|
UINT8 mode3_left;
|
|
UINT8 mode3_right;
|
|
UINT8 mode4_left;
|
|
UINT8 mode4_right;
|
|
};
|
|
|
|
|
|
typedef struct _gb_sound_t gb_sound_t;
|
|
struct _gb_sound_t
|
|
{
|
|
//sound_stream *channel;
|
|
//int rate;
|
|
UINT32 rate; // fixes bad calculations of length_mode3_table
|
|
|
|
INT32 env_length_table[8];
|
|
INT32 swp_time_table[8];
|
|
UINT32 period_table[MAX_FREQUENCIES];
|
|
UINT32 period_mode3_table[MAX_FREQUENCIES];
|
|
UINT32 period_mode4_table[8][16];
|
|
UINT32 length_table[64];
|
|
UINT32 length_mode3_table[256];
|
|
|
|
struct SOUND snd_1;
|
|
struct SOUND snd_2;
|
|
struct SOUND snd_3;
|
|
struct SOUND snd_4;
|
|
struct SOUNDC snd_control;
|
|
|
|
UINT8 snd_regs[0x30];
|
|
|
|
UINT8 LoudWaveChn;
|
|
UINT8 LowNoiseChn;
|
|
UINT8 AccuracyHack;
|
|
};
|
|
|
|
|
|
|
|
|
|
/***************************************************************************
|
|
INLINE FUNCTIONS
|
|
***************************************************************************/
|
|
|
|
/*INLINE gb_sound_t *get_token(running_device *device)
|
|
{
|
|
assert(device != NULL);
|
|
assert(device->type() == GAMEBOY);
|
|
return (gb_sound_t *) downcast<legacy_device_base *>(device)->token();
|
|
}*/
|
|
|
|
|
|
/***************************************************************************
|
|
PROTOTYPES
|
|
***************************************************************************/
|
|
|
|
//static STREAM_UPDATE( gameboy_update );
|
|
|
|
|
|
/***************************************************************************
|
|
IMPLEMENTATION
|
|
***************************************************************************/
|
|
|
|
//READ8_DEVICE_HANDLER( gb_wave_r )
|
|
UINT8 gb_wave_r(void *_info, offs_t offset)
|
|
{
|
|
//gb_sound_t *gb = get_token(device);
|
|
gb_sound_t *gb = (gb_sound_t *)_info;
|
|
|
|
/* TODO: properly emulate scrambling of wave ram area when playback is active */
|
|
return ( gb->snd_regs[ AUD3W0 + offset ] | gb->snd_3.on );
|
|
}
|
|
|
|
//WRITE8_DEVICE_HANDLER( gb_wave_w )
|
|
void gb_wave_w(void *_info, offs_t offset, UINT8 data)
|
|
{
|
|
//gb_sound_t *gb = get_token(device);
|
|
gb_sound_t *gb = (gb_sound_t *)_info;
|
|
|
|
gb->snd_regs[ AUD3W0 + offset ] = data;
|
|
}
|
|
|
|
//READ8_DEVICE_HANDLER( gb_sound_r )
|
|
UINT8 gb_sound_r(void *_info, offs_t offset)
|
|
{
|
|
//gb_sound_t *gb = get_token(device);
|
|
gb_sound_t *gb = (gb_sound_t *)_info;
|
|
|
|
switch( offset ) {
|
|
case 0x05:
|
|
case 0x0F:
|
|
return 0xFF;
|
|
case NR52:
|
|
return 0x70 | gb->snd_regs[offset];
|
|
default:
|
|
return gb->snd_regs[offset];
|
|
}
|
|
}
|
|
|
|
//static void gb_sound_w_internal(running_device *device, int offset, UINT8 data )
|
|
static void gb_sound_w_internal(gb_sound_t *gb, int offset, UINT8 data )
|
|
{
|
|
//gb_sound_t *gb = get_token(device);
|
|
|
|
/* Store the value */
|
|
gb->snd_regs[offset] = data;
|
|
|
|
switch( offset )
|
|
{
|
|
/*MODE 1 */
|
|
case NR10: /* Sweep (R/W) */
|
|
gb->snd_1.swp_shift = data & 0x7;
|
|
gb->snd_1.swp_direction = (data & 0x8) >> 3;
|
|
gb->snd_1.swp_direction |= gb->snd_1.swp_direction - 1;
|
|
gb->snd_1.swp_time = gb->swp_time_table[ (data & 0x70) >> 4 ];
|
|
break;
|
|
case NR11: /* Sound length/Wave pattern duty (R/W) */
|
|
gb->snd_1.duty = (data & 0xC0) >> 6;
|
|
gb->snd_1.length = gb->length_table[data & 0x3F];
|
|
break;
|
|
case NR12: /* Envelope (R/W) */
|
|
gb->snd_1.env_value = data >> 4;
|
|
gb->snd_1.env_direction = (data & 0x8) >> 3;
|
|
gb->snd_1.env_direction |= gb->snd_1.env_direction - 1;
|
|
gb->snd_1.env_length = gb->env_length_table[data & 0x7];
|
|
break;
|
|
case NR13: /* Frequency lo (R/W) */
|
|
gb->snd_1.frequency = ((gb->snd_regs[NR14]&0x7)<<8) | gb->snd_regs[NR13];
|
|
gb->snd_1.period = gb->period_table[gb->snd_1.frequency];
|
|
break;
|
|
case NR14: /* Frequency hi / Initialize (R/W) */
|
|
gb->snd_1.mode = (data & 0x40) >> 6;
|
|
gb->snd_1.frequency = ((gb->snd_regs[NR14]&0x7)<<8) | gb->snd_regs[NR13];
|
|
gb->snd_1.period = gb->period_table[gb->snd_1.frequency];
|
|
if( data & 0x80 )
|
|
{
|
|
if( !gb->snd_1.on )
|
|
gb->snd_1.pos = 0;
|
|
gb->snd_1.on = 1;
|
|
gb->snd_1.count = 0;
|
|
gb->snd_1.env_value = gb->snd_regs[NR12] >> 4;
|
|
gb->snd_1.env_count = 0;
|
|
gb->snd_1.swp_count = 0;
|
|
gb->snd_1.signal = 0x1;
|
|
gb->snd_regs[NR52] |= 0x1;
|
|
}
|
|
break;
|
|
|
|
/*MODE 2 */
|
|
case NR21: /* Sound length/Wave pattern duty (R/W) */
|
|
gb->snd_2.duty = (data & 0xC0) >> 6;
|
|
gb->snd_2.length = gb->length_table[data & 0x3F];
|
|
break;
|
|
case NR22: /* Envelope (R/W) */
|
|
// gb->snd_2.env_value = data >> 4;
|
|
// gb->snd_2.env_direction = (data & 0x8 ) >> 3;
|
|
// Thanks to Delek for the fix
|
|
gb->snd_2.env_direction = (data & 0x8) >> 3;
|
|
if (gb->snd_2.env_direction)
|
|
{
|
|
gb->snd_2.env_value ++;
|
|
if (gb->snd_2.env_value > 0x0F)
|
|
gb->snd_2.env_value = 0;
|
|
}
|
|
else
|
|
{
|
|
gb->snd_2.env_value = data >> 4;
|
|
}
|
|
gb->snd_2.env_direction |= gb->snd_2.env_direction - 1;
|
|
gb->snd_2.env_length = gb->env_length_table[data & 0x7];
|
|
break;
|
|
case NR23: /* Frequency lo (R/W) */
|
|
gb->snd_2.period = gb->period_table[((gb->snd_regs[NR24]&0x7)<<8) | gb->snd_regs[NR23]];
|
|
break;
|
|
case NR24: /* Frequency hi / Initialize (R/W) */
|
|
gb->snd_2.mode = (data & 0x40) >> 6;
|
|
gb->snd_2.period = gb->period_table[((gb->snd_regs[NR24]&0x7)<<8) | gb->snd_regs[NR23]];
|
|
if( data & 0x80 )
|
|
{
|
|
if( !gb->snd_2.on )
|
|
gb->snd_2.pos = 0;
|
|
gb->snd_2.on = 1;
|
|
gb->snd_2.count = 0;
|
|
gb->snd_2.env_value = gb->snd_regs[NR22] >> 4;
|
|
gb->snd_2.env_count = 0;
|
|
gb->snd_2.signal = 0x1;
|
|
gb->snd_regs[NR52] |= 0x2;
|
|
}
|
|
break;
|
|
|
|
/*MODE 3 */
|
|
case NR30: /* Sound On/Off (R/W) */
|
|
gb->snd_3.on = (data & 0x80) >> 7;
|
|
break;
|
|
case NR31: /* Sound Length (R/W) */
|
|
gb->snd_3.length = gb->length_mode3_table[data];
|
|
break;
|
|
case NR32: /* Select Output Level */
|
|
gb->snd_3.level = (data & 0x60) >> 5;
|
|
break;
|
|
case NR33: /* Frequency lo (W) */
|
|
gb->snd_3.period = gb->period_mode3_table[((gb->snd_regs[NR34]&0x7)<<8) + gb->snd_regs[NR33]];
|
|
break;
|
|
case NR34: /* Frequency hi / Initialize (W) */
|
|
gb->snd_3.mode = (data & 0x40) >> 6;
|
|
gb->snd_3.period = gb->period_mode3_table[((gb->snd_regs[NR34]&0x7)<<8) + gb->snd_regs[NR33]];
|
|
if( data & 0x80 )
|
|
{
|
|
if( !gb->snd_3.on )
|
|
{
|
|
gb->snd_3.pos = 0;
|
|
gb->snd_3.offset = 0;
|
|
gb->snd_3.duty = 0;
|
|
}
|
|
gb->snd_3.on = 1;
|
|
gb->snd_3.count = 0;
|
|
gb->snd_3.duty = 1;
|
|
gb->snd_3.dutycount = 0;
|
|
gb->snd_regs[NR52] |= 0x4;
|
|
}
|
|
break;
|
|
|
|
/*MODE 4 */
|
|
case NR41: /* Sound Length (R/W) */
|
|
gb->snd_4.length = gb->length_table[data & 0x3F];
|
|
break;
|
|
case NR42: /* Envelope (R/W) */
|
|
gb->snd_4.env_value = data >> 4;
|
|
gb->snd_4.env_direction = (data & 0x8 ) >> 3;
|
|
gb->snd_4.env_direction |= gb->snd_4.env_direction - 1;
|
|
gb->snd_4.env_length = gb->env_length_table[data & 0x7];
|
|
break;
|
|
case NR43: /* Polynomial Counter/Frequency */
|
|
gb->snd_4.period = gb->period_mode4_table[data & 0x7][(data & 0xF0) >> 4];
|
|
gb->snd_4.ply_step = (data & 0x8) >> 3;
|
|
break;
|
|
case NR44: /* Counter/Consecutive / Initialize (R/W) */
|
|
gb->snd_4.mode = (data & 0x40) >> 6;
|
|
if( data & 0x80 )
|
|
{
|
|
if( !gb->snd_4.on )
|
|
gb->snd_4.pos = 0;
|
|
gb->snd_4.on = 1;
|
|
gb->snd_4.count = 0;
|
|
gb->snd_4.env_value = gb->snd_regs[NR42] >> 4;
|
|
gb->snd_4.env_count = 0;
|
|
//gb->snd_4.signal = mame_rand(device->machine);
|
|
gb->snd_4.signal = rand() & 0xFF;
|
|
gb->snd_4.ply_value = 0x7fff;
|
|
gb->snd_regs[NR52] |= 0x8;
|
|
}
|
|
break;
|
|
|
|
/* CONTROL */
|
|
case NR50: /* Channel Control / On/Off / Volume (R/W) */
|
|
gb->snd_control.vol_left = data & 0x7;
|
|
gb->snd_control.vol_right = (data & 0x70) >> 4;
|
|
break;
|
|
case NR51: /* Selection of Sound Output Terminal */
|
|
gb->snd_control.mode1_right = data & 0x1;
|
|
gb->snd_control.mode1_left = (data & 0x10) >> 4;
|
|
gb->snd_control.mode2_right = (data & 0x2) >> 1;
|
|
gb->snd_control.mode2_left = (data & 0x20) >> 5;
|
|
gb->snd_control.mode3_right = (data & 0x4) >> 2;
|
|
gb->snd_control.mode3_left = (data & 0x40) >> 6;
|
|
gb->snd_control.mode4_right = (data & 0x8) >> 3;
|
|
gb->snd_control.mode4_left = (data & 0x80) >> 7;
|
|
break;
|
|
case NR52: /* Sound On/Off (R/W) */
|
|
/* Only bit 7 is writable, writing to bits 0-3 does NOT enable or
|
|
disable sound. They are read-only */
|
|
gb->snd_control.on = (data & 0x80) >> 7;
|
|
if( !gb->snd_control.on )
|
|
{
|
|
gb_sound_w_internal( gb, NR10, 0x80 );
|
|
gb_sound_w_internal( gb, NR11, 0x3F );
|
|
gb_sound_w_internal( gb, NR12, 0x00 );
|
|
gb_sound_w_internal( gb, NR13, 0xFE );
|
|
gb_sound_w_internal( gb, NR14, 0xBF );
|
|
// gb_sound_w_internal( gb, NR20, 0xFF );
|
|
gb_sound_w_internal( gb, NR21, 0x3F );
|
|
gb_sound_w_internal( gb, NR22, 0x00 );
|
|
gb_sound_w_internal( gb, NR23, 0xFF );
|
|
gb_sound_w_internal( gb, NR24, 0xBF );
|
|
gb_sound_w_internal( gb, NR30, 0x7F );
|
|
gb_sound_w_internal( gb, NR31, 0xFF );
|
|
gb_sound_w_internal( gb, NR32, 0x9F );
|
|
gb_sound_w_internal( gb, NR33, 0xFF );
|
|
gb_sound_w_internal( gb, NR34, 0xBF );
|
|
// gb_sound_w_internal( gb, NR40, 0xFF );
|
|
gb_sound_w_internal( gb, NR41, 0xFF );
|
|
gb_sound_w_internal( gb, NR42, 0x00 );
|
|
gb_sound_w_internal( gb, NR43, 0x00 );
|
|
gb_sound_w_internal( gb, NR44, 0xBF );
|
|
gb_sound_w_internal( gb, NR50, 0x00 );
|
|
gb_sound_w_internal( gb, NR51, 0x00 );
|
|
gb->snd_1.on = 0;
|
|
gb->snd_2.on = 0;
|
|
gb->snd_3.on = 0;
|
|
gb->snd_4.on = 0;
|
|
gb->snd_regs[offset] = 0;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
//WRITE8_DEVICE_HANDLER( gb_sound_w )
|
|
void gb_sound_w(void *_info, offs_t offset, UINT8 data)
|
|
{
|
|
//gb_sound_t *gb = get_token(device);
|
|
gb_sound_t *gb = (gb_sound_t *)_info;
|
|
|
|
/* change in registers so update first */
|
|
//stream_update(gb->channel);
|
|
|
|
if (offset < AUD3W0)
|
|
{
|
|
/* Only register NR52 is accessible if the sound controller is disabled */
|
|
if( !gb->snd_control.on && offset != NR52 )
|
|
{
|
|
return;
|
|
}
|
|
|
|
gb_sound_w_internal( gb, offset, data );
|
|
}
|
|
else if (offset <= AUD3WF)
|
|
{
|
|
gb->snd_regs[offset] = data;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
//static STREAM_UPDATE( gameboy_update )
|
|
void gameboy_update(void *_info, stream_sample_t **outputs, int samples)
|
|
{
|
|
//gb_sound_t *gb = get_token(device);
|
|
gb_sound_t *gb = (gb_sound_t *)_info;
|
|
stream_sample_t *outl = outputs[0];
|
|
stream_sample_t *outr = outputs[1];
|
|
stream_sample_t sample, left, right, mode4_mask;
|
|
|
|
while( samples-- > 0 )
|
|
{
|
|
left = right = 0;
|
|
|
|
/* Mode 1 - Wave with Envelope and Sweep */
|
|
if( gb->snd_1.on && ! gb->snd_1.Muted )
|
|
{
|
|
sample = gb->snd_1.signal * gb->snd_1.env_value;
|
|
if (! gb->AccuracyHack)
|
|
{
|
|
gb->snd_1.pos++;
|
|
if( gb->snd_1.pos == (UINT32)(gb->snd_1.period / wave_duty_table[gb->snd_1.duty]) >> FIXED_POINT)
|
|
{
|
|
gb->snd_1.signal = -gb->snd_1.signal;
|
|
}
|
|
else if( gb->snd_1.pos > (gb->snd_1.period >> FIXED_POINT) )
|
|
{
|
|
gb->snd_1.pos = 0;
|
|
gb->snd_1.signal = -gb->snd_1.signal;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// accuracy hack - makes high frequencies sound better
|
|
gb->snd_1.pos += 1 << FIXED_POINT;
|
|
if( (gb->snd_1.pos >> FIXED_POINT) == (UINT32)(gb->snd_1.period / wave_duty_table[gb->snd_1.duty]) >> FIXED_POINT)
|
|
{
|
|
gb->snd_1.signal = -gb->snd_1.signal;
|
|
}
|
|
else if( gb->snd_1.pos >= gb->snd_1.period )
|
|
{
|
|
gb->snd_1.pos -= gb->snd_1.period;
|
|
gb->snd_1.signal = -gb->snd_1.signal;
|
|
}
|
|
}
|
|
|
|
if( gb->snd_1.length && gb->snd_1.mode )
|
|
{
|
|
gb->snd_1.count++;
|
|
if( gb->snd_1.count >= gb->snd_1.length )
|
|
{
|
|
gb->snd_1.on = 0;
|
|
gb->snd_regs[NR52] &= 0xFE;
|
|
}
|
|
}
|
|
|
|
if( gb->snd_1.env_length )
|
|
{
|
|
gb->snd_1.env_count++;
|
|
if( gb->snd_1.env_count >= gb->snd_1.env_length )
|
|
{
|
|
gb->snd_1.env_count = 0;
|
|
gb->snd_1.env_value += gb->snd_1.env_direction;
|
|
if( gb->snd_1.env_value < 0 )
|
|
gb->snd_1.env_value = 0;
|
|
if( gb->snd_1.env_value > 15 )
|
|
gb->snd_1.env_value = 15;
|
|
}
|
|
}
|
|
|
|
if( gb->snd_1.swp_time )
|
|
{
|
|
gb->snd_1.swp_count++;
|
|
if( gb->snd_1.swp_count >= gb->snd_1.swp_time )
|
|
{
|
|
gb->snd_1.swp_count = 0;
|
|
if( gb->snd_1.swp_direction > 0 )
|
|
{
|
|
gb->snd_1.frequency -= gb->snd_1.frequency / (1 << gb->snd_1.swp_shift );
|
|
if( gb->snd_1.frequency <= 0 )
|
|
{
|
|
gb->snd_1.on = 0;
|
|
gb->snd_regs[NR52] &= 0xFE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
gb->snd_1.frequency += gb->snd_1.frequency / (1 << gb->snd_1.swp_shift );
|
|
if( gb->snd_1.frequency >= MAX_FREQUENCIES )
|
|
{
|
|
gb->snd_1.frequency = MAX_FREQUENCIES - 1;
|
|
}
|
|
}
|
|
|
|
gb->snd_1.period = gb->period_table[gb->snd_1.frequency];
|
|
}
|
|
}
|
|
|
|
if( gb->snd_control.mode1_left )
|
|
left += sample;
|
|
if( gb->snd_control.mode1_right )
|
|
right += sample;
|
|
}
|
|
|
|
/* Mode 2 - Wave with Envelope */
|
|
if( gb->snd_2.on && ! gb->snd_2.Muted )
|
|
{
|
|
sample = gb->snd_2.signal * gb->snd_2.env_value;
|
|
if (! gb->AccuracyHack)
|
|
{
|
|
gb->snd_2.pos++;
|
|
if( gb->snd_2.pos == (UINT32)(gb->snd_2.period / wave_duty_table[gb->snd_2.duty]) >> FIXED_POINT)
|
|
{
|
|
gb->snd_2.signal = -gb->snd_2.signal;
|
|
}
|
|
else if( gb->snd_2.pos > (gb->snd_2.period >> FIXED_POINT) )
|
|
{
|
|
gb->snd_2.pos = 0;
|
|
gb->snd_2.signal = -gb->snd_2.signal;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
gb->snd_2.pos += 1 << FIXED_POINT;
|
|
if( (gb->snd_2.pos >> FIXED_POINT) == (UINT32)(gb->snd_2.period / wave_duty_table[gb->snd_2.duty]) >> FIXED_POINT)
|
|
{
|
|
gb->snd_2.signal = -gb->snd_2.signal;
|
|
}
|
|
else if( gb->snd_2.pos >= gb->snd_2.period )
|
|
{
|
|
gb->snd_2.pos -= gb->snd_2.period;
|
|
gb->snd_2.signal = -gb->snd_2.signal;
|
|
}
|
|
}
|
|
|
|
if( gb->snd_2.length && gb->snd_2.mode )
|
|
{
|
|
gb->snd_2.count++;
|
|
if( gb->snd_2.count >= gb->snd_2.length )
|
|
{
|
|
gb->snd_2.on = 0;
|
|
gb->snd_regs[NR52] &= 0xFD;
|
|
}
|
|
}
|
|
|
|
if( gb->snd_2.env_length )
|
|
{
|
|
gb->snd_2.env_count++;
|
|
if( gb->snd_2.env_count >= gb->snd_2.env_length )
|
|
{
|
|
gb->snd_2.env_count = 0;
|
|
gb->snd_2.env_value += gb->snd_2.env_direction;
|
|
if( gb->snd_2.env_value < 0 )
|
|
gb->snd_2.env_value = 0;
|
|
if( gb->snd_2.env_value > 15 )
|
|
gb->snd_2.env_value = 15;
|
|
}
|
|
}
|
|
|
|
if( gb->snd_control.mode2_left )
|
|
left += sample;
|
|
if( gb->snd_control.mode2_right )
|
|
right += sample;
|
|
}
|
|
|
|
/* Mode 3 - Wave patterns from WaveRAM */
|
|
if( gb->snd_3.on && ! gb->snd_3.Muted )
|
|
{
|
|
/* NOTE: This is extremely close, but not quite right.
|
|
The problem is for GB frequencies above 2000 the frequency gets
|
|
clipped. This is caused because gb->snd_3.pos is never 0 at the test.*/
|
|
sample = gb->snd_regs[AUD3W0 + (gb->snd_3.offset/2)];
|
|
if( !(gb->snd_3.offset % 2) )
|
|
{
|
|
sample >>= 4;
|
|
}
|
|
sample = (sample & 0xF) - 8;
|
|
if (gb->LoudWaveChn)
|
|
sample <<= 1;
|
|
|
|
if( gb->snd_3.level )
|
|
sample >>= (gb->snd_3.level - 1);
|
|
else
|
|
sample = 0;
|
|
|
|
if (! gb->AccuracyHack)
|
|
{
|
|
gb->snd_3.pos++;
|
|
if( gb->snd_3.pos >= ((UINT32)(((gb->snd_3.period ) >> 21)) + gb->snd_3.duty) )
|
|
{
|
|
gb->snd_3.pos = 0;
|
|
if( gb->snd_3.dutycount == ((UINT32)(((gb->snd_3.period ) >> FIXED_POINT)) % 32) )
|
|
{
|
|
gb->snd_3.duty--;
|
|
}
|
|
gb->snd_3.dutycount++;
|
|
gb->snd_3.offset++;
|
|
if( gb->snd_3.offset > 31 )
|
|
{
|
|
gb->snd_3.offset = 0;
|
|
gb->snd_3.duty = 1;
|
|
gb->snd_3.dutycount = 0;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
gb->snd_3.pos += 1 << 21;
|
|
if( gb->snd_3.pos >= (UINT32)gb->snd_3.period)
|
|
{
|
|
gb->snd_3.pos -= (UINT32)gb->snd_3.period;
|
|
gb->snd_3.dutycount++;
|
|
gb->snd_3.offset++;
|
|
if( gb->snd_3.offset > 31 )
|
|
{
|
|
gb->snd_3.offset = 0;
|
|
gb->snd_3.dutycount = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
if( gb->snd_3.length && gb->snd_3.mode )
|
|
{
|
|
gb->snd_3.count++;
|
|
if( gb->snd_3.count >= gb->snd_3.length )
|
|
{
|
|
gb->snd_3.on = 0;
|
|
gb->snd_regs[NR52] &= 0xFB;
|
|
}
|
|
}
|
|
|
|
if( gb->snd_control.mode3_left )
|
|
left += sample;
|
|
if( gb->snd_control.mode3_right )
|
|
right += sample;
|
|
}
|
|
|
|
/* Mode 4 - Noise with Envelope */
|
|
if( gb->snd_4.on && ! gb->snd_4.Muted )
|
|
{
|
|
/* Similar problem to Mode 3, we seem to miss some notes */
|
|
sample = gb->snd_4.signal & gb->snd_4.env_value;
|
|
sample -= gb->snd_4.env_value / 2; // make Bipolar
|
|
if (! gb->LowNoiseChn)
|
|
sample <<= 1; // that's more like VisualBoy Advance (and sounds better)
|
|
gb->snd_4.pos++;
|
|
if( gb->snd_4.pos == (gb->snd_4.period >> (FIXED_POINT + 1)) )
|
|
{
|
|
/* Using a Polynomial Counter (aka Linear Feedback Shift Register)
|
|
Mode 4 has a 7 bit and 15 bit counter so we need to shift the
|
|
bits around accordingly */
|
|
mode4_mask = (((gb->snd_4.ply_value & 0x2) >> 1) ^ (gb->snd_4.ply_value & 0x1)) << (gb->snd_4.ply_step ? 6 : 14);
|
|
gb->snd_4.ply_value >>= 1;
|
|
gb->snd_4.ply_value |= mode4_mask;
|
|
gb->snd_4.ply_value &= (gb->snd_4.ply_step ? 0x7f : 0x7fff);
|
|
gb->snd_4.signal = (INT8)gb->snd_4.ply_value;
|
|
}
|
|
else if( gb->snd_4.pos > (gb->snd_4.period >> FIXED_POINT) )
|
|
{
|
|
gb->snd_4.pos = 0;
|
|
mode4_mask = (((gb->snd_4.ply_value & 0x2) >> 1) ^ (gb->snd_4.ply_value & 0x1)) << (gb->snd_4.ply_step ? 6 : 14);
|
|
gb->snd_4.ply_value >>= 1;
|
|
gb->snd_4.ply_value |= mode4_mask;
|
|
gb->snd_4.ply_value &= (gb->snd_4.ply_step ? 0x7f : 0x7fff);
|
|
gb->snd_4.signal = (INT8)gb->snd_4.ply_value;
|
|
}
|
|
|
|
if( gb->snd_4.length && gb->snd_4.mode )
|
|
{
|
|
gb->snd_4.count++;
|
|
if( gb->snd_4.count >= gb->snd_4.length )
|
|
{
|
|
gb->snd_4.on = 0;
|
|
gb->snd_regs[NR52] &= 0xF7;
|
|
}
|
|
}
|
|
|
|
if( gb->snd_4.env_length )
|
|
{
|
|
gb->snd_4.env_count++;
|
|
if( gb->snd_4.env_count >= gb->snd_4.env_length )
|
|
{
|
|
gb->snd_4.env_count = 0;
|
|
gb->snd_4.env_value += gb->snd_4.env_direction;
|
|
if( gb->snd_4.env_value < 0 )
|
|
gb->snd_4.env_value = 0;
|
|
if( gb->snd_4.env_value > 15 )
|
|
gb->snd_4.env_value = 15;
|
|
}
|
|
}
|
|
|
|
if( gb->snd_control.mode4_left )
|
|
left += sample;
|
|
if( gb->snd_control.mode4_right )
|
|
right += sample;
|
|
}
|
|
|
|
/* Adjust for master volume */
|
|
left *= gb->snd_control.vol_left;
|
|
right *= gb->snd_control.vol_right;
|
|
|
|
/* pump up the volume */
|
|
left <<= 6;
|
|
right <<= 6;
|
|
|
|
/* Update the buffers */
|
|
*(outl++) = left;
|
|
*(outr++) = right;
|
|
}
|
|
|
|
gb->snd_regs[NR52] = (gb->snd_regs[NR52]&0xf0) | gb->snd_1.on | (gb->snd_2.on << 1) | (gb->snd_3.on << 2) | (gb->snd_4.on << 3);
|
|
}
|
|
|
|
|
|
//static DEVICE_START( gameboy_sound )
|
|
int device_start_gameboy_sound(void **_info, int clock, int Flags, int SampleRate)
|
|
{
|
|
//gb_sound_t *gb = get_token(device);
|
|
gb_sound_t *gb;
|
|
int I, J;
|
|
|
|
gb = (gb_sound_t *) calloc(1, sizeof(gb_sound_t));
|
|
*_info = (void *) gb;
|
|
|
|
gb->LoudWaveChn = (Flags & 0x01) >> 0;
|
|
gb->LowNoiseChn = (Flags & 0x02) >> 1;
|
|
gb->AccuracyHack = ! ((Flags & 0x04) >> 2);
|
|
|
|
memset(&gb->snd_1, 0, sizeof(gb->snd_1));
|
|
memset(&gb->snd_2, 0, sizeof(gb->snd_2));
|
|
memset(&gb->snd_3, 0, sizeof(gb->snd_3));
|
|
memset(&gb->snd_4, 0, sizeof(gb->snd_4));
|
|
|
|
//gb->channel = stream_create(device, 0, 2, device->machine->sample_rate, 0, gameboy_update);
|
|
//gb->rate = device->machine->sample_rate;
|
|
gb->rate = SampleRate;
|
|
|
|
/* Calculate the envelope and sweep tables */
|
|
for( I = 0; I < 8; I++ )
|
|
{
|
|
gb->env_length_table[I] = (I * ((1 << FIXED_POINT) / 64) * gb->rate) >> FIXED_POINT;
|
|
gb->swp_time_table[I] = (((I << FIXED_POINT) / 128) * gb->rate) >> (FIXED_POINT - 1);
|
|
}
|
|
|
|
/* Calculate the period tables */
|
|
for( I = 0; I < MAX_FREQUENCIES; I++ )
|
|
{
|
|
gb->period_table[I] = ((1 << FIXED_POINT) / (131072 / (2048 - I))) * gb->rate;
|
|
gb->period_mode3_table[I] = ((1 << FIXED_POINT) / (65536 / (2048 - I))) * gb->rate;
|
|
}
|
|
/* Calculate the period table for mode 4 */
|
|
for( I = 0; I < 8; I++ )
|
|
{
|
|
for( J = 0; J < 16; J++ )
|
|
{
|
|
/* I is the dividing ratio of frequencies
|
|
J is the shift clock frequency */
|
|
gb->period_mode4_table[I][J] = ((1 << FIXED_POINT) / (524288 / ((I == 0)?0.5:I) / (1 << (J + 1)))) * gb->rate;
|
|
}
|
|
}
|
|
|
|
/* Calculate the length table */
|
|
for( I = 0; I < 64; I++ )
|
|
{
|
|
gb->length_table[I] = ((64 - I) * ((1 << FIXED_POINT)/256) * gb->rate) >> FIXED_POINT;
|
|
}
|
|
/* Calculate the length table for mode 3 */
|
|
for( I = 0; I < 256; I++ )
|
|
{
|
|
gb->length_mode3_table[I] = ((256 - I) * ((1 << FIXED_POINT)/256) * gb->rate) >> FIXED_POINT;
|
|
}
|
|
|
|
gb->snd_1.Muted = 0x00;
|
|
gb->snd_2.Muted = 0x00;
|
|
gb->snd_3.Muted = 0x00;
|
|
gb->snd_4.Muted = 0x00;
|
|
|
|
return gb->rate;
|
|
}
|
|
|
|
void device_stop_gameboy_sound(void *_info)
|
|
{
|
|
free(_info);
|
|
return;
|
|
}
|
|
|
|
void device_reset_gameboy_sound(void *_info)
|
|
{
|
|
gb_sound_t *gb = (gb_sound_t *)_info;
|
|
|
|
// moved there from device_start
|
|
gb_sound_w_internal( gb, NR52, 0x00 );
|
|
gb->snd_regs[AUD3W0] = 0xac;
|
|
gb->snd_regs[AUD3W1] = 0xdd;
|
|
gb->snd_regs[AUD3W2] = 0xda;
|
|
gb->snd_regs[AUD3W3] = 0x48;
|
|
gb->snd_regs[AUD3W4] = 0x36;
|
|
gb->snd_regs[AUD3W5] = 0x02;
|
|
gb->snd_regs[AUD3W6] = 0xcf;
|
|
gb->snd_regs[AUD3W7] = 0x16;
|
|
gb->snd_regs[AUD3W8] = 0x2c;
|
|
gb->snd_regs[AUD3W9] = 0x04;
|
|
gb->snd_regs[AUD3WA] = 0xe5;
|
|
gb->snd_regs[AUD3WB] = 0x2c;
|
|
gb->snd_regs[AUD3WC] = 0xac;
|
|
gb->snd_regs[AUD3WD] = 0xdd;
|
|
gb->snd_regs[AUD3WE] = 0xda;
|
|
gb->snd_regs[AUD3WF] = 0x48;
|
|
|
|
return;
|
|
}
|
|
|
|
void gameboy_sound_set_mute_mask(void *_info, UINT32 MuteMask)
|
|
{
|
|
gb_sound_t *gb = (gb_sound_t *)_info;
|
|
|
|
gb->snd_1.Muted = (MuteMask >> 0) & 0x01;
|
|
gb->snd_2.Muted = (MuteMask >> 1) & 0x01;
|
|
gb->snd_3.Muted = (MuteMask >> 2) & 0x01;
|
|
gb->snd_4.Muted = (MuteMask >> 3) & 0x01;
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
/*DEVICE_GET_INFO( gameboy_sound )
|
|
{
|
|
switch (state)
|
|
{
|
|
// --- the following bits of info are returned as 64-bit signed integers ---
|
|
case DEVINFO_INT_TOKEN_BYTES: info->i = sizeof(gb_sound_t); break;
|
|
|
|
// --- the following bits of info are returned as pointers to data or functions ---
|
|
case DEVINFO_FCT_START: info->start = DEVICE_START_NAME(gameboy_sound); break;
|
|
|
|
// --- the following bits of info are returned as NULL-terminated strings ---
|
|
case DEVINFO_STR_NAME: strcpy(info->s, "LR35902"); break;
|
|
case DEVINFO_STR_SOURCE_FILE: strcpy(info->s, __FILE__); break;
|
|
}
|
|
}*/
|
|
|
|
//DEFINE_LEGACY_SOUND_DEVICE(GAMEBOY, gameboy_sound);
|