+implemented Blip Buffer linear interpolation in SN76489 core

+optimized SN76489 core
+added configurable SN76489 Noise boost
+added savestate version check
This commit is contained in:
ekeeke31 2009-05-27 14:07:14 +00:00
parent 5f3fce01ec
commit 1cfb7e6f44
14 changed files with 535 additions and 297 deletions

View File

@ -5,22 +5,26 @@ Genesis Plus GX 1.3.2 (??/??/????) (Eke-Eke)
[Core] [Core]
------ ------
* modified SN76489 cut-off frequency * optimized SN76489 core
* modified SN76489 cut-off frequency (thanks to Steve Snake)
* added SN76489 linear interpolation using Blip Buffer (thanks to Blargg)
* added an option to boost SN76489 Noise Channel
* removed now outdated Gens YM2612 core * removed now outdated Gens YM2612 core
* improved MAME YM2612 emulation accuracy (SSG-EG, CSM mode...), thanks to Nemesis for his tests on real hardware * improved YM2612 emulation accuracy (SSG-EG, CSM mode...) (thanks to Nemesis for his tests on real hardware)
* fixed FM context saving/loading. * fixed YM2612 context saving/loading.
* improved sprites masking emulation: fixes 3D level in Mickey Mania, thanks to Nemesis for his test ROM. * added 3 Band Equalizer for improved & fully configurable sound filtering (taken from Softdev's NeoCD Redux)
* improved sprites masking emulation: fixes 3D level in Mickey Mania (thanks to Nemesis for his test program)
* fixed lightgun autodetection: fixes cursor position in Lethal Enforcers II * fixed lightgun autodetection: fixes cursor position in Lethal Enforcers II
* various code cleanup & optimization * various code cleanup & optimization
[Gamecube/Wii] [Gamecube/Wii]
* improved audio/video synchronization: fixes video skipping issues with 60Hz modes * improved audio/video synchronization: fixes video skipping issues in 60Hz modes
* fixed stability issues and a few (potential) memory leaks * fixed stability issues and some (potential) memory leaks
* added screenshot feature * added internal screenshot feature
* improved lightgun cursors * improved lightgun cursors
* new FONT & GUI engines: use internal IPL FONT, GX hardware & multithreading for fast rendering. * new FONT & GUI engines: use internal IPL FONT, GX hardware & multithreading for fast rendering.
* new GUI layout: includes IR pointing, ROM snapshots, menu effects, sound effects & more (check the README for more details) * new interface: incl. IR pointing, ROM snapshots, menu effects, sound effects, BGM... (check the README for more details)

View File

@ -66,13 +66,14 @@ void config_default(void)
strncpy(config.version,VERSION,16); strncpy(config.version,VERSION,16);
/* sound options */ /* sound options */
config.psg_preamp = 150; config.psg_preamp = 150;
config.fm_preamp = 100; config.fm_preamp = 100;
config.hq_fm = 1; config.hq_fm = 1;
config.filter = 1; config.psgBoostNoise = 0;
config.lg = 1.0; config.filter = 1;
config.mg = 1.0; config.lg = 1.0;
config.hg = 1.0; config.mg = 1.0;
config.hg = 1.0;
/* system options */ /* system options */
config.region_detect = 0; config.region_detect = 0;

View File

@ -32,6 +32,7 @@ typedef struct
{ {
char version[16]; char version[16];
uint8 hq_fm; uint8 hq_fm;
uint8 psgBoostNoise;
int32 psg_preamp; int32 psg_preamp;
int32 fm_preamp; int32 fm_preamp;
uint8 filter; uint8 filter;

View File

@ -120,7 +120,15 @@ static int FAT_ManageFile(char *filename, u8 direction, u8 filetype)
sram.crc = crc32 (0, sram.sram, 0x10000); sram.crc = crc32 (0, sram.sram, 0x10000);
system_reset (); system_reset ();
} }
else state_load(savebuffer); /* STATE */ else
{
/* STATE */
if (!state_load(savebuffer))
{
GUI_WaitPrompt("Error","File version is not compatible !");
return 0;
}
}
sprintf (fname, "Loaded %d bytes successfully", done); sprintf (fname, "Loaded %d bytes successfully", done);
GUI_WaitPrompt("Information",fname); GUI_WaitPrompt("Information",fname);

View File

@ -217,12 +217,13 @@ static gui_item items_options[5] =
}; };
/* Audio options menu */ /* Audio options menu */
static gui_item items_audio[7] = static gui_item items_audio[8] =
{ {
{NULL,NULL,"High-Quality FM: LINEAR", "Setup YM2612 resampling", 52,132,276,48}, {NULL,NULL,"High-Quality FM: LINEAR", "Setup YM2612 resampling", 52,132,276,48},
{NULL,NULL,"PSG Noise Boost: OFF", "Boost PSG Noise Channel", 52,132,276,48},
{NULL,NULL,"PSG Volume: 2.50", "Adjust SN76489 output level", 52,132,276,48}, {NULL,NULL,"PSG Volume: 2.50", "Adjust SN76489 output level", 52,132,276,48},
{NULL,NULL,"FM Volume: 1.00", "Adjust YM2612 output level", 52,132,276,48}, {NULL,NULL,"FM Volume: 1.00", "Adjust YM2612 output level", 52,132,276,48},
{NULL,NULL,"Filtering: 3-BAND EQ", "Setup Audio filtering", 52,132,276,48}, {NULL,NULL,"Filtering: 3-BAND EQ", "Setup Audio filtering", 52,132,276,48},
{NULL,NULL,"Low Gain: 1.00", "Adjust EQ Low Gain", 52,132,276,48}, {NULL,NULL,"Low Gain: 1.00", "Adjust EQ Low Gain", 52,132,276,48},
{NULL,NULL,"Middle Gain: 1.00", "Adjust EQ Middle Gain", 52,132,276,48}, {NULL,NULL,"Middle Gain: 1.00", "Adjust EQ Middle Gain", 52,132,276,48},
{NULL,NULL,"High Gain: 1.00", "Adjust EQ High Gain", 52,132,276,48}, {NULL,NULL,"High Gain: 1.00", "Adjust EQ High Gain", 52,132,276,48},
@ -436,7 +437,7 @@ static gui_menu menu_audio =
{ {
"Audio Settings", "Audio Settings",
0,0, 0,0,
7,4,6, 8,4,6,
items_audio, items_audio,
buttons_list, buttons_list,
bg_list, bg_list,
@ -701,21 +702,22 @@ static void soundmenu ()
if (config.hq_fm == 0) sprintf (items[0].text, "High-Quality FM: OFF"); if (config.hq_fm == 0) sprintf (items[0].text, "High-Quality FM: OFF");
else if (config.hq_fm == 1) sprintf (items[0].text, "High-Quality FM: LINEAR"); else if (config.hq_fm == 1) sprintf (items[0].text, "High-Quality FM: LINEAR");
else sprintf (items[0].text, "High-Quality FM: SINC"); else sprintf (items[0].text, "High-Quality FM: SINC");
sprintf (items[1].text, "PSG Volume: %1.2f", psg_volume); sprintf (items[1].text, "PSG Noise Boost: %s", config.psgBoostNoise ? "ON":"OFF");
sprintf (items[2].text, "FM Volume: %1.2f", (double)config.fm_preamp/100.0); sprintf (items[2].text, "PSG Volume: %1.2f", psg_volume);
if (config.filter == 2) sprintf (items[3].text, "Filtering: 3-BAND EQ"); sprintf (items[3].text, "FM Volume: %1.2f", (double)config.fm_preamp/100.0);
else if (config.filter == 1) sprintf (items[3].text, "Filtering: LOW PASS"); if (config.filter == 2) sprintf (items[4].text, "Filtering: 3-BAND EQ");
else sprintf (items[3].text, "Filtering: OFF"); else if (config.filter == 1) sprintf (items[4].text, "Filtering: LOW PASS");
sprintf (items[4].text, "Low Gain: %1.2f", config.lg); else sprintf (items[4].text, "Filtering: OFF");
sprintf (items[5].text, "Middle Gain: %1.2f", config.mg); sprintf (items[5].text, "Low Gain: %1.2f", config.lg);
sprintf (items[6].text, "High Gain: %1.2f", config.hg); sprintf (items[6].text, "Middle Gain: %1.2f", config.mg);
sprintf (items[7].text, "High Gain: %1.2f", config.hg);
GUI_InitMenu(m); GUI_InitMenu(m);
if (config.filter < 2) if (config.filter < 2)
m->max_items = 4; m->max_items = 5;
else else
m->max_items = 7; m->max_items = 8;
GUI_SlideMenuTitle(m,strlen("Audio ")); GUI_SlideMenuTitle(m,strlen("Audio "));
@ -745,59 +747,65 @@ static void soundmenu ()
break; break;
case 1: case 1:
GUI_OptionBox(m,0,"PSG Volume",(void *)&psg_volume,0.01,0.0,5.0,0); config.psgBoostNoise ^= 1;
sprintf (items[1].text, "PSG Volume: %1.2f", psg_volume); sprintf (items[1].text, "PSG Noise Boost: %s", config.psgBoostNoise ? "ON":"OFF");
config.psg_preamp = (int)(psg_volume * 100.0); SN76489_BoostNoise(config.psgBoostNoise);
break; break;
case 2: case 2:
GUI_OptionBox(m,0,"FM Volume",(void *)&fm_volume,0.01,0.0,5.0,0); GUI_OptionBox(m,0,"PSG Volume",(void *)&psg_volume,0.01,0.0,5.0,0);
sprintf (items[2].text, "FM Volume: %1.2f", (double)config.fm_preamp/100.0); sprintf (items[2].text, "PSG Volume: %1.2f", psg_volume);
config.fm_preamp = (int)(fm_volume * 100.0); config.psg_preamp = (int)(psg_volume * 100.0);
break; break;
case 3: case 3:
GUI_OptionBox(m,0,"FM Volume",(void *)&fm_volume,0.01,0.0,5.0,0);
sprintf (items[3].text, "FM Volume: %1.2f", (double)config.fm_preamp/100.0);
config.fm_preamp = (int)(fm_volume * 100.0);
break;
case 4:
config.filter ++; config.filter ++;
if (config.filter > 2) config.filter = 0; if (config.filter > 2) config.filter = 0;
if (config.filter == 2) if (config.filter == 2)
sprintf (items[3].text, "Filtering: 3-BAND EQ"); sprintf (items[4].text, "Filtering: 3-BAND EQ");
else if (config.filter == 1) else if (config.filter == 1)
sprintf (items[3].text, "Filtering: LOW PASS"); sprintf (items[4].text, "Filtering: LOW PASS");
else else
sprintf (items[3].text, "Filtering: OFF"); sprintf (items[4].text, "Filtering: OFF");
if (config.filter < 2) if (config.filter < 2)
{ {
/* reset menu selection */ /* reset menu selection */
m->offset = 0; m->offset = 1;
m->selected = 3; m->selected = 3;
m->max_items = 4; m->max_items = 5;
} }
else else
{ {
/* enable items */ /* enable items */
m->max_items = 7; m->max_items = 8;
/* intialize EQ */ /* intialize EQ */
audio_init_equalizer(); audio_init_equalizer();
} }
break; break;
case 4:
GUI_OptionBox(m,0,"Low Gain",(void *)&config.lg,0.01,0.0,2.0,0);
sprintf (items[4].text, "Low Gain: %1.2f", config.lg);
audio_set_equalizer();
break;
case 5: case 5:
GUI_OptionBox(m,0,"Middle Gain",(void *)&config.mg,0.01,0.0,2.0,0); GUI_OptionBox(m,0,"Low Gain",(void *)&config.lg,0.01,0.0,2.0,0);
sprintf (items[5].text, "Middle Gain: %1.2f", config.mg); sprintf (items[5].text, "Low Gain: %1.2f", config.lg);
audio_set_equalizer(); audio_set_equalizer();
break; break;
case 6: case 6:
GUI_OptionBox(m,0,"Middle Gain",(void *)&config.mg,0.01,0.0,2.0,0);
sprintf (items[6].text, "Middle Gain: %1.2f", config.mg);
audio_set_equalizer();
break;
case 7:
GUI_OptionBox(m,0,"High Gain",(void *)&config.hg,0.01,0.0,2.0,0); GUI_OptionBox(m,0,"High Gain",(void *)&config.hg,0.01,0.0,2.0,0);
sprintf (items[6].text, "High Gain: %1.2f", config.hg); sprintf (items[7].text, "High Gain: %1.2f", config.hg);
audio_set_equalizer(); audio_set_equalizer();
break; break;
@ -807,7 +815,7 @@ static void soundmenu ()
} }
} }
m->max_items = 7; m->max_items = 8;
GUI_DeleteMenu(m); GUI_DeleteMenu(m);
} }

View File

@ -27,9 +27,9 @@
#define DEFAULT_PATH "/genplus" #define DEFAULT_PATH "/genplus"
#ifdef HW_RVL #ifdef HW_RVL
#define VERSION "version 1.3.2W" #define VERSION "version 1.3.2bW"
#else #else
#define VERSION "version 1.3.2G" #define VERSION "version 1.3.2bG"
#endif #endif
/* globals */ /* globals */

163
source/sound/blip.c Normal file
View File

@ -0,0 +1,163 @@
/* http://www.slack.net/~ant/ */
#include "blip.h"
#include <assert.h>
#include <string.h>
#include <stdlib.h>
#include <stddef.h>
/* Copyright (C) 2003-2008 Shay Green. This module is free software; you
can redistribute it and/or modify it under the terms of the GNU Lesser
General Public License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version. This
module is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
details. You should have received a copy of the GNU Lesser General Public
License along with this module; if not, write to the Free Software Foundation,
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
enum { buf_extra = 2 }; /* extra samples to save past end */
enum { time_bits = 16 }; /* bits in fraction of fixed-point sample counts */
enum { time_unit = 1 << time_bits };
enum { phase_bits = 15 }; /* bits in fraction of deltas in buffer */
enum { phase_count = 1 << phase_bits };
enum { phase_shift = time_bits - phase_bits };
typedef int buf_t; /* type of element in delta buffer */
struct blip_buffer_t
{
int factor; /* clocks to samples conversion factor */
int offset; /* fractional position of clock 0 in delta buffer */
int amp; /* current output amplitude (sum of all deltas up to now) */
int size; /* size of delta buffer */
buf_t buf [65536]; /* delta buffer, only size elements actually allocated */
};
blip_buffer_t* blip_alloc( int clock_rate, int sample_rate, int size )
{
/* Allocate space for structure and delta buffer */
blip_buffer_t* s = (blip_buffer_t*) malloc(
offsetof (blip_buffer_t, buf) + (size + buf_extra) * sizeof (buf_t) );
if ( s != NULL )
{
/* Calculate output:input ratio and convert to fixed-point */
double ratio = (double) sample_rate / clock_rate;
s->factor = (int) (ratio * time_unit + 0.5);
s->size = size;
blip_clear( s );
}
return s;
}
void blip_free( blip_buffer_t* s )
{
free( s );
}
void blip_clear( blip_buffer_t* s )
{
s->offset = 0;
s->amp = 0;
memset( s->buf, 0, (s->size + buf_extra) * sizeof (buf_t) );
}
void blip_add( blip_buffer_t* s, int clocks, int delta )
{
/* Convert to fixed-point time in terms of output samples */
int fixed_time = clocks * s->factor + s->offset;
/* Extract whole and fractional parts */
int index = fixed_time >> time_bits; /* whole */
int phase = fixed_time >> phase_shift & (phase_count - 1); /* fraction */
/* Split delta between first and second samples */
int second = delta * phase;
int first = delta * phase_count - second;
/* Be sure index is within buffer */
assert( index >= 0 && index+1 < s->size + buf_extra );
/* Add deltas to buffer */
s->buf [index ] += first;
s->buf [index+1] += second;
}
int blip_clocks_needed( const blip_buffer_t* s, int samples )
{
int fixed_needed;
if ( samples > s->size )
samples = s->size;
/* Fixed-point number of samples needed in addition to those in buffer */
fixed_needed = samples * time_unit - s->offset;
/* If more are needed, convert to clocks and round up */
return (fixed_needed <= 0) ? 0 : (fixed_needed - 1) / s->factor + 1;
}
void blip_end_frame( blip_buffer_t* s, int clocks )
{
s->offset += clocks * s->factor;
/* Ensure time wasn't past end of buffer */
assert( blip_samples_avail( s ) <= s->size );
}
int blip_samples_avail( const blip_buffer_t* s )
{
return s->offset >> time_bits;
}
/* Removes n samples from buffer */
static void remove_samples( blip_buffer_t* s, int n )
{
int remain = blip_samples_avail( s ) + buf_extra - n;
s->offset -= n * time_unit;
assert( s->offset >= 0 );
/* Copy remaining samples to beginning of buffer and clear the rest */
memmove( s->buf, &s->buf [n], remain * sizeof (buf_t) );
memset( &s->buf [remain], 0, n * sizeof (buf_t) );
}
int blip_read_samples( blip_buffer_t* s, short out [], int count, int stereo )
{
/* can't read more than available */
int avail = blip_samples_avail( s );
if ( count > avail )
count = avail;
if ( count )
{
/* Sum deltas and write out */
int i;
for ( i = 0; i < count; ++i )
{
int sample;
/* Apply slight high-pass filter */
s->amp -= s->amp >> 9;
/* Add next delta */
s->amp += s->buf [i];
/* Calculate output sample */
sample = s->amp >> phase_bits;
/* Keep within 16-bit sample range */
if ( sample < -32768 ) sample = -32768;
if ( sample > +32767 ) sample = +32767;
out [i << stereo] = sample;
}
remove_samples( s, count );
}
return count;
}

51
source/sound/blip.h Normal file
View File

@ -0,0 +1,51 @@
/* Fast sound synthesis buffer for use in real-time emulators of electronic
sound generator chips like those in early video game consoles. Uses linear
interpolation. Higher-quality versions are available that use sinc-based
band-limited synthesis. */
#ifndef BLIP_H
#define BLIP_H
#ifdef __cplusplus
extern "C" {
#endif
/* Creates a new blip_buffer with specified input clock rate, output
sample rate, and size (in samples), or returns NULL if out of memory. */
typedef struct blip_buffer_t blip_buffer_t;
blip_buffer_t* blip_alloc( int clock_rate, int sample_rate, int size );
/* Frees memory used by a blip_buffer. No effect if NULL is passed. */
void blip_free( blip_buffer_t* );
/* Removes all samples and clears buffer. */
void blip_clear( blip_buffer_t* );
/* Adds an amplitude transition of delta at specified time in source clocks.
Delta can be negative. */
void blip_add( blip_buffer_t*, int time, int delta );
/* Number of additional clocks needed until n samples will be available.
If buffer cannot even hold n samples, returns number of clocks until buffer
becomes full. */
int blip_clocks_needed( const blip_buffer_t*, int samples_needed );
/* Ends current time frame of specified duration and make its samples available
(along with any still-unread samples) for reading with read_samples(), then
begins a new time frame at the end of the current frame. */
void blip_end_frame( blip_buffer_t*, int duration );
/* Number of samples available for reading with read(). */
int blip_samples_avail( const blip_buffer_t* );
/* Reads at most n samples out of buffer into out, removing them from from
the buffer. Returns number of samples actually read and removed. If stereo is
true, increments 'out' one extra time after writing each sample, to allow
easy interleving of two channels into a stereo output buffer. */
int blip_read_samples( blip_buffer_t*, short out [], int n, int stereo );
#ifdef __cplusplus
}
#endif
#endif

View File

@ -19,47 +19,62 @@
- Removed SN76489_GetValues(). - Removed SN76489_GetValues().
- Removed some unused variables. - Removed some unused variables.
25/04/07 Eke-Eke 25/04/07 Eke-Eke (Genesis Plus GX)
Modified for use with GenesisPlus Gamecube's port: - Removed stereo GG support (unused)
- made SN76489_Update outputs 16bits mono samples - Rade SN76489_Update outputs 16bits mono samples
- replaced volume table with VGM plugin's one - Replaced volume table with VGM plugin's one
05/01/2009 Eke-Eke 05/01/09 Eke-Eke (Genesis Plus GX)
Modified Cut-Off frequency (according to Steve Snake: http://www.smspower.org/forums/viewtopic.php?t=1746) - Modified Cut-Off frequency (according to Steve Snake: http://www.smspower.org/forums/viewtopic.php?t=1746)
25/05/09 Eke-Eke (Genesis Plus GX)
- Removed multichip support (unused)
- Removed alternate volume table, panning & mute support (unused)
- Removed configurable Feedback and Shift Register Width (always use Sega ones)
- Added linear resampling using Blip Buffer (Blargg's implementation: http://www.smspower.org/forums/viewtopic.php?t=11376)
*/ */
#include "shared.h" #include "shared.h"
#include "blip.h"
#include <float.h>
#include <float.h> // for FLT_MIN /* Initial state of shift register */
#include <string.h> // for memcpy #define NoiseInitialState 0x8000
#define NoiseInitialState 0x8000 /* Initial state of shift register */ /* Value below which PSG does not output */
#define PSG_CUTOFF 0x1 /* Value below which PSG does not output */ /*#define PSG_CUTOFF 0x6*/
#define PSG_CUTOFF 0x1
static const int PSGVolumeValues[2][16] = { static const int PSGVolumeValues[16] =
{
/* These values are taken from a real SMS2's output */ /* These values are taken from a real SMS2's output */
{892,892,892,760,623,497,404,323,257,198,159,123,96,75,60,0}, /* I can't remember why 892... :P some scaling I did at some point */ /*{892,892,892,760,623,497,404,323,257,198,159,123,96,75,60,0}, *//* I can't remember why 892... :P some scaling I did at some point */
/* these values are true volumes for 2dB drops at each step (multiply previous by 10^-0.1), normalised at 760 */ /* these values are true volumes for 2dB drops at each step (multiply previous by 10^-0.1), normalised at 760 */
{1516,1205,957,760,603,479,381,303,240,191,152,120,96,76,60,0} 1516,1205,957,760,603,479,381,303,240,191,152,120,96,76,60,0
}; };
static SN76489_Context SN76489[MAX_SN76489]; static SN76489_Context SN76489;
void SN76489_Init(int which, int PSGClockValue, int SamplingRate) void SN76489_Init(int PSGClockValue, int SamplingRate)
{ {
SN76489_Context *p = &SN76489[which]; SN76489_Context *p = &SN76489;
p->dClock=(float)(PSGClockValue)/16.0/(float)SamplingRate;
SN76489_Config(which, MUTE_ALLON, VOL_FULL, FB_SEGAVDP, SRW_SEGAVDP, 1); /* first unallocate memory */
SN76489_Reset(which); SN76489_Shutdown();
/* SamplingRate*16 instead of PSGClockValue/16 since division would lose some
precision. blip_alloc doesn't care about the absolute sampling rate, just the
ratio to clock rate. */
p->blip_buffer = blip_alloc(PSGClockValue, SamplingRate * 16, SamplingRate / 4);
SN76489_Reset();
} }
void SN76489_Reset(int which) void SN76489_Reset()
{ {
SN76489_Context *p = &SN76489[which]; SN76489_Context *p = &SN76489;
int i; int i;
p->PSGStereo = 0xFF;
for(i = 0; i <= 3; i++) for(i = 0; i <= 3; i++)
{ {
/* Initialise PSG state */ /* Initialise PSG state */
@ -73,11 +88,8 @@ void SN76489_Reset(int which)
/* Set flip-flops to 1 */ /* Set flip-flops to 1 */
p->ToneFreqPos[i] = 1; p->ToneFreqPos[i] = 1;
/* Set intermediate positions to do-not-use value */ /* Clear current amplitudes in Blip delta buffer */
p->IntermediatePos[i] = FLT_MIN; p->chan_amp[i] = 0;
/* Set panning to centre */
p->panning[0]=127;
} }
p->LatchedRegister=0; p->LatchedRegister=0;
@ -85,38 +97,30 @@ void SN76489_Reset(int which)
/* Initialise noise generator */ /* Initialise noise generator */
p->NoiseShiftRegister=NoiseInitialState; p->NoiseShiftRegister=NoiseInitialState;
/* Zero clock */ /* Clear Blip delta buffer */
p->Clock=0; blip_clear(p->blip_buffer);
} }
void SN76489_Shutdown(void) void SN76489_Shutdown(void)
{ {
SN76489_Context *p = &SN76489;
if (p->blip_buffer) blip_free(p->blip_buffer);
p->blip_buffer = NULL;
} }
void SN76489_Config(int which, int mute, int volume, int feedback, int sr_width, int boost_noise) void SN76489_SetContext(uint8 *data)
{ {
SN76489_Context *p = &SN76489[which]; memcpy(&SN76489, data, sizeof(SN76489_Context));
p->Mute = mute;
p->VolumeArray = volume;
p->WhiteNoiseFeedback = feedback;
p->SRWidth = sr_width;
p->BoostNoise = boost_noise;
} }
void SN76489_SetContext(int which, uint8 *data) void SN76489_GetContext(uint8 *data)
{ {
memcpy(&SN76489[which], data, sizeof(SN76489_Context)); memcpy(data, &SN76489, sizeof(SN76489_Context));
} }
void SN76489_GetContext(int which, uint8 *data) uint8 *SN76489_GetContextPtr(void)
{ {
memcpy(data, &SN76489[which], sizeof(SN76489_Context)); return (uint8 *)&SN76489;
}
uint8 *SN76489_GetContextPtr(int which)
{
return (uint8 *)&SN76489[which];
} }
int SN76489_GetContextSize(void) int SN76489_GetContextSize(void)
@ -124,9 +128,9 @@ int SN76489_GetContextSize(void)
return sizeof(SN76489_Context); return sizeof(SN76489_Context);
} }
void SN76489_Write(int which, int data) void SN76489_Write(int data)
{ {
SN76489_Context *p = &SN76489[which]; SN76489_Context *p = &SN76489;
if (data&0x80) if (data&0x80)
{ {
@ -165,137 +169,146 @@ void SN76489_Write(int which, int data)
} }
} }
void SN76489_GGStereoWrite(int which, int data) /* Updates channel amplitude in delta buffer. Call whenever amplitude might have changed. */
static inline void UpdateChanAmplitude(SN76489_Context* chip, int i, int time)
{ {
SN76489_Context *p = &SN76489[which]; /* Build stereo result into buffer */
p->PSGStereo=data; int buffer = chip->Channels[i];
}
void SN76489_Update(int which, INT16 *buffer, int length) /* Update amplitudes in left and right buffers */
{ int delta = buffer - chip->chan_amp[i];
SN76489_Context *p = &SN76489[which]; if (delta != 0)
int i, j;
for(j = 0; j < length; j++)
{ {
/* update output */ chip->chan_amp[i] = buffer;
for (i=0;i<=2;++i) blip_add(chip->blip_buffer, time, delta);
if (p->IntermediatePos[i]!=FLT_MIN)
p->Channels[i]=(short)((p->Mute >> i & 0x1)*PSGVolumeValues[p->VolumeArray][p->Registers[2*i+1]]*p->IntermediatePos[i]);
else
p->Channels[i]=(p->Mute >> i & 0x1)*PSGVolumeValues[p->VolumeArray][p->Registers[2*i+1]]*p->ToneFreqPos[i];
p->Channels[3]=(short)((p->Mute >> 3 & 0x1)*PSGVolumeValues[p->VolumeArray][p->Registers[7]]*(p->NoiseShiftRegister & 0x1));
if (p->BoostNoise) p->Channels[3]<<=1; /* double noise volume */
buffer[j] =0;
for (i=0;i<=3;++i) buffer[j] += p->Channels[i];
/* update tone */
p->Clock+=p->dClock;
p->NumClocksForSample=(int)p->Clock; /* truncates */
p->Clock-=p->NumClocksForSample; /* remove integer part */
/* Looks nicer in Delphi... */
/* Clock:=Clock+p->dClock; */
/* NumClocksForSample:=Trunc(Clock); */
/* Clock:=Frac(Clock); */
/* Decrement tone channel counters */
for (i=0;i<=2;++i)
p->ToneFreqVals[i]-=p->NumClocksForSample;
/* Noise channel: match to tone2 or decrement its counter */
if (p->NoiseFreq==0x80) p->ToneFreqVals[3]=p->ToneFreqVals[2];
else p->ToneFreqVals[3]-=p->NumClocksForSample;
/* Tone channels: */
for (i=0;i<=2;++i) {
if (p->ToneFreqVals[i]<=0) { /* If it gets below 0... */
if (p->Registers[i*2]>PSG_CUTOFF) {
/* Calculate how much of the sample is + and how much is - */
/* Go to floating point and include the clock fraction for extreme accuracy :D */
/* Store as long int, maybe it's faster? I'm not very good at this */
p->IntermediatePos[i]=(p->NumClocksForSample-p->Clock+2*p->ToneFreqVals[i])*p->ToneFreqPos[i]/(p->NumClocksForSample+p->Clock);
p->ToneFreqPos[i]=-p->ToneFreqPos[i]; /* Flip the flip-flop */
} else {
p->ToneFreqPos[i]=1; /* stuck value */
p->IntermediatePos[i]=FLT_MIN;
}
p->ToneFreqVals[i]+=p->Registers[i*2]*(p->NumClocksForSample/p->Registers[i*2]+1);
} else p->IntermediatePos[i]=FLT_MIN;
}
/* Noise channel */
if (p->ToneFreqVals[3]<=0) { /* If it gets below 0... */
p->ToneFreqPos[3]=-p->ToneFreqPos[3]; /* Flip the flip-flop */
if (p->NoiseFreq!=0x80) /* If not matching tone2, reset counter */
p->ToneFreqVals[3]+=p->NoiseFreq*(p->NumClocksForSample/p->NoiseFreq+1);
if (p->ToneFreqPos[3]==1) { /* Only once per cycle... */
int Feedback;
if (p->Registers[6]&0x4) { /* White noise */
/* Calculate parity of fed-back bits for feedback */
switch (p->WhiteNoiseFeedback) {
/* Do some optimised calculations for common (known) feedback values */
case 0x0003: /* SC-3000, BBC %00000011 */
case 0x0009: /* SMS, GG, MD %00001001 */
/* If two bits fed back, I can do Feedback=(nsr & fb) && (nsr & fb ^ fb) */
/* since that's (one or more bits set) && (not all bits set) */
Feedback=((p->NoiseShiftRegister&p->WhiteNoiseFeedback) && ((p->NoiseShiftRegister&p->WhiteNoiseFeedback)^p->WhiteNoiseFeedback));
break;
default: /* Default handler for all other feedback values */
Feedback=p->NoiseShiftRegister&p->WhiteNoiseFeedback;
Feedback^=Feedback>>8;
Feedback^=Feedback>>4;
Feedback^=Feedback>>2;
Feedback^=Feedback>>1;
Feedback&=1;
break;
}
} else /* Periodic noise */
Feedback=p->NoiseShiftRegister&1;
p->NoiseShiftRegister=(p->NoiseShiftRegister>>1) | (Feedback << (p->SRWidth-1));
/* Original code: */
/* p->NoiseShiftRegister=(p->NoiseShiftRegister>>1) | ((p->Registers[6]&0x4?((p->NoiseShiftRegister&0x9) && (p->NoiseShiftRegister&0x9^0x9)):p->NoiseShiftRegister&1)<<15); */
}
}
} }
} }
/*void SN76489_UpdateOne(int which, int *l, int *r) /* Updates tone amplitude in delta buffer. Call whenever amplitude might have changed. */
static inline void UpdateToneAmplitude(SN76489_Context* chip, int i, int time)
{ {
INT16 tl,tr; /* Tone channels */
INT16 *buff[2]={&tl,&tr}; chip->Channels[i]= PSGVolumeValues[chip->Registers[2 * i + 1]] * chip->ToneFreqPos[i];
SN76489_Update(which,buff,1);
*l=tl;
*r=tr;
}*/
int SN76489_GetMute(int which) UpdateChanAmplitude(chip, i, time);
{
return SN76489[which].Mute;
} }
void SN76489_SetMute(int which, int val) /* Updates noise amplitude in delta buffer. Call whenever amplitude might have changed. */
static inline void UpdateNoiseAmplitude(SN76489_Context* chip, int time)
{ {
SN76489[which].Mute=val; /* Noise channel */
chip->Channels[3] = PSGVolumeValues[chip->Registers[7]] * ( chip->NoiseShiftRegister & 0x1 );
/* Boost noise volume */
chip->Channels[3] <<= chip->BoostNoise;
UpdateChanAmplitude(chip, 3, time);
} }
int SN76489_GetVolType(int which) /* Runs tone channel for clock_length clocks */
static inline void RunTone(SN76489_Context* chip, int i, int clock_length)
{ {
return SN76489[which].VolumeArray; int time;
/* Update in case a register changed etc. */
UpdateToneAmplitude(chip, i, 0);
/* Time of next transition */
time = chip->ToneFreqVals[i];
/* Process any transitions that occur within clocks we're running */
while (time < clock_length)
{
if (chip->Registers[i*2]>PSG_CUTOFF) {
/* Flip the flip-flop */
chip->ToneFreqPos[i] = -chip->ToneFreqPos[i];
} else {
/* stuck value */
chip->ToneFreqPos[i] = 1;
}
UpdateToneAmplitude(chip, i, time);
/* Advance to time of next transition */
time += chip->Registers[i*2] + 1;
}
/* Calculate new value for register, now that next transition is past number of clocks we're running */
chip->ToneFreqVals[i] = time - clock_length;
} }
void SN76489_SetVolType(int which, int val) /* Runs noise channel for clock_length clocks */
static inline void RunNoise(SN76489_Context* chip, int clock_length)
{ {
SN76489[which].VolumeArray=val; int time;
/* Noise channel: match to tone2 if in slave mode */
int NoiseFreq = chip->NoiseFreq;
if (NoiseFreq == 0x80)
{
NoiseFreq = chip->Registers[2*2];
chip->ToneFreqVals[3] = chip->ToneFreqVals[2];
}
/* Update in case a register changed etc. */
UpdateNoiseAmplitude(chip, 0);
/* Time of next transition */
time = chip->ToneFreqVals[3];
/* Process any transitions that occur within clocks we're running */
while ( time < clock_length )
{
/* Flip the flip-flop */
chip->ToneFreqPos[3] = -chip->ToneFreqPos[3];
if (chip->ToneFreqPos[3] == 1) {
/* On the positive edge of the square wave (only once per cycle) */
int Feedback;
if ( chip->Registers[6] & 0x4 ) {
/* White noise */
/* Calculate parity of fed-back bits for feedback */
/* Do some optimised calculations for common (known) feedback values */
/* If two bits fed back, I can do Feedback=(nsr & fb) && (nsr & fb ^ fb) */
/* since that's (one or more bits set) && (not all bits set) */
Feedback = ( ( chip->NoiseShiftRegister & FB_SEGAVDP)
&& ( (chip->NoiseShiftRegister & FB_SEGAVDP) ^ FB_SEGAVDP) );
} else /* Periodic noise */
Feedback=chip->NoiseShiftRegister&1;
chip->NoiseShiftRegister=(chip->NoiseShiftRegister>>1) | (Feedback << (SRW_SEGAVDP-1));
UpdateNoiseAmplitude(chip, time);
}
/* Advance to time of next transition */
time += NoiseFreq + 1;
}
/* Calculate new value for register, now that next transition is past number of clocks we're running */
chip->ToneFreqVals[3] = time - clock_length;
} }
void SN76489_SetPanning(int which, int ch0, int ch1, int ch2, int ch3) void SN76489_Update(INT16 *buffer, int length)
{ {
SN76489[which].panning[0]=ch0; int i;
SN76489[which].panning[1]=ch1;
SN76489[which].panning[2]=ch2; SN76489_Context *p = &SN76489;
SN76489[which].panning[3]=ch3;
/* Determine how many clocks we need to run until 'length' samples are available */
int clock_length = blip_clocks_needed(p->blip_buffer, length);
/* Run noise first, since it might use current value of third tone frequency counter */
RunNoise(p, clock_length);
/* Run tone channels */
for( i = 0; i <= 2; ++i )
RunTone(p, i, clock_length);
/* Read samples into output buffer */
blip_end_frame(p->blip_buffer,clock_length);
blip_read_samples(p->blip_buffer,buffer,length,0);
} }
void SN76489_BoostNoise(int boost)
{
SN76489.BoostNoise = boost;
}

View File

@ -19,86 +19,61 @@
- Removed SN76489_GetValues(). - Removed SN76489_GetValues().
- Removed some unused variables. - Removed some unused variables.
25/04/07 Eke-Eke 25/04/07 Eke-Eke (Genesis Plus GX)
Modified for use with GenesisPlus Gamecube's port: - Removed stereo GG support (unused)
- made SN76489_Update outputs 16bits mono samples - Rade SN76489_Update outputs 16bits mono samples
- replaced volume table with VGM plugin's one - Replaced volume table with VGM plugin's one
05/01/09 Eke-Eke (Genesis Plus GX)
- Modified Cut-Off frequency (according to Steve Snake: http://www.smspower.org/forums/viewtopic.php?t=1746)
25/05/09 Eke-Eke (Genesis Plus GX)
- Removed multichip support (unused)
- Removed alternate volume table, panning & mute support (unused)
- Removed configurable Feedback and Shift Register Width (always use Sega ones)
- Added linear resampling using Blip Buffer (Blargg's implementation: http://www.smspower.org/forums/viewtopic.php?t=11376)
*/ */
#ifndef _SN76489_H_ #ifndef _SN76489_H_
#define _SN76489_H_ #define _SN76489_H_
#define MAX_SN76489 1 /* SN76489 clone in Sega's VDP chips (315-5124, 315-5246, 315-5313, Game Gear) */
#define FB_SEGAVDP 0x0009
/* #define SRW_SEGAVDP 16
More testing is needed to find and confirm feedback patterns for
SN76489 variants and compatible chips.
*/
enum feedback_patterns {
FB_BBCMICRO = 0x8005, /* Texas Instruments TMS SN76489N (original) from BBC Micro computer */
FB_SC3000 = 0x0006, /* Texas Instruments TMS SN76489AN (rev. A) from SC-3000H computer */
FB_SEGAVDP = 0x0009, /* SN76489 clone in Sega's VDP chips (315-5124, 315-5246, 315-5313, Game Gear) */
};
enum sr_widths {
SRW_SC3000BBCMICRO = 15,
SRW_SEGAVDP = 16
};
enum volume_modes {
VOL_TRUNC = 0, /* Volume levels 13-15 are identical */
VOL_FULL = 1, /* Volume levels 13-15 are unique */
};
enum mute_values {
MUTE_ALLOFF = 0, /* All channels muted */
MUTE_TONE1 = 1, /* Tone 1 mute control */
MUTE_TONE2 = 2, /* Tone 2 mute control */
MUTE_TONE3 = 4, /* Tone 3 mute control */
MUTE_NOISE = 8, /* Noise mute control */
MUTE_ALLON = 15, /* All channels enabled */
};
typedef struct typedef struct
{ {
int Mute; // per-channel muting /* Configuration */
int VolumeArray; int BoostNoise; // double noise volume when non-zero
int BoostNoise; // double noise volume when non-zero
/* Variables */
float Clock;
float dClock;
int PSGStereo;
int NumClocksForSample;
int WhiteNoiseFeedback;
int SRWidth;
/* PSG registers: */
int Registers[8]; /* Tone, vol x4 */
int LatchedRegister;
int NoiseShiftRegister;
int NoiseFreq; /* Noise channel signal generator frequency */
/* Output calculation variables */
int ToneFreqVals[4]; /* Frequency register values (counters) */
int ToneFreqPos[4]; /* Frequency channel flip-flops */
int Channels[4]; /* Value of each channel, before stereo is applied */
float IntermediatePos[4]; /* intermediate values used at boundaries between + and - (does not need double accuracy)*/
int panning[4]; /* fake stereo - 0..127..254 */ /* PSG registers: */
int Registers[8]; /* Tone, vol x4 */
int LatchedRegister;
int NoiseShiftRegister;
int NoiseFreq; /* Noise channel signal generator frequency */
/* Output calculation variables */
int ToneFreqVals[4]; /* Frequency register values (counters) */
int ToneFreqPos[4]; /* Frequency channel flip-flops */
int Channels[4]; /* Value of each channel, before stereo is applied */
/* Blip-Buffer variables */
struct blip_buffer_t* blip_buffer; /* delta resampler */
int chan_amp[4]; /* current channel amplitudes in delta buffers */
} SN76489_Context; } SN76489_Context;
/* Function prototypes */ /* Function prototypes */
extern void SN76489_Init(int which, int PSGClockValue, int SamplingRate); extern void SN76489_Init(int PSGClockValue, int SamplingRate);
extern void SN76489_Reset(int which); extern void SN76489_Reset(void);
extern void SN76489_Config(int which, int mute, int volume, int feedback, int sw_width, int boost_noise); extern void SN76489_Shutdown(void);
extern void SN76489_SetContext(int which, uint8 *data); extern void SN76489_SetContext(uint8 *data);
extern void SN76489_GetContext(int which, uint8 *data); extern void SN76489_GetContext(uint8 *data);
extern uint8 *SN76489_GetContextPtr(int which); extern uint8 *SN76489_GetContextPtr(void);
extern int SN76489_GetContextSize(void); extern int SN76489_GetContextSize(void);
extern void SN76489_Write(int which, int data); extern void SN76489_Write(int data);
extern void SN76489_Update(int which, INT16 *buffer, int length); extern void SN76489_Update(INT16 *buffer, int length);
extern void SN76489_BoostNoise(int boost);
#endif /* _SN76489_H_ */ #endif /* _SN76489_H_ */

View File

@ -58,7 +58,7 @@ static inline void psg_update()
if(snd.psg.curStage - snd.psg.lastStage > 0) if(snd.psg.curStage - snd.psg.lastStage > 0)
{ {
int16 *tempBuffer = snd.psg.buffer + snd.psg.lastStage; int16 *tempBuffer = snd.psg.buffer + snd.psg.lastStage;
SN76489_Update (0, tempBuffer, snd.psg.curStage - snd.psg.lastStage); SN76489_Update(tempBuffer, snd.psg.curStage - snd.psg.lastStage);
snd.psg.lastStage = snd.psg.curStage; snd.psg.lastStage = snd.psg.curStage;
} }
} }
@ -74,15 +74,11 @@ void sound_init(int rate)
/* YM2612 is emulated at original frequency (VLCK/144) */ /* YM2612 is emulated at original frequency (VLCK/144) */
if (config.hq_fm) if (config.hq_fm)
{
m68cycles_per_sample[0] = 144; m68cycles_per_sample[0] = 144;
}
/* initialize sound chips */ /* initialize sound chips */
SN76489_Init(0, (int)zclk, rate); SN76489_Init((int)zclk,rate);
SN76489_Config(0, MUTE_ALLON, VOL_FULL, FB_SEGAVDP, SRW_SEGAVDP, 0); YM2612Init((int)vclk,rate);
YM2612Init ((int)vclk, rate);
} }
void sound_update(int fm_len, int psg_len) void sound_update(int fm_len, int psg_len)
@ -127,5 +123,5 @@ void psg_write(unsigned int cpu, unsigned int data)
{ {
snd.psg.curStage = psg_sample_cnt(cpu); snd.psg.curStage = psg_sample_cnt(cpu);
psg_update(); psg_update();
SN76489_Write(0, data); SN76489_Write(data);
} }

View File

@ -22,6 +22,7 @@
#include "shared.h" #include "shared.h"
static unsigned char state[STATE_SIZE]; static unsigned char state[STATE_SIZE];
#define load_param(param, size) \ #define load_param(param, size) \
@ -32,7 +33,7 @@ static unsigned char state[STATE_SIZE];
memcpy(&state[bufferptr], param, size); \ memcpy(&state[bufferptr], param, size); \
bufferptr+= size; bufferptr+= size;
void state_load(unsigned char *buffer) int state_load(unsigned char *buffer)
{ {
/* buffer size */ /* buffer size */
int bufferptr = 0; int bufferptr = 0;
@ -43,6 +44,14 @@ void state_load(unsigned char *buffer)
outbytes = STATE_SIZE; outbytes = STATE_SIZE;
uncompress ((Bytef *)state, &outbytes, (Bytef *)(buffer + 4), inbytes); uncompress ((Bytef *)state, &outbytes, (Bytef *)(buffer + 4), inbytes);
/* version check */
char version[16];
load_param(version,16);
if (strncmp(version,STATE_VERSION,16))
{
return 0;
}
/* reset system */ /* reset system */
system_reset(); system_reset();
m68k_memory_map[0].base = default_rom; m68k_memory_map[0].base = default_rom;
@ -81,7 +90,7 @@ void state_load(unsigned char *buffer)
bufferptr+= YM2612GetContextSize(); bufferptr+= YM2612GetContextSize();
// PSG // PSG
load_param(SN76489_GetContextPtr (0),SN76489_GetContextSize ()); load_param(SN76489_GetContextPtr(),SN76489_GetContextSize());
// 68000 // 68000
uint16 tmp16; uint16 tmp16;
@ -108,12 +117,19 @@ void state_load(unsigned char *buffer)
// Z80 // Z80
load_param(&Z80, sizeof(Z80_Regs)); load_param(&Z80, sizeof(Z80_Regs));
return 1;
} }
int state_save(unsigned char *buffer) int state_save(unsigned char *buffer)
{ {
/* buffer size */ /* buffer size */
int bufferptr = 0; int bufferptr = 16;
/* version string */
char version[16];
strncpy(version,STATE_VERSION,16);
save_param(version, 16);
// GENESIS // GENESIS
save_param(work_ram, sizeof(work_ram)); save_param(work_ram, sizeof(work_ram));
@ -145,7 +161,7 @@ int state_save(unsigned char *buffer)
save_param(YM2612GetContextPtr(),YM2612GetContextSize()); save_param(YM2612GetContextPtr(),YM2612GetContextSize());
// PSG // PSG
save_param(SN76489_GetContextPtr (0),SN76489_GetContextSize ()); save_param(SN76489_GetContextPtr(),SN76489_GetContextSize());
// 68000 // 68000
uint16 tmp16; uint16 tmp16;

View File

@ -23,10 +23,11 @@
#ifndef _STATE_H_ #ifndef _STATE_H_
#define _STATE_H_ #define _STATE_H_
#define STATE_SIZE 0x28000 #define STATE_SIZE 0x28000
#define STATE_VERSION "GENPLUS-GX 1.3.2"
/* Function prototypes */ /* Function prototypes */
extern void state_load(unsigned char *buffer); extern int state_load(unsigned char *buffer);
extern int state_save(unsigned char *buffer); extern int state_save(unsigned char *buffer);
#endif #endif

View File

@ -272,7 +272,7 @@ void system_reset (void)
vdp_reset (); vdp_reset ();
render_reset (); render_reset ();
io_reset(); io_reset();
SN76489_Reset(0); SN76489_Reset();
/* Sound Buffers */ /* Sound Buffers */
if (snd.psg.buffer) memset (snd.psg.buffer, 0, SND_SIZE); if (snd.psg.buffer) memset (snd.psg.buffer, 0, SND_SIZE);
@ -288,6 +288,7 @@ void system_shutdown (void)
gen_shutdown (); gen_shutdown ();
vdp_shutdown (); vdp_shutdown ();
render_shutdown (); render_shutdown ();
SN76489_Shutdown();
} }
/**************************************************************** /****************************************************************