Initial commit

This commit is contained in:
Anthony Wang 2020-05-16 18:37:34 -05:00
parent b73ca90cf0
commit 63cf8eb131
84 changed files with 14548 additions and 1 deletions

5
.gitignore vendored Normal file
View file

@ -0,0 +1,5 @@
.sconsign.dblite
badnes
*.o
roms
*.nes

3
.gitmodules vendored Normal file
View file

@ -0,0 +1,3 @@
[submodule "simpleini"]
path = simpleini
url = https://github.com/brofield/simpleini.git

View file

@ -1,2 +1,11 @@
# BadNES
An extremely minimal work-in-progress NES emulator
```
____ _ _ _ ______ _____
| _ \ | | \ | | ____|/ ____|
| |_) | __ _ __| | \| | |__ | (___
| _ < / _` |/ _` | . ` | __| \___ \
| |_) | (_| | (_| | |\ | |____ ____) |
|____/ \__,_|\__,_|_| \_|______|_____/
EMULATOR
```
An extremely minimal work-in-progress NES emulator based on [LaiNES](https://github.com/AndreaOrru/LaiNES).

15
SConstruct Normal file
View file

@ -0,0 +1,15 @@
from os import environ
VariantDir('build/src', 'src', duplicate=0)
VariantDir('build/lib', 'lib', duplicate=0)
flags = ['-O3', '-march=native', '-std=c++14']
env = Environment(ENV = environ,
CXX = 'clang++',
CPPFLAGS = ['-Wno-unused-value'],
CXXFLAGS = flags,
LINKFLAGS = flags,
CPPPATH = ['#simpleini', '#lib/include', '#src/include'],
LIBS = ['SDL2', 'SDL2_image', 'SDL2_ttf'])
env.Program('badnes', Glob('build/*/*.cpp') + Glob('build/*/*/*.cpp'))

398
lib/Blip_Buffer.cpp Normal file
View file

@ -0,0 +1,398 @@
// Blip_Buffer 0.3.3. http://www.slack.net/~ant/libs/
#include "Blip_Buffer.h"
#include <string.h>
#include <math.h>
/* Copyright (C) 2003-2005 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
#include BLARGG_SOURCE_BEGIN
Blip_Buffer::Blip_Buffer()
{
samples_per_sec = 44100;
buffer_ = NULL;
// try to cause assertion failure if buffer is used before these are set
clocks_per_sec = 0;
factor_ = ~0ul;
offset_ = 0;
buffer_size_ = 0;
length_ = 0;
bass_freq_ = 16;
}
void Blip_Buffer::clear( bool entire_buffer )
{
long count = (entire_buffer ? buffer_size_ : samples_avail());
offset_ = 0;
reader_accum = 0;
memset( buffer_, sample_offset & 0xFF, (count + widest_impulse_) * sizeof (buf_t_) );
}
blargg_err_t Blip_Buffer::sample_rate( long new_rate, int msec )
{
unsigned new_size = (UINT_MAX >> BLIP_BUFFER_ACCURACY) + 1 - widest_impulse_ - 64;
if ( msec != blip_default_length )
{
size_t s = (new_rate * (msec + 1) + 999) / 1000;
if ( s < new_size )
new_size = s;
else
require( false ); // requested buffer length exceeds limit
}
if ( buffer_size_ != new_size )
{
delete [] buffer_;
buffer_ = NULL; // allow for exception in allocation below
buffer_size_ = 0;
offset_ = 0;
buffer_ = BLARGG_NEW buf_t_ [new_size + widest_impulse_];
BLARGG_CHECK_ALLOC( buffer_ );
}
buffer_size_ = new_size;
length_ = new_size * 1000 / new_rate - 1;
if ( msec )
assert( length_ == msec ); // ensure length is same as that passed in
samples_per_sec = new_rate;
if ( clocks_per_sec )
clock_rate( clocks_per_sec ); // recalculate factor
bass_freq( bass_freq_ ); // recalculate shift
clear();
return blargg_success;
}
void Blip_Buffer::clock_rate( long cps )
{
clocks_per_sec = cps;
factor_ = (unsigned long) floor( (double) samples_per_sec / cps *
(1L << BLIP_BUFFER_ACCURACY) + 0.5 );
require( factor_ > 0 ); // clock_rate/sample_rate ratio is too large
}
Blip_Buffer::~Blip_Buffer()
{
delete [] buffer_;
}
void Blip_Buffer::bass_freq( int freq )
{
bass_freq_ = freq;
if ( freq == 0 ) {
bass_shift = 31; // 32 or greater invokes undefined behavior elsewhere
return;
}
bass_shift = 1 + (int) floor( 1.442695041 * log( 0.124 * samples_per_sec / freq ) );
if ( bass_shift < 0 )
bass_shift = 0;
if ( bass_shift > 24 )
bass_shift = 24;
}
long Blip_Buffer::count_samples( blip_time_t t ) const {
return (resampled_time( t ) >> BLIP_BUFFER_ACCURACY) - (offset_ >> BLIP_BUFFER_ACCURACY);
}
void Blip_Impulse_::init( blip_pair_t_* imps, int w, int r, int fb )
{
fine_bits = fb;
width = w;
impulses = (imp_t*) imps;
generate = true;
volume_unit_ = -1.0;
res = r;
buf = NULL;
impulse = &impulses [width * res * 2 * (fine_bits ? 2 : 1)];
offset = 0;
}
const int impulse_bits = 15;
const long impulse_amp = 1L << impulse_bits;
const long impulse_offset = impulse_amp / 2;
void Blip_Impulse_::scale_impulse( int unit, imp_t* imp_in ) const
{
long offset = ((long) unit << impulse_bits) - impulse_offset * unit +
(1 << (impulse_bits - 1));
imp_t* imp = imp_in;
imp_t* fimp = impulse;
for ( int n = res / 2 + 1; n--; )
{
int error = unit;
for ( int nn = width; nn--; )
{
long a = ((long) *fimp++ * unit + offset) >> impulse_bits;
error -= a - unit;
*imp++ = (imp_t) a;
}
// add error to middle
imp [-width / 2 - 1] += (imp_t) error;
}
if ( res > 2 ) {
// second half is mirror-image
const imp_t* rev = imp - width - 1;
for ( int nn = (res / 2 - 1) * width - 1; nn--; )
*imp++ = *--rev;
*imp++ = (imp_t) unit;
}
// copy to odd offset
*imp++ = (imp_t) unit;
memcpy( imp, imp_in, (res * width - 1) * sizeof *imp );
}
const int max_res = 1 << blip_res_bits_;
void Blip_Impulse_::fine_volume_unit()
{
// to do: find way of merging in-place without temporary buffer
imp_t temp [max_res * 2 * Blip_Buffer::widest_impulse_];
scale_impulse( (offset & 0xffff) << fine_bits, temp );
imp_t* imp2 = impulses + res * 2 * width;
scale_impulse( offset & 0xffff, imp2 );
// merge impulses
imp_t* imp = impulses;
imp_t* src2 = temp;
for ( int n = res / 2 * 2 * width; n--; ) {
*imp++ = *imp2++;
*imp++ = *imp2++;
*imp++ = *src2++;
*imp++ = *src2++;
}
}
void Blip_Impulse_::volume_unit( double new_unit )
{
if ( new_unit == volume_unit_ )
return;
if ( generate )
treble_eq( blip_eq_t( -8.87, 8800, 44100 ) );
volume_unit_ = new_unit;
offset = 0x10001 * (unsigned long) floor( volume_unit_ * 0x10000 + 0.5 );
if ( fine_bits )
fine_volume_unit();
else
scale_impulse( offset & 0xffff, impulses );
}
static const double pi = 3.1415926535897932384626433832795029L;
void Blip_Impulse_::treble_eq( const blip_eq_t& new_eq )
{
if ( !generate && new_eq.treble == eq.treble && new_eq.cutoff == eq.cutoff &&
new_eq.sample_rate == eq.sample_rate )
return; // already calculated with same parameters
generate = false;
eq = new_eq;
double treble = pow( 10.0, 1.0 / 20 * eq.treble ); // dB (-6dB = 0.50)
if ( treble < 0.000005 )
treble = 0.000005;
const double treble_freq = 22050.0; // treble level at 22 kHz harmonic
const double sample_rate = eq.sample_rate;
const double pt = treble_freq * 2 / sample_rate;
double cutoff = eq.cutoff * 2 / sample_rate;
if ( cutoff >= pt * 0.95 || cutoff >= 0.95 ) {
cutoff = 0.5;
treble = 1.0;
}
// DSF Synthesis (See T. Stilson & J. Smith (1996),
// Alias-free digital synthesis of classic analog waveforms)
// reduce adjacent impulse interference by using small part of wide impulse
const double n_harm = 4096;
const double rolloff = pow( treble, 1.0 / (n_harm * pt - n_harm * cutoff) );
const double rescale = 1.0 / pow( rolloff, n_harm * cutoff );
const double pow_a_n = rescale * pow( rolloff, n_harm );
const double pow_a_nc = rescale * pow( rolloff, n_harm * cutoff );
double total = 0.0;
const double to_angle = pi / 2 / n_harm / max_res;
float buf [max_res * (Blip_Buffer::widest_impulse_ - 2) / 2];
const int size = max_res * (width - 2) / 2;
for ( int i = size; i--; )
{
double angle = (i * 2 + 1) * to_angle;
// equivalent
//double y = dsf( angle, n_harm * cutoff, 1.0 );
//y -= rescale * dsf( angle, n_harm * cutoff, rolloff );
//y += rescale * dsf( angle, n_harm, rolloff );
const double cos_angle = cos( angle );
const double cos_nc_angle = cos( n_harm * cutoff * angle );
const double cos_nc1_angle = cos( (n_harm * cutoff - 1.0) * angle );
double b = 2.0 - 2.0 * cos_angle;
double a = 1.0 - cos_angle - cos_nc_angle + cos_nc1_angle;
double d = 1.0 + rolloff * (rolloff - 2.0 * cos_angle);
double c = pow_a_n * rolloff * cos( (n_harm - 1.0) * angle ) -
pow_a_n * cos( n_harm * angle ) -
pow_a_nc * rolloff * cos_nc1_angle +
pow_a_nc * cos_nc_angle;
// optimization of a / b + c / d
double y = (a * d + c * b) / (b * d);
// fixed window which affects wider impulses more
if ( width > 12 ) {
double window = cos( n_harm / 1.25 / Blip_Buffer::widest_impulse_ * angle );
y *= window * window;
}
total += (float) y;
buf [i] = (float) y;
}
// integrate runs of length 'max_res'
double factor = impulse_amp * 0.5 / total; // 0.5 accounts for other mirrored half
imp_t* imp = impulse;
const int step = max_res / res;
int offset = res > 1 ? max_res : max_res / 2;
for ( int n = res / 2 + 1; n--; offset -= step )
{
for ( int w = -width / 2; w < width / 2; w++ )
{
double sum = 0;
for ( int i = max_res; i--; )
{
int index = w * max_res + offset + i;
if ( index < 0 )
index = -index - 1;
if ( index < size )
sum += buf [index];
}
*imp++ = (imp_t) floor( sum * factor + (impulse_offset + 0.5) );
}
}
// rescale
double unit = volume_unit_;
if ( unit >= 0 ) {
volume_unit_ = -1;
volume_unit( unit );
}
}
void Blip_Buffer::remove_samples( long count )
{
require( buffer_ ); // sample rate must have been set
if ( !count ) // optimization
return;
remove_silence( count );
// Allows synthesis slightly past time passed to end_frame(), as long as it's
// not more than an output sample.
// to do: kind of hacky, could add run_until() which keeps track of extra synthesis
int const copy_extra = 1;
// copy remaining samples to beginning and clear old samples
long remain = samples_avail() + widest_impulse_ + copy_extra;
if ( count >= remain )
memmove( buffer_, buffer_ + count, remain * sizeof (buf_t_) );
else
memcpy( buffer_, buffer_ + count, remain * sizeof (buf_t_) );
memset( buffer_ + remain, sample_offset & 0xFF, count * sizeof (buf_t_) );
}
#include BLARGG_ENABLE_OPTIMIZER
long Blip_Buffer::read_samples( blip_sample_t* out, long max_samples, bool stereo )
{
require( buffer_ ); // sample rate must have been set
long count = samples_avail();
if ( count > max_samples )
count = max_samples;
if ( !count )
return 0; // optimization
int sample_offset = this->sample_offset;
int bass_shift = this->bass_shift;
buf_t_* buf = buffer_;
long accum = reader_accum;
if ( !stereo ) {
for ( long n = count; n--; ) {
long s = accum >> accum_fract;
accum -= accum >> bass_shift;
accum += (long (*buf++) - sample_offset) << accum_fract;
*out++ = (blip_sample_t) s;
// clamp sample
if ( (BOOST::int16_t) s != s )
out [-1] = blip_sample_t (0x7FFF - (s >> 24));
}
}
else {
for ( long n = count; n--; ) {
long s = accum >> accum_fract;
accum -= accum >> bass_shift;
accum += (long (*buf++) - sample_offset) << accum_fract;
*out = (blip_sample_t) s;
out += 2;
// clamp sample
if ( (BOOST::int16_t) s != s )
out [-2] = blip_sample_t (0x7FFF - (s >> 24));
}
}
reader_accum = accum;
remove_samples( count );
return count;
}
void Blip_Buffer::mix_samples( const blip_sample_t* in, long count )
{
buf_t_* buf = &buffer_ [(offset_ >> BLIP_BUFFER_ACCURACY) + (widest_impulse_ / 2 - 1)];
int prev = 0;
while ( count-- ) {
int s = *in++;
*buf += s - prev;
prev = s;
++buf;
}
*buf -= *--in;
}

201
lib/Multi_Buffer.cpp Normal file
View file

@ -0,0 +1,201 @@
// Blip_Buffer 0.3.3. http://www.slack.net/~ant/libs/
#include "Multi_Buffer.h"
/* Copyright (C) 2003-2005 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
#include BLARGG_SOURCE_BEGIN
Multi_Buffer::Multi_Buffer( int spf ) : samples_per_frame_( spf )
{
length_ = 0;
sample_rate_ = 0;
}
blargg_err_t Multi_Buffer::set_channel_count( int )
{
return blargg_success;
}
Mono_Buffer::Mono_Buffer() : Multi_Buffer( 1 )
{
}
Mono_Buffer::~Mono_Buffer()
{
}
blargg_err_t Mono_Buffer::sample_rate( long rate, int msec )
{
BLARGG_RETURN_ERR( buf.sample_rate( rate, msec ) );
return Multi_Buffer::sample_rate( buf.sample_rate(), buf.length() );
}
Mono_Buffer::channel_t Mono_Buffer::channel( int index )
{
channel_t ch;
ch.center = &buf;
ch.left = &buf;
ch.right = &buf;
return ch;
}
void Mono_Buffer::end_frame( blip_time_t t, bool )
{
buf.end_frame( t );
}
Stereo_Buffer::Stereo_Buffer() : Multi_Buffer( 2 )
{
chan.center = &bufs [0];
chan.left = &bufs [1];
chan.right = &bufs [2];
}
Stereo_Buffer::~Stereo_Buffer()
{
}
blargg_err_t Stereo_Buffer::sample_rate( long rate, int msec )
{
for ( int i = 0; i < buf_count; i++ )
BLARGG_RETURN_ERR( bufs [i].sample_rate( rate, msec ) );
return Multi_Buffer::sample_rate( bufs [0].sample_rate(), bufs [0].length() );
}
void Stereo_Buffer::clock_rate( long rate )
{
for ( int i = 0; i < buf_count; i++ )
bufs [i].clock_rate( rate );
}
void Stereo_Buffer::bass_freq( int bass )
{
for ( unsigned i = 0; i < buf_count; i++ )
bufs [i].bass_freq( bass );
}
void Stereo_Buffer::clear()
{
stereo_added = false;
was_stereo = false;
for ( int i = 0; i < buf_count; i++ )
bufs [i].clear();
}
void Stereo_Buffer::end_frame( blip_time_t clock_count, bool stereo )
{
for ( unsigned i = 0; i < buf_count; i++ )
bufs [i].end_frame( clock_count );
stereo_added |= stereo;
}
long Stereo_Buffer::read_samples( blip_sample_t* out, long count )
{
require( !(count & 1) ); // count must be even
count = (unsigned) count / 2;
long avail = bufs [0].samples_avail();
if ( count > avail )
count = avail;
if ( count )
{
if ( stereo_added || was_stereo )
{
mix_stereo( out, count );
bufs [0].remove_samples( count );
bufs [1].remove_samples( count );
bufs [2].remove_samples( count );
}
else
{
mix_mono( out, count );
bufs [0].remove_samples( count );
bufs [1].remove_silence( count );
bufs [2].remove_silence( count );
}
// to do: this might miss opportunities for optimization
if ( !bufs [0].samples_avail() ) {
was_stereo = stereo_added;
stereo_added = false;
}
}
return count * 2;
}
#include BLARGG_ENABLE_OPTIMIZER
void Stereo_Buffer::mix_stereo( blip_sample_t* out, long count )
{
Blip_Reader left;
Blip_Reader right;
Blip_Reader center;
left.begin( bufs [1] );
right.begin( bufs [2] );
int bass = center.begin( bufs [0] );
while ( count-- )
{
int c = center.read();
long l = c + left.read();
long r = c + right.read();
center.next( bass );
out [0] = l;
out [1] = r;
out += 2;
if ( (BOOST::int16_t) l != l )
out [-2] = 0x7FFF - (l >> 24);
left.next( bass );
right.next( bass );
if ( (BOOST::int16_t) r != r )
out [-1] = 0x7FFF - (r >> 24);
}
center.end( bufs [0] );
right.end( bufs [2] );
left.end( bufs [1] );
}
void Stereo_Buffer::mix_mono( blip_sample_t* out, long count )
{
Blip_Reader in;
int bass = in.begin( bufs [0] );
while ( count-- )
{
long s = in.read();
in.next( bass );
out [0] = s;
out [1] = s;
out += 2;
if ( (BOOST::int16_t) s != s ) {
s = 0x7FFF - (s >> 24);
out [-2] = s;
out [-1] = s;
}
}
in.end( bufs [0] );
}

341
lib/Nes_Apu.cpp Normal file
View file

@ -0,0 +1,341 @@
// Nes_Snd_Emu 0.1.7. http://www.slack.net/~ant/libs/
#include "Nes_Apu.h"
/* Copyright (C) 2003-2005 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
#include BLARGG_SOURCE_BEGIN
Nes_Apu::Nes_Apu()
{
dmc.apu = this;
dmc.rom_reader = NULL;
square1.synth = &square_synth;
square2.synth = &square_synth;
irq_notifier_ = NULL;
oscs [0] = &square1;
oscs [1] = &square2;
oscs [2] = &triangle;
oscs [3] = &noise;
oscs [4] = &dmc;
output( NULL );
volume( 1.0 );
reset( false );
}
Nes_Apu::~Nes_Apu()
{
}
void Nes_Apu::treble_eq( const blip_eq_t& eq )
{
square_synth.treble_eq( eq );
triangle.synth.treble_eq( eq );
noise.synth.treble_eq( eq );
dmc.synth.treble_eq( eq );
}
void Nes_Apu::buffer_cleared()
{
square1.last_amp = 0;
square2.last_amp = 0;
triangle.last_amp = 0;
noise.last_amp = 0;
dmc.last_amp = 0;
}
void Nes_Apu::enable_nonlinear( double v )
{
dmc.nonlinear = true;
square_synth.volume( 1.3 * 0.25751258 / 0.742467605 * 0.25 * v );
const double tnd = 0.75 / 202 * 0.48;
triangle.synth.volume_unit( 3 * tnd );
noise.synth.volume_unit( 2 * tnd );
dmc.synth.volume_unit( tnd );
buffer_cleared();
}
void Nes_Apu::volume( double v )
{
dmc.nonlinear = false;
square_synth.volume( 0.1128 * v );
triangle.synth.volume( 0.12765 * v );
noise.synth.volume( 0.0741 * v );
dmc.synth.volume( 0.42545 * v );
}
void Nes_Apu::output( Blip_Buffer* buffer )
{
for ( int i = 0; i < osc_count; i++ )
osc_output( i, buffer );
}
void Nes_Apu::reset( bool pal_mode, int initial_dmc_dac )
{
// to do: time pal frame periods exactly
frame_period = pal_mode ? 8314 : 7458;
dmc.pal_mode = pal_mode;
square1.reset();
square2.reset();
triangle.reset();
noise.reset();
dmc.reset();
last_time = 0;
osc_enables = 0;
irq_flag = false;
earliest_irq_ = no_irq;
frame_delay = 1;
write_register( 0, 0x4017, 0x00 );
write_register( 0, 0x4015, 0x00 );
for ( cpu_addr_t addr = start_addr; addr <= 0x4013; addr++ )
write_register( 0, addr, (addr & 3) ? 0x00 : 0x10 );
dmc.dac = initial_dmc_dac;
if ( !dmc.nonlinear )
dmc.last_amp = initial_dmc_dac; // prevent output transition
}
void Nes_Apu::irq_changed()
{
cpu_time_t new_irq = dmc.next_irq;
if ( dmc.irq_flag | irq_flag ) {
new_irq = 0;
}
else if ( new_irq > next_irq ) {
new_irq = next_irq;
}
if ( new_irq != earliest_irq_ ) {
earliest_irq_ = new_irq;
if ( irq_notifier_ )
irq_notifier_( irq_data );
}
}
// frames
void Nes_Apu::run_until( cpu_time_t end_time )
{
require( end_time >= last_time );
if ( end_time == last_time )
return;
while ( true )
{
// earlier of next frame time or end time
cpu_time_t time = last_time + frame_delay;
if ( time > end_time )
time = end_time;
frame_delay -= time - last_time;
// run oscs to present
square1.run( last_time, time );
square2.run( last_time, time );
triangle.run( last_time, time );
noise.run( last_time, time );
dmc.run( last_time, time );
last_time = time;
if ( time == end_time )
break; // no more frames to run
// take frame-specific actions
frame_delay = frame_period;
switch ( frame++ )
{
case 0:
if ( !(frame_mode & 0xc0) ) {
next_irq = time + frame_period * 4 + 1;
irq_flag = true;
}
// fall through
case 2:
// clock length and sweep on frames 0 and 2
square1.clock_length( 0x20 );
square2.clock_length( 0x20 );
noise.clock_length( 0x20 );
triangle.clock_length( 0x80 ); // different bit for halt flag on triangle
square1.clock_sweep( -1 );
square2.clock_sweep( 0 );
break;
case 1:
// frame 1 is slightly shorter
frame_delay -= 2;
break;
case 3:
frame = 0;
// frame 3 is almost twice as long in mode 1
if ( frame_mode & 0x80 )
frame_delay += frame_period - 6;
break;
}
// clock envelopes and linear counter every frame
triangle.clock_linear_counter();
square1.clock_envelope();
square2.clock_envelope();
noise.clock_envelope();
}
}
void Nes_Apu::end_frame( cpu_time_t end_time )
{
if ( end_time > last_time )
run_until( end_time );
// make times relative to new frame
last_time -= end_time;
require( last_time >= 0 );
if ( next_irq != no_irq ) {
next_irq -= end_time;
assert( next_irq >= 0 );
}
if ( dmc.next_irq != no_irq ) {
dmc.next_irq -= end_time;
assert( dmc.next_irq >= 0 );
}
if ( earliest_irq_ != no_irq ) {
earliest_irq_ -= end_time;
if ( earliest_irq_ < 0 )
earliest_irq_ = 0;
}
}
// registers
static const unsigned char length_table [0x20] = {
0x0A, 0xFE, 0x14, 0x02, 0x28, 0x04, 0x50, 0x06,
0xA0, 0x08, 0x3C, 0x0A, 0x0E, 0x0C, 0x1A, 0x0E,
0x0C, 0x10, 0x18, 0x12, 0x30, 0x14, 0x60, 0x16,
0xC0, 0x18, 0x48, 0x1A, 0x10, 0x1C, 0x20, 0x1E
};
void Nes_Apu::write_register( cpu_time_t time, cpu_addr_t addr, int data )
{
require( addr > 0x20 ); // addr must be actual address (i.e. 0x40xx)
require( (unsigned) data <= 0xff );
// Ignore addresses outside range
if ( addr < start_addr || end_addr < addr )
return;
run_until( time );
if ( addr < 0x4014 )
{
// Write to channel
int osc_index = (addr - start_addr) >> 2;
Nes_Osc* osc = oscs [osc_index];
int reg = addr & 3;
osc->regs [reg] = data;
osc->reg_written [reg] = true;
if ( osc_index == 4 )
{
// handle DMC specially
dmc.write_register( reg, data );
}
else if ( reg == 3 )
{
// load length counter
if ( (osc_enables >> osc_index) & 1 )
osc->length_counter = length_table [(data >> 3) & 0x1f];
// reset square phase
if ( osc_index < 2 )
((Nes_Square*) osc)->phase = Nes_Square::phase_range - 1;
}
}
else if ( addr == 0x4015 )
{
// Channel enables
for ( int i = osc_count; i--; )
if ( !((data >> i) & 1) )
oscs [i]->length_counter = 0;
bool recalc_irq = dmc.irq_flag;
dmc.irq_flag = false;
int old_enables = osc_enables;
osc_enables = data;
if ( !(data & 0x10) ) {
dmc.next_irq = no_irq;
recalc_irq = true;
}
else if ( !(old_enables & 0x10) ) {
dmc.start(); // dmc just enabled
}
if ( recalc_irq )
irq_changed();
}
else if ( addr == 0x4017 )
{
// Frame mode
frame_mode = data;
bool irq_enabled = !(data & 0x40);
irq_flag &= irq_enabled;
next_irq = no_irq;
// mode 1
frame_delay = (frame_delay & 1);
frame = 0;
if ( !(data & 0x80) )
{
// mode 0
frame = 1;
frame_delay += frame_period;
if ( irq_enabled )
next_irq = time + frame_delay + frame_period * 3;
}
irq_changed();
}
}
int Nes_Apu::read_status( cpu_time_t time )
{
run_until( time - 1 );
int result = (dmc.irq_flag << 7) | (irq_flag << 6);
for ( int i = 0; i < osc_count; i++ )
if ( oscs [i]->length_counter )
result |= 1 << i;
run_until( time );
if ( irq_flag ) {
irq_flag = false;
irq_changed();
}
return result;
}

160
lib/Nes_Namco.cpp Normal file
View file

@ -0,0 +1,160 @@
// Nes_Snd_Emu 0.1.7. http://www.slack.net/~ant/libs/
#include "Nes_Namco.h"
/* Copyright (C) 2003-2005 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
#include BLARGG_SOURCE_BEGIN
Nes_Namco::Nes_Namco()
{
output( NULL );
volume( 1.0 );
reset();
}
Nes_Namco::~Nes_Namco()
{
}
void Nes_Namco::reset()
{
addr_reg = 0;
int i;
for ( i = 0; i < reg_count; i++ )
reg [i] = 0;
for ( i = 0; i < osc_count; i++ )
{
Namco_Osc& osc = oscs [i];
osc.delay = 0;
osc.last_amp = 0;
osc.wave_pos = 0;
}
}
void Nes_Namco::output( Blip_Buffer* buf )
{
for ( int i = 0; i < osc_count; i++ )
osc_output( i, buf );
}
BOOST::uint8_t& Nes_Namco::access()
{
int addr = addr_reg & 0x7f;
if ( addr_reg & 0x80 )
addr_reg = (addr + 1) | 0x80;
return reg [addr];
}
/*
void Nes_Namco::reflect_state( Tagged_Data& data )
{
reflect_int16( data, 'ADDR', &addr_reg );
static const char hex [17] = "0123456789ABCDEF";
int i;
for ( i = 0; i < reg_count; i++ )
reflect_int16( data, 'RG\0\0' + hex [i >> 4] * 0x100 + hex [i & 15], &reg [i] );
for ( i = 0; i < osc_count; i++ )
{
reflect_int32( data, 'DLY0' + i, &oscs [i].delay );
reflect_int16( data, 'POS0' + i, &oscs [i].wave_pos );
}
}
*/
void Nes_Namco::end_frame( cpu_time_t time )
{
if ( time > last_time )
run_until( time );
last_time -= time;
assert( last_time >= 0 );
}
#include BLARGG_ENABLE_OPTIMIZER
void Nes_Namco::run_until( cpu_time_t nes_end_time )
{
int active_oscs = ((reg [0x7f] >> 4) & 7) + 1;
for ( int i = osc_count - active_oscs; i < osc_count; i++ )
{
Namco_Osc& osc = oscs [i];
Blip_Buffer* output = osc.output;
if ( !output )
continue;
Blip_Buffer::resampled_time_t time =
output->resampled_time( last_time ) + osc.delay;
Blip_Buffer::resampled_time_t end_time = output->resampled_time( nes_end_time );
osc.delay = 0;
if ( time < end_time )
{
const BOOST::uint8_t* osc_reg = &reg [i * 8 + 0x40];
if ( !(osc_reg [4] & 0xe0) )
continue;
int volume = osc_reg [7] & 15;
if ( !volume )
continue;
long freq = (osc_reg [4] & 3) * 0x10000 + osc_reg [2] * 0x100L + osc_reg [0];
if ( !freq )
continue;
Blip_Buffer::resampled_time_t period =
output->resampled_duration( 983040 ) / freq * active_oscs;
int wave_size = (8 - ((osc_reg [4] >> 2) & 7)) * 4;
if ( !wave_size )
continue;
int last_amp = osc.last_amp;
int wave_pos = osc.wave_pos;
do
{
// read wave sample
int addr = wave_pos + osc_reg [6];
int sample = reg [addr >> 1];
wave_pos++;
if ( addr & 1 )
sample >>= 4;
sample = (sample & 15) * volume;
// output impulse if amplitude changed
int delta = sample - last_amp;
if ( delta )
{
last_amp = sample;
synth.offset_resampled( time, delta, output );
}
// next sample
time += period;
if ( wave_pos >= wave_size )
wave_pos = 0;
}
while ( time < end_time );
osc.wave_pos = wave_pos;
osc.last_amp = last_amp;
}
osc.delay = time - end_time;
}
last_time = nes_end_time;
}

498
lib/Nes_Oscs.cpp Normal file
View file

@ -0,0 +1,498 @@
// Nes_Snd_Emu 0.1.7. http://www.slack.net/~ant/libs/
#include "Nes_Apu.h"
/* Copyright (C) 2003-2005 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
#include BLARGG_SOURCE_BEGIN
// Nes_Osc
void Nes_Osc::clock_length( int halt_mask )
{
if ( length_counter && !(regs [0] & halt_mask) )
length_counter--;
}
void Nes_Envelope::clock_envelope()
{
int period = regs [0] & 15;
if ( reg_written [3] ) {
reg_written [3] = false;
env_delay = period;
envelope = 15;
}
else if ( --env_delay < 0 ) {
env_delay = period;
if ( envelope | (regs [0] & 0x20) )
envelope = (envelope - 1) & 15;
}
}
int Nes_Envelope::volume() const
{
return length_counter == 0 ? 0 : (regs [0] & 0x10) ? (regs [0] & 15) : envelope;
}
// Nes_Square
void Nes_Square::clock_sweep( int negative_adjust )
{
int sweep = regs [1];
if ( --sweep_delay < 0 )
{
reg_written [1] = true;
int period = this->period();
int shift = sweep & shift_mask;
if ( shift && (sweep & 0x80) && period >= 8 )
{
int offset = period >> shift;
if ( sweep & negate_flag )
offset = negative_adjust - offset;
if ( period + offset < 0x800 )
{
period += offset;
// rewrite period
regs [2] = period & 0xff;
regs [3] = (regs [3] & ~7) | ((period >> 8) & 7);
}
}
}
if ( reg_written [1] ) {
reg_written [1] = false;
sweep_delay = (sweep >> 4) & 7;
}
}
void Nes_Square::run( cpu_time_t time, cpu_time_t end_time )
{
if ( !output )
return;
const int volume = this->volume();
const int period = this->period();
int offset = period >> (regs [1] & shift_mask);
if ( regs [1] & negate_flag )
offset = 0;
const int timer_period = (period + 1) * 2;
if ( volume == 0 || period < 8 || (period + offset) >= 0x800 )
{
if ( last_amp ) {
synth->offset( time, -last_amp, output );
last_amp = 0;
}
time += delay;
if ( time < end_time )
{
// maintain proper phase
int count = (end_time - time + timer_period - 1) / timer_period;
phase = (phase + count) & (phase_range - 1);
time += (long) count * timer_period;
}
}
else
{
// handle duty select
int duty_select = (regs [0] >> 6) & 3;
int duty = 1 << duty_select; // 1, 2, 4, 2
int amp = 0;
if ( duty_select == 3 ) {
duty = 2; // negated 25%
amp = volume;
}
if ( phase < duty )
amp ^= volume;
int delta = update_amp( amp );
if ( delta )
synth->offset( time, delta, output );
time += delay;
if ( time < end_time )
{
Blip_Buffer* const output = this->output;
const Synth* synth = this->synth;
int delta = amp * 2 - volume;
int phase = this->phase;
do {
phase = (phase + 1) & (phase_range - 1);
if ( phase == 0 || phase == duty ) {
delta = -delta;
synth->offset_inline( time, delta, output );
}
time += timer_period;
}
while ( time < end_time );
last_amp = (delta + volume) >> 1;
this->phase = phase;
}
}
delay = time - end_time;
}
// Nes_Triangle
void Nes_Triangle::clock_linear_counter()
{
if ( reg_written [3] )
linear_counter = regs [0] & 0x7f;
else if ( linear_counter )
linear_counter--;
if ( !(regs [0] & 0x80) )
reg_written [3] = false;
}
inline int Nes_Triangle::calc_amp() const
{
int amp = phase_range - phase;
if ( amp < 0 )
amp = phase - (phase_range + 1);
return amp;
}
void Nes_Triangle::run( cpu_time_t time, cpu_time_t end_time )
{
if ( !output )
return;
// to do: track phase when period < 3
// to do: Output 7.5 on dac when period < 2? More accurate, but results in more clicks.
int delta = update_amp( calc_amp() );
if ( delta )
synth.offset( time, delta, output );
time += delay;
const int timer_period = period() + 1;
if ( length_counter == 0 || linear_counter == 0 || timer_period < 3 )
{
time = end_time;
}
else if ( time < end_time )
{
Blip_Buffer* const output = this->output;
int phase = this->phase;
int volume = 1;
if ( phase > phase_range ) {
phase -= phase_range;
volume = -volume;
}
do {
if ( --phase == 0 ) {
phase = phase_range;
volume = -volume;
}
else {
synth.offset_inline( time, volume, output );
}
time += timer_period;
}
while ( time < end_time );
if ( volume < 0 )
phase += phase_range;
this->phase = phase;
last_amp = calc_amp();
}
delay = time - end_time;
}
// Nes_Dmc
void Nes_Dmc::reset()
{
address = 0;
dac = 0;
buf = 0;
bits_remain = 1;
bits = 0;
buf_empty = true;
silence = true;
next_irq = Nes_Apu::no_irq;
irq_flag = false;
irq_enabled = false;
Nes_Osc::reset();
period = 0x036;
}
void Nes_Dmc::recalc_irq()
{
cpu_time_t irq = Nes_Apu::no_irq;
if ( irq_enabled && length_counter )
irq = apu->last_time + delay +
((length_counter - 1) * 8 + bits_remain - 1) * cpu_time_t (period) + 1;
if ( irq != next_irq ) {
next_irq = irq;
apu->irq_changed();
}
}
int Nes_Dmc::count_reads( cpu_time_t time, cpu_time_t* last_read ) const
{
if ( last_read )
*last_read = time;
if ( length_counter == 0 )
return 0; // not reading
long first_read = apu->last_time + delay + long (bits_remain - 1) * period;
long avail = time - first_read;
if ( avail <= 0 )
return 0;
int count = (avail - 1) / (period * 8) + 1;
if ( !(regs [0] & loop_flag) && count > length_counter )
count = length_counter;
if ( last_read ) {
*last_read = first_read + (count - 1) * (period * 8) + 1;
assert( *last_read <= time );
assert( count == count_reads( *last_read, NULL ) );
assert( count - 1 == count_reads( *last_read - 1, NULL ) );
}
return count;
}
static const short dmc_period_table [2] [16] = {
0x1ac, 0x17c, 0x154, 0x140, 0x11e, 0x0fe, 0x0e2, 0x0d6, // NTSC
0x0be, 0x0a0, 0x08e, 0x080, 0x06a, 0x054, 0x048, 0x036,
0x18e, 0x161, 0x13c, 0x129, 0x10a, 0x0ec, 0x0d2, 0x0c7, // PAL (totally untested)
0x0b1, 0x095, 0x084, 0x077, 0x062, 0x04e, 0x043, 0x032 // to do: verify PAL periods
};
inline void Nes_Dmc::reload_sample()
{
address = 0x4000 + regs [2] * 0x40;
length_counter = regs [3] * 0x10 + 1;
}
static const unsigned char dac_table [128] = {
0, 0, 1, 2, 2, 3, 3, 4, 5, 5, 6, 7, 7, 8, 8, 9,
10, 10, 11, 11, 12, 13, 13, 14, 14, 15, 15, 16, 17, 17, 18, 18,
19, 19, 20, 20, 21, 21, 22, 22, 23, 23, 24, 24, 25, 25, 26, 26,
27, 27, 28, 28, 29, 29, 30, 30, 31, 31, 32, 32, 32, 33, 33, 34,
34, 35, 35, 35, 36, 36, 37, 37, 38, 38, 38, 39, 39, 40, 40, 40,
41, 41, 42, 42, 42, 43, 43, 44, 44, 44, 45, 45, 45, 46, 46, 47,
47, 47, 48, 48, 48, 49, 49, 49, 50, 50, 50, 51, 51, 51, 52, 52,
52, 53, 53, 53, 54, 54, 54, 55, 55, 55, 56, 56, 56, 57, 57, 57
};
void Nes_Dmc::write_register( int addr, int data )
{
if ( addr == 0 ) {
period = dmc_period_table [pal_mode] [data & 15];
irq_enabled = (data & 0xc0) == 0x80; // enabled only if loop disabled
irq_flag &= irq_enabled;
recalc_irq();
}
else if ( addr == 1 )
{
if ( !nonlinear )
{
// adjust last_amp so that "pop" amplitude will be properly non-linear
// with respect to change in dac
int old_amp = dac_table [dac];
dac = data & 0x7F;
int diff = dac_table [dac] - old_amp;
last_amp = dac - diff;
}
dac = data & 0x7F;
}
}
void Nes_Dmc::start()
{
reload_sample();
fill_buffer();
recalc_irq();
}
void Nes_Dmc::fill_buffer()
{
if ( buf_empty && length_counter )
{
require( rom_reader ); // rom_reader must be set
buf = rom_reader( rom_reader_data, 0x8000u + address );
address = (address + 1) & 0x7FFF;
buf_empty = false;
if ( --length_counter == 0 )
{
if ( regs [0] & loop_flag ) {
reload_sample();
}
else {
apu->osc_enables &= ~0x10;
irq_flag = irq_enabled;
next_irq = Nes_Apu::no_irq;
apu->irq_changed();
}
}
}
}
void Nes_Dmc::run( cpu_time_t time, cpu_time_t end_time )
{
if ( !output )
return;
int delta = update_amp( dac );
if ( delta )
synth.offset( time, delta, output );
time += delay;
if ( time < end_time )
{
int bits_remain = this->bits_remain;
if ( silence && buf_empty )
{
int count = (end_time - time + period - 1) / period;
bits_remain = (bits_remain - 1 + 8 - (count % 8)) % 8 + 1;
time += count * period;
}
else
{
Blip_Buffer* const output = this->output;
const int period = this->period;
int bits = this->bits;
int dac = this->dac;
do
{
if ( !silence )
{
const int step = (bits & 1) * 4 - 2;
bits >>= 1;
if ( unsigned (dac + step) <= 0x7F ) {
dac += step;
synth.offset_inline( time, step, output );
}
}
time += period;
if ( --bits_remain == 0 )
{
bits_remain = 8;
if ( buf_empty ) {
silence = true;
}
else {
silence = false;
bits = buf;
buf_empty = true;
fill_buffer();
}
}
}
while ( time < end_time );
this->dac = dac;
this->last_amp = dac;
this->bits = bits;
}
this->bits_remain = bits_remain;
}
delay = time - end_time;
}
// Nes_Noise
#include BLARGG_ENABLE_OPTIMIZER
static const short noise_period_table [16] = {
0x004, 0x008, 0x010, 0x020, 0x040, 0x060, 0x080, 0x0A0,
0x0CA, 0x0FE, 0x17C, 0x1FC, 0x2FA, 0x3F8, 0x7F2, 0xFE4
};
void Nes_Noise::run( cpu_time_t time, cpu_time_t end_time )
{
if ( !output )
return;
const int volume = this->volume();
int amp = (noise & 1) ? volume : 0;
int delta = update_amp( amp );
if ( delta )
synth.offset( time, delta, output );
time += delay;
if ( time < end_time )
{
const int mode_flag = 0x80;
int period = noise_period_table [regs [2] & 15];
if ( !volume )
{
// round to next multiple of period
time += (end_time - time + period - 1) / period * period;
// approximate noise cycling while muted, by shuffling up noise register
// to do: precise muted noise cycling?
if ( !(regs [2] & mode_flag) ) {
int feedback = (noise << 13) ^ (noise << 14);
noise = (feedback & 0x4000) | (noise >> 1);
}
}
else
{
Blip_Buffer* const output = this->output;
// using resampled time avoids conversion in synth.offset()
Blip_Buffer::resampled_time_t rperiod = output->resampled_duration( period );
Blip_Buffer::resampled_time_t rtime = output->resampled_time( time );
int noise = this->noise;
int delta = amp * 2 - volume;
const int tap = (regs [2] & mode_flag ? 8 : 13);
do {
int feedback = (noise << tap) ^ (noise << 14);
time += period;
if ( (noise + 1) & 2 ) {
// bits 0 and 1 of noise differ
delta = -delta;
synth.offset_resampled( rtime, delta, output );
}
rtime += rperiod;
noise = (feedback & 0x4000) | (noise >> 1);
}
while ( time < end_time );
last_amp = (delta + volume) >> 1;
this->noise = noise;
}
}
delay = time - end_time;
}

231
lib/Nes_Vrc6.cpp Normal file
View file

@ -0,0 +1,231 @@
// Nes_Snd_Emu 0.1.7. http://www.slack.net/~ant/libs/
#include "Nes_Vrc6.h"
/* Copyright (C) 2003-2005 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
#include BLARGG_SOURCE_BEGIN
Nes_Vrc6::Nes_Vrc6()
{
output( NULL );
volume( 1.0 );
reset();
}
Nes_Vrc6::~Nes_Vrc6()
{
}
void Nes_Vrc6::reset()
{
last_time = 0;
for ( int i = 0; i < osc_count; i++ )
{
Vrc6_Osc& osc = oscs [i];
for ( int j = 0; j < reg_count; j++ )
osc.regs [j] = 0;
osc.delay = 0;
osc.last_amp = 0;
osc.phase = 1;
osc.amp = 0;
}
}
void Nes_Vrc6::volume( double v )
{
v *= 0.0967 * 2;
saw_synth.volume( v );
square_synth.volume( v * 0.5 );
}
void Nes_Vrc6::treble_eq( blip_eq_t const& eq )
{
saw_synth.treble_eq( eq );
square_synth.treble_eq( eq );
}
void Nes_Vrc6::output( Blip_Buffer* buf )
{
for ( int i = 0; i < osc_count; i++ )
osc_output( i, buf );
}
void Nes_Vrc6::run_until( cpu_time_t time )
{
require( time >= last_time );
run_square( oscs [0], time );
run_square( oscs [1], time );
run_saw( time );
last_time = time;
}
void Nes_Vrc6::write_osc( cpu_time_t time, int osc_index, int reg, int data )
{
require( (unsigned) osc_index < osc_count );
require( (unsigned) reg < reg_count );
run_until( time );
oscs [osc_index].regs [reg] = data;
}
void Nes_Vrc6::end_frame( cpu_time_t time )
{
if ( time > last_time )
run_until( time );
last_time -= time;
assert( last_time >= 0 );
}
void Nes_Vrc6::save_snapshot( vrc6_snapshot_t* out ) const
{
out->saw_amp = oscs [2].amp;
for ( int i = 0; i < osc_count; i++ )
{
Vrc6_Osc const& osc = oscs [i];
for ( int r = 0; r < reg_count; r++ )
out->regs [i] [r] = osc.regs [r];
out->delays [i] = osc.delay;
out->phases [i] = osc.phase;
}
}
void Nes_Vrc6::load_snapshot( vrc6_snapshot_t const& in )
{
reset();
oscs [2].amp = in.saw_amp;
for ( int i = 0; i < osc_count; i++ )
{
Vrc6_Osc& osc = oscs [i];
for ( int r = 0; r < reg_count; r++ )
osc.regs [r] = in.regs [i] [r];
osc.delay = in.delays [i];
osc.phase = in.phases [i];
}
if ( !oscs [2].phase )
oscs [2].phase = 1;
}
#include BLARGG_ENABLE_OPTIMIZER
void Nes_Vrc6::run_square( Vrc6_Osc& osc, cpu_time_t end_time )
{
Blip_Buffer* output = osc.output;
if ( !output )
return;
int volume = osc.regs [0] & 15;
if ( !(osc.regs [2] & 0x80) )
volume = 0;
int gate = osc.regs [0] & 0x80;
int duty = ((osc.regs [0] >> 4) & 7) + 1;
int delta = ((gate || osc.phase < duty) ? volume : 0) - osc.last_amp;
cpu_time_t time = last_time;
if ( delta )
{
osc.last_amp += delta;
square_synth.offset( time, delta, output );
}
time += osc.delay;
osc.delay = 0;
int period = osc.period();
if ( volume && !gate && period > 4 )
{
if ( time < end_time )
{
int phase = osc.phase;
do
{
phase++;
if ( phase == 16 )
{
phase = 0;
osc.last_amp = volume;
square_synth.offset( time, volume, output );
}
if ( phase == duty )
{
osc.last_amp = 0;
square_synth.offset( time, -volume, output );
}
time += period;
}
while ( time < end_time );
osc.phase = phase;
}
osc.delay = time - end_time;
}
}
void Nes_Vrc6::run_saw( cpu_time_t end_time )
{
Vrc6_Osc& osc = oscs [2];
Blip_Buffer* output = osc.output;
if ( !output )
return;
int amp = osc.amp;
int amp_step = osc.regs [0] & 0x3F;
cpu_time_t time = last_time;
int last_amp = osc.last_amp;
if ( !(osc.regs [2] & 0x80) || !(amp_step | amp) )
{
osc.delay = 0;
int delta = (amp >> 3) - last_amp;
last_amp = amp >> 3;
saw_synth.offset( time, delta, output );
}
else
{
time += osc.delay;
if ( time < end_time )
{
int period = osc.period() * 2;
int phase = osc.phase;
do
{
if ( --phase == 0 )
{
phase = 7;
amp = 0;
}
int delta = (amp >> 3) - last_amp;
if ( delta )
{
last_amp = amp >> 3;
saw_synth.offset( time, delta, output );
}
time += period;
amp = (amp + amp_step) & 0xFF;
}
while ( time < end_time );
osc.phase = phase;
osc.amp = amp;
}
osc.delay = time - end_time;
}
osc.last_amp = last_amp;
}

189
lib/Nonlinear_Buffer.cpp Normal file
View file

@ -0,0 +1,189 @@
// Nes_Snd_Emu 0.1.7. http://www.slack.net/~ant/libs/
#include "Nonlinear_Buffer.h"
#include "Nes_Apu.h"
/* Library Copyright (C) 2003-2005 Shay Green. This library 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 library; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
#include BLARGG_SOURCE_BEGIN
// Nonlinear_Buffer
Nonlinear_Buffer::Nonlinear_Buffer() :
Multi_Buffer( 1 )
{
}
Nonlinear_Buffer::~Nonlinear_Buffer()
{
}
void Nonlinear_Buffer::enable_nonlinearity( Nes_Apu& apu, bool b )
{
if ( b )
clear();
nonlinearizer.enable( apu, b );
for ( int i = 0; i < apu.osc_count; i++ )
apu.osc_output( i, (i >= 2 ? &tnd : &buf) );
}
blargg_err_t Nonlinear_Buffer::sample_rate( long rate, int msec )
{
BLARGG_RETURN_ERR( buf.sample_rate( rate, msec ) );
BLARGG_RETURN_ERR( tnd.sample_rate( rate, msec ) );
return Multi_Buffer::sample_rate( buf.sample_rate(), buf.length() );
}
void Nonlinear_Buffer::clock_rate( long rate )
{
buf.clock_rate( rate );
tnd.clock_rate( rate );
}
void Nonlinear_Buffer::bass_freq( int freq )
{
buf.bass_freq( freq );
tnd.bass_freq( freq );
}
void Nonlinear_Buffer::clear()
{
nonlinearizer.clear();
buf.clear();
tnd.clear();
}
Nonlinear_Buffer::channel_t Nonlinear_Buffer::channel( int i )
{
channel_t c;
c.center = &buf;
if ( 2 <= i && i <= 4 )
c.center = &tnd; // only use for triangle, noise, and dmc
c.left = c.center;
c.right = c.center;
return c;
}
void Nonlinear_Buffer::end_frame( blip_time_t length, bool )
{
buf.end_frame( length );
tnd.end_frame( length );
}
long Nonlinear_Buffer::samples_avail() const
{
return buf.samples_avail();
}
#include BLARGG_ENABLE_OPTIMIZER
long Nonlinear_Buffer::read_samples( blip_sample_t* out, long count )
{
count = nonlinearizer.make_nonlinear( tnd, count );
if ( count )
{
Blip_Reader lin;
Blip_Reader nonlin;
int lin_bass = lin.begin( buf );
int nonlin_bass = nonlin.begin( tnd );
for ( int n = count; n--; )
{
int s = lin.read() + nonlin.read();
lin.next( lin_bass );
nonlin.next( nonlin_bass );
*out++ = s;
if ( (BOOST::int16_t) s != s )
out [-1] = 0x7FFF - (s >> 24);
}
lin.end( buf );
nonlin.end( tnd );
buf.remove_samples( count );
tnd.remove_samples( count );
}
return count;
}
// Nes_Nonlinearizer
Nes_Nonlinearizer::Nes_Nonlinearizer()
{
nonlinear = false;
double gain = 0x7fff * 1.3;
// don't use entire range, so any overflow will stay within table
int const range = half * 0.75; // to do: must match that in Nes_Apu.cpp
for ( int i = 0; i < half * 2; i++ )
{
int out = i << shift;
if ( i > half )
{
int j = i - half;
if ( j >= range )
j = range - 1;
double n = 202.0 / (range - 1) * j;
double d = 163.67 / (24329.0 / n + 100);
out = int (d * gain) + 0x8000;
assert( out < 0x10000 );
}
table [i] = out;
}
clear();
}
void Nes_Nonlinearizer::enable( Nes_Apu& apu, bool b )
{
nonlinear = b;
if ( b )
apu.enable_nonlinear( 1.0 );
else
apu.volume( 1.0 );
}
long Nes_Nonlinearizer::make_nonlinear( Blip_Buffer& buf, long count )
{
long avail = buf.samples_avail();
if ( count > avail )
count = avail;
if ( count && nonlinear )
{
const int zero_offset = 0x7f7f; // to do: use private constant from Blip_Buffer.h
#define ENTRY( s ) (table [((s) >> shift) & entry_mask])
BOOST::uint16_t* p = buf.buffer_;
unsigned prev = ENTRY( accum );
long accum = this->accum;
for ( unsigned n = count; n--; )
{
accum += (long) *p - zero_offset;
check( (accum >> shift) < half * 2 );
unsigned entry = ENTRY( accum );
*p++ = entry - prev + zero_offset;
prev = entry;
}
this->accum = accum;
}
return count;
}

138
lib/Sound_Queue.cpp Normal file
View file

@ -0,0 +1,138 @@
// Nes_Snd_Emu 0.1.7. http://www.slack.net/~ant/
#include "Sound_Queue.h"
#include <assert.h>
#include <string.h>
/* Copyright (C) 2005 by Shay Green. Permission is hereby granted, free of
charge, to any person obtaining a copy of this software module and associated
documentation files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use, copy, modify,
merge, publish, distribute, sublicense, and/or sell copies of the Software, and
to permit persons to whom the Software is furnished to do so, subject to the
following conditions: The above copyright notice and this permission notice
shall be included in all copies or substantial portions of the Software. THE
SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
// Return current SDL_GetError() string, or str if SDL didn't have a string
static const char* sdl_error( const char* str )
{
const char* sdl_str = SDL_GetError();
if ( sdl_str && *sdl_str )
str = sdl_str;
return str;
}
Sound_Queue::Sound_Queue()
{
bufs = NULL;
free_sem = NULL;
write_buf = 0;
write_pos = 0;
read_buf = 0;
sound_open = false;
}
Sound_Queue::~Sound_Queue()
{
if ( sound_open )
{
SDL_PauseAudio( true );
SDL_CloseAudio();
}
if ( free_sem )
SDL_DestroySemaphore( free_sem );
delete [] bufs;
}
int Sound_Queue::sample_count() const
{
int buf_free = SDL_SemValue( free_sem ) * buf_size + (buf_size - write_pos);
return buf_size * buf_count - buf_free;
}
const char* Sound_Queue::init( long sample_rate, int chan_count )
{
assert( !bufs ); // can only be initialized once
bufs = new sample_t [(long) buf_size * buf_count];
if ( !bufs )
return "Out of memory";
free_sem = SDL_CreateSemaphore( buf_count - 1 );
if ( !free_sem )
return sdl_error( "Couldn't create semaphore" );
SDL_AudioSpec as;
as.freq = sample_rate;
as.format = AUDIO_S16SYS;
as.channels = chan_count;
as.silence = 0;
as.samples = buf_size;
as.size = 0;
as.callback = fill_buffer_;
as.userdata = this;
if ( SDL_OpenAudio( &as, NULL ) < 0 )
return sdl_error( "Couldn't open SDL audio" );
SDL_PauseAudio( false );
sound_open = true;
return NULL;
}
inline Sound_Queue::sample_t* Sound_Queue::buf( int index )
{
assert( (unsigned) index < buf_count );
return bufs + (long) index * buf_size;
}
void Sound_Queue::write( const sample_t* in, int count )
{
while ( count )
{
int n = buf_size - write_pos;
if ( n > count )
n = count;
memcpy( buf( write_buf ) + write_pos, in, n * sizeof (sample_t) );
in += n;
write_pos += n;
count -= n;
if ( write_pos >= buf_size )
{
write_pos = 0;
write_buf = (write_buf + 1) % buf_count;
SDL_SemWait( free_sem );
}
}
}
void Sound_Queue::fill_buffer( Uint8* out, int count )
{
if ( SDL_SemValue( free_sem ) < buf_count - 1 )
{
memcpy( out, buf( read_buf ), count );
read_buf = (read_buf + 1) % buf_count;
SDL_SemPost( free_sem );
}
else
{
memset( out, 0, count );
}
}
void Sound_Queue::fill_buffer_( void* user_data, Uint8* out, int count )
{
((Sound_Queue*) user_data)->fill_buffer( out, count );
}

124
lib/apu_snapshot.cpp Normal file
View file

@ -0,0 +1,124 @@
// Nes_Snd_Emu 0.1.7. http://www.slack.net/~ant/libs/
#include "apu_snapshot.h"
#include "Nes_Apu.h"
/* Copyright (C) 2003-2005 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */
#include BLARGG_SOURCE_BEGIN
template<int mode>
struct apu_reflection
{
#define REFLECT( apu, state ) (mode ? void (apu = state) : void (state = apu))
static void reflect_env( apu_snapshot_t::env_t& state, Nes_Envelope& osc )
{
REFLECT( state [0], osc.env_delay );
REFLECT( state [1], osc.envelope );
REFLECT( state [2], osc.reg_written [3] );
}
static void reflect_square( apu_snapshot_t::square_t& state, Nes_Square& osc )
{
reflect_env( state.env, osc );
REFLECT( state.delay, osc.delay );
REFLECT( state.length, osc.length_counter );
REFLECT( state.phase, osc.phase );
REFLECT( state.swp_delay, osc.sweep_delay );
REFLECT( state.swp_reset, osc.reg_written [1] );
}
static void reflect_triangle( apu_snapshot_t::triangle_t& state, Nes_Triangle& osc )
{
REFLECT( state.delay, osc.delay );
REFLECT( state.length, osc.length_counter );
REFLECT( state.linear_counter, osc.linear_counter );
REFLECT( state.linear_mode, osc.reg_written [3] );
}
static void reflect_noise( apu_snapshot_t::noise_t& state, Nes_Noise& osc )
{
reflect_env( state.env, osc );
REFLECT( state.delay, osc.delay );
REFLECT( state.length, osc.length_counter );
REFLECT( state.shift_reg, osc.noise );
}
static void reflect_dmc( apu_snapshot_t::dmc_t& state, Nes_Dmc& osc )
{
REFLECT( state.delay, osc.delay );
REFLECT( state.remain, osc.length_counter );
REFLECT( state.buf, osc.buf );
REFLECT( state.bits_remain, osc.bits_remain );
REFLECT( state.bits, osc.bits );
REFLECT( state.buf_empty, osc.buf_empty );
REFLECT( state.silence, osc.silence );
REFLECT( state.irq_flag, osc.irq_flag );
if ( mode )
state.addr = osc.address | 0x8000;
else
osc.address = state.addr & 0x7fff;
}
};
void Nes_Apu::save_snapshot( apu_snapshot_t* state ) const
{
for ( int i = 0; i < osc_count * 4; i++ )
state->w40xx [i] = oscs [i >> 2]->regs [i & 3];
state->w40xx [0x11] = dmc.dac;
state->w4015 = osc_enables;
state->w4017 = frame_mode;
state->delay = frame_delay;
state->step = frame;
state->irq_flag = irq_flag;
typedef apu_reflection<1> refl;
Nes_Apu& apu = *(Nes_Apu*) this; // const_cast
refl::reflect_square ( state->square1, apu.square1 );
refl::reflect_square ( state->square2, apu.square2 );
refl::reflect_triangle( state->triangle, apu.triangle );
refl::reflect_noise ( state->noise, apu.noise );
refl::reflect_dmc ( state->dmc, apu.dmc );
}
void Nes_Apu::load_snapshot( apu_snapshot_t const& state )
{
reset();
write_register( 0, 0x4017, state.w4017 );
write_register( 0, 0x4015, state.w4015 );
for ( int i = 0; i < osc_count * 4; i++ )
{
int n = state.w40xx [i];
oscs [i >> 2]->regs [i & 3] = n;
write_register( 0, 0x4000 + i, n );
}
frame_delay = state.delay;
frame = state.step;
irq_flag = state.irq_flag;
typedef apu_reflection<0> refl;
apu_snapshot_t& st = (apu_snapshot_t&) state; // const_cast
refl::reflect_square ( st.square1, square1 );
refl::reflect_square ( st.square2, square2 );
refl::reflect_triangle( st.triangle, triangle );
refl::reflect_noise ( st.noise, noise );
refl::reflect_dmc ( st.dmc, dmc );
dmc.recalc_irq();
dmc.last_amp = dmc.dac;
}

253
lib/include/Blip_Buffer.h Normal file
View file

@ -0,0 +1,253 @@
// Buffer of sound samples into which band-limited waveforms can be synthesized
// using Blip_Wave or Blip_Synth.
// Blip_Buffer 0.3.3. Copyright (C) 2003-2005 Shay Green. GNU LGPL license.
#ifndef BLIP_BUFFER_H
#define BLIP_BUFFER_H
#include "blargg_common.h"
class Blip_Reader;
// Source time unit.
typedef long blip_time_t;
// Type of sample produced. Signed 16-bit format.
typedef BOOST::int16_t blip_sample_t;
// Make buffer as large as possible (currently about 65000 samples)
const int blip_default_length = 0;
class Blip_Buffer {
public:
// Construct an empty buffer.
Blip_Buffer();
~Blip_Buffer();
// Set output sample rate and buffer length in milliseconds (1/1000 sec),
// then clear buffer. If length is not specified, make as large as possible.
// If there is insufficient memory for the buffer, sets the buffer length
// to 0 and returns error string (or propagates exception if compiler supports it).
blargg_err_t sample_rate( long samples_per_sec, int msec_length = blip_default_length );
// to do: rename to set_sample_rate
// Length of buffer, in milliseconds
int length() const;
// Current output sample rate
long sample_rate() const;
// Number of source time units per second
void clock_rate( long );
long clock_rate() const;
// Set frequency at which high-pass filter attenuation passes -3dB
void bass_freq( int frequency );
// Remove all available samples and clear buffer to silence. If 'entire_buffer' is
// false, just clear out any samples waiting rather than the entire buffer.
void clear( bool entire_buffer = true );
// to do:
// Notify Blip_Buffer that synthesis has been performed until specified time
//void run_until( blip_time_t );
// End current time frame of specified duration and make its samples available
// (along with any still-unread samples) for reading with read_samples(). Begin
// a new time frame at the end of the current frame. All transitions must have
// been added before 'time'.
void end_frame( blip_time_t time );
// Number of samples available for reading with read_samples()
long samples_avail() const;
// Read at most 'max_samples' out of buffer into 'dest', removing them from from
// the buffer. Return number of samples actually read and removed. If stereo is
// true, increment 'dest' one extra time after writing each sample, to allow
// easy interleving of two channels into a stereo output buffer.
long read_samples( blip_sample_t* dest, long max_samples, bool stereo = false );
// Remove 'count' samples from those waiting to be read
void remove_samples( long count );
// Number of samples delay from synthesis to samples read out
int output_latency() const;
// Experimental external buffer mixing support
// Number of raw samples that can be mixed within frame of specified duration
long count_samples( blip_time_t duration ) const;
// Mix 'count' samples from 'buf' into buffer.
void mix_samples( const blip_sample_t* buf, long count );
// not documented yet
void remove_silence( long count );
typedef unsigned long resampled_time_t;
resampled_time_t resampled_time( blip_time_t t ) const {
return t * resampled_time_t (factor_) + offset_;
}
resampled_time_t resampled_duration( int t ) const {
return t * resampled_time_t (factor_);
}
private:
// noncopyable
Blip_Buffer( const Blip_Buffer& );
Blip_Buffer& operator = ( const Blip_Buffer& );
// Don't use the following members. They are public only for technical reasons.
public:
enum { widest_impulse_ = 24 };
typedef BOOST::uint16_t buf_t_;
unsigned long factor_;
resampled_time_t offset_;
buf_t_* buffer_;
unsigned buffer_size_;
private:
long reader_accum;
int bass_shift;
long samples_per_sec;
long clocks_per_sec;
int bass_freq_;
int length_;
enum { accum_fract = 15 }; // less than 16 to give extra sample range
enum { sample_offset = 0x7F7F }; // repeated byte allows memset to clear buffer
friend class Blip_Reader;
};
// Low-pass equalization parameters (see notes.txt)
class blip_eq_t {
public:
blip_eq_t( double treble = 0 );
blip_eq_t( double treble, long cutoff, long sample_rate );
private:
double treble;
long cutoff;
long sample_rate;
friend class Blip_Impulse_;
};
// not documented yet (see Multi_Buffer.cpp for an example of use)
class Blip_Reader {
const Blip_Buffer::buf_t_* buf;
long accum;
#ifdef __MWERKS__
void operator = ( struct foobar ); // helps optimizer
#endif
public:
// avoid anything which might cause optimizer to put object in memory
int begin( Blip_Buffer& blip_buf ) {
buf = blip_buf.buffer_;
accum = blip_buf.reader_accum;
return blip_buf.bass_shift;
}
int read() const {
return accum >> Blip_Buffer::accum_fract;
}
void next( int bass_shift = 9 ) {
accum -= accum >> bass_shift;
accum += ((long) *buf++ - Blip_Buffer::sample_offset) << Blip_Buffer::accum_fract;
}
void end( Blip_Buffer& blip_buf ) {
blip_buf.reader_accum = accum;
}
};
// End of public interface
#ifndef BLIP_BUFFER_ACCURACY
#define BLIP_BUFFER_ACCURACY 16
#endif
const int blip_res_bits_ = 5;
typedef BOOST::uint32_t blip_pair_t_;
class Blip_Impulse_ {
typedef BOOST::uint16_t imp_t;
blip_eq_t eq;
double volume_unit_;
imp_t* impulses;
imp_t* impulse;
int width;
int fine_bits;
int res;
bool generate;
void fine_volume_unit();
void scale_impulse( int unit, imp_t* ) const;
public:
Blip_Buffer* buf;
BOOST::uint32_t offset;
void init( blip_pair_t_* impulses, int width, int res, int fine_bits = 0 );
void volume_unit( double );
void treble_eq( const blip_eq_t& );
};
inline blip_eq_t::blip_eq_t( double t ) :
treble( t ), cutoff( 0 ), sample_rate( 44100 ) {
}
inline blip_eq_t::blip_eq_t( double t, long c, long sr ) :
treble( t ), cutoff( c ), sample_rate( sr ) {
}
inline int Blip_Buffer::length() const {
return length_;
}
inline long Blip_Buffer::samples_avail() const {
return long (offset_ >> BLIP_BUFFER_ACCURACY);
}
inline long Blip_Buffer::sample_rate() const {
return samples_per_sec;
}
inline void Blip_Buffer::end_frame( blip_time_t t ) {
offset_ += t * factor_;
assert(( "Blip_Buffer::end_frame(): Frame went past end of buffer",
samples_avail() <= (long) buffer_size_ ));
}
inline void Blip_Buffer::remove_silence( long count ) {
assert(( "Blip_Buffer::remove_silence(): Tried to remove more samples than available",
count <= samples_avail() ));
offset_ -= resampled_time_t (count) << BLIP_BUFFER_ACCURACY;
}
inline int Blip_Buffer::output_latency() const {
return widest_impulse_ / 2;
}
inline long Blip_Buffer::clock_rate() const {
return clocks_per_sec;
}
// MSVC6 fix
typedef Blip_Buffer::resampled_time_t blip_resampled_time_t;
#include "Blip_Synth.h"
#endif

203
lib/include/Blip_Synth.h Normal file
View file

@ -0,0 +1,203 @@
// Blip_Synth and Blip_Wave are waveform transition synthesizers for adding
// waveforms to a Blip_Buffer.
// Blip_Buffer 0.3.3. Copyright (C) 2003-2005 Shay Green. GNU LGPL license.
#ifndef BLIP_SYNTH_H
#define BLIP_SYNTH_H
#ifndef BLIP_BUFFER_H
#include "Blip_Buffer.h"
#endif
// Quality level. Higher levels are slower, and worse in a few cases.
// Use blip_good_quality as a starting point.
const int blip_low_quality = 1;
const int blip_med_quality = 2;
const int blip_good_quality = 3;
const int blip_high_quality = 4;
// Blip_Synth is a transition waveform synthesizer which adds band-limited
// offsets (transitions) into a Blip_Buffer. For a simpler interface, use
// Blip_Wave (below).
//
// Range specifies the greatest expected offset that will occur. For a
// waveform that goes between +amp and -amp, range should be amp * 2 (half
// that if it only goes between +amp and 0). When range is large, a higher
// accuracy scheme is used; to force this even when range is small, pass
// the negative of range (i.e. -range).
template<int quality,int range>
class Blip_Synth {
BOOST_STATIC_ASSERT( 1 <= quality && quality <= 5 );
BOOST_STATIC_ASSERT( -32768 <= range && range <= 32767 );
enum {
abs_range = (range < 0) ? -range : range,
fine_mode = (range > 512 || range < 0),
width = (quality < 5 ? quality * 4 : Blip_Buffer::widest_impulse_),
res = 1 << blip_res_bits_,
impulse_size = width / 2 * (fine_mode + 1),
base_impulses_size = width / 2 * (res / 2 + 1),
fine_bits = (fine_mode ? (abs_range <= 64 ? 2 : abs_range <= 128 ? 3 :
abs_range <= 256 ? 4 : abs_range <= 512 ? 5 : abs_range <= 1024 ? 6 :
abs_range <= 2048 ? 7 : 8) : 0)
};
blip_pair_t_ impulses [impulse_size * res * 2 + base_impulses_size];
Blip_Impulse_ impulse;
public:
Blip_Synth() { impulse.init( impulses, width, res, fine_bits ); }
// Configure low-pass filter (see notes.txt). Not optimized for real-time control
void treble_eq( const blip_eq_t& eq ) { impulse.treble_eq( eq ); }
// Set volume of a transition at amplitude 'range' by setting volume_unit
// to v / range
void volume( double v ) { impulse.volume_unit( v * (1.0 / abs_range) ); }
// Set base volume unit of transitions, where 1.0 is a full swing between the
// positive and negative extremes. Not optimized for real-time control.
void volume_unit( double unit ) { impulse.volume_unit( unit ); }
// Default Blip_Buffer used for output when none is specified for a given call
Blip_Buffer* output() const { return impulse.buf; }
void output( Blip_Buffer* b ) { impulse.buf = b; }
// Add an amplitude offset (transition) with a magnitude of delta * volume_unit
// into the specified buffer (default buffer if none specified) at the
// specified source time. Delta can be positive or negative. To increase
// performance by inlining code at the call site, use offset_inline().
void offset( blip_time_t, int delta, Blip_Buffer* ) const;
void offset_resampled( blip_resampled_time_t, int delta, Blip_Buffer* ) const;
void offset_resampled( blip_resampled_time_t t, int o ) const {
offset_resampled( t, o, impulse.buf );
}
void offset( blip_time_t t, int delta ) const {
offset( t, delta, impulse.buf );
}
void offset_inline( blip_time_t time, int delta, Blip_Buffer* buf ) const {
offset_resampled( time * buf->factor_ + buf->offset_, delta, buf );
}
void offset_inline( blip_time_t time, int delta ) const {
offset_inline( time, delta, impulse.buf );
}
};
// Blip_Wave is a synthesizer for adding a *single* waveform to a Blip_Buffer.
// A wave is built from a series of delays and new amplitudes. This provides a
// simpler interface than Blip_Synth, nothing more.
template<int quality,int range>
class Blip_Wave {
Blip_Synth<quality,range> synth;
blip_time_t time_;
int last_amp;
public:
// Start wave at time 0 and amplitude 0
Blip_Wave() : time_( 0 ), last_amp( 0 ) { }
// See Blip_Synth for description
void volume( double v ) { synth.volume( v ); }
void volume_unit( double v ) { synth.volume_unit( v ); }
void treble_eq( const blip_eq_t& eq){ synth.treble_eq( eq ); }
Blip_Buffer* output() const { return synth.output(); }
void output( Blip_Buffer* b ) { synth.output( b ); if ( !b ) time_ = last_amp = 0; }
// Current time in frame
blip_time_t time() const { return time_; }
void time( blip_time_t t ) { time_ = t; }
// Current amplitude of wave
int amplitude() const { return last_amp; }
void amplitude( int );
// Move forward by 't' time units
void delay( blip_time_t t ) { time_ += t; }
// End time frame of specified duration. Localize time to new frame.
void end_frame( blip_time_t duration ) {
assert(( "Blip_Wave::end_frame(): Wave hadn't yet been run for entire frame",
duration <= time_ ));
time_ -= duration;
}
};
// End of public interface
template<int quality,int range>
void Blip_Wave<quality,range>::amplitude( int amp ) {
int delta = amp - last_amp;
last_amp = amp;
synth.offset_inline( time_, delta );
}
template<int quality,int range>
inline void Blip_Synth<quality,range>::offset_resampled( blip_resampled_time_t time,
int delta, Blip_Buffer* blip_buf ) const
{
typedef blip_pair_t_ pair_t;
unsigned sample_index = (time >> BLIP_BUFFER_ACCURACY) & ~1;
assert(( "Blip_Synth/Blip_wave: Went past end of buffer",
sample_index < blip_buf->buffer_size_ ));
enum { const_offset = Blip_Buffer::widest_impulse_ / 2 - width / 2 };
pair_t* buf = (pair_t*) &blip_buf->buffer_ [const_offset + sample_index];
enum { shift = BLIP_BUFFER_ACCURACY - blip_res_bits_ };
enum { mask = res * 2 - 1 };
const pair_t* imp = &impulses [((time >> shift) & mask) * impulse_size];
pair_t offset = impulse.offset * delta;
if ( !fine_bits )
{
// normal mode
for ( int n = width / 4; n; --n )
{
pair_t t0 = buf [0] - offset;
pair_t t1 = buf [1] - offset;
t0 += imp [0] * delta;
t1 += imp [1] * delta;
imp += 2;
buf [0] = t0;
buf [1] = t1;
buf += 2;
}
}
else
{
// fine mode
enum { sub_range = 1 << fine_bits };
delta += sub_range / 2;
int delta2 = (delta & (sub_range - 1)) - sub_range / 2;
delta >>= fine_bits;
for ( int n = width / 4; n; --n )
{
pair_t t0 = buf [0] - offset;
pair_t t1 = buf [1] - offset;
t0 += imp [0] * delta2;
t0 += imp [1] * delta;
t1 += imp [2] * delta2;
t1 += imp [3] * delta;
imp += 4;
buf [0] = t0;
buf [1] = t1;
buf += 2;
}
}
}
template<int quality,int range>
void Blip_Synth<quality,range>::offset( blip_time_t time, int delta, Blip_Buffer* buf ) const {
offset_resampled( time * buf->factor_ + buf->offset_, delta, buf );
}
#endif

157
lib/include/Multi_Buffer.h Normal file
View file

@ -0,0 +1,157 @@
// Multi-channel sound buffer interface, and basic mono and stereo buffers
// Blip_Buffer 0.3.3. Copyright (C) 2003-2005 Shay Green. GNU LGPL license.
#ifndef MULTI_BUFFER_H
#define MULTI_BUFFER_H
#include "Blip_Buffer.h"
// Multi_Buffer is an interface to one or more Blip_Buffers mapped to one or
// more channels consisting of left, center, and right buffers.
class Multi_Buffer {
public:
Multi_Buffer( int samples_per_frame );
virtual ~Multi_Buffer() { }
// Set the number of channels available
virtual blargg_err_t set_channel_count( int );
// Get indexed channel, from 0 to channel count - 1
struct channel_t {
Blip_Buffer* center;
Blip_Buffer* left;
Blip_Buffer* right;
};
virtual channel_t channel( int index ) = 0;
// See Blip_Buffer.h
// to do: rename to set_sample_rate
virtual blargg_err_t sample_rate( long rate, int msec = blip_default_length ) = 0;
virtual void clock_rate( long ) = 0;
virtual void bass_freq( int ) = 0;
virtual void clear() = 0;
long sample_rate() const;
// Length of buffer, in milliseconds
int length() const;
// See Blip_Buffer.h. For optimal operation, pass false for 'added_stereo'
// if nothing was added to the left and right buffers of any channel for
// this time frame.
virtual void end_frame( blip_time_t, bool added_stereo = true ) = 0;
// Number of samples per output frame (1 = mono, 2 = stereo)
int samples_per_frame() const;
// See Blip_Buffer.h
virtual long read_samples( blip_sample_t*, long ) = 0;
virtual long samples_avail() const = 0;
private:
// noncopyable
Multi_Buffer( const Multi_Buffer& );
Multi_Buffer& operator = ( const Multi_Buffer& );
long sample_rate_;
int length_;
int const samples_per_frame_;
};
// Mono_Buffer uses a single buffer and outputs mono samples.
class Mono_Buffer : public Multi_Buffer {
Blip_Buffer buf;
public:
Mono_Buffer();
~Mono_Buffer();
// Buffer used for all channels
Blip_Buffer* center();
// See Multi_Buffer
blargg_err_t sample_rate( long rate, int msec = blip_default_length );
using Multi_Buffer::sample_rate;
void clock_rate( long );
void bass_freq( int );
void clear();
channel_t channel( int );
void end_frame( blip_time_t, bool unused = true );
long samples_avail() const;
long read_samples( blip_sample_t*, long );
};
// Stereo_Buffer uses three buffers (one for center) and outputs stereo sample pairs.
class Stereo_Buffer : public Multi_Buffer {
public:
Stereo_Buffer();
~Stereo_Buffer();
// Buffers used for all channels
Blip_Buffer* center();
Blip_Buffer* left();
Blip_Buffer* right();
// See Multi_Buffer
blargg_err_t sample_rate( long, int msec = blip_default_length );
using Multi_Buffer::sample_rate;
void clock_rate( long );
void bass_freq( int );
void clear();
channel_t channel( int index );
void end_frame( blip_time_t, bool added_stereo = true );
long samples_avail() const;
long read_samples( blip_sample_t*, long );
private:
enum { buf_count = 3 };
Blip_Buffer bufs [buf_count];
channel_t chan;
bool stereo_added;
bool was_stereo;
void mix_stereo( blip_sample_t*, long );
void mix_mono( blip_sample_t*, long );
};
// End of public interface
inline blargg_err_t Multi_Buffer::sample_rate( long rate, int msec )
{
sample_rate_ = rate;
length_ = msec;
return blargg_success;
}
inline int Multi_Buffer::samples_per_frame() const { return samples_per_frame_; }
inline Blip_Buffer* Stereo_Buffer::left() { return &bufs [1]; }
inline Blip_Buffer* Stereo_Buffer::center() { return &bufs [0]; }
inline Blip_Buffer* Stereo_Buffer::right() { return &bufs [2]; }
inline long Stereo_Buffer::samples_avail() const { return bufs [0].samples_avail() * 2; }
inline Stereo_Buffer::channel_t Stereo_Buffer::channel( int index ) { return chan; }
inline long Multi_Buffer::sample_rate() const { return sample_rate_; }
inline int Multi_Buffer::length() const { return length_; }
inline Blip_Buffer* Mono_Buffer::center() { return &buf; }
inline void Mono_Buffer::clock_rate( long rate ) { buf.clock_rate( rate ); }
inline void Mono_Buffer::clear() { buf.clear(); }
inline void Mono_Buffer::bass_freq( int freq ) { buf.bass_freq( freq ); }
inline long Mono_Buffer::read_samples( blip_sample_t* p, long s ) { return buf.read_samples( p, s ); }
inline long Mono_Buffer::samples_avail() const { return buf.samples_avail(); }
#endif

162
lib/include/Nes_Apu.h Normal file
View file

@ -0,0 +1,162 @@
// NES 2A03 APU sound chip emulator
// Nes_Snd_Emu 0.1.7. Copyright (C) 2003-2005 Shay Green. GNU LGPL license.
#ifndef NES_APU_H
#define NES_APU_H
typedef long cpu_time_t; // CPU clock cycle count
typedef unsigned cpu_addr_t; // 16-bit memory address
#include "Nes_Oscs.h"
struct apu_snapshot_t;
class Nonlinear_Buffer;
class Nes_Apu {
public:
Nes_Apu();
~Nes_Apu();
// Set buffer to generate all sound into, or disable sound if NULL
void output( Blip_Buffer* );
// Set memory reader callback used by DMC oscillator to fetch samples.
// When callback is invoked, 'user_data' is passed unchanged as the
// first parameter.
void dmc_reader( int (*callback)( void* user_data, cpu_addr_t ), void* user_data = NULL );
// All time values are the number of CPU clock cycles relative to the
// beginning of the current time frame. Before resetting the CPU clock
// count, call end_frame( last_cpu_time ).
// Write to register (0x4000-0x4017, except 0x4014 and 0x4016)
enum { start_addr = 0x4000 };
enum { end_addr = 0x4017 };
void write_register( cpu_time_t, cpu_addr_t, int data );
// Read from status register at 0x4015
enum { status_addr = 0x4015 };
int read_status( cpu_time_t );
// Run all oscillators up to specified time, end current time frame, then
// start a new time frame at time 0. Time frames have no effect on emulation
// and each can be whatever length is convenient.
void end_frame( cpu_time_t );
// Additional optional features (can be ignored without any problem)
// Reset internal frame counter, registers, and all oscillators.
// Use PAL timing if pal_timing is true, otherwise use NTSC timing.
// Set the DMC oscillator's initial DAC value to initial_dmc_dac without
// any audible click.
void reset( bool pal_timing = false, int initial_dmc_dac = 0 );
// Save/load snapshot of exact emulation state
void save_snapshot( apu_snapshot_t* out ) const;
void load_snapshot( apu_snapshot_t const& );
// Set overall volume (default is 1.0)
void volume( double );
// Reset oscillator amplitudes. Must be called when clearing buffer while
// using non-linear sound.
void buffer_cleared();
// Set treble equalization (see notes.txt).
void treble_eq( const blip_eq_t& );
// Set sound output of specific oscillator to buffer. If buffer is NULL,
// the specified oscillator is muted and emulation accuracy is reduced.
// The oscillators are indexed as follows: 0) Square 1, 1) Square 2,
// 2) Triangle, 3) Noise, 4) DMC.
enum { osc_count = 5 };
void osc_output( int index, Blip_Buffer* buffer );
// Set IRQ time callback that is invoked when the time of earliest IRQ
// may have changed, or NULL to disable. When callback is invoked,
// 'user_data' is passed unchanged as the first parameter.
void irq_notifier( void (*callback)( void* user_data ), void* user_data = NULL );
// Get time that APU-generated IRQ will occur if no further register reads
// or writes occur. If IRQ is already pending, returns irq_waiting. If no
// IRQ will occur, returns no_irq.
enum { no_irq = LONG_MAX / 2 + 1 };
enum { irq_waiting = 0 };
cpu_time_t earliest_irq() const;
// Count number of DMC reads that would occur if 'run_until( t )' were executed.
// If last_read is not NULL, set *last_read to the earliest time that
// 'count_dmc_reads( time )' would result in the same result.
int count_dmc_reads( cpu_time_t t, cpu_time_t* last_read = NULL ) const;
// Run APU until specified time, so that any DMC memory reads can be
// accounted for (i.e. inserting CPU wait states).
void run_until( cpu_time_t );
// End of public interface.
private:
friend class Nes_Nonlinearizer;
void enable_nonlinear( double volume );
private:
// noncopyable
Nes_Apu( const Nes_Apu& );
Nes_Apu& operator = ( const Nes_Apu& );
Nes_Osc* oscs [osc_count];
Nes_Square square1;
Nes_Square square2;
Nes_Noise noise;
Nes_Triangle triangle;
Nes_Dmc dmc;
cpu_time_t last_time; // has been run until this time in current frame
cpu_time_t earliest_irq_;
cpu_time_t next_irq;
int frame_period;
int frame_delay; // cycles until frame counter runs next
int frame; // current frame (0-3)
int osc_enables;
int frame_mode;
bool irq_flag;
void (*irq_notifier_)( void* user_data );
void* irq_data;
Nes_Square::Synth square_synth; // shared by squares
void irq_changed();
void state_restored();
friend struct Nes_Dmc;
};
inline void Nes_Apu::osc_output( int osc, Blip_Buffer* buf )
{
assert(( "Nes_Apu::osc_output(): Index out of range", 0 <= osc && osc < osc_count ));
oscs [osc]->output = buf;
}
inline cpu_time_t Nes_Apu::earliest_irq() const
{
return earliest_irq_;
}
inline void Nes_Apu::dmc_reader( int (*func)( void*, cpu_addr_t ), void* user_data )
{
dmc.rom_reader_data = user_data;
dmc.rom_reader = func;
}
inline void Nes_Apu::irq_notifier( void (*func)( void* user_data ), void* user_data )
{
irq_notifier_ = func;
irq_data = user_data;
}
inline int Nes_Apu::count_dmc_reads( cpu_time_t time, cpu_time_t* last_read ) const
{
return dmc.count_reads( time, last_read );
}
#endif

86
lib/include/Nes_Namco.h Normal file
View file

@ -0,0 +1,86 @@
// Namco 106 sound chip emulator
// Nes_Snd_Emu 0.1.7. Copyright (C) 2003-2005 Shay Green. GNU LGPL license.
#ifndef NES_NAMCO_H
#define NES_NAMCO_H
#include "Nes_Apu.h"
struct namco_snapshot_t;
class Nes_Namco {
public:
Nes_Namco();
~Nes_Namco();
// See Nes_Apu.h for reference.
void volume( double );
void treble_eq( const blip_eq_t& );
void output( Blip_Buffer* );
enum { osc_count = 8 };
void osc_output( int index, Blip_Buffer* );
void reset();
void end_frame( cpu_time_t );
// Read/write data register is at 0x4800
enum { data_reg_addr = 0x4800 };
void write_data( cpu_time_t, int );
int read_data();
// Write-only address register is at 0xF800
enum { addr_reg_addr = 0xF800 };
void write_addr( int );
// to do: implement save/restore
void save_snapshot( namco_snapshot_t* out );
void load_snapshot( namco_snapshot_t const& );
private:
// noncopyable
Nes_Namco( const Nes_Namco& );
Nes_Namco& operator = ( const Nes_Namco& );
struct Namco_Osc {
long delay;
Blip_Buffer* output;
short last_amp;
short wave_pos;
};
Namco_Osc oscs [osc_count];
cpu_time_t last_time;
int addr_reg;
enum { reg_count = 0x80 };
BOOST::uint8_t reg [reg_count];
Blip_Synth<blip_good_quality,15> synth;
BOOST::uint8_t& access();
void run_until( cpu_time_t );
};
inline void Nes_Namco::volume( double v ) { synth.volume( 0.10 / osc_count * v ); }
inline void Nes_Namco::treble_eq( const blip_eq_t& eq ) { synth.treble_eq( eq ); }
inline void Nes_Namco::write_addr( int v ) { addr_reg = v; }
inline int Nes_Namco::read_data() { return access(); }
inline void Nes_Namco::osc_output( int i, Blip_Buffer* buf )
{
assert( (unsigned) i < osc_count );
oscs [i].output = buf;
}
inline void Nes_Namco::write_data( cpu_time_t time, int data )
{
run_until( time );
access() = data;
}
#endif

142
lib/include/Nes_Oscs.h Normal file
View file

@ -0,0 +1,142 @@
// Private oscillators used by Nes_Apu
// Nes_Snd_Emu 0.1.7. Copyright (C) 2003-2005 Shay Green. GNU LGPL license.
#ifndef NES_OSCS_H
#define NES_OSCS_H
#include "Blip_Buffer.h"
class Nes_Apu;
struct Nes_Osc
{
unsigned char regs [4];
bool reg_written [4];
Blip_Buffer* output;
int length_counter;// length counter (0 if unused by oscillator)
int delay; // delay until next (potential) transition
int last_amp; // last amplitude oscillator was outputting
void clock_length( int halt_mask );
int period() const {
return (regs [3] & 7) * 0x100 + (regs [2] & 0xff);
}
void reset() {
delay = 0;
last_amp = 0;
}
int update_amp( int amp ) {
int delta = amp - last_amp;
last_amp = amp;
return delta;
}
};
struct Nes_Envelope : Nes_Osc
{
int envelope;
int env_delay;
void clock_envelope();
int volume() const;
void reset() {
envelope = 0;
env_delay = 0;
Nes_Osc::reset();
}
};
// Nes_Square
struct Nes_Square : Nes_Envelope
{
enum { negate_flag = 0x08 };
enum { shift_mask = 0x07 };
enum { phase_range = 8 };
int phase;
int sweep_delay;
typedef Blip_Synth<blip_good_quality,15> Synth;
const Synth* synth; // shared between squares
void clock_sweep( int adjust );
void run( cpu_time_t, cpu_time_t );
void reset() {
sweep_delay = 0;
Nes_Envelope::reset();
}
};
// Nes_Triangle
struct Nes_Triangle : Nes_Osc
{
enum { phase_range = 16 };
int phase;
int linear_counter;
Blip_Synth<blip_good_quality,15> synth;
int calc_amp() const;
void run( cpu_time_t, cpu_time_t );
void clock_linear_counter();
void reset() {
linear_counter = 0;
phase = phase_range;
Nes_Osc::reset();
}
};
// Nes_Noise
struct Nes_Noise : Nes_Envelope
{
int noise;
Blip_Synth<blip_med_quality,15> synth;
void run( cpu_time_t, cpu_time_t );
void reset() {
noise = 1 << 14;
Nes_Envelope::reset();
}
};
// Nes_Dmc
struct Nes_Dmc : Nes_Osc
{
int address; // address of next byte to read
int period;
//int length_counter; // bytes remaining to play (already defined in Nes_Osc)
int buf;
int bits_remain;
int bits;
bool buf_empty;
bool silence;
enum { loop_flag = 0x40 };
int dac;
cpu_time_t next_irq;
bool irq_enabled;
bool irq_flag;
bool pal_mode;
bool nonlinear;
int (*rom_reader)( void*, cpu_addr_t ); // needs to be initialized to rom read function
void* rom_reader_data;
Nes_Apu* apu;
Blip_Synth<blip_med_quality,127> synth;
void start();
void write_register( int, int );
void run( cpu_time_t, cpu_time_t );
void recalc_irq();
void fill_buffer();
void reload_sample();
void reset();
int count_reads( cpu_time_t, cpu_time_t* ) const;
};
#endif

85
lib/include/Nes_Vrc6.h Normal file
View file

@ -0,0 +1,85 @@
// Konami VRC6 sound chip emulator
// Nes_Snd_Emu 0.1.7. Copyright (C) 2003-2005 Shay Green. GNU LGPL license.
#ifndef NES_VRC6_H
#define NES_VRC6_H
#include "Nes_Apu.h"
struct vrc6_snapshot_t;
class Nes_Vrc6 {
public:
Nes_Vrc6();
~Nes_Vrc6();
// See Nes_Apu.h for reference
void reset();
void volume( double );
void treble_eq( blip_eq_t const& );
void output( Blip_Buffer* );
enum { osc_count = 3 };
void osc_output( int index, Blip_Buffer* );
void end_frame( cpu_time_t );
void save_snapshot( vrc6_snapshot_t* ) const;
void load_snapshot( vrc6_snapshot_t const& );
// Oscillator 0 write-only registers are at $9000-$9002
// Oscillator 1 write-only registers are at $A000-$A002
// Oscillator 2 write-only registers are at $B000-$B002
enum { reg_count = 3 };
enum { base_addr = 0x9000 };
enum { addr_step = 0x1000 };
void write_osc( cpu_time_t, int osc, int reg, int data );
private:
// noncopyable
Nes_Vrc6( const Nes_Vrc6& );
Nes_Vrc6& operator = ( const Nes_Vrc6& );
struct Vrc6_Osc
{
BOOST::uint8_t regs [3];
Blip_Buffer* output;
int delay;
int last_amp;
int phase;
int amp; // only used by saw
int period() const
{
return (regs [2] & 0x0f) * 0x100L + regs [1] + 1;
}
};
Vrc6_Osc oscs [osc_count];
cpu_time_t last_time;
Blip_Synth<blip_med_quality,31> saw_synth;
Blip_Synth<blip_good_quality,15> square_synth;
void run_until( cpu_time_t );
void run_square( Vrc6_Osc& osc, cpu_time_t );
void run_saw( cpu_time_t );
};
struct vrc6_snapshot_t
{
BOOST::uint8_t regs [3] [3];
BOOST::uint8_t saw_amp;
BOOST::uint16_t delays [3];
BOOST::uint8_t phases [3];
BOOST::uint8_t unused;
};
BOOST_STATIC_ASSERT( sizeof (vrc6_snapshot_t) == 20 );
inline void Nes_Vrc6::osc_output( int i, Blip_Buffer* buf )
{
assert( (unsigned) i < osc_count );
oscs [i].output = buf;
}
#endif

View file

@ -0,0 +1,65 @@
// NES non-linear audio output handling.
// Nes_Snd_Emu 0.1.7. Copyright (C) 2003-2005 Shay Green. GNU LGPL license.
#ifndef NONLINEAR_BUFFER_H
#define NONLINEAR_BUFFER_H
#include "Multi_Buffer.h"
class Nes_Apu;
// Use to make samples non-linear in Blip_Buffer used for triangle, noise, and DMC only
class Nes_Nonlinearizer {
public:
Nes_Nonlinearizer();
// Must be called when buffer is cleared
void clear() { accum = 0x8000; }
// Enable/disable non-linear output
void enable( Nes_Apu&, bool = true );
// Make at most 'count' samples in buffer non-linear and return number
// of samples modified. This many samples must then be read out of the buffer.
long make_nonlinear( Blip_Buffer&, long count );
private:
enum { shift = 5 };
enum { half = 0x8000 >> shift };
enum { entry_mask = half * 2 - 1 };
BOOST::uint16_t table [half * 2];
long accum;
bool nonlinear;
};
class Nonlinear_Buffer : public Multi_Buffer {
public:
Nonlinear_Buffer();
~Nonlinear_Buffer();
// Enable/disable non-linear output
void enable_nonlinearity( Nes_Apu&, bool = true );
// Blip_Buffer to output other sound chips to
Blip_Buffer* buffer() { return &buf; }
// See Multi_Buffer.h
blargg_err_t sample_rate( long rate, int msec = blip_default_length );
using Multi_Buffer::sample_rate;
void clock_rate( long );
void bass_freq( int );
void clear();
channel_t channel( int );
void end_frame( blip_time_t, bool unused = true );
long samples_avail() const;
long read_samples( blip_sample_t*, long );
private:
Blip_Buffer buf;
Blip_Buffer tnd;
Nes_Nonlinearizer nonlinearizer;
};
#endif

44
lib/include/Sound_Queue.h Normal file
View file

@ -0,0 +1,44 @@
// Simple sound queue for synchronous sound handling in SDL
// Copyright (C) 2005 Shay Green. MIT license.
#ifndef SOUND_QUEUE_H
#define SOUND_QUEUE_H
#include <SDL2/SDL.h>
// Simple SDL sound wrapper that has a synchronous interface
class Sound_Queue {
public:
Sound_Queue();
~Sound_Queue();
// Initialize with specified sample rate and channel count.
// Returns NULL on success, otherwise error string.
const char* init( long sample_rate, int chan_count = 1 );
// Number of samples in buffer waiting to be played
int sample_count() const;
// Write samples to buffer and block until enough space is available
typedef short sample_t;
void write( const sample_t*, int count );
private:
enum { buf_size = 2048 };
enum { buf_count = 3 };
sample_t* volatile bufs;
SDL_sem* volatile free_sem;
int volatile read_buf;
int write_buf;
int write_pos;
bool sound_open;
sample_t* buf( int index );
void fill_buffer( Uint8*, int );
static void fill_buffer_( void*, Uint8*, int );
};
#endif

View file

@ -0,0 +1,75 @@
// NES APU snapshot support
// Nes_Snd_Emu 0.1.7. Copyright (C) 2003-2005 Shay Green. GNU LGPL license.
#ifndef APU_SNAPSHOT_H
#define APU_SNAPSHOT_H
#include "blargg_common.h"
struct apu_snapshot_t
{
typedef BOOST::uint8_t byte;
typedef byte env_t [3];
/*struct env_t {
byte delay;
byte env;3
byte written;
};*/
byte w40xx [0x14]; // $4000-$4013
byte w4015; // enables
byte w4017; // mode
BOOST::uint16_t delay;
byte step;
byte irq_flag;
struct square_t {
BOOST::uint16_t delay;
env_t env;
byte length;
byte phase;
byte swp_delay;
byte swp_reset;
byte unused [1];
};
square_t square1;
square_t square2;
struct triangle_t {
BOOST::uint16_t delay;
byte length;
byte phase;
byte linear_counter;
byte linear_mode;
} triangle;
struct noise_t {
BOOST::uint16_t delay;
env_t env;
byte length;
BOOST::uint16_t shift_reg;
} noise;
struct dmc_t {
BOOST::uint16_t delay;
BOOST::uint16_t remain;
BOOST::uint16_t addr;
byte buf;
byte bits_remain;
byte bits;
byte buf_empty;
byte silence;
byte irq_flag;
} dmc;
enum { tag = 'APUR' };
void swap();
};
BOOST_STATIC_ASSERT( sizeof (apu_snapshot_t) == 72 );
#endif

180
lib/include/blargg_common.h Normal file
View file

@ -0,0 +1,180 @@
// Sets up common environment for Shay Green's libraries.
//
// Don't modify this file directly; #define HAVE_CONFIG_H and put your
// configuration into "config.h".
// Copyright (C) 2004-2005 Shay Green.
#ifndef BLARGG_COMMON_H
#define BLARGG_COMMON_H
// Allow prefix configuration file *which can re-include blargg_common.h*
// (probably indirectly).
#ifdef HAVE_CONFIG_H
#undef BLARGG_COMMON_H
#include "config.h"
#define BLARGG_COMMON_H
#endif
// Source files use #include BLARGG_ENABLE_OPTIMIZER before performance-critical code
#ifndef BLARGG_ENABLE_OPTIMIZER
#define BLARGG_ENABLE_OPTIMIZER "blargg_common.h"
#endif
// Source files have #include BLARGG_SOURCE_BEGIN at the beginning
#ifndef BLARGG_SOURCE_BEGIN
#define BLARGG_SOURCE_BEGIN "blargg_source.h"
#endif
// Determine compiler's language support
#if defined (__MWERKS__)
// Metrowerks CodeWarrior
#define BLARGG_COMPILER_HAS_NAMESPACE 1
#if !__option(bool)
#define BLARGG_COMPILER_HAS_BOOL 0
#endif
#elif defined (_MSC_VER)
// Microsoft Visual C++
#if _MSC_VER < 1100
#define BLARGG_COMPILER_HAS_BOOL 0
#endif
#elif defined (__GNUC__)
// GNU C++
#define BLARGG_COMPILER_HAS_NAMESPACE 1
#define BLARGG_COMPILER_HAS_BOOL 1
#elif defined (__MINGW32__)
// Mingw?
#define BLARGG_COMPILER_HAS_BOOL 1
#elif __cplusplus < 199711
// Pre-ISO C++ compiler
#define BLARGG_COMPILER_HAS_BOOL 0
#define BLARGG_NEW new
#define STATIC_CAST( type ) (type)
#endif
// STATIC_CAST(T) (expr) -> static_cast< T > (expr)
#ifndef STATIC_CAST
#define STATIC_CAST( type ) static_cast< type >
#endif
// Set up boost
#include "boost/config.hpp"
#ifndef BOOST_MINIMAL
#define BOOST boost
#ifndef BLARGG_COMPILER_HAS_NAMESPACE
#define BLARGG_COMPILER_HAS_NAMESPACE 1
#endif
#ifndef BLARGG_COMPILER_HAS_BOOL
#define BLARGG_COMPILER_HAS_BOOL 1
#endif
#endif
// Bool support
#ifndef BLARGG_COMPILER_HAS_BOOL
#define BLARGG_COMPILER_HAS_BOOL 1
#elif !BLARGG_COMPILER_HAS_BOOL
typedef int bool;
const bool true = 1;
const bool false = 0;
#endif
// Set up namespace support
#ifndef BLARGG_COMPILER_HAS_NAMESPACE
#define BLARGG_COMPILER_HAS_NAMESPACE 0
#endif
#ifndef BLARGG_USE_NAMESPACE
#define BLARGG_USE_NAMESPACE BLARGG_COMPILER_HAS_NAMESPACE
#endif
#ifndef BOOST
#if BLARGG_USE_NAMESPACE
#define BOOST boost
#else
#define BOOST
#endif
#endif
#undef BLARGG_BEGIN_NAMESPACE
#undef BLARGG_END_NAMESPACE
#if BLARGG_USE_NAMESPACE
#define BLARGG_BEGIN_NAMESPACE( name ) namespace name {
#define BLARGG_END_NAMESPACE }
#else
#define BLARGG_BEGIN_NAMESPACE( name )
#define BLARGG_END_NAMESPACE
#endif
#if BLARGG_USE_NAMESPACE
#define STD std
#else
#define STD
#endif
// BOOST::uint8_t, BOOST::int16_t, etc.
#include "boost/cstdint.hpp"
// BOOST_STATIC_ASSERT( expr )
#include "boost/static_assert.hpp"
// Common standard headers
#if BLARGG_COMPILER_HAS_NAMESPACE
#include <cstddef>
#include <cassert>
#include <new>
#else
#include <stddef.h>
#include <assert.h>
#endif
// blargg_err_t (NULL on success, otherwise error string)
typedef const char* blargg_err_t;
const blargg_err_t blargg_success = 0;
// BLARGG_NEW is used in place of 'new' to create objects. By default,
// nothrow new is used.
#ifndef BLARGG_NEW
#define BLARGG_NEW new (STD::nothrow)
#endif
// BLARGG_BIG_ENDIAN and BLARGG_LITTLE_ENDIAN
// Only needed if modules are used which must know byte order.
#if !defined (BLARGG_BIG_ENDIAN) && !defined (BLARGG_LITTLE_ENDIAN)
#if defined (__powerc) || defined (macintosh)
#define BLARGG_BIG_ENDIAN 1
#elif defined (_MSC_VER) && defined (_M_IX86)
#define BLARGG_LITTLE_ENDIAN 1
#endif
#endif
// BLARGG_NONPORTABLE (allow use of nonportable optimizations/features)
#ifndef BLARGG_NONPORTABLE
#define BLARGG_NONPORTABLE 0
#endif
#ifdef BLARGG_MOST_PORTABLE
#error "BLARGG_MOST_PORTABLE has been removed; use BLARGG_NONPORTABLE."
#endif
// BLARGG_CPU_*
#if !defined (BLARGG_CPU_POWERPC) && !defined (BLARGG_CPU_X86)
#if defined (__powerc)
#define BLARGG_CPU_POWERPC 1
#elif defined (_MSC_VER) && defined (_M_IX86)
#define BLARGG_CPU_X86 1
#endif
#endif
#endif

View file

@ -0,0 +1,43 @@
// By default, #included at beginning of library source files
// Copyright (C) 2005 Shay Green.
#ifndef BLARGG_SOURCE_H
#define BLARGG_SOURCE_H
// If debugging is enabled, abort program if expr is false. Meant for checking
// internal state and consistency. A failed assertion indicates a bug in the module.
// void assert( bool expr );
#include <assert.h>
// If debugging is enabled and expr is false, abort program. Meant for checking
// caller-supplied parameters and operations that are outside the control of the
// module. A failed requirement indicates a bug outside the module.
// void require( bool expr );
#undef require
#define require( expr ) assert(( "unmet requirement", expr ))
// Like printf() except output goes to debug log file. Might be defined to do
// nothing (not even evaluate its arguments).
// void dprintf( const char* format, ... );
#undef dprintf
#define dprintf (1) ? ((void) 0) : (void)
// If enabled, evaluate expr and if false, make debug log entry with source file
// and line. Meant for finding situations that should be examined further, but that
// don't indicate a problem. In all cases, execution continues normally.
#undef check
#define check( expr ) ((void) 0)
// If expr returns non-NULL error string, return it from current function, otherwise continue.
#define BLARGG_RETURN_ERR( expr ) do { \
blargg_err_t blargg_return_err_ = (expr); \
if ( blargg_return_err_ ) return blargg_return_err_; \
} while ( 0 )
// If ptr is NULL, return out of memory error string.
#define BLARGG_CHECK_ALLOC( ptr ) do { if ( !(ptr) ) return "Out of memory"; } while ( 0 )
#endif

View file

@ -0,0 +1,13 @@
// Boost substitute. For full boost library see http://boost.org
#ifndef BOOST_CONFIG_HPP
#define BOOST_CONFIG_HPP
#define BOOST_MINIMAL 1
#define BLARGG_BEGIN_NAMESPACE( name )
#define BLARGG_END_NAMESPACE
#endif

View file

@ -0,0 +1,42 @@
// Boost substitute. For full boost library see http://boost.org
#ifndef BOOST_CSTDINT_HPP
#define BOOST_CSTDINT_HPP
#if BLARGG_USE_NAMESPACE
#include <climits>
#else
#include <limits.h>
#endif
BLARGG_BEGIN_NAMESPACE( boost )
#if UCHAR_MAX != 0xFF || SCHAR_MAX != 0x7F
# error "No suitable 8-bit type available"
#endif
typedef unsigned char uint8_t;
typedef signed char int8_t;
#if USHRT_MAX != 0xFFFF
# error "No suitable 16-bit type available"
#endif
typedef short int16_t;
typedef unsigned short uint16_t;
#if ULONG_MAX == 0xFFFFFFFF
typedef long int32_t;
typedef unsigned long uint32_t;
#elif UINT_MAX == 0xFFFFFFFF
typedef int int32_t;
typedef unsigned int uint32_t;
#else
# error "No suitable 32-bit type available"
#endif
BLARGG_END_NAMESPACE
#endif

View file

@ -0,0 +1,22 @@
// Boost substitute. For full boost library see http://boost.org
#ifndef BOOST_STATIC_ASSERT_HPP
#define BOOST_STATIC_ASSERT_HPP
#if defined (_MSC_VER) && _MSC_VER <= 1200
// MSVC6 can't handle the ##line concatenation
#define BOOST_STATIC_ASSERT( expr ) struct { int n [1 / ((expr) ? 1 : 0)]; }
#else
#define BOOST_STATIC_ASSERT3( expr, line ) \
typedef int boost_static_assert_##line [1 / ((expr) ? 1 : 0)]
#define BOOST_STATIC_ASSERT2( expr, line ) BOOST_STATIC_ASSERT3( expr, line )
#define BOOST_STATIC_ASSERT( expr ) BOOST_STATIC_ASSERT2( expr, __LINE__ )
#endif
#endif

50
old/apu.cpp Normal file
View file

@ -0,0 +1,50 @@
#include "gui.hpp"
#include "cpu.hpp"
#include "apu.hpp"
namespace APU {
Nes_Apu apu;
Blip_Buffer buf;
const int OUT_SIZE = 4096;
blip_sample_t outBuf[OUT_SIZE];
void init()
{
buf.sample_rate(96000);
buf.clock_rate(1789773);
apu.output(&buf);
apu.dmc_reader(CPU::dmc_read);
}
void reset()
{
apu.reset();
buf.clear();
}
template <bool write> u8 access(int elapsed, u16 addr, u8 v)
{
if (write)
apu.write_register(elapsed, addr, v);
else if (addr == apu.status_addr)
v = apu.read_status(elapsed);
return v;
}
template u8 access<0>(int, u16, u8); template u8 access<1>(int, u16, u8);
void run_frame(int elapsed)
{
apu.end_frame(elapsed);
buf.end_frame(elapsed);
if (buf.samples_avail() >= OUT_SIZE)
GUI::new_samples(outBuf, buf.read_samples(outBuf, OUT_SIZE));
}
}

73
old/cartridge.cpp Normal file
View file

@ -0,0 +1,73 @@
#include <cstdio>
#include "apu.hpp"
#include "cpu.hpp"
#include "mappers/mapper0.hpp"
#include "mappers/mapper1.hpp"
#include "mappers/mapper2.hpp"
#include "mappers/mapper3.hpp"
#include "mappers/mapper4.hpp"
#include "ppu.hpp"
#include "cartridge.hpp"
namespace Cartridge {
Mapper* mapper = nullptr; // Mapper chip.
/* PRG-ROM access */
template <bool wr> u8 access(u16 addr, u8 v)
{
if (!wr) return mapper->read(addr);
else return mapper->write(addr, v);
}
template u8 access<0>(u16, u8); template u8 access<1>(u16, u8);
/* CHR-ROM/RAM access */
template <bool wr> u8 chr_access(u16 addr, u8 v)
{
if (!wr) return mapper->chr_read(addr);
else return mapper->chr_write(addr, v);
}
template u8 chr_access<0>(u16, u8); template u8 chr_access<1>(u16, u8);
void signal_scanline()
{
mapper->signal_scanline();
}
/* Load the ROM from a file. */
void load(const char* fileName)
{
FILE* f = fopen(fileName, "rb");
fseek(f, 0, SEEK_END);
int size = ftell(f);
fseek(f, 0, SEEK_SET);
u8* rom = new u8[size];
fread(rom, size, 1, f);
fclose(f);
int mapperNum = (rom[7] & 0xF0) | (rom[6] >> 4);
if (loaded()) delete mapper;
switch (mapperNum)
{
case 0: mapper = new Mapper0(rom); break;
case 1: mapper = new Mapper1(rom); break;
case 2: mapper = new Mapper2(rom); break;
case 3: mapper = new Mapper3(rom); break;
case 4: mapper = new Mapper4(rom); break;
}
CPU::power();
PPU::reset();
APU::reset();
}
bool loaded()
{
return mapper != nullptr;
}
}

155
old/config.cpp Normal file
View file

@ -0,0 +1,155 @@
#include <cstdlib>
#include <SimpleIni.h>
#include "config.hpp"
#include "gui.hpp"
namespace GUI {
/* Settings */
CSimpleIniA ini(true, false, false);
/* Window settings */
int last_window_size = 1;
/* Controls settings */
SDL_Scancode KEY_A [] = { SDL_SCANCODE_A, SDL_SCANCODE_ESCAPE };
SDL_Scancode KEY_B [] = { SDL_SCANCODE_S, SDL_SCANCODE_ESCAPE };
SDL_Scancode KEY_SELECT[] = { SDL_SCANCODE_SPACE, SDL_SCANCODE_ESCAPE };
SDL_Scancode KEY_START [] = { SDL_SCANCODE_RETURN, SDL_SCANCODE_ESCAPE };
SDL_Scancode KEY_UP [] = { SDL_SCANCODE_UP, SDL_SCANCODE_ESCAPE };
SDL_Scancode KEY_DOWN [] = { SDL_SCANCODE_DOWN, SDL_SCANCODE_ESCAPE };
SDL_Scancode KEY_LEFT [] = { SDL_SCANCODE_LEFT, SDL_SCANCODE_ESCAPE };
SDL_Scancode KEY_RIGHT [] = { SDL_SCANCODE_RIGHT, SDL_SCANCODE_ESCAPE };
int BTN_UP [] = { -1, -1 };
int BTN_DOWN [] = { -1, -1 };
int BTN_LEFT [] = { -1, -1 };
int BTN_RIGHT [] = { -1, -1 };
int BTN_A [] = { -1, -1 };
int BTN_B [] = { -1, -1 };
int BTN_SELECT[] = { -1, -1 };
int BTN_START [] = { -1, -1 };
bool useJoystick[] = { false, false };
/* Ensure config directory exists */
const char* get_config_path(char* buf, int buflen)
{
/* Bail on the complex stuff if we don't need it */
if (!USE_CONFIG_DIR)
return CONFIG_FALLBACK;
/* First, get the home directory */
char homepath[CONFIG_PATH_MAX];
char path[CONFIG_PATH_MAX];
char * home = getenv("HOME");
if (home == NULL)
return CONFIG_FALLBACK;
snprintf(homepath, sizeof(homepath), "%s/.config", home);
/* Then, .config as a folder */
int res = mkdir(homepath, CONFIG_DIR_DEFAULT_MODE);
int err = errno;
if (res == -1 && err != EEXIST)
return CONFIG_FALLBACK;
snprintf(path, sizeof(path), "%s/%s", homepath, CONFIG_DIR_NAME);
/* Finally, CONFIG_DIR_NAME as a sub-folder */
res = mkdir(path, CONFIG_DIR_DEFAULT_MODE);
err = errno;
if (res == -1 && err != EEXIST)
return CONFIG_FALLBACK;
snprintf(buf, buflen, "%s/settings", path);
return buf;
}
/* Load settings */
void load_settings()
{
/* Files */
char path[CONFIG_PATH_MAX];
ini.LoadFile(get_config_path(path, sizeof(path)));
/* Screen settings */
int screen_size = atoi(ini.GetValue("screen", "size", "1"));
if (screen_size < 1 || screen_size > 4)
screen_size = 1;
set_size(screen_size);
/* Control settings */
for (int p = 0; p <= 1; p++)
{
const char* section = (p == 0) ? "controls p1" : "controls p2";
useJoystick[p] = (ini.GetValue(section, "usejoy", "no"))[0] == 'y';
if (useJoystick[p])
{
BTN_UP[p] = atoi(ini.GetValue(section, "UP", "-1"));
BTN_DOWN[p] = atoi(ini.GetValue(section, "DOWN", "-1"));
BTN_LEFT[p] = atoi(ini.GetValue(section, "LEFT", "-1"));
BTN_RIGHT[p] = atoi(ini.GetValue(section, "RIGHT", "-1"));
BTN_A[p] = atoi(ini.GetValue(section, "A", "-1"));
BTN_B[p] = atoi(ini.GetValue(section, "B", "-1"));
BTN_SELECT[p] = atoi(ini.GetValue(section, "SELECT", "-1"));
BTN_START[p] = atoi(ini.GetValue(section, "START", "-1"));
}
else
{
KEY_UP[p] = (SDL_Scancode)atoi(ini.GetValue(section, "UP", "82"));
KEY_DOWN[p] = (SDL_Scancode)atoi(ini.GetValue(section, "DOWN", "81"));
KEY_LEFT[p] = (SDL_Scancode)atoi(ini.GetValue(section, "LEFT", "80"));
KEY_RIGHT[p] = (SDL_Scancode)atoi(ini.GetValue(section, "RIGHT", "79"));
KEY_A[p] = (SDL_Scancode)atoi(ini.GetValue(section, "A", "4"));
KEY_B[p] = (SDL_Scancode)atoi(ini.GetValue(section, "B", "22"));
KEY_SELECT[p] = (SDL_Scancode)atoi(ini.GetValue(section, "SELECT", "44"));
KEY_START[p] = (SDL_Scancode)atoi(ini.GetValue(section, "START", "40"));
}
}
}
/* Save settings */
void save_settings()
{
/* Screen settings */
char buf[10];
sprintf(buf, "%d", last_window_size);
ini.SetValue("screen", "size", buf);
/* Control settings */
for (int p = 0; p < 2; p++)
{
const char* section = (p == 0) ? "controls p1" : "controls p2";
sprintf(buf, "%d", useJoystick[p] ? BTN_UP[p] : KEY_UP[p]);
ini.SetValue(section, "UP", buf);
sprintf(buf, "%d", useJoystick[p] ? BTN_DOWN[p] : KEY_DOWN[p]);
ini.SetValue(section, "DOWN", buf);
sprintf(buf, "%d", useJoystick[p] ? BTN_LEFT[p] : KEY_LEFT[p]);
ini.SetValue(section, "LEFT", buf);
sprintf(buf, "%d", useJoystick[p] ? BTN_RIGHT[p] : KEY_RIGHT[p]);
ini.SetValue(section, "RIGHT", buf);
sprintf(buf, "%d", useJoystick[p] ? BTN_A[p] : KEY_A[p]);
ini.SetValue(section, "A", buf);
sprintf(buf, "%d", useJoystick[p] ? BTN_B[p] : KEY_B[p]);
ini.SetValue(section, "B", buf);
sprintf(buf, "%d", useJoystick[p] ? BTN_SELECT[p] : KEY_SELECT[p]);
ini.SetValue(section, "SELECT", buf);
sprintf(buf, "%d", useJoystick[p] ? BTN_START[p] : KEY_START[p]);
ini.SetValue(section, "START", buf);
ini.SetValue(section, "usejoy", useJoystick[p] ? "yes" : "no");
}
char path[CONFIG_PATH_MAX];
ini.SaveFile(get_config_path(path, sizeof(path)));
}
}

282
old/cpu.cpp Normal file
View file

@ -0,0 +1,282 @@
#include <cstdlib>
#include <cstring>
#include <iostream>
#include "apu.hpp"
#include "cartridge.hpp"
#include "joypad.hpp"
#include "ppu.hpp"
#include "cpu.hpp"
namespace CPU {
/* CPU state */
u8 ram[0x800];
u8 A, X, Y, S;
u16 PC;
Flags P;
bool nmi, irq;
// Remaining clocks to end frame:
const int TOTAL_CYCLES = 29781;
int remainingCycles;
inline int elapsed() { return TOTAL_CYCLES - remainingCycles; }
/* Cycle emulation */
#define T tick()
inline void tick() { PPU::step(); PPU::step(); PPU::step(); remainingCycles--; }
/* Flags updating */
inline void upd_cv(u8 x, u8 y, s16 r) { P[C] = (r>0xFF); P[V] = ~(x^y) & (x^r) & 0x80; }
inline void upd_nz(u8 x) { P[N] = x & 0x80; P[Z] = (x == 0); }
// Does adding I to A cross a page?
inline bool cross(u16 a, u8 i) { return ((a+i) & 0xFF00) != ((a & 0xFF00)); }
/* Memory access */
void dma_oam(u8 bank);
template<bool wr> inline u8 access(u16 addr, u8 v = 0)
{
u8* r;
switch (addr)
{
case 0x0000 ... 0x1FFF: r = &ram[addr % 0x800]; if (wr) *r = v; return *r; // RAM.
case 0x2000 ... 0x3FFF: return PPU::access<wr>(addr % 8, v); // PPU.
// APU:
case 0x4000 ... 0x4013:
case 0x4015: return APU::access<wr>(elapsed(), addr, v);
case 0x4017: if (wr) return APU::access<wr>(elapsed(), addr, v);
else return Joypad::read_state(1); // Joypad 1.
case 0x4014: if (wr) dma_oam(v); break; // OAM DMA.
case 0x4016: if (wr) { Joypad::write_strobe(v & 1); break; } // Joypad strobe.
else return Joypad::read_state(0); // Joypad 0.
case 0x4018 ... 0xFFFF: return Cartridge::access<wr>(addr, v); // Cartridge.
}
return 0;
}
inline u8 wr(u16 a, u8 v) { T; return access<1>(a, v); }
inline u8 rd(u16 a) { T; return access<0>(a); }
inline u16 rd16_d(u16 a, u16 b) { return rd(a) | (rd(b) << 8); } // Read from A and B and merge.
inline u16 rd16(u16 a) { return rd16_d(a, a+1); }
inline u8 push(u8 v) { return wr(0x100 + (S--), v); }
inline u8 pop() { return rd(0x100 + (++S)); }
void dma_oam(u8 bank) { for (int i = 0; i < 256; i++) wr(0x2014, rd(bank*0x100 + i)); }
/* Addressing modes */
inline u16 imm() { return PC++; }
inline u16 imm16() { PC += 2; return PC - 2; }
inline u16 abs() { return rd16(imm16()); }
inline u16 _abx() { T; return abs() + X; } // Exception.
inline u16 abx() { u16 a = abs(); if (cross(a, X)) T; return a + X; }
inline u16 aby() { u16 a = abs(); if (cross(a, Y)) T; return a + Y; }
inline u16 zp() { return rd(imm()); }
inline u16 zpx() { T; return (zp() + X) % 0x100; }
inline u16 zpy() { T; return (zp() + Y) % 0x100; }
inline u16 izx() { u8 i = zpx(); return rd16_d(i, (i+1) % 0x100); }
inline u16 _izy() { u8 i = zp(); return rd16_d(i, (i+1) % 0x100) + Y; } // Exception.
inline u16 izy() { u16 a = _izy(); if (cross(a-Y, Y)) T; return a; }
/* STx */
template<u8& r, Mode m> void st() { wr( m() , r); }
template<> void st<A,izy>() { T; wr(_izy() , A); } // Exceptions.
template<> void st<A,abx>() { T; wr( abs() + X, A); } // ...
template<> void st<A,aby>() { T; wr( abs() + Y, A); } // ...
#define G u16 a = m(); u8 p = rd(a) /* Fetch parameter */
template<u8& r, Mode m> void ld() { G; upd_nz(r = p); } // LDx
template<u8& r, Mode m> void cmp() { G; upd_nz(r - p); P[C] = (r >= p); } // CMP, CPx
/* Arithmetic and bitwise */
template<Mode m> void ADC() { G ; s16 r = A + p + P[C]; upd_cv(A, p, r); upd_nz(A = r); }
template<Mode m> void SBC() { G ^ 0xFF; s16 r = A + p + P[C]; upd_cv(A, p, r); upd_nz(A = r); }
template<Mode m> void BIT() { G; P[Z] = !(A & p); P[N] = p & 0x80; P[V] = p & 0x40; }
template<Mode m> void AND() { G; upd_nz(A &= p); }
template<Mode m> void EOR() { G; upd_nz(A ^= p); }
template<Mode m> void ORA() { G; upd_nz(A |= p); }
/* Read-Modify-Write */
template<Mode m> void ASL() { G; P[C] = p & 0x80; T; upd_nz(wr(a, p << 1)); }
template<Mode m> void LSR() { G; P[C] = p & 0x01; T; upd_nz(wr(a, p >> 1)); }
template<Mode m> void ROL() { G; u8 c = P[C] ; P[C] = p & 0x80; T; upd_nz(wr(a, (p << 1) | c) ); }
template<Mode m> void ROR() { G; u8 c = P[C] << 7; P[C] = p & 0x01; T; upd_nz(wr(a, c | (p >> 1)) ); }
template<Mode m> void DEC() { G; T; upd_nz(wr(a, --p)); }
template<Mode m> void INC() { G; T; upd_nz(wr(a, ++p)); }
#undef G
/* DEx, INx */
template<u8& r> void dec() { upd_nz(--r); T; }
template<u8& r> void inc() { upd_nz(++r); T; }
/* Bit shifting on the accumulator */
void ASL_A() { P[C] = A & 0x80; upd_nz(A <<= 1); T; }
void LSR_A() { P[C] = A & 0x01; upd_nz(A >>= 1); T; }
void ROL_A() { u8 c = P[C] ; P[C] = A & 0x80; upd_nz(A = ((A << 1) | c) ); T; }
void ROR_A() { u8 c = P[C] << 7; P[C] = A & 0x01; upd_nz(A = (c | (A >> 1)) ); T; }
/* Txx (move values between registers) */
template<u8& s, u8& d> void tr() { upd_nz(d = s); T; }
template<> void tr<X,S>() { S = X; T; } // TSX, exception.
/* Stack operations */
void PLP() { T; T; P.set(pop()); }
void PHP() { T; push(P.get() | (1 << 4)); } // B flag set.
void PLA() { T; T; A = pop(); upd_nz(A); }
void PHA() { T; push(A); }
/* Flow control (branches, jumps) */
template<Flag f, bool v> void br()
{
s8 j = rd(imm());
if (P[f] == v) {
if (cross(PC, j)) T;
T; PC += j;
}
}
void JMP_IND() { u16 i = rd16(imm16()); PC = rd16_d(i, (i&0xFF00) | ((i+1) % 0x100)); }
void JMP() { PC = rd16(imm16()); }
void JSR() { u16 t = PC+1; T; push(t >> 8); push(t); PC = rd16(imm16()); }
/* Return instructions */
void RTS() { T; T; PC = (pop() | (pop() << 8)) + 1; T; }
void RTI() { PLP(); PC = pop() | (pop() << 8); }
template<Flag f, bool v> void flag() { P[f] = v; T; } // Clear and set flags.
template<IntType t> void INT()
{
T; if (t != BRK) T; // BRK already performed the fetch.
if (t != RESET) // Writes on stack are inhibited on RESET.
{
push(PC >> 8); push(PC & 0xFF);
push(P.get() | ((t == BRK) << 4)); // Set B if BRK.
}
else { S -= 3; T; T; T; }
P[I] = true;
/* NMI Reset IRQ BRK */
constexpr u16 vect[] = { 0xFFFA, 0xFFFC, 0xFFFE, 0xFFFE };
PC = rd16(vect[t]);
if (t == NMI) nmi = false;
}
void NOP() { T; }
/* Execute a CPU instruction */
void exec()
{
switch (rd(PC++)) // Fetch the opcode.
{
// Select the right function to emulate the instruction:
case 0x00: return INT<BRK>() ; case 0x01: return ORA<izx>() ;
case 0x05: return ORA<zp>() ; case 0x06: return ASL<zp>() ;
case 0x08: return PHP() ; case 0x09: return ORA<imm>() ;
case 0x0A: return ASL_A() ; case 0x0D: return ORA<abs>() ;
case 0x0E: return ASL<abs>() ; case 0x10: return br<N,0>() ;
case 0x11: return ORA<izy>() ; case 0x15: return ORA<zpx>() ;
case 0x16: return ASL<zpx>() ; case 0x18: return flag<C,0>() ;
case 0x19: return ORA<aby>() ; case 0x1D: return ORA<abx>() ;
case 0x1E: return ASL<_abx>() ; case 0x20: return JSR() ;
case 0x21: return AND<izx>() ; case 0x24: return BIT<zp>() ;
case 0x25: return AND<zp>() ; case 0x26: return ROL<zp>() ;
case 0x28: return PLP() ; case 0x29: return AND<imm>() ;
case 0x2A: return ROL_A() ; case 0x2C: return BIT<abs>() ;
case 0x2D: return AND<abs>() ; case 0x2E: return ROL<abs>() ;
case 0x30: return br<N,1>() ; case 0x31: return AND<izy>() ;
case 0x35: return AND<zpx>() ; case 0x36: return ROL<zpx>() ;
case 0x38: return flag<C,1>() ; case 0x39: return AND<aby>() ;
case 0x3D: return AND<abx>() ; case 0x3E: return ROL<_abx>() ;
case 0x40: return RTI() ; case 0x41: return EOR<izx>() ;
case 0x45: return EOR<zp>() ; case 0x46: return LSR<zp>() ;
case 0x48: return PHA() ; case 0x49: return EOR<imm>() ;
case 0x4A: return LSR_A() ; case 0x4C: return JMP() ;
case 0x4D: return EOR<abs>() ; case 0x4E: return LSR<abs>() ;
case 0x50: return br<V,0>() ; case 0x51: return EOR<izy>() ;
case 0x55: return EOR<zpx>() ; case 0x56: return LSR<zpx>() ;
case 0x58: return flag<I,0>() ; case 0x59: return EOR<aby>() ;
case 0x5D: return EOR<abx>() ; case 0x5E: return LSR<_abx>() ;
case 0x60: return RTS() ; case 0x61: return ADC<izx>() ;
case 0x65: return ADC<zp>() ; case 0x66: return ROR<zp>() ;
case 0x68: return PLA() ; case 0x69: return ADC<imm>() ;
case 0x6A: return ROR_A() ; case 0x6C: return JMP_IND() ;
case 0x6D: return ADC<abs>() ; case 0x6E: return ROR<abs>() ;
case 0x70: return br<V,1>() ; case 0x71: return ADC<izy>() ;
case 0x75: return ADC<zpx>() ; case 0x76: return ROR<zpx>() ;
case 0x78: return flag<I,1>() ; case 0x79: return ADC<aby>() ;
case 0x7D: return ADC<abx>() ; case 0x7E: return ROR<_abx>() ;
case 0x81: return st<A,izx>() ; case 0x84: return st<Y,zp>() ;
case 0x85: return st<A,zp>() ; case 0x86: return st<X,zp>() ;
case 0x88: return dec<Y>() ; case 0x8A: return tr<X,A>() ;
case 0x8C: return st<Y,abs>() ; case 0x8D: return st<A,abs>() ;
case 0x8E: return st<X,abs>() ; case 0x90: return br<C,0>() ;
case 0x91: return st<A,izy>() ; case 0x94: return st<Y,zpx>() ;
case 0x95: return st<A,zpx>() ; case 0x96: return st<X,zpy>() ;
case 0x98: return tr<Y,A>() ; case 0x99: return st<A,aby>() ;
case 0x9A: return tr<X,S>() ; case 0x9D: return st<A,abx>() ;
case 0xA0: return ld<Y,imm>() ; case 0xA1: return ld<A,izx>() ;
case 0xA2: return ld<X,imm>() ; case 0xA4: return ld<Y,zp>() ;
case 0xA5: return ld<A,zp>() ; case 0xA6: return ld<X,zp>() ;
case 0xA8: return tr<A,Y>() ; case 0xA9: return ld<A,imm>() ;
case 0xAA: return tr<A,X>() ; case 0xAC: return ld<Y,abs>() ;
case 0xAD: return ld<A,abs>() ; case 0xAE: return ld<X,abs>() ;
case 0xB0: return br<C,1>() ; case 0xB1: return ld<A,izy>() ;
case 0xB4: return ld<Y,zpx>() ; case 0xB5: return ld<A,zpx>() ;
case 0xB6: return ld<X,zpy>() ; case 0xB8: return flag<V,0>() ;
case 0xB9: return ld<A,aby>() ; case 0xBA: return tr<S,X>() ;
case 0xBC: return ld<Y,abx>() ; case 0xBD: return ld<A,abx>() ;
case 0xBE: return ld<X,aby>() ; case 0xC0: return cmp<Y,imm>();
case 0xC1: return cmp<A,izx>(); case 0xC4: return cmp<Y,zp>() ;
case 0xC5: return cmp<A,zp>() ; case 0xC6: return DEC<zp>() ;
case 0xC8: return inc<Y>() ; case 0xC9: return cmp<A,imm>();
case 0xCA: return dec<X>() ; case 0xCC: return cmp<Y,abs>();
case 0xCD: return cmp<A,abs>(); case 0xCE: return DEC<abs>() ;
case 0xD0: return br<Z,0>() ; case 0xD1: return cmp<A,izy>();
case 0xD5: return cmp<A,zpx>(); case 0xD6: return DEC<zpx>() ;
case 0xD8: return flag<D,0>() ; case 0xD9: return cmp<A,aby>();
case 0xDD: return cmp<A,abx>(); case 0xDE: return DEC<_abx>() ;
case 0xE0: return cmp<X,imm>(); case 0xE1: return SBC<izx>() ;
case 0xE4: return cmp<X,zp>() ; case 0xE5: return SBC<zp>() ;
case 0xE6: return INC<zp>() ; case 0xE8: return inc<X>() ;
case 0xE9: return SBC<imm>() ; case 0xEA: return NOP() ;
case 0xEC: return cmp<X,abs>(); case 0xED: return SBC<abs>() ;
case 0xEE: return INC<abs>() ; case 0xF0: return br<Z,1>() ;
case 0xF1: return SBC<izy>() ; case 0xF5: return SBC<zpx>() ;
case 0xF6: return INC<zpx>() ; case 0xF8: return flag<D,1>() ;
case 0xF9: return SBC<aby>() ; case 0xFD: return SBC<abx>() ;
case 0xFE: return INC<_abx>() ;
default:
std::cout << "Invalid Opcode! PC: " << PC << " Opcode: 0x" << std::hex << (int)(rd(PC - 1)) << "\n";
return NOP();
}
}
void set_nmi(bool v) { nmi = v; }
void set_irq(bool v) { irq = v; }
int dmc_read(void*, cpu_addr_t addr) { return access<0>(addr); }
/* Turn on the CPU */
void power()
{
remainingCycles = 0;
P.set(0x04);
A = X = Y = S = 0x00;
memset(ram, 0xFF, sizeof(ram));
nmi = irq = false;
INT<RESET>();
}
/* Run the CPU for roughly a frame */
void run_frame()
{
remainingCycles += TOTAL_CYCLES;
while (remainingCycles > 0)
{
if (nmi) INT<NMI>();
else if (irq and !P[I]) INT<IRQ>();
exec();
}
APU::run_frame(elapsed());
}
}

308
old/gui.cpp Normal file
View file

@ -0,0 +1,308 @@
#include <csignal>
#include <SDL2/SDL_image.h>
#include <SDL2/SDL_ttf.h>
#include "Sound_Queue.h"
#include "apu.hpp"
#include "cartridge.hpp"
#include "cpu.hpp"
#include "menu.hpp"
#include "gui.hpp"
#include "config.hpp"
namespace GUI {
// SDL structures:
SDL_Window* window;
SDL_Renderer* renderer;
SDL_Texture* gameTexture;
SDL_Texture* background;
TTF_Font* font;
u8 const* keys;
Sound_Queue* soundQueue;
SDL_Joystick* joystick[] = { nullptr, nullptr };
// Menus:
Menu* menu;
Menu* mainMenu;
Menu* settingsMenu;
Menu* videoMenu;
Menu* keyboardMenu[2];
Menu* joystickMenu[2];
FileMenu* fileMenu;
bool pause = true;
/* Set the window size multiplier */
void set_size(int mul)
{
last_window_size = mul;
SDL_SetWindowSize(window, WIDTH * mul, HEIGHT * mul);
SDL_SetWindowPosition(window, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED);
}
/* Initialize GUI */
void init()
{
// Initialize graphics system:
SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_JOYSTICK);
SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "linear");
TTF_Init();
for (int i = 0; i < SDL_NumJoysticks(); i++)
joystick[i] = SDL_JoystickOpen(i);
APU::init();
soundQueue = new Sound_Queue;
soundQueue->init(96000);
// Initialize graphics structures:
window = SDL_CreateWindow ("LaiNES",
SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
WIDTH * last_window_size, HEIGHT * last_window_size, 0);
renderer = SDL_CreateRenderer(window, -1,
SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC);
SDL_RenderSetLogicalSize(renderer, WIDTH, HEIGHT);
gameTexture = SDL_CreateTexture (renderer,
SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING,
WIDTH, HEIGHT);
font = TTF_OpenFont("res/font.ttf", FONT_SZ);
keys = SDL_GetKeyboardState(0);
// Initial background:
SDL_Surface* backSurface = IMG_Load("res/init.png");
background = SDL_CreateTextureFromSurface(renderer, backSurface);
SDL_SetTextureColorMod(background, 60, 60, 60);
SDL_FreeSurface(backSurface);
// Menus:
mainMenu = new Menu;
mainMenu->add(new Entry("Load ROM", []{ menu = fileMenu; }));
mainMenu->add(new Entry("Settings", []{ menu = settingsMenu; }));
mainMenu->add(new Entry("Exit", []{ exit(0); }));
settingsMenu = new Menu;
settingsMenu->add(new Entry("<", []{ menu = mainMenu; }));
settingsMenu->add(new Entry("Video", []{ menu = videoMenu; }));
settingsMenu->add(new Entry("Controller 1", []{ menu = useJoystick[0] ? joystickMenu[0] : keyboardMenu[0]; }));
settingsMenu->add(new Entry("Controller 2", []{ menu = useJoystick[1] ? joystickMenu[1] : keyboardMenu[1]; }));
settingsMenu->add(new Entry("Save Settings", []{ save_settings(); menu = mainMenu; }));
videoMenu = new Menu;
videoMenu->add(new Entry("<", []{ menu = settingsMenu; }));
videoMenu->add(new Entry("Size 1x", []{ set_size(1); }));
videoMenu->add(new Entry("Size 2x", []{ set_size(2); }));
videoMenu->add(new Entry("Size 3x", []{ set_size(3); }));
videoMenu->add(new Entry("Size 4x", []{ set_size(4); }));
for (int i = 0; i < 2; i++)
{
keyboardMenu[i] = new Menu;
keyboardMenu[i]->add(new Entry("<", []{ menu = settingsMenu; }));
if (joystick[i] != nullptr)
keyboardMenu[i]->add(new Entry("Joystick >", [=]{ menu = joystickMenu[i]; useJoystick[i] = true; }));
keyboardMenu[i]->add(new ControlEntry("Up", &KEY_UP[i]));
keyboardMenu[i]->add(new ControlEntry("Down", &KEY_DOWN[i]));
keyboardMenu[i]->add(new ControlEntry("Left", &KEY_LEFT[i]));
keyboardMenu[i]->add(new ControlEntry("Right", &KEY_RIGHT[i]));
keyboardMenu[i]->add(new ControlEntry("A", &KEY_A[i]));
keyboardMenu[i]->add(new ControlEntry("B", &KEY_B[i]));
keyboardMenu[i]->add(new ControlEntry("Start", &KEY_START[i]));
keyboardMenu[i]->add(new ControlEntry("Select", &KEY_SELECT[i]));
if (joystick[i] != nullptr)
{
joystickMenu[i] = new Menu;
joystickMenu[i]->add(new Entry("<", []{ menu = settingsMenu; }));
joystickMenu[i]->add(new Entry("< Keyboard", [=]{ menu = keyboardMenu[i]; useJoystick[i] = false; }));
joystickMenu[i]->add(new ControlEntry("Up", &BTN_UP[i]));
joystickMenu[i]->add(new ControlEntry("Down", &BTN_DOWN[i]));
joystickMenu[i]->add(new ControlEntry("Left", &BTN_LEFT[i]));
joystickMenu[i]->add(new ControlEntry("Right", &BTN_RIGHT[i]));
joystickMenu[i]->add(new ControlEntry("A", &BTN_A[i]));
joystickMenu[i]->add(new ControlEntry("B", &BTN_B[i]));
joystickMenu[i]->add(new ControlEntry("Start", &BTN_START[i]));
joystickMenu[i]->add(new ControlEntry("Select", &BTN_SELECT[i]));
}
}
fileMenu = new FileMenu;
menu = mainMenu;
}
/* Render a texture on screen */
void render_texture(SDL_Texture* texture, int x, int y)
{
int w, h;
SDL_Rect dest;
SDL_QueryTexture(texture, NULL, NULL, &dest.w, &dest.h);
if (x == TEXT_CENTER)
dest.x = WIDTH/2 - dest.w/2;
else if (x == TEXT_RIGHT)
dest.x = WIDTH - dest.w - 10;
else
dest.x = x + 10;
dest.y = y + 5;
SDL_RenderCopy(renderer, texture, NULL, &dest);
}
/* Generate a texture from text */
SDL_Texture* gen_text(std::string text, SDL_Color color)
{
SDL_Surface* surface = TTF_RenderText_Blended(font, text.c_str(), color);
SDL_Texture* texture = SDL_CreateTextureFromSurface(renderer, surface);
SDL_FreeSurface(surface);
return texture;
}
/* Get the joypad state from SDL */
u8 get_joypad_state(int n)
{
const int DEAD_ZONE = 8000;
u8 j = 0;
if (useJoystick[n])
{
j |= (SDL_JoystickGetButton(joystick[n], BTN_A[n])) << 0; // A.
j |= (SDL_JoystickGetButton(joystick[n], BTN_B[n])) << 1; // B.
j |= (SDL_JoystickGetButton(joystick[n], BTN_SELECT[n])) << 2; // Select.
j |= (SDL_JoystickGetButton(joystick[n], BTN_START[n])) << 3; // Start.
j |= (SDL_JoystickGetButton(joystick[n], BTN_UP[n])) << 4; // Up.
j |= (SDL_JoystickGetAxis(joystick[n], 1) < -DEAD_ZONE) << 4;
j |= (SDL_JoystickGetButton(joystick[n], BTN_DOWN[n])) << 5; // Down.
j |= (SDL_JoystickGetAxis(joystick[n], 1) > DEAD_ZONE) << 5;
j |= (SDL_JoystickGetButton(joystick[n], BTN_LEFT[n])) << 6; // Left.
j |= (SDL_JoystickGetAxis(joystick[n], 0) < -DEAD_ZONE) << 6;
j |= (SDL_JoystickGetButton(joystick[n], BTN_RIGHT[n])) << 7; // Right.
j |= (SDL_JoystickGetAxis(joystick[n], 0) > DEAD_ZONE) << 7;
}
else
{
j |= (keys[KEY_A[n]]) << 0;
j |= (keys[KEY_B[n]]) << 1;
j |= (keys[KEY_SELECT[n]]) << 2;
j |= (keys[KEY_START[n]]) << 3;
j |= (keys[KEY_UP[n]]) << 4;
j |= (keys[KEY_DOWN[n]]) << 5;
j |= (keys[KEY_LEFT[n]]) << 6;
j |= (keys[KEY_RIGHT[n]]) << 7;
}
return j;
}
/* Send the rendered frame to the GUI */
void new_frame(u32* pixels)
{
SDL_UpdateTexture(gameTexture, NULL, pixels, WIDTH * sizeof(u32));
}
void new_samples(const blip_sample_t* samples, size_t count)
{
soundQueue->write(samples, count);
}
/* Render the screen */
void render()
{
SDL_RenderClear(renderer);
// Draw the NES screen:
if (Cartridge::loaded())
SDL_RenderCopy(renderer, gameTexture, NULL, NULL);
else
SDL_RenderCopy(renderer, background, NULL, NULL);
// Draw the menu:
if (pause) menu->render();
SDL_RenderPresent(renderer);
}
/* Play/stop the game */
void toggle_pause()
{
pause = not pause;
menu = mainMenu;
if (pause)
SDL_SetTextureColorMod(gameTexture, 60, 60, 60);
else
SDL_SetTextureColorMod(gameTexture, 255, 255, 255);
}
/* Prompt for a key, return the scancode */
SDL_Scancode query_key()
{
SDL_Texture* prompt = gen_text("Press a key...", { 255, 255, 255 });
render_texture(prompt, TEXT_CENTER, HEIGHT - FONT_SZ*4);
SDL_RenderPresent(renderer);
SDL_Event e;
while (true)
{
SDL_PollEvent(&e);
if (e.type == SDL_KEYDOWN)
return e.key.keysym.scancode;
}
}
int query_button()
{
SDL_Texture* prompt = gen_text("Press a button...", { 255, 255, 255 });
render_texture(prompt, TEXT_CENTER, HEIGHT - FONT_SZ*4);
SDL_RenderPresent(renderer);
SDL_Event e;
while (true)
{
SDL_PollEvent(&e);
if (e.type == SDL_JOYBUTTONDOWN)
return e.jbutton.button;
}
}
/* Run the emulator */
void run()
{
SDL_Event e;
// Framerate control:
u32 frameStart, frameTime;
const int FPS = 60;
const int DELAY = 1000.0f / FPS;
while (true)
{
frameStart = SDL_GetTicks();
// Handle events:
while (SDL_PollEvent(&e))
switch (e.type)
{
case SDL_QUIT: return;
case SDL_KEYDOWN:
if (keys[SDL_SCANCODE_ESCAPE] and Cartridge::loaded())
toggle_pause();
else if (pause)
menu->update(keys);
}
if (not pause) CPU::run_frame();
render();
// Wait to mantain framerate:
frameTime = SDL_GetTicks() - frameStart;
if (frameTime < DELAY)
SDL_Delay((int)(DELAY - frameTime));
}
}
}

13
old/include/apu.hpp Normal file
View file

@ -0,0 +1,13 @@
#pragma once
#include "common.hpp"
namespace APU {
template <bool write> u8 access(int elapsed, u16 addr, u8 v = 0);
void run_frame(int elapsed);
void reset();
void init();
}

14
old/include/cartridge.hpp Normal file
View file

@ -0,0 +1,14 @@
#pragma once
#include "common.hpp"
namespace Cartridge {
template <bool wr> u8 access(u16 addr, u8 v = 0);
template <bool wr> u8 chr_access(u16 addr, u8 v = 0);
void signal_scanline();
void load(const char* fileName);
bool loaded();
}

10
old/include/common.hpp Normal file
View file

@ -0,0 +1,10 @@
#pragma once
#include <cstdint>
#define NTH_BIT(x, n) (((x) >> (n)) & 1)
/* Integer type shortcuts */
typedef uint8_t u8; typedef int8_t s8;
typedef uint16_t u16; typedef int16_t s16;
typedef uint32_t u32; typedef int32_t s32;
typedef uint64_t u64; typedef int64_t s64;

39
old/include/config.hpp Normal file
View file

@ -0,0 +1,39 @@
#pragma once
#include <cerrno>
#include <sys/stat.h>
#include <SDL2/SDL.h>
#define CONFIG_DIR_DEFAULT_MODE S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH
#define USE_CONFIG_DIR true
#define CONFIG_DIR_NAME "LaiNES"
#define CONFIG_FALLBACK ".laines-settings"
/* PATH_MAX is a portability nightmare. */
#define CONFIG_PATH_MAX 1024
namespace GUI {
/* Loading and saving */
void load_settings();
void save_settings();
const char* get_config_path(char * buf, int buflen);
extern int last_window_size;
extern SDL_Scancode KEY_A [];
extern SDL_Scancode KEY_B [];
extern SDL_Scancode KEY_SELECT[];
extern SDL_Scancode KEY_START [];
extern SDL_Scancode KEY_UP [];
extern SDL_Scancode KEY_DOWN [];
extern SDL_Scancode KEY_LEFT [];
extern SDL_Scancode KEY_RIGHT [];
extern int BTN_UP [];
extern int BTN_DOWN [];
extern int BTN_LEFT [];
extern int BTN_RIGHT [];
extern int BTN_A [];
extern int BTN_B [];
extern int BTN_SELECT[];
extern int BTN_START [];
extern bool useJoystick[];
}

32
old/include/cpu.hpp Normal file
View file

@ -0,0 +1,32 @@
#pragma once
#include "common.hpp"
#include <Nes_Apu.h>
namespace CPU {
enum IntType { NMI, RESET, IRQ, BRK }; // Interrupt type.
typedef u16 (*Mode)(void); // Addressing mode.
/* Processor flags */
enum Flag {C, Z, I, D, V, N};
class Flags
{
bool f[6];
public:
bool& operator[] (const int i) { return f[i]; }
u8 get() { return f[C] | f[Z] << 1 | f[I] << 2 | f[D] << 3 | 1 << 5 | f[V] << 6 | f[N] << 7; }
void set(u8 p) { f[C] = NTH_BIT(p, 0); f[Z] = NTH_BIT(p, 1); f[I] = NTH_BIT(p, 2);
f[D] = NTH_BIT(p, 3); f[V] = NTH_BIT(p, 6); f[N] = NTH_BIT(p, 7); }
};
void set_nmi(bool v = true);
void set_irq(bool v = true);
int dmc_read(void*, cpu_addr_t addr);
void power();
void run_frame();
}

30
old/include/gui.hpp Normal file
View file

@ -0,0 +1,30 @@
#pragma once
#include <SDL2/SDL.h>
#include <string>
#include <Nes_Apu.h>
#include "common.hpp"
namespace GUI {
// Screen size:
const unsigned WIDTH = 256;
const unsigned HEIGHT = 240;
const int TEXT_CENTER = -1;
const int TEXT_RIGHT = -2;
const unsigned FONT_SZ = 15;
void init();
void toggle_pause();
SDL_Scancode query_key();
int query_button();
void run();
SDL_Texture* gen_text(std::string text, SDL_Color color);
void render_texture(SDL_Texture* texture, int x, int y);
u8 get_joypad_state(int n);
void new_frame(u32* pixels);
void new_samples(const blip_sample_t* samples, size_t count);
void set_size(int mul);
}

11
old/include/joypad.hpp Normal file
View file

@ -0,0 +1,11 @@
#pragma once
#include "common.hpp"
namespace Joypad {
u8 read_state(int n);
void write_strobe(bool v);
}

32
old/include/mapper.hpp Normal file
View file

@ -0,0 +1,32 @@
#pragma once
#include <cstring>
#include "common.hpp"
class Mapper
{
u8* rom;
bool chrRam = false;
protected:
u32 prgMap[4];
u32 chrMap[8];
u8 *prg, *chr, *prgRam;
u32 prgSize, chrSize, prgRamSize;
template <int pageKBs> void map_prg(int slot, int bank);
template <int pageKBs> void map_chr(int slot, int bank);
public:
Mapper(u8* rom);
~Mapper();
u8 read(u16 addr);
virtual u8 write(u16 addr, u8 v) { return v; }
u8 chr_read(u16 addr);
virtual u8 chr_write(u16 addr, u8 v) { return v; }
virtual void signal_scanline() {}
};

View file

@ -0,0 +1,13 @@
#pragma once
#include "mapper.hpp"
class Mapper0 : public Mapper
{
public:
Mapper0(u8* rom) : Mapper(rom)
{
map_prg<32>(0, 0);
map_chr<8> (0, 0);
}
};

View file

@ -0,0 +1,23 @@
#pragma once
#include "mapper.hpp"
class Mapper1 : public Mapper
{
int writeN;
u8 tmpReg;
u8 regs[4];
void apply();
public:
Mapper1(u8* rom) : Mapper(rom)
{
regs[0] = 0x0C;
writeN = tmpReg = regs[1] = regs[2] = regs[3] = 0;
apply();
}
u8 write(u16 addr, u8 v);
u8 chr_write(u16 addr, u8 v);
};

View file

@ -0,0 +1,22 @@
#pragma once
#include "mapper.hpp"
class Mapper2 : public Mapper
{
u8 regs[1];
bool vertical_mirroring;
void apply();
public:
Mapper2(u8* rom) : Mapper(rom)
{
regs[0] = 0;
vertical_mirroring = rom[6] & 0x01;
apply();
}
u8 write(u16 addr, u8 v);
u8 chr_write(u16 addr, u8 v);
};

View file

@ -0,0 +1,24 @@
#pragma once
#include "mapper.hpp"
class Mapper3 : public Mapper
{
u8 regs[1];
bool vertical_mirroring;
bool PRG_size_16k;
void apply();
public:
Mapper3(u8* rom) : Mapper(rom)
{
PRG_size_16k = rom[4] == 1;
vertical_mirroring = rom[6] & 0x01;
regs[0] = 0;
apply();
}
u8 write(u16 addr, u8 v);
u8 chr_write(u16 addr, u8 v);
};

View file

@ -0,0 +1,35 @@
#pragma once
#include "mapper.hpp"
class Mapper4 : public Mapper
{
u8 reg8000;
u8 regs[8];
bool horizMirroring;
u8 irqPeriod;
u8 irqCounter;
bool irqEnabled;
void apply();
public:
Mapper4(u8* rom) : Mapper(rom)
{
for (int i = 0; i < 8; i++)
regs[i] = 0;
horizMirroring = true;
irqEnabled = false;
irqPeriod = irqCounter = 0;
map_prg<8>(3, -1);
apply();
}
u8 write(u16 addr, u8 v);
u8 chr_write(u16 addr, u8 v);
void signal_scanline();
};

73
old/include/menu.hpp Normal file
View file

@ -0,0 +1,73 @@
#pragma once
#include <functional>
#include <SDL2/SDL.h>
#include <string>
#include <vector>
#include "gui.hpp"
namespace GUI {
class Entry
{
std::string label;
std::function<void()> callback;
bool selected = false;
SDL_Texture* whiteTexture = nullptr;
SDL_Texture* redTexture = nullptr;
public:
Entry(std::string label, std::function<void()> callback = []{});
~Entry();
void set_label(std::string label);
inline std::string& get_label() { return label; }
virtual void select() { selected = true; };
virtual void unselect() { selected = false; };
void trigger() { callback(); };
virtual void render(int x, int y);
};
class ControlEntry : public Entry
{
SDL_Scancode* key;
int* button;
Entry* keyEntry;
public:
ControlEntry(std::string action, SDL_Scancode* key);
ControlEntry(std::string action, int* button);
void select() { Entry::select(); keyEntry->select(); }
void unselect() { Entry::unselect(); keyEntry->unselect(); }
void render(int x, int y) { Entry::render(x, y); keyEntry->render(TEXT_RIGHT, y); }
};
class Menu
{
const int MAX_ENTRY = GUI::HEIGHT / FONT_SZ - 1;
int cursor = 0;
int top = 0;
int bottom = MAX_ENTRY;
public:
std::vector<Entry*> entries;
void add(Entry* entry);
void clear();
void sort_by_label();
void update(u8 const* keys);
void render();
};
class FileMenu : public Menu
{
void change_dir(std::string dir);
public:
FileMenu();
};
}

93
old/include/ppu.hpp Normal file
View file

@ -0,0 +1,93 @@
#pragma once
#include "common.hpp"
namespace PPU {
enum Scanline { VISIBLE, POST, NMI, PRE };
enum Mirroring { VERTICAL, HORIZONTAL };
/* Sprite buffer */
struct Sprite
{
u8 id; // Index in OAM.
u8 x; // X position.
u8 y; // Y position.
u8 tile; // Tile index.
u8 attr; // Attributes.
u8 dataL; // Tile data (low).
u8 dataH; // Tile data (high).
};
/* PPUCTRL ($2000) register */
union Ctrl
{
struct
{
unsigned nt : 2; // Nametable ($2000 / $2400 / $2800 / $2C00).
unsigned incr : 1; // Address increment (1 / 32).
unsigned sprTbl : 1; // Sprite pattern table ($0000 / $1000).
unsigned bgTbl : 1; // BG pattern table ($0000 / $1000).
unsigned sprSz : 1; // Sprite size (8x8 / 8x16).
unsigned slave : 1; // PPU master/slave.
unsigned nmi : 1; // Enable NMI.
};
u8 r;
};
/* PPUMASK ($2001) register */
union Mask
{
struct
{
unsigned gray : 1; // Grayscale.
unsigned bgLeft : 1; // Show background in leftmost 8 pixels.
unsigned sprLeft : 1; // Show sprite in leftmost 8 pixels.
unsigned bg : 1; // Show background.
unsigned spr : 1; // Show sprites.
unsigned red : 1; // Intensify reds.
unsigned green : 1; // Intensify greens.
unsigned blue : 1; // Intensify blues.
};
u8 r;
};
/* PPUSTATUS ($2002) register */
union Status
{
struct
{
unsigned bus : 5; // Not significant.
unsigned sprOvf : 1; // Sprite overflow.
unsigned sprHit : 1; // Sprite 0 Hit.
unsigned vBlank : 1; // In VBlank?
};
u8 r;
};
/* Loopy's VRAM address */
union Addr
{
struct
{
unsigned cX : 5; // Coarse X.
unsigned cY : 5; // Coarse Y.
unsigned nt : 2; // Nametable.
unsigned fY : 3; // Fine Y.
};
struct
{
unsigned l : 8;
unsigned h : 7;
};
unsigned addr : 14;
unsigned r : 15;
};
template <bool write> u8 access(u16 index, u8 v = 0);
void set_mirroring(Mirroring mode);
void step();
void reset();
}

33
old/joypad.cpp Normal file
View file

@ -0,0 +1,33 @@
#include "gui.hpp"
namespace Joypad {
u8 joypad_bits[2]; // Joypad shift registers.
bool strobe; // Joypad strobe latch.
/* Read joypad state (NES register format) */
u8 read_state(int n)
{
// When strobe is high, it keeps reading A:
if (strobe)
return 0x40 | (GUI::get_joypad_state(n) & 1);
// Get the status of a button and shift the register:
u8 j = 0x40 | (joypad_bits[n] & 1);
joypad_bits[n] = 0x80 | (joypad_bits[n] >> 1);
return j;
}
void write_strobe(bool v)
{
// Read the joypad data on strobe's transition 1 -> 0.
if (strobe and !v)
for (int i = 0; i < 2; i++)
joypad_bits[i] = GUI::get_joypad_state(i);
strobe = v;
}
}

11
old/main.cpp Normal file
View file

@ -0,0 +1,11 @@
#include "gui.hpp"
#include "config.hpp"
int main(int argc, char *argv[])
{
GUI::load_settings();
GUI::init();
GUI::run();
return 0;
}

72
old/mapper.cpp Normal file
View file

@ -0,0 +1,72 @@
#include "ppu.hpp"
#include "mapper.hpp"
Mapper::Mapper(u8* rom) : rom(rom)
{
// Read infos from header:
prgSize = rom[4] * 0x4000;
chrSize = rom[5] * 0x2000;
prgRamSize = rom[8] ? rom[8] * 0x2000 : 0x2000;
set_mirroring((rom[6] & 1) ? PPU::VERTICAL : PPU::HORIZONTAL);
this->prg = rom + 16;
this->prgRam = new u8[prgRamSize];
// CHR ROM:
if (chrSize)
this->chr = rom + 16 + prgSize;
// CHR RAM:
else
{
chrRam = true;
chrSize = 0x2000;
this->chr = new u8[chrSize];
}
}
Mapper::~Mapper()
{
delete rom;
delete prgRam;
if (chrRam)
delete chr;
}
/* Access to memory */
u8 Mapper::read(u16 addr)
{
if (addr >= 0x8000)
return prg[prgMap[(addr - 0x8000) / 0x2000] + ((addr - 0x8000) % 0x2000)];
else
return prgRam[addr - 0x6000];
}
u8 Mapper::chr_read(u16 addr)
{
return chr[chrMap[addr / 0x400] + (addr % 0x400)];
}
/* PRG mapping functions */
template <int pageKBs> void Mapper::map_prg(int slot, int bank)
{
if (bank < 0)
bank = (prgSize / (0x400*pageKBs)) + bank;
for (int i = 0; i < (pageKBs/8); i++)
prgMap[(pageKBs/8) * slot + i] = (pageKBs*0x400*bank + 0x2000*i) % prgSize;
}
template void Mapper::map_prg<32>(int, int);
template void Mapper::map_prg<16>(int, int);
template void Mapper::map_prg<8> (int, int);
/* CHR mapping functions */
template <int pageKBs> void Mapper::map_chr(int slot, int bank)
{
for (int i = 0; i < pageKBs; i++)
chrMap[pageKBs*slot + i] = (pageKBs*0x400*bank + 0x400*i) % chrSize;
}
template void Mapper::map_chr<8>(int, int);
template void Mapper::map_chr<4>(int, int);
template void Mapper::map_chr<2>(int, int);
template void Mapper::map_chr<1>(int, int);

82
old/mappers/mapper1.cpp Normal file
View file

@ -0,0 +1,82 @@
#include "ppu.hpp"
#include "mappers/mapper1.hpp"
/* Apply the registers state */
void Mapper1::apply()
{
// 16KB PRG:
if (regs[0] & 0b1000)
{
// 0x8000 swappable, 0xC000 fixed to bank 0x0F:
if (regs[0] & 0b100)
{
map_prg<16>(0, regs[3] & 0xF);
map_prg<16>(1, 0xF);
}
// 0x8000 fixed to bank 0x00, 0xC000 swappable:
else
{
map_prg<16>(0, 0);
map_prg<16>(1, regs[3] & 0xF);
}
}
// 32KB PRG:
else
map_prg<32>(0, (regs[3] & 0xF) >> 1);
// 4KB CHR:
if (regs[0] & 0b10000)
{
map_chr<4>(0, regs[1]);
map_chr<4>(1, regs[2]);
}
// 8KB CHR:
else
map_chr<8>(0, regs[1] >> 1);
// Set mirroring:
switch (regs[0] & 0b11)
{
case 2: set_mirroring(PPU::VERTICAL); break;
case 3: set_mirroring(PPU::HORIZONTAL); break;
}
}
u8 Mapper1::write(u16 addr, u8 v)
{
// PRG RAM write;
if (addr < 0x8000)
prgRam[addr - 0x6000] = v;
// Mapper register write:
else if (addr & 0x8000)
{
// Reset:
if (v & 0x80)
{
writeN = 0;
tmpReg = 0;
regs[0] |= 0x0C;
apply();
}
else
{
// Write a bit into the temporary register:
tmpReg = ((v & 1) << 4) | (tmpReg >> 1);
// Finished writing all the bits:
if (++writeN == 5)
{
regs[(addr >> 13) & 0b11] = tmpReg;
writeN = 0;
tmpReg = 0;
apply();
}
}
}
return v;
}
u8 Mapper1::chr_write(u16 addr, u8 v)
{
return chr[addr] = v;
}

40
old/mappers/mapper2.cpp Normal file
View file

@ -0,0 +1,40 @@
#include "ppu.hpp"
#include "mappers/mapper2.hpp"
/* Based off of https://wiki.nesdev.com/w/index.php/UxROM */
/* Apply the registers state */
void Mapper2::apply()
{
/*
* 16 kb PRG ROM Banks
* 0x8000 - 0xBFFF swappable
* 0xC000 - 0xFFFF fixed
*/
map_prg<16>(0, regs[0] & 0xF);
map_prg<16>(1, 0xF);
/* 8k of CHR */
map_chr<8>(0, 0);
/* mirroring is based on the header (soldered) */
set_mirroring(vertical_mirroring?PPU::VERTICAL:PPU::HORIZONTAL);
}
u8 Mapper2::write(u16 addr, u8 v)
{
/* check for bus contingency? (addr & 0x8000 == v?) nah */
/* bank switching */
if (addr & 0x8000)
{
regs[0] = v;
apply();
}
return v;
}
u8 Mapper2::chr_write(u16 addr, u8 v)
{
return chr[addr] = v;
}

50
old/mappers/mapper3.cpp Normal file
View file

@ -0,0 +1,50 @@
#include "ppu.hpp"
#include "mappers/mapper3.hpp"
/* Based off of https://wiki.nesdev.com/w/index.php/INES_Mapper_003 */
/* Apply the registers state */
void Mapper3::apply()
{
if (PRG_size_16k)
{
/*
* mirror the bottom on the top
* 0x8000 - 0xBFFF ==
* 0xC000 - 0xFFFF
*/
map_prg<16>(0,0);
map_prg<16>(1,0);
}
else
{
/* no mirroring */
map_prg<16>(0,0);
map_prg<16>(1,1);
}
/* 8k bankswitched CHR */
map_chr<8>(0, regs[0] & 0b11);
/* mirroring is based on the header (soldered) */
set_mirroring(vertical_mirroring?PPU::VERTICAL:PPU::HORIZONTAL);
}
u8 Mapper3::write(u16 addr, u8 v)
{
/* check for bus contingency? */
/* chr bank switching */
if (addr & 0x8000)
{
regs[0] = v;
apply();
}
return v;
}
u8 Mapper3::chr_write(u16 addr, u8 v)
{
return chr[addr] = v;
}

78
old/mappers/mapper4.cpp Normal file
View file

@ -0,0 +1,78 @@
#include "cpu.hpp"
#include "ppu.hpp"
#include "mappers/mapper4.hpp"
void Mapper4::apply()
{
map_prg<8>(1, regs[7]);
// PRG Mode 0:
if (!(reg8000 & (1 << 6)))
{
map_prg<8>(0, regs[6]);
map_prg<8>(2, -2);
}
// PRG Mode 1:
else
{
map_prg<8>(0, -2);
map_prg<8>(2, regs[6]);
}
// CHR Mode 0:
if (!(reg8000 & (1 << 7)))
{
map_chr<2>(0, regs[0] >> 1);
map_chr<2>(1, regs[1] >> 1);
for (int i = 0; i < 4; i++)
map_chr<1>(4 + i, regs[2 + i]);
}
// CHR Mode 1:
else
{
for (int i = 0; i < 4; i++)
map_chr<1>(i, regs[2 + i]);
map_chr<2>(2, regs[0] >> 1);
map_chr<2>(3, regs[1] >> 1);
}
set_mirroring(horizMirroring ? PPU::HORIZONTAL : PPU::VERTICAL);
}
u8 Mapper4::write(u16 addr, u8 v)
{
if (addr < 0x8000)
prgRam[addr - 0x6000] = v;
else if (addr & 0x8000)
{
switch (addr & 0xE001)
{
case 0x8000: reg8000 = v; break;
case 0x8001: regs[reg8000 & 0b111] = v; break;
case 0xA000: horizMirroring = v & 1; break;
case 0xC000: irqPeriod = v; break;
case 0xC001: irqCounter = 0; break;
case 0xE000: CPU::set_irq(irqEnabled = false); break;
case 0xE001: irqEnabled = true; break;
}
apply();
}
return v;
}
u8 Mapper4::chr_write(u16 addr, u8 v)
{
return chr[addr] = v;
}
void Mapper4::signal_scanline()
{
if (irqCounter == 0)
irqCounter = irqPeriod;
else
irqCounter--;
if (irqEnabled and irqCounter == 0)
CPU::set_irq();
}

151
old/menu.cpp Normal file
View file

@ -0,0 +1,151 @@
#include <algorithm>
#include <dirent.h>
#include <unistd.h>
#include "cartridge.hpp"
#include "menu.hpp"
namespace GUI {
using namespace std;
Entry::Entry(string label, function<void()> callback) : callback(callback)
{
set_label(label);
}
Entry::~Entry()
{
SDL_DestroyTexture(whiteTexture);
SDL_DestroyTexture(redTexture);
}
void Entry::set_label(string label)
{
this->label = label;
if (whiteTexture != nullptr) SDL_DestroyTexture(whiteTexture);
if (redTexture != nullptr) SDL_DestroyTexture(redTexture);
whiteTexture = gen_text(label, { 255, 255, 255 });
redTexture = gen_text(label, { 255, 0, 0 });
}
void Entry::render(int x, int y) {
render_texture(selected ? redTexture : whiteTexture, x, y);
}
ControlEntry::ControlEntry(string action, SDL_Scancode* key) : key(key),
Entry::Entry(
action,
[&]{ keyEntry->set_label(SDL_GetScancodeName(*(this->key) = query_key())); })
{
this->keyEntry = new Entry(SDL_GetScancodeName(*key), []{});
}
ControlEntry::ControlEntry(string action, int* button) : button(button),
Entry::Entry(
action,
[&]{ keyEntry->set_label(to_string(*(this->button) = query_button())); })
{
this->keyEntry = new Entry(to_string(*button), []{});
}
void Menu::add(Entry* entry)
{
if (entries.empty())
entry->select();
entries.push_back(entry);
}
void Menu::clear()
{
for (auto entry : entries)
delete entry;
entries.clear();
cursor = 0;
}
void Menu::sort_by_label()
{
if (entries.empty())
return;
entries[0]->unselect();
sort(entries.begin(), entries.end(), [](Entry* a, Entry* b) {
return a->get_label() < b->get_label();
});
entries[0]->select();
}
void Menu::update(u8 const* keys)
{
int oldCursor = cursor;
if (keys[SDL_SCANCODE_DOWN] and cursor < entries.size() - 1)
{
cursor++;
if (cursor == bottom) {
bottom += 1;
top += 1;
}
}
else if (keys[SDL_SCANCODE_UP] and cursor > 0)
{
cursor--;
if (cursor < top) {
top -= 1;
bottom -= 1;
}
}
entries[oldCursor]->unselect();
entries[cursor]->select();
if (keys[SDL_SCANCODE_RETURN])
entries[cursor]->trigger();
}
void Menu::render()
{
for (int i = top; i < entries.size() && i < bottom; ++i)
{
int y = (i - top) * FONT_SZ;
entries[i]->render(TEXT_CENTER, y);
}
}
void FileMenu::change_dir(string dir)
{
clear();
struct dirent* dirp;
DIR* dp = opendir(dir.c_str());
while ((dirp = readdir(dp)) != NULL)
{
string name = dirp->d_name;
string path = dir + "/" + name;
if (name[0] == '.' and name != "..") continue;
if (dirp->d_type == DT_DIR)
add(new Entry(name + "/",
[=]{ change_dir(path); }));
else if (name.size() > 4 and name.substr(name.size() - 4) == ".nes")
add(new Entry(name,
[=]{ Cartridge::load(path.c_str()); toggle_pause(); }));
}
closedir(dp);
sort_by_label();
}
FileMenu::FileMenu()
{
char cwd[512];
change_dir(getcwd(cwd, 512));
}
}

9
old/palette.inc Normal file
View file

@ -0,0 +1,9 @@
u32 nesRgb[] =
{ 0x7C7C7C, 0x0000FC, 0x0000BC, 0x4428BC, 0x940084, 0xA80020, 0xA81000, 0x881400,
0x503000, 0x007800, 0x006800, 0x005800, 0x004058, 0x000000, 0x000000, 0x000000,
0xBCBCBC, 0x0078F8, 0x0058F8, 0x6844FC, 0xD800CC, 0xE40058, 0xF83800, 0xE45C10,
0xAC7C00, 0x00B800, 0x00A800, 0x00A844, 0x008888, 0x000000, 0x000000, 0x000000,
0xF8F8F8, 0x3CBCFC, 0x6888FC, 0x9878F8, 0xF878F8, 0xF85898, 0xF87858, 0xFCA044,
0xF8B800, 0xB8F818, 0x58D854, 0x58F898, 0x00E8D8, 0x787878, 0x000000, 0x000000,
0xFCFCFC, 0xA4E4FC, 0xB8B8F8, 0xD8B8F8, 0xF8B8F8, 0xF8A4C0, 0xF0D0B0, 0xFCE0A8,
0xF8D878, 0xD8F878, 0xB8F8B8, 0xB8F8D8, 0x00FCFC, 0xF8D8F8, 0x000000, 0x000000 };

353
old/ppu.cpp Normal file
View file

@ -0,0 +1,353 @@
#include "cartridge.hpp"
#include "cpu.hpp"
#include "gui.hpp"
#include "ppu.hpp"
namespace PPU {
#include "palette.inc"
Mirroring mirroring; // Mirroring mode.
u8 ciRam[0x800]; // VRAM for nametables.
u8 cgRam[0x20]; // VRAM for palettes.
u8 oamMem[0x100]; // VRAM for sprite properties.
Sprite oam[8], secOam[8]; // Sprite buffers.
u32 pixels[256 * 240]; // Video buffer.
Addr vAddr, tAddr; // Loopy V, T.
u8 fX; // Fine X.
u8 oamAddr; // OAM address.
Ctrl ctrl; // PPUCTRL ($2000) register.
Mask mask; // PPUMASK ($2001) register.
Status status; // PPUSTATUS ($2002) register.
// Background latches:
u8 nt, at, bgL, bgH;
// Background shift registers:
u8 atShiftL, atShiftH; u16 bgShiftL, bgShiftH;
bool atLatchL, atLatchH;
// Rendering counters:
int scanline, dot;
bool frameOdd;
inline bool rendering() { return mask.bg || mask.spr; }
inline int spr_height() { return ctrl.sprSz ? 16 : 8; }
/* Get CIRAM address according to mirroring */
u16 nt_mirror(u16 addr)
{
switch (mirroring)
{
case VERTICAL: return addr % 0x800;
case HORIZONTAL: return ((addr / 2) & 0x400) + (addr % 0x400);
default: return addr - 0x2000;
}
}
void set_mirroring(Mirroring mode) { mirroring = mode; }
/* Access PPU memory */
u8 rd(u16 addr)
{
switch (addr)
{
case 0x0000 ... 0x1FFF: return Cartridge::chr_access<0>(addr); // CHR-ROM/RAM.
case 0x2000 ... 0x3EFF: return ciRam[nt_mirror(addr)]; // Nametables.
case 0x3F00 ... 0x3FFF: // Palettes:
if ((addr & 0x13) == 0x10) addr &= ~0x10;
return cgRam[addr & 0x1F] & (mask.gray ? 0x30 : 0xFF);
default: return 0;
}
}
void wr(u16 addr, u8 v)
{
switch (addr)
{
case 0x0000 ... 0x1FFF: Cartridge::chr_access<1>(addr, v); break; // CHR-ROM/RAM.
case 0x2000 ... 0x3EFF: ciRam[nt_mirror(addr)] = v; break; // Nametables.
case 0x3F00 ... 0x3FFF: // Palettes:
if ((addr & 0x13) == 0x10) addr &= ~0x10;
cgRam[addr & 0x1F] = v; break;
}
}
/* Access PPU through registers. */
template <bool write> u8 access(u16 index, u8 v)
{
static u8 res; // Result of the operation.
static u8 buffer; // VRAM read buffer.
static bool latch; // Detect second reading.
/* Write into register */
if (write)
{
res = v;
switch (index)
{
case 0: ctrl.r = v; tAddr.nt = ctrl.nt; break; // PPUCTRL ($2000).
case 1: mask.r = v; break; // PPUMASK ($2001).
case 3: oamAddr = v; break; // OAMADDR ($2003).
case 4: oamMem[oamAddr++] = v; break; // OAMDATA ($2004).
case 5: // PPUSCROLL ($2005).
if (!latch) { fX = v & 7; tAddr.cX = v >> 3; } // First write.
else { tAddr.fY = v & 7; tAddr.cY = v >> 3; } // Second write.
latch = !latch; break;
case 6: // PPUADDR ($2006).
if (!latch) { tAddr.h = v & 0x3F; } // First write.
else { tAddr.l = v; vAddr.r = tAddr.r; } // Second write.
latch = !latch; break;
case 7: wr(vAddr.addr, v); vAddr.addr += ctrl.incr ? 32 : 1; // PPUDATA ($2007).
}
}
/* Read from register */
else
switch (index)
{
// PPUSTATUS ($2002):
case 2: res = (res & 0x1F) | status.r; status.vBlank = 0; latch = 0; break;
case 4: res = oamMem[oamAddr]; break; // OAMDATA ($2004).
case 7: // PPUDATA ($2007).
if (vAddr.addr <= 0x3EFF)
{
res = buffer;
buffer = rd(vAddr.addr);
}
else
res = buffer = rd(vAddr.addr);
vAddr.addr += ctrl.incr ? 32 : 1;
}
return res;
}
template u8 access<0>(u16, u8); template u8 access<1>(u16, u8);
/* Calculate graphics addresses */
inline u16 nt_addr() { return 0x2000 | (vAddr.r & 0xFFF); }
inline u16 at_addr() { return 0x23C0 | (vAddr.nt << 10) | ((vAddr.cY / 4) << 3) | (vAddr.cX / 4); }
inline u16 bg_addr() { return (ctrl.bgTbl * 0x1000) + (nt * 16) + vAddr.fY; }
/* Increment the scroll by one pixel */
inline void h_scroll() { if (!rendering()) return; if (vAddr.cX == 31) vAddr.r ^= 0x41F; else vAddr.cX++; }
inline void v_scroll()
{
if (!rendering()) return;
if (vAddr.fY < 7) vAddr.fY++;
else
{
vAddr.fY = 0;
if (vAddr.cY == 31) vAddr.cY = 0;
else if (vAddr.cY == 29) { vAddr.cY = 0; vAddr.nt ^= 0b10; }
else vAddr.cY++;
}
}
/* Copy scrolling data from loopy T to loopy V */
inline void h_update() { if (!rendering()) return; vAddr.r = (vAddr.r & ~0x041F) | (tAddr.r & 0x041F); }
inline void v_update() { if (!rendering()) return; vAddr.r = (vAddr.r & ~0x7BE0) | (tAddr.r & 0x7BE0); }
/* Put new data into the shift registers */
inline void reload_shift()
{
bgShiftL = (bgShiftL & 0xFF00) | bgL;
bgShiftH = (bgShiftH & 0xFF00) | bgH;
atLatchL = (at & 1);
atLatchH = (at & 2);
}
/* Clear secondary OAM */
void clear_oam()
{
for (int i = 0; i < 8; i++)
{
secOam[i].id = 64;
secOam[i].y = 0xFF;
secOam[i].tile = 0xFF;
secOam[i].attr = 0xFF;
secOam[i].x = 0xFF;
secOam[i].dataL = 0;
secOam[i].dataH = 0;
}
}
/* Fill secondary OAM with the sprite infos for the next scanline */
void eval_sprites()
{
int n = 0;
for (int i = 0; i < 64; i++)
{
int line = (scanline == 261 ? -1 : scanline) - oamMem[i*4 + 0];
// If the sprite is in the scanline, copy its properties into secondary OAM:
if (line >= 0 and line < spr_height())
{
secOam[n].id = i;
secOam[n].y = oamMem[i*4 + 0];
secOam[n].tile = oamMem[i*4 + 1];
secOam[n].attr = oamMem[i*4 + 2];
secOam[n].x = oamMem[i*4 + 3];
if (++n >= 8)
{
status.sprOvf = true;
break;
}
}
}
}
/* Load the sprite info into primary OAM and fetch their tile data. */
void load_sprites()
{
u16 addr;
for (int i = 0; i < 8; i++)
{
oam[i] = secOam[i]; // Copy secondary OAM into primary.
// Different address modes depending on the sprite height:
if (spr_height() == 16)
addr = ((oam[i].tile & 1) * 0x1000) + ((oam[i].tile & ~1) * 16);
else
addr = ( ctrl.sprTbl * 0x1000) + ( oam[i].tile * 16);
unsigned sprY = (scanline - oam[i].y) % spr_height(); // Line inside the sprite.
if (oam[i].attr & 0x80) sprY ^= spr_height() - 1; // Vertical flip.
addr += sprY + (sprY & 8); // Select the second tile if on 8x16.
oam[i].dataL = rd(addr + 0);
oam[i].dataH = rd(addr + 8);
}
}
/* Process a pixel, draw it if it's on screen */
void pixel()
{
u8 palette = 0, objPalette = 0;
bool objPriority = 0;
int x = dot - 2;
if (scanline < 240 and x >= 0 and x < 256)
{
if (mask.bg and not (!mask.bgLeft && x < 8))
{
// Background:
palette = (NTH_BIT(bgShiftH, 15 - fX) << 1) |
NTH_BIT(bgShiftL, 15 - fX);
if (palette)
palette |= ((NTH_BIT(atShiftH, 7 - fX) << 1) |
NTH_BIT(atShiftL, 7 - fX)) << 2;
}
// Sprites:
if (mask.spr and not (!mask.sprLeft && x < 8))
for (int i = 7; i >= 0; i--)
{
if (oam[i].id == 64) continue; // Void entry.
unsigned sprX = x - oam[i].x;
if (sprX >= 8) continue; // Not in range.
if (oam[i].attr & 0x40) sprX ^= 7; // Horizontal flip.
u8 sprPalette = (NTH_BIT(oam[i].dataH, 7 - sprX) << 1) |
NTH_BIT(oam[i].dataL, 7 - sprX);
if (sprPalette == 0) continue; // Transparent pixel.
if (oam[i].id == 0 && palette && x != 255) status.sprHit = true;
sprPalette |= (oam[i].attr & 3) << 2;
objPalette = sprPalette + 16;
objPriority = oam[i].attr & 0x20;
}
// Evaluate priority:
if (objPalette && (palette == 0 || objPriority == 0)) palette = objPalette;
pixels[scanline*256 + x] = nesRgb[rd(0x3F00 + (rendering() ? palette : 0))];
}
// Perform background shifts:
bgShiftL <<= 1; bgShiftH <<= 1;
atShiftL = (atShiftL << 1) | atLatchL;
atShiftH = (atShiftH << 1) | atLatchH;
}
/* Execute a cycle of a scanline */
template<Scanline s> void scanline_cycle()
{
static u16 addr;
if (s == NMI and dot == 1) { status.vBlank = true; if (ctrl.nmi) CPU::set_nmi(); }
else if (s == POST and dot == 0) GUI::new_frame(pixels);
else if (s == VISIBLE or s == PRE)
{
// Sprites:
switch (dot)
{
case 1: clear_oam(); if (s == PRE) { status.sprOvf = status.sprHit = false; } break;
case 257: eval_sprites(); break;
case 321: load_sprites(); break;
}
// Background:
switch (dot)
{
case 2 ... 255: case 322 ... 337:
pixel();
switch (dot % 8)
{
// Nametable:
case 1: addr = nt_addr(); reload_shift(); break;
case 2: nt = rd(addr); break;
// Attribute:
case 3: addr = at_addr(); break;
case 4: at = rd(addr); if (vAddr.cY & 2) at >>= 4;
if (vAddr.cX & 2) at >>= 2; break;
// Background (low bits):
case 5: addr = bg_addr(); break;
case 6: bgL = rd(addr); break;
// Background (high bits):
case 7: addr += 8; break;
case 0: bgH = rd(addr); h_scroll(); break;
} break;
case 256: pixel(); bgH = rd(addr); v_scroll(); break; // Vertical bump.
case 257: pixel(); reload_shift(); h_update(); break; // Update horizontal position.
case 280 ... 304: if (s == PRE) v_update(); break; // Update vertical position.
// No shift reloading:
case 1: addr = nt_addr(); if (s == PRE) status.vBlank = false; break;
case 321: case 339: addr = nt_addr(); break;
// Nametable fetch instead of attribute:
case 338: nt = rd(addr); break;
case 340: nt = rd(addr); if (s == PRE && rendering() && frameOdd) dot++;
}
// Signal scanline to mapper:
if (dot == 260 && rendering()) Cartridge::signal_scanline();
}
}
/* Execute a PPU cycle. */
void step()
{
switch (scanline)
{
case 0 ... 239: scanline_cycle<VISIBLE>(); break;
case 240: scanline_cycle<POST>(); break;
case 241: scanline_cycle<NMI>(); break;
case 261: scanline_cycle<PRE>(); break;
}
// Update dot and scanline counters:
if (++dot > 340)
{
dot %= 341;
if (++scanline > 261)
{
scanline = 0;
frameOdd ^= 1;
}
}
}
void reset()
{
frameOdd = false;
scanline = dot = 0;
ctrl.r = mask.r = status.r = 0;
memset(pixels, 0x00, sizeof(pixels));
memset(ciRam, 0xFF, sizeof(ciRam));
memset(oamMem, 0x00, sizeof(oamMem));
}
}

BIN
res/font.ttf Normal file

Binary file not shown.

BIN
res/init.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 408 KiB

539
simpleini/ConvertUTF.c Normal file
View file

@ -0,0 +1,539 @@
/*
* Copyright 2001-2004 Unicode, Inc.
*
* Disclaimer
*
* This source code is provided as is by Unicode, Inc. No claims are
* made as to fitness for any particular purpose. No warranties of any
* kind are expressed or implied. The recipient agrees to determine
* applicability of information provided. If this file has been
* purchased on magnetic or optical media from Unicode, Inc., the
* sole remedy for any claim will be exchange of defective media
* within 90 days of receipt.
*
* Limitations on Rights to Redistribute This Code
*
* Unicode, Inc. hereby grants the right to freely use the information
* supplied in this file in the creation of products supporting the
* Unicode Standard, and to make copies of this file in any form
* for internal or external distribution as long as this notice
* remains attached.
*/
/* ---------------------------------------------------------------------
Conversions between UTF32, UTF-16, and UTF-8. Source code file.
Author: Mark E. Davis, 1994.
Rev History: Rick McGowan, fixes & updates May 2001.
Sept 2001: fixed const & error conditions per
mods suggested by S. Parent & A. Lillich.
June 2002: Tim Dodd added detection and handling of incomplete
source sequences, enhanced error detection, added casts
to eliminate compiler warnings.
July 2003: slight mods to back out aggressive FFFE detection.
Jan 2004: updated switches in from-UTF8 conversions.
Oct 2004: updated to use UNI_MAX_LEGAL_UTF32 in UTF-32 conversions.
See the header file "ConvertUTF.h" for complete documentation.
------------------------------------------------------------------------ */
#include "ConvertUTF.h"
#ifdef CVTUTF_DEBUG
#include <stdio.h>
#endif
static const int halfShift = 10; /* used for shifting by 10 bits */
static const UTF32 halfBase = 0x0010000UL;
static const UTF32 halfMask = 0x3FFUL;
#define UNI_SUR_HIGH_START (UTF32)0xD800
#define UNI_SUR_HIGH_END (UTF32)0xDBFF
#define UNI_SUR_LOW_START (UTF32)0xDC00
#define UNI_SUR_LOW_END (UTF32)0xDFFF
#define false 0
#define true 1
/* --------------------------------------------------------------------- */
ConversionResult ConvertUTF32toUTF16 (
const UTF32** sourceStart, const UTF32* sourceEnd,
UTF16** targetStart, UTF16* targetEnd, ConversionFlags flags) {
ConversionResult result = conversionOK;
const UTF32* source = *sourceStart;
UTF16* target = *targetStart;
while (source < sourceEnd) {
UTF32 ch;
if (target >= targetEnd) {
result = targetExhausted; break;
}
ch = *source++;
if (ch <= UNI_MAX_BMP) { /* Target is a character <= 0xFFFF */
/* UTF-16 surrogate values are illegal in UTF-32; 0xffff or 0xfffe are both reserved values */
if (ch >= UNI_SUR_HIGH_START && ch <= UNI_SUR_LOW_END) {
if (flags == strictConversion) {
--source; /* return to the illegal value itself */
result = sourceIllegal;
break;
} else {
*target++ = UNI_REPLACEMENT_CHAR;
}
} else {
*target++ = (UTF16)ch; /* normal case */
}
} else if (ch > UNI_MAX_LEGAL_UTF32) {
if (flags == strictConversion) {
result = sourceIllegal;
} else {
*target++ = UNI_REPLACEMENT_CHAR;
}
} else {
/* target is a character in range 0xFFFF - 0x10FFFF. */
if (target + 1 >= targetEnd) {
--source; /* Back up source pointer! */
result = targetExhausted; break;
}
ch -= halfBase;
*target++ = (UTF16)((ch >> halfShift) + UNI_SUR_HIGH_START);
*target++ = (UTF16)((ch & halfMask) + UNI_SUR_LOW_START);
}
}
*sourceStart = source;
*targetStart = target;
return result;
}
/* --------------------------------------------------------------------- */
ConversionResult ConvertUTF16toUTF32 (
const UTF16** sourceStart, const UTF16* sourceEnd,
UTF32** targetStart, UTF32* targetEnd, ConversionFlags flags) {
ConversionResult result = conversionOK;
const UTF16* source = *sourceStart;
UTF32* target = *targetStart;
UTF32 ch, ch2;
while (source < sourceEnd) {
const UTF16* oldSource = source; /* In case we have to back up because of target overflow. */
ch = *source++;
/* If we have a surrogate pair, convert to UTF32 first. */
if (ch >= UNI_SUR_HIGH_START && ch <= UNI_SUR_HIGH_END) {
/* If the 16 bits following the high surrogate are in the source buffer... */
if (source < sourceEnd) {
ch2 = *source;
/* If it's a low surrogate, convert to UTF32. */
if (ch2 >= UNI_SUR_LOW_START && ch2 <= UNI_SUR_LOW_END) {
ch = ((ch - UNI_SUR_HIGH_START) << halfShift)
+ (ch2 - UNI_SUR_LOW_START) + halfBase;
++source;
} else if (flags == strictConversion) { /* it's an unpaired high surrogate */
--source; /* return to the illegal value itself */
result = sourceIllegal;
break;
}
} else { /* We don't have the 16 bits following the high surrogate. */
--source; /* return to the high surrogate */
result = sourceExhausted;
break;
}
} else if (flags == strictConversion) {
/* UTF-16 surrogate values are illegal in UTF-32 */
if (ch >= UNI_SUR_LOW_START && ch <= UNI_SUR_LOW_END) {
--source; /* return to the illegal value itself */
result = sourceIllegal;
break;
}
}
if (target >= targetEnd) {
source = oldSource; /* Back up source pointer! */
result = targetExhausted; break;
}
*target++ = ch;
}
*sourceStart = source;
*targetStart = target;
#ifdef CVTUTF_DEBUG
if (result == sourceIllegal) {
fprintf(stderr, "ConvertUTF16toUTF32 illegal seq 0x%04x,%04x\n", ch, ch2);
fflush(stderr);
}
#endif
return result;
}
/* --------------------------------------------------------------------- */
/*
* Index into the table below with the first byte of a UTF-8 sequence to
* get the number of trailing bytes that are supposed to follow it.
* Note that *legal* UTF-8 values can't have 4 or 5-bytes. The table is
* left as-is for anyone who may want to do such conversion, which was
* allowed in earlier algorithms.
*/
static const char trailingBytesForUTF8[256] = {
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 3,3,3,3,3,3,3,3,4,4,4,4,5,5,5,5
};
/*
* Magic values subtracted from a buffer value during UTF8 conversion.
* This table contains as many values as there might be trailing bytes
* in a UTF-8 sequence.
*/
static const UTF32 offsetsFromUTF8[6] = { 0x00000000UL, 0x00003080UL, 0x000E2080UL,
0x03C82080UL, 0xFA082080UL, 0x82082080UL };
/*
* Once the bits are split out into bytes of UTF-8, this is a mask OR-ed
* into the first byte, depending on how many bytes follow. There are
* as many entries in this table as there are UTF-8 sequence types.
* (I.e., one byte sequence, two byte... etc.). Remember that sequencs
* for *legal* UTF-8 will be 4 or fewer bytes total.
*/
static const UTF8 firstByteMark[7] = { 0x00, 0x00, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC };
/* --------------------------------------------------------------------- */
/* The interface converts a whole buffer to avoid function-call overhead.
* Constants have been gathered. Loops & conditionals have been removed as
* much as possible for efficiency, in favor of drop-through switches.
* (See "Note A" at the bottom of the file for equivalent code.)
* If your compiler supports it, the "isLegalUTF8" call can be turned
* into an inline function.
*/
/* --------------------------------------------------------------------- */
ConversionResult ConvertUTF16toUTF8 (
const UTF16** sourceStart, const UTF16* sourceEnd,
UTF8** targetStart, UTF8* targetEnd, ConversionFlags flags) {
ConversionResult result = conversionOK;
const UTF16* source = *sourceStart;
UTF8* target = *targetStart;
while (source < sourceEnd) {
UTF32 ch;
unsigned short bytesToWrite = 0;
const UTF32 byteMask = 0xBF;
const UTF32 byteMark = 0x80;
const UTF16* oldSource = source; /* In case we have to back up because of target overflow. */
ch = *source++;
/* If we have a surrogate pair, convert to UTF32 first. */
if (ch >= UNI_SUR_HIGH_START && ch <= UNI_SUR_HIGH_END) {
/* If the 16 bits following the high surrogate are in the source buffer... */
if (source < sourceEnd) {
UTF32 ch2 = *source;
/* If it's a low surrogate, convert to UTF32. */
if (ch2 >= UNI_SUR_LOW_START && ch2 <= UNI_SUR_LOW_END) {
ch = ((ch - UNI_SUR_HIGH_START) << halfShift)
+ (ch2 - UNI_SUR_LOW_START) + halfBase;
++source;
} else if (flags == strictConversion) { /* it's an unpaired high surrogate */
--source; /* return to the illegal value itself */
result = sourceIllegal;
break;
}
} else { /* We don't have the 16 bits following the high surrogate. */
--source; /* return to the high surrogate */
result = sourceExhausted;
break;
}
} else if (flags == strictConversion) {
/* UTF-16 surrogate values are illegal in UTF-32 */
if (ch >= UNI_SUR_LOW_START && ch <= UNI_SUR_LOW_END) {
--source; /* return to the illegal value itself */
result = sourceIllegal;
break;
}
}
/* Figure out how many bytes the result will require */
if (ch < (UTF32)0x80) { bytesToWrite = 1;
} else if (ch < (UTF32)0x800) { bytesToWrite = 2;
} else if (ch < (UTF32)0x10000) { bytesToWrite = 3;
} else if (ch < (UTF32)0x110000) { bytesToWrite = 4;
} else { bytesToWrite = 3;
ch = UNI_REPLACEMENT_CHAR;
}
target += bytesToWrite;
if (target > targetEnd) {
source = oldSource; /* Back up source pointer! */
target -= bytesToWrite; result = targetExhausted; break;
}
switch (bytesToWrite) { /* note: everything falls through. */
case 4: *--target = (UTF8)((ch | byteMark) & byteMask); ch >>= 6;
case 3: *--target = (UTF8)((ch | byteMark) & byteMask); ch >>= 6;
case 2: *--target = (UTF8)((ch | byteMark) & byteMask); ch >>= 6;
case 1: *--target = (UTF8)(ch | firstByteMark[bytesToWrite]);
}
target += bytesToWrite;
}
*sourceStart = source;
*targetStart = target;
return result;
}
/* --------------------------------------------------------------------- */
/*
* Utility routine to tell whether a sequence of bytes is legal UTF-8.
* This must be called with the length pre-determined by the first byte.
* If not calling this from ConvertUTF8to*, then the length can be set by:
* length = trailingBytesForUTF8[*source]+1;
* and the sequence is illegal right away if there aren't that many bytes
* available.
* If presented with a length > 4, this returns false. The Unicode
* definition of UTF-8 goes up to 4-byte sequences.
*/
static Boolean isLegalUTF8(const UTF8 *source, int length) {
UTF8 a;
const UTF8 *srcptr = source+length;
switch (length) {
default: return false;
/* Everything else falls through when "true"... */
case 4: if ((a = (*--srcptr)) < 0x80 || a > 0xBF) return false;
case 3: if ((a = (*--srcptr)) < 0x80 || a > 0xBF) return false;
case 2: if ((a = (*--srcptr)) > 0xBF) return false;
switch (*source) {
/* no fall-through in this inner switch */
case 0xE0: if (a < 0xA0) return false; break;
case 0xED: if (a > 0x9F) return false; break;
case 0xF0: if (a < 0x90) return false; break;
case 0xF4: if (a > 0x8F) return false; break;
default: if (a < 0x80) return false;
}
case 1: if (*source >= 0x80 && *source < 0xC2) return false;
}
if (*source > 0xF4) return false;
return true;
}
/* --------------------------------------------------------------------- */
/*
* Exported function to return whether a UTF-8 sequence is legal or not.
* This is not used here; it's just exported.
*/
Boolean isLegalUTF8Sequence(const UTF8 *source, const UTF8 *sourceEnd) {
int length = trailingBytesForUTF8[*source]+1;
if (source+length > sourceEnd) {
return false;
}
return isLegalUTF8(source, length);
}
/* --------------------------------------------------------------------- */
ConversionResult ConvertUTF8toUTF16 (
const UTF8** sourceStart, const UTF8* sourceEnd,
UTF16** targetStart, UTF16* targetEnd, ConversionFlags flags) {
ConversionResult result = conversionOK;
const UTF8* source = *sourceStart;
UTF16* target = *targetStart;
while (source < sourceEnd) {
UTF32 ch = 0;
unsigned short extraBytesToRead = trailingBytesForUTF8[*source];
if (source + extraBytesToRead >= sourceEnd) {
result = sourceExhausted; break;
}
/* Do this check whether lenient or strict */
if (! isLegalUTF8(source, extraBytesToRead+1)) {
result = sourceIllegal;
break;
}
/*
* The cases all fall through. See "Note A" below.
*/
switch (extraBytesToRead) {
case 5: ch += *source++; ch <<= 6; /* remember, illegal UTF-8 */
case 4: ch += *source++; ch <<= 6; /* remember, illegal UTF-8 */
case 3: ch += *source++; ch <<= 6;
case 2: ch += *source++; ch <<= 6;
case 1: ch += *source++; ch <<= 6;
case 0: ch += *source++;
}
ch -= offsetsFromUTF8[extraBytesToRead];
if (target >= targetEnd) {
source -= (extraBytesToRead+1); /* Back up source pointer! */
result = targetExhausted; break;
}
if (ch <= UNI_MAX_BMP) { /* Target is a character <= 0xFFFF */
/* UTF-16 surrogate values are illegal in UTF-32 */
if (ch >= UNI_SUR_HIGH_START && ch <= UNI_SUR_LOW_END) {
if (flags == strictConversion) {
source -= (extraBytesToRead+1); /* return to the illegal value itself */
result = sourceIllegal;
break;
} else {
*target++ = UNI_REPLACEMENT_CHAR;
}
} else {
*target++ = (UTF16)ch; /* normal case */
}
} else if (ch > UNI_MAX_UTF16) {
if (flags == strictConversion) {
result = sourceIllegal;
source -= (extraBytesToRead+1); /* return to the start */
break; /* Bail out; shouldn't continue */
} else {
*target++ = UNI_REPLACEMENT_CHAR;
}
} else {
/* target is a character in range 0xFFFF - 0x10FFFF. */
if (target + 1 >= targetEnd) {
source -= (extraBytesToRead+1); /* Back up source pointer! */
result = targetExhausted; break;
}
ch -= halfBase;
*target++ = (UTF16)((ch >> halfShift) + UNI_SUR_HIGH_START);
*target++ = (UTF16)((ch & halfMask) + UNI_SUR_LOW_START);
}
}
*sourceStart = source;
*targetStart = target;
return result;
}
/* --------------------------------------------------------------------- */
ConversionResult ConvertUTF32toUTF8 (
const UTF32** sourceStart, const UTF32* sourceEnd,
UTF8** targetStart, UTF8* targetEnd, ConversionFlags flags) {
ConversionResult result = conversionOK;
const UTF32* source = *sourceStart;
UTF8* target = *targetStart;
while (source < sourceEnd) {
UTF32 ch;
unsigned short bytesToWrite = 0;
const UTF32 byteMask = 0xBF;
const UTF32 byteMark = 0x80;
ch = *source++;
if (flags == strictConversion ) {
/* UTF-16 surrogate values are illegal in UTF-32 */
if (ch >= UNI_SUR_HIGH_START && ch <= UNI_SUR_LOW_END) {
--source; /* return to the illegal value itself */
result = sourceIllegal;
break;
}
}
/*
* Figure out how many bytes the result will require. Turn any
* illegally large UTF32 things (> Plane 17) into replacement chars.
*/
if (ch < (UTF32)0x80) { bytesToWrite = 1;
} else if (ch < (UTF32)0x800) { bytesToWrite = 2;
} else if (ch < (UTF32)0x10000) { bytesToWrite = 3;
} else if (ch <= UNI_MAX_LEGAL_UTF32) { bytesToWrite = 4;
} else { bytesToWrite = 3;
ch = UNI_REPLACEMENT_CHAR;
result = sourceIllegal;
}
target += bytesToWrite;
if (target > targetEnd) {
--source; /* Back up source pointer! */
target -= bytesToWrite; result = targetExhausted; break;
}
switch (bytesToWrite) { /* note: everything falls through. */
case 4: *--target = (UTF8)((ch | byteMark) & byteMask); ch >>= 6;
case 3: *--target = (UTF8)((ch | byteMark) & byteMask); ch >>= 6;
case 2: *--target = (UTF8)((ch | byteMark) & byteMask); ch >>= 6;
case 1: *--target = (UTF8) (ch | firstByteMark[bytesToWrite]);
}
target += bytesToWrite;
}
*sourceStart = source;
*targetStart = target;
return result;
}
/* --------------------------------------------------------------------- */
ConversionResult ConvertUTF8toUTF32 (
const UTF8** sourceStart, const UTF8* sourceEnd,
UTF32** targetStart, UTF32* targetEnd, ConversionFlags flags) {
ConversionResult result = conversionOK;
const UTF8* source = *sourceStart;
UTF32* target = *targetStart;
while (source < sourceEnd) {
UTF32 ch = 0;
unsigned short extraBytesToRead = trailingBytesForUTF8[*source];
if (source + extraBytesToRead >= sourceEnd) {
result = sourceExhausted; break;
}
/* Do this check whether lenient or strict */
if (! isLegalUTF8(source, extraBytesToRead+1)) {
result = sourceIllegal;
break;
}
/*
* The cases all fall through. See "Note A" below.
*/
switch (extraBytesToRead) {
case 5: ch += *source++; ch <<= 6;
case 4: ch += *source++; ch <<= 6;
case 3: ch += *source++; ch <<= 6;
case 2: ch += *source++; ch <<= 6;
case 1: ch += *source++; ch <<= 6;
case 0: ch += *source++;
}
ch -= offsetsFromUTF8[extraBytesToRead];
if (target >= targetEnd) {
source -= (extraBytesToRead+1); /* Back up the source pointer! */
result = targetExhausted; break;
}
if (ch <= UNI_MAX_LEGAL_UTF32) {
/*
* UTF-16 surrogate values are illegal in UTF-32, and anything
* over Plane 17 (> 0x10FFFF) is illegal.
*/
if (ch >= UNI_SUR_HIGH_START && ch <= UNI_SUR_LOW_END) {
if (flags == strictConversion) {
source -= (extraBytesToRead+1); /* return to the illegal value itself */
result = sourceIllegal;
break;
} else {
*target++ = UNI_REPLACEMENT_CHAR;
}
} else {
*target++ = ch;
}
} else { /* i.e., ch > UNI_MAX_LEGAL_UTF32 */
result = sourceIllegal;
*target++ = UNI_REPLACEMENT_CHAR;
}
}
*sourceStart = source;
*targetStart = target;
return result;
}
/* ---------------------------------------------------------------------
Note A.
The fall-through switches in UTF-8 reading code save a
temp variable, some decrements & conditionals. The switches
are equivalent to the following loop:
{
int tmpBytesToRead = extraBytesToRead+1;
do {
ch += *source++;
--tmpBytesToRead;
if (tmpBytesToRead) ch <<= 6;
} while (tmpBytesToRead > 0);
}
In UTF-8 writing code, the switches on "bytesToWrite" are
similarly unrolled loops.
--------------------------------------------------------------------- */

149
simpleini/ConvertUTF.h Normal file
View file

@ -0,0 +1,149 @@
/*
* Copyright 2001-2004 Unicode, Inc.
*
* Disclaimer
*
* This source code is provided as is by Unicode, Inc. No claims are
* made as to fitness for any particular purpose. No warranties of any
* kind are expressed or implied. The recipient agrees to determine
* applicability of information provided. If this file has been
* purchased on magnetic or optical media from Unicode, Inc., the
* sole remedy for any claim will be exchange of defective media
* within 90 days of receipt.
*
* Limitations on Rights to Redistribute This Code
*
* Unicode, Inc. hereby grants the right to freely use the information
* supplied in this file in the creation of products supporting the
* Unicode Standard, and to make copies of this file in any form
* for internal or external distribution as long as this notice
* remains attached.
*/
/* ---------------------------------------------------------------------
Conversions between UTF32, UTF-16, and UTF-8. Header file.
Several funtions are included here, forming a complete set of
conversions between the three formats. UTF-7 is not included
here, but is handled in a separate source file.
Each of these routines takes pointers to input buffers and output
buffers. The input buffers are const.
Each routine converts the text between *sourceStart and sourceEnd,
putting the result into the buffer between *targetStart and
targetEnd. Note: the end pointers are *after* the last item: e.g.
*(sourceEnd - 1) is the last item.
The return result indicates whether the conversion was successful,
and if not, whether the problem was in the source or target buffers.
(Only the first encountered problem is indicated.)
After the conversion, *sourceStart and *targetStart are both
updated to point to the end of last text successfully converted in
the respective buffers.
Input parameters:
sourceStart - pointer to a pointer to the source buffer.
The contents of this are modified on return so that
it points at the next thing to be converted.
targetStart - similarly, pointer to pointer to the target buffer.
sourceEnd, targetEnd - respectively pointers to the ends of the
two buffers, for overflow checking only.
These conversion functions take a ConversionFlags argument. When this
flag is set to strict, both irregular sequences and isolated surrogates
will cause an error. When the flag is set to lenient, both irregular
sequences and isolated surrogates are converted.
Whether the flag is strict or lenient, all illegal sequences will cause
an error return. This includes sequences such as: <F4 90 80 80>, <C0 80>,
or <A0> in UTF-8, and values above 0x10FFFF in UTF-32. Conformant code
must check for illegal sequences.
When the flag is set to lenient, characters over 0x10FFFF are converted
to the replacement character; otherwise (when the flag is set to strict)
they constitute an error.
Output parameters:
The value "sourceIllegal" is returned from some routines if the input
sequence is malformed. When "sourceIllegal" is returned, the source
value will point to the illegal value that caused the problem. E.g.,
in UTF-8 when a sequence is malformed, it points to the start of the
malformed sequence.
Author: Mark E. Davis, 1994.
Rev History: Rick McGowan, fixes & updates May 2001.
Fixes & updates, Sept 2001.
------------------------------------------------------------------------ */
/* ---------------------------------------------------------------------
The following 4 definitions are compiler-specific.
The C standard does not guarantee that wchar_t has at least
16 bits, so wchar_t is no less portable than unsigned short!
All should be unsigned values to avoid sign extension during
bit mask & shift operations.
------------------------------------------------------------------------ */
typedef unsigned int UTF32; /* at least 32 bits */
typedef unsigned short UTF16; /* at least 16 bits */
typedef unsigned char UTF8; /* typically 8 bits */
typedef unsigned char Boolean; /* 0 or 1 */
/* Some fundamental constants */
#define UNI_REPLACEMENT_CHAR (UTF32)0x0000FFFD
#define UNI_MAX_BMP (UTF32)0x0000FFFF
#define UNI_MAX_UTF16 (UTF32)0x0010FFFF
#define UNI_MAX_UTF32 (UTF32)0x7FFFFFFF
#define UNI_MAX_LEGAL_UTF32 (UTF32)0x0010FFFF
typedef enum {
conversionOK, /* conversion successful */
sourceExhausted, /* partial character in source, but hit end */
targetExhausted, /* insuff. room in target for conversion */
sourceIllegal /* source sequence is illegal/malformed */
} ConversionResult;
typedef enum {
strictConversion = 0,
lenientConversion
} ConversionFlags;
/* This is for C++ and does no harm in C */
#ifdef __cplusplus
extern "C" {
#endif
ConversionResult ConvertUTF8toUTF16 (
const UTF8** sourceStart, const UTF8* sourceEnd,
UTF16** targetStart, UTF16* targetEnd, ConversionFlags flags);
ConversionResult ConvertUTF16toUTF8 (
const UTF16** sourceStart, const UTF16* sourceEnd,
UTF8** targetStart, UTF8* targetEnd, ConversionFlags flags);
ConversionResult ConvertUTF8toUTF32 (
const UTF8** sourceStart, const UTF8* sourceEnd,
UTF32** targetStart, UTF32* targetEnd, ConversionFlags flags);
ConversionResult ConvertUTF32toUTF8 (
const UTF32** sourceStart, const UTF32* sourceEnd,
UTF8** targetStart, UTF8* targetEnd, ConversionFlags flags);
ConversionResult ConvertUTF16toUTF32 (
const UTF16** sourceStart, const UTF16* sourceEnd,
UTF32** targetStart, UTF32* targetEnd, ConversionFlags flags);
ConversionResult ConvertUTF32toUTF16 (
const UTF32** sourceStart, const UTF32* sourceEnd,
UTF16** targetStart, UTF16* targetEnd, ConversionFlags flags);
Boolean isLegalUTF8Sequence(const UTF8 *source, const UTF8 *sourceEnd);
#ifdef __cplusplus
}
#endif
/* --------------------------------------------------------------------- */

20
simpleini/LICENCE.txt Normal file
View file

@ -0,0 +1,20 @@
The MIT License (MIT)
Copyright (c) 2006-2013 Brodie Thiesfield
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

28
simpleini/Makefile Normal file
View file

@ -0,0 +1,28 @@
CC=g++
CFLAGS=-Wall
CPPFLAGS=-Wall
OBJS=testsi.o test1.o snippets.o ConvertUTF.o
help:
@echo This makefile is just for the test program \(use \"make clean all test\"\)
@echo Just include the SimpleIni.h header file to use it.
all: $(OBJS)
$(CC) -o testsi $(OBJS)
clean:
rm -f core *.o testsi
data:
sed 's/\r\n$$/\n/g' < test1-expected.ini > unix.out
mv unix.out test1-expected.ini
test: testsi
./testsi -u -m -l test1-input.ini > test1-blah.ini
diff test1-output.ini test1-expected.ini
install:
@echo No install required. Just include the SimpleIni.h header file to use it.
testsi.o test1.o snippets.o : SimpleIni.h

150
simpleini/README.md Normal file
View file

@ -0,0 +1,150 @@
simpleini
=========
A cross-platform library that provides a simple API to read and write INI-style configuration files. It supports data files in ASCII, MBCS and Unicode. It is designed explicitly to be portable to any platform and has been tested on Windows, WinCE and Linux. Released as open-source and free using the MIT licence.
# Feature Summary
- MIT Licence allows free use in all software (including GPL and commercial)
- multi-platform (Windows 95/98/ME/NT/2K/XP/2003, Windows CE, Linux, Unix)
- loading and saving of INI-style configuration files
- configuration files can have any newline format on all platforms
- liberal acceptance of file format
* key/values with no section
* removal of whitespace around sections, keys and values
- support for multi-line values (values with embedded newline characters)
- optional support for multiple keys with the same name
- optional case-insensitive sections and keys (for ASCII characters only)
- saves files with sections and keys in the same order as they were loaded
- preserves comments on the file, section and keys where possible.
- supports both char or wchar_t programming interfaces
- supports both MBCS (system locale) and UTF-8 file encodings
- system locale does not need to be UTF-8 on Linux/Unix to load UTF-8 file
- support for non-ASCII characters in section, keys, values and comments
- support for non-standard character types or file encodings via user-written converter classes
- support for adding/modifying values programmatically
- compiles cleanly in the following compilers:
* Windows/VC6 (warning level 3)
* Windows/VC.NET 2003 (warning level 4)
* Windows/VC 2005 (warning level 4)
* Linux/gcc (-Wall)
* Windows/MinGW GCC
# Documentation
Full documentation of the interface is available in doxygen format.
# Examples
These snippets are included with the distribution in the file snippets.cpp.
### SIMPLE USAGE
```c++
CSimpleIniA ini;
ini.SetUnicode();
ini.LoadFile("myfile.ini");
const char * pVal = ini.GetValue("section", "key", "default");
ini.SetValue("section", "key", "newvalue");
```
### LOADING DATA
```c++
// load from a data file
CSimpleIniA ini(a_bIsUtf8, a_bUseMultiKey, a_bUseMultiLine);
SI_Error rc = ini.LoadFile(a_pszFile);
if (rc < 0) return false;
// load from a string
std::string strData;
rc = ini.LoadData(strData.c_str(), strData.size());
if (rc < 0) return false;
```
### GETTING SECTIONS AND KEYS
```c++
// get all sections
CSimpleIniA::TNamesDepend sections;
ini.GetAllSections(sections);
// get all keys in a section
CSimpleIniA::TNamesDepend keys;
ini.GetAllKeys("section-name", keys);
```
### GETTING VALUES
```c++
// get the value of a key
const char * pszValue = ini.GetValue("section-name",
"key-name", NULL /*default*/);
// get the value of a key which may have multiple
// values. If bHasMultipleValues is true, then just
// one value has been returned
bool bHasMultipleValues;
pszValue = ini.GetValue("section-name", "key-name",
NULL /*default*/, &amp;bHasMultipleValues);
// get all values of a key with multiple values
CSimpleIniA::TNamesDepend values;
ini.GetAllValues("section-name", "key-name", values);
// sort the values into the original load order
values.sort(CSimpleIniA::Entry::LoadOrder());
// output all of the items
CSimpleIniA::TNamesDepend::const_iterator i;
for (i = values.begin(); i != values.end(); ++i) {
printf("key-name = '%s'\n", i->pItem);
}
```
### MODIFYING DATA
```c++
// adding a new section
rc = ini.SetValue("new-section", NULL, NULL);
if (rc < 0) return false;
printf("section: %s\n", rc == SI_INSERTED ?
"inserted" : "updated");
// adding a new key ("new-section" will be added
// automatically if it doesn't already exist)
rc = ini.SetValue("new-section", "new-key", "value");
if (rc < 0) return false;
printf("key: %s\n", rc == SI_INSERTED ?
"inserted" : "updated");
// changing the value of a key
rc = ini.SetValue("section", "key", "updated-value");
if (rc < 0) return false;
printf("key: %s\n", rc == SI_INSERTED ?
"inserted" : "updated");
```
### DELETING DATA
```c++
// deleting a key from a section. Optionally the entire
// section may be deleted if it is now empty.
ini.Delete("section-name", "key-name",
true /*delete the section if empty*/);
// deleting an entire section and all keys in it
ini.Delete("section-name", NULL);
```
### SAVING DATA
```c++
// save the data to a string
rc = ini.Save(strData);
if (rc < 0) return false;
// save the data back to the file
rc = ini.SaveFile(a_pszFile);
if (rc < 0) return false;
```

3437
simpleini/SimpleIni.h Normal file

File diff suppressed because it is too large Load diff

29
simpleini/SimpleIni.sln Normal file
View file

@ -0,0 +1,29 @@
Microsoft Visual Studio Solution File, Format Version 8.00
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "SimpleIni", "SimpleIni.vcproj", "{C23240A6-AA9D-4827-AF06-C98E97CA6DFB}"
ProjectSection(ProjectDependencies) = postProject
EndProjectSection
EndProject
Global
GlobalSection(SolutionConfiguration) = preSolution
Debug = Debug
Debug Unicode = Debug Unicode
Release = Release
Release Unicode = Release Unicode
EndGlobalSection
GlobalSection(ProjectDependencies) = postSolution
EndGlobalSection
GlobalSection(ProjectConfiguration) = postSolution
{C23240A6-AA9D-4827-AF06-C98E97CA6DFB}.Debug.ActiveCfg = Debug|Win32
{C23240A6-AA9D-4827-AF06-C98E97CA6DFB}.Debug.Build.0 = Debug|Win32
{C23240A6-AA9D-4827-AF06-C98E97CA6DFB}.Debug Unicode.ActiveCfg = Debug Unicode|Win32
{C23240A6-AA9D-4827-AF06-C98E97CA6DFB}.Debug Unicode.Build.0 = Debug Unicode|Win32
{C23240A6-AA9D-4827-AF06-C98E97CA6DFB}.Release.ActiveCfg = Release|Win32
{C23240A6-AA9D-4827-AF06-C98E97CA6DFB}.Release.Build.0 = Release|Win32
{C23240A6-AA9D-4827-AF06-C98E97CA6DFB}.Release Unicode.ActiveCfg = Release Unicode|Win32
{C23240A6-AA9D-4827-AF06-C98E97CA6DFB}.Release Unicode.Build.0 = Release Unicode|Win32
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
EndGlobalSection
GlobalSection(ExtensibilityAddIns) = postSolution
EndGlobalSection
EndGlobal

291
simpleini/SimpleIni.vcproj Normal file
View file

@ -0,0 +1,291 @@
<?xml version="1.0" encoding="shift_jis"?>
<VisualStudioProject
ProjectType="Visual C++"
Version="7.10"
Name="SimpleIni"
ProjectGUID="{C23240A6-AA9D-4827-AF06-C98E97CA6DFB}"
RootNamespace="SimpleIni"
Keyword="Win32Proj">
<Platforms>
<Platform
Name="Win32"/>
</Platforms>
<Configurations>
<Configuration
Name="Debug|Win32"
OutputDirectory="Debug"
IntermediateDirectory="Debug"
ConfigurationType="1"
CharacterSet="2">
<Tool
Name="VCCLCompilerTool"
Optimization="0"
PreprocessorDefinitions="WIN32;_DEBUG;_CONSOLE"
MinimalRebuild="TRUE"
BasicRuntimeChecks="3"
RuntimeLibrary="5"
UsePrecompiledHeader="0"
WarningLevel="4"
Detect64BitPortabilityProblems="TRUE"
DebugInformationFormat="4"/>
<Tool
Name="VCCustomBuildTool"/>
<Tool
Name="VCLinkerTool"
OutputFile="$(OutDir)/testsi.exe"
LinkIncremental="2"
GenerateDebugInformation="TRUE"
ProgramDatabaseFile="$(OutDir)/SimpleIni.pdb"
SubSystem="1"
TargetMachine="1"/>
<Tool
Name="VCMIDLTool"/>
<Tool
Name="VCPostBuildEventTool"/>
<Tool
Name="VCPreBuildEventTool"/>
<Tool
Name="VCPreLinkEventTool"/>
<Tool
Name="VCResourceCompilerTool"/>
<Tool
Name="VCWebServiceProxyGeneratorTool"/>
<Tool
Name="VCXMLDataGeneratorTool"/>
<Tool
Name="VCWebDeploymentTool"/>
<Tool
Name="VCManagedWrapperGeneratorTool"/>
<Tool
Name="VCAuxiliaryManagedWrapperGeneratorTool"/>
</Configuration>
<Configuration
Name="Release|Win32"
OutputDirectory="Release"
IntermediateDirectory="Release"
ConfigurationType="1"
CharacterSet="2">
<Tool
Name="VCCLCompilerTool"
PreprocessorDefinitions="WIN32;NDEBUG;_CONSOLE"
RuntimeLibrary="4"
UsePrecompiledHeader="0"
WarningLevel="4"
Detect64BitPortabilityProblems="TRUE"
DebugInformationFormat="3"/>
<Tool
Name="VCCustomBuildTool"/>
<Tool
Name="VCLinkerTool"
OutputFile="$(OutDir)/testsi.exe"
LinkIncremental="1"
GenerateDebugInformation="TRUE"
SubSystem="1"
OptimizeReferences="2"
EnableCOMDATFolding="2"
TargetMachine="1"/>
<Tool
Name="VCMIDLTool"/>
<Tool
Name="VCPostBuildEventTool"/>
<Tool
Name="VCPreBuildEventTool"/>
<Tool
Name="VCPreLinkEventTool"/>
<Tool
Name="VCResourceCompilerTool"/>
<Tool
Name="VCWebServiceProxyGeneratorTool"/>
<Tool
Name="VCXMLDataGeneratorTool"/>
<Tool
Name="VCWebDeploymentTool"/>
<Tool
Name="VCManagedWrapperGeneratorTool"/>
<Tool
Name="VCAuxiliaryManagedWrapperGeneratorTool"/>
</Configuration>
<Configuration
Name="Debug Unicode|Win32"
OutputDirectory="$(ConfigurationName)"
IntermediateDirectory="$(ConfigurationName)"
ConfigurationType="1"
CharacterSet="1">
<Tool
Name="VCCLCompilerTool"
Optimization="0"
PreprocessorDefinitions="WIN32;_DEBUG;_CONSOLE"
MinimalRebuild="TRUE"
BasicRuntimeChecks="3"
RuntimeLibrary="5"
UsePrecompiledHeader="0"
WarningLevel="4"
Detect64BitPortabilityProblems="TRUE"
DebugInformationFormat="4"/>
<Tool
Name="VCCustomBuildTool"/>
<Tool
Name="VCLinkerTool"
OutputFile="$(OutDir)/testsi.exe"
LinkIncremental="2"
GenerateDebugInformation="TRUE"
ProgramDatabaseFile="$(OutDir)/SimpleIni.pdb"
SubSystem="1"
TargetMachine="1"/>
<Tool
Name="VCMIDLTool"/>
<Tool
Name="VCPostBuildEventTool"/>
<Tool
Name="VCPreBuildEventTool"/>
<Tool
Name="VCPreLinkEventTool"/>
<Tool
Name="VCResourceCompilerTool"/>
<Tool
Name="VCWebServiceProxyGeneratorTool"/>
<Tool
Name="VCXMLDataGeneratorTool"/>
<Tool
Name="VCWebDeploymentTool"/>
<Tool
Name="VCManagedWrapperGeneratorTool"/>
<Tool
Name="VCAuxiliaryManagedWrapperGeneratorTool"/>
</Configuration>
<Configuration
Name="Release Unicode|Win32"
OutputDirectory="$(ConfigurationName)"
IntermediateDirectory="$(ConfigurationName)"
ConfigurationType="1"
CharacterSet="1">
<Tool
Name="VCCLCompilerTool"
PreprocessorDefinitions="WIN32;NDEBUG;_CONSOLE"
RuntimeLibrary="4"
UsePrecompiledHeader="0"
WarningLevel="4"
Detect64BitPortabilityProblems="TRUE"
DebugInformationFormat="3"/>
<Tool
Name="VCCustomBuildTool"/>
<Tool
Name="VCLinkerTool"
OutputFile="$(OutDir)/testsi.exe"
LinkIncremental="1"
GenerateDebugInformation="TRUE"
SubSystem="1"
OptimizeReferences="2"
EnableCOMDATFolding="2"
TargetMachine="1"/>
<Tool
Name="VCMIDLTool"/>
<Tool
Name="VCPostBuildEventTool"/>
<Tool
Name="VCPreBuildEventTool"/>
<Tool
Name="VCPreLinkEventTool"/>
<Tool
Name="VCResourceCompilerTool"/>
<Tool
Name="VCWebServiceProxyGeneratorTool"/>
<Tool
Name="VCXMLDataGeneratorTool"/>
<Tool
Name="VCWebDeploymentTool"/>
<Tool
Name="VCManagedWrapperGeneratorTool"/>
<Tool
Name="VCAuxiliaryManagedWrapperGeneratorTool"/>
</Configuration>
</Configurations>
<References>
</References>
<Files>
<Filter
Name="Source Files"
Filter="cpp;c;cxx;def;odl;idl;hpj;bat;asm;asmx"
UniqueIdentifier="{4FC737F1-C7A5-4376-A066-2A32D752A2FF}">
<File
RelativePath=".\snippets.cpp">
</File>
<File
RelativePath=".\test1.cpp">
</File>
<File
RelativePath=".\testsi.cpp">
</File>
</Filter>
<Filter
Name="Library Files"
Filter="h;hpp;hxx;hm;inl;inc;xsd"
UniqueIdentifier="{93995380-89BD-4b04-88EB-625FBE52EBFB}">
<File
RelativePath=".\SimpleIni.h">
</File>
</Filter>
<Filter
Name="Resource Files"
Filter="rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx"
UniqueIdentifier="{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}">
<File
RelativePath=".\Makefile">
</File>
<File
RelativePath=".\testsi-EUCJP.ini">
</File>
<File
RelativePath=".\testsi-SJIS.ini">
</File>
<File
RelativePath=".\testsi-UTF8.ini">
</File>
</Filter>
<Filter
Name="Conversion Files"
Filter="">
<File
RelativePath=".\ConvertUTF.c">
</File>
<File
RelativePath=".\ConvertUTF.h">
</File>
</Filter>
<Filter
Name="Documentation"
Filter="">
<File
RelativePath=".\simpleini.doxy">
<FileConfiguration
Name="Debug|Win32">
<Tool
Name="VCCustomBuildTool"
CommandLine="if not exist &quot;C:\Program Files\doxygen\bin\doxygen.exe&quot; goto done
echo Generating documentation...
&quot;C:\Program Files\doxygen\bin\doxygen.exe&quot; $(InputDir)simpleini.doxy
:done
"
Outputs="d:\src\simpleini-doc\html\index.html"/>
</FileConfiguration>
</File>
</Filter>
<Filter
Name="Tests"
Filter="">
<File
RelativePath=".\test1-expected.ini">
</File>
<File
RelativePath=".\test1-input.ini">
</File>
<File
RelativePath=".\test1-output.ini">
</File>
</Filter>
</Files>
<Globals>
</Globals>
</VisualStudioProject>

36
simpleini/ini.syn Normal file
View file

@ -0,0 +1,36 @@
; Syntax file for ini files - contributed by Brodie Thiesfield
;
; Suggested Colors:
; Comments (;#) Comments, Comments 2 Green
; Sections Characters Red
; Values Strings Blue
C=1
[Syntax]
Namespace1 = 6
IgnoreCase = Yes
KeyWordLength = 1
BracketChars =
OperatorChars =
PreprocStart =
SyntaxStart =
SyntaxEnd =
HexPrefix =
CommentStart =
CommentEnd =
CommentStartAlt =
CommentEndAlt =
SingleComment = #
SingleCommentCol =
SingleCommentAlt = ;
SingleCommentColAlt =
SingleCommentEsc =
StringsSpanLines = No
StringStart =
StringEnd =
StringAlt = =
StringEsc =
CharStart = [
CharEnd = ]
CharEsc =

26
simpleini/package.cmd Normal file
View file

@ -0,0 +1,26 @@
set VERSION=4.15
set SEVENZIP="C:\Program Files\7-Zip\7z.exe"
FOR /F "tokens=*" %%G IN ('DIR /AD /B /S Debug*') DO (
DEL /S /Q "%%G"
RD "%%G"
)
FOR /F "tokens=*" %%G IN ('DIR /AD /B /S Release*') DO (
DEL /S /Q "%%G"
RD "%%G"
)
DEL /Q "SimpleIni.ncb"
ATTRIB -H "SimpleIni.suo"
DEL /Q "SimpleIni.suo"
DEL /Q "SimpleIni.opt"
DEL /Q testsi-out*.ini
DEL /Q test1-blah.ini
DEL /Q test1-output.ini
START "Generate documentation" /WAIT "C:\Program Files (x86)\doxygen\bin\doxygen.exe" SimpleIni.doxy
cd ..
del simpleini-%VERSION%.zip
%SEVENZIP% a -tzip -r- -x!simpleini\.svn simpleini-%VERSION%.zip simpleini\*
del simpleini-doc.zip
%SEVENZIP% a -tzip -r simpleini-doc.zip simpleini-doc\*
cd simpleini

1321
simpleini/simpleini.doxy Normal file

File diff suppressed because it is too large Load diff

178
simpleini/simpleini.dsp Normal file
View file

@ -0,0 +1,178 @@
# Microsoft Developer Studio Project File - Name="simpleini" - Package Owner=<4>
# Microsoft Developer Studio Generated Build File, Format Version 6.00
# ** DO NOT EDIT **
# TARGTYPE "Win32 (x86) Console Application" 0x0103
CFG=simpleini - Win32 Debug Unicode
!MESSAGE This is not a valid makefile. To build this project using NMAKE,
!MESSAGE use the Export Makefile command and run
!MESSAGE
!MESSAGE NMAKE /f "simpleini.mak".
!MESSAGE
!MESSAGE You can specify a configuration when running NMAKE
!MESSAGE by defining the macro CFG on the command line. For example:
!MESSAGE
!MESSAGE NMAKE /f "simpleini.mak" CFG="simpleini - Win32 Debug Unicode"
!MESSAGE
!MESSAGE Possible choices for configuration are:
!MESSAGE
!MESSAGE "simpleini - Win32 Release" (based on "Win32 (x86) Console Application")
!MESSAGE "simpleini - Win32 Debug" (based on "Win32 (x86) Console Application")
!MESSAGE "simpleini - Win32 Debug Unicode" (based on "Win32 (x86) Console Application")
!MESSAGE "simpleini - Win32 Release Unicode" (based on "Win32 (x86) Console Application")
!MESSAGE
# Begin Project
# PROP AllowPerConfigDependencies 0
# PROP Scc_ProjName ""
# PROP Scc_LocalPath ""
CPP=cl.exe
RSC=rc.exe
!IF "$(CFG)" == "simpleini - Win32 Release"
# PROP BASE Use_MFC 0
# PROP BASE Use_Debug_Libraries 0
# PROP BASE Output_Dir "Release"
# PROP BASE Intermediate_Dir "Release"
# PROP BASE Target_Dir ""
# PROP Use_MFC 0
# PROP Use_Debug_Libraries 0
# PROP Output_Dir "Release"
# PROP Intermediate_Dir "Release"
# PROP Ignore_Export_Lib 0
# PROP Target_Dir ""
# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /c
# ADD CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /FD /c
# SUBTRACT CPP /YX
# ADD BASE RSC /l 0xc09 /d "NDEBUG"
# ADD RSC /l 0xc09 /d "NDEBUG"
BSC32=bscmake.exe
# ADD BASE BSC32 /nologo
# ADD BSC32 /nologo
LINK32=link.exe
# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /machine:I386
# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /machine:I386 /out:"Release/testsi.exe"
!ELSEIF "$(CFG)" == "simpleini - Win32 Debug"
# PROP BASE Use_MFC 0
# PROP BASE Use_Debug_Libraries 1
# PROP BASE Output_Dir "simpleini___Win32_Debug"
# PROP BASE Intermediate_Dir "simpleini___Win32_Debug"
# PROP BASE Target_Dir ""
# PROP Use_MFC 0
# PROP Use_Debug_Libraries 1
# PROP Output_Dir "Debug"
# PROP Intermediate_Dir "Debug"
# PROP Ignore_Export_Lib 0
# PROP Target_Dir ""
# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /GZ /c
# ADD CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /FD /GZ /c
# SUBTRACT CPP /Fr /YX
# ADD BASE RSC /l 0xc09 /d "_DEBUG"
# ADD RSC /l 0xc09 /d "_DEBUG"
BSC32=bscmake.exe
# ADD BASE BSC32 /nologo
# ADD BSC32 /nologo
LINK32=link.exe
# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386 /pdbtype:sept
# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386 /out:"Debug/testsi.exe" /pdbtype:sept
!ELSEIF "$(CFG)" == "simpleini - Win32 Debug Unicode"
# PROP BASE Use_MFC 0
# PROP BASE Use_Debug_Libraries 1
# PROP BASE Output_Dir "Debug Unicode"
# PROP BASE Intermediate_Dir "Debug Unicode"
# PROP BASE Target_Dir ""
# PROP Use_MFC 0
# PROP Use_Debug_Libraries 1
# PROP Output_Dir "Debug Unicode"
# PROP Intermediate_Dir "Debug Unicode"
# PROP Ignore_Export_Lib 0
# PROP Target_Dir ""
# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /GZ /c
# ADD CPP /nologo /W3 /Gm /GX /ZI /Od /D "_DEBUG" /D "WIN32" /D "_CONSOLE" /D "_UNICODE" /D "UNICODE" /D "SI_USE_GENERIC_CONVERSION" /FD /GZ /c
# SUBTRACT CPP /YX
# ADD BASE RSC /l 0xc09 /d "_DEBUG"
# ADD RSC /l 0xc09 /d "_DEBUG"
BSC32=bscmake.exe
# ADD BASE BSC32 /nologo
# ADD BSC32 /nologo
LINK32=link.exe
# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386 /pdbtype:sept
# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386 /out:"Debug Unicode/testsi.exe" /pdbtype:sept
!ELSEIF "$(CFG)" == "simpleini - Win32 Release Unicode"
# PROP BASE Use_MFC 0
# PROP BASE Use_Debug_Libraries 0
# PROP BASE Output_Dir "Release Unicode"
# PROP BASE Intermediate_Dir "Release Unicode"
# PROP BASE Target_Dir ""
# PROP Use_MFC 0
# PROP Use_Debug_Libraries 0
# PROP Output_Dir "Release Unicode"
# PROP Intermediate_Dir "Release Unicode"
# PROP Ignore_Export_Lib 0
# PROP Target_Dir ""
# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /c
# ADD CPP /nologo /W3 /GX /O2 /D "NDEBUG" /D "WIN32" /D "_CONSOLE" /D "_UNICODE" /D "UNICODE" /D "SI_USE_GENERIC_CONVERSION" /FD /c
# SUBTRACT CPP /YX
# ADD BASE RSC /l 0xc09 /d "NDEBUG"
# ADD RSC /l 0xc09 /d "NDEBUG"
BSC32=bscmake.exe
# ADD BASE BSC32 /nologo
# ADD BSC32 /nologo
LINK32=link.exe
# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /machine:I386
# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /machine:I386 /out:"Release Unicode/testsi.exe"
!ENDIF
# Begin Target
# Name "simpleini - Win32 Release"
# Name "simpleini - Win32 Debug"
# Name "simpleini - Win32 Debug Unicode"
# Name "simpleini - Win32 Release Unicode"
# Begin Group "Source Files"
# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat"
# Begin Source File
SOURCE=.\snippets.cpp
# End Source File
# Begin Source File
SOURCE=.\test1.cpp
# End Source File
# Begin Source File
SOURCE=.\testsi.cpp
# End Source File
# End Group
# Begin Group "Library Files"
# PROP Default_Filter ""
# Begin Source File
SOURCE=.\SimpleIni.h
# End Source File
# End Group
# Begin Group "Generic Files"
# PROP Default_Filter ""
# Begin Source File
SOURCE=.\ConvertUTF.c
# End Source File
# Begin Source File
SOURCE=.\ConvertUTF.h
# End Source File
# End Group
# End Target
# End Project

29
simpleini/simpleini.dsw Normal file
View file

@ -0,0 +1,29 @@
Microsoft Developer Studio Workspace File, Format Version 6.00
# WARNING: DO NOT EDIT OR DELETE THIS WORKSPACE FILE!
###############################################################################
Project: "simpleini"=.\simpleini.dsp - Package Owner=<4>
Package=<5>
{{{
}}}
Package=<4>
{{{
}}}
###############################################################################
Global:
Package=<5>
{{{
}}}
Package=<3>
{{{
}}}
###############################################################################

128
simpleini/snippets.cpp Normal file
View file

@ -0,0 +1,128 @@
// File: snippets.cpp
// Library: SimpleIni
// Author: Brodie Thiesfield <code@jellycan.com>
// Source: http://code.jellycan.com/simpleini/
//
// Snippets that are used on the website
#ifdef _WIN32
# pragma warning(disable: 4786)
#endif
#ifndef _WIN32
# include <unistd.h>
#endif
#include <fstream>
#define SI_SUPPORT_IOSTREAMS
#include "SimpleIni.h"
bool
snippets(
const char * a_pszFile,
bool a_bIsUtf8,
bool a_bUseMultiKey,
bool a_bUseMultiLine
)
{
// LOADING DATA
// load from a data file
CSimpleIniA ini(a_bIsUtf8, a_bUseMultiKey, a_bUseMultiLine);
SI_Error rc = ini.LoadFile(a_pszFile);
if (rc < 0) return false;
// load from a string
std::string strData;
rc = ini.LoadData(strData.c_str(), strData.size());
if (rc < 0) return false;
// GETTING SECTIONS AND KEYS
// get all sections
CSimpleIniA::TNamesDepend sections;
ini.GetAllSections(sections);
// get all keys in a section
CSimpleIniA::TNamesDepend keys;
ini.GetAllKeys("section-name", keys);
// GETTING VALUES
// get the value of a key
const char * pszValue = ini.GetValue("section-name",
"key-name", NULL /*default*/);
// get the value of a key which may have multiple
// values. If bHasMultipleValues is true, then just
// one value has been returned
bool bHasMultipleValues;
pszValue = ini.GetValue("section-name", "key-name",
NULL /*default*/, &bHasMultipleValues);
// get all values of a key with multiple values
CSimpleIniA::TNamesDepend values;
ini.GetAllValues("section-name", "key-name", values);
// sort the values into the original load order
#if defined(_MSC_VER) && _MSC_VER <= 1200
/** STL of VC6 doesn't allow me to specify my own comparator for list::sort() */
values.sort();
#else
values.sort(CSimpleIniA::Entry::LoadOrder());
#endif
// output all of the items
CSimpleIniA::TNamesDepend::const_iterator i;
for (i = values.begin(); i != values.end(); ++i) {
printf("key-name = '%s'\n", i->pItem);
}
// MODIFYING DATA
// adding a new section
rc = ini.SetValue("new-section", NULL, NULL);
if (rc < 0) return false;
printf("section: %s\n", rc == SI_INSERTED ?
"inserted" : "updated");
// adding a new key ("new-section" will be added
// automatically if it doesn't already exist.
rc = ini.SetValue("new-section", "new-key", "value");
if (rc < 0) return false;
printf("key: %s\n", rc == SI_INSERTED ?
"inserted" : "updated");
// changing the value of a key
rc = ini.SetValue("section", "key", "updated-value");
if (rc < 0) return false;
printf("key: %s\n", rc == SI_INSERTED ?
"inserted" : "updated");
// DELETING DATA
// deleting a key with a value from a section.
// Optionally the entire section may be deleted if
// it is now empty.
ini.DeleteValue("section-name", "key-name", "value",
true /*delete the section if empty*/);
// deleting a key with any value from a section.
ini.Delete("section-name", "key-name",
true /*delete the section if empty*/);
// deleting an entire section and all keys in it
ini.Delete("section-name", NULL);
// SAVING DATA
// save the data to a string
rc = ini.Save(strData);
if (rc < 0) return false;
// save the data back to the file
rc = ini.SaveFile(a_pszFile);
if (rc < 0) return false;
return true;
}

24
simpleini/test.cmd Normal file
View file

@ -0,0 +1,24 @@
@echo off
Debug\testsi.exe -u -m -l test1-input.ini > test1-blah.ini
fc test1-expected.ini test1-output.ini
if errorlevel 1 goto error
"Debug Unicode\testsi.exe" -u -m -l test1-input.ini > test1-blah.ini
fc test1-expected.ini test1-output.ini
if errorlevel 1 goto error
Release\testsi.exe -u -m -l test1-input.ini > test1-blah.ini
fc test1-expected.ini test1-output.ini
if errorlevel 1 goto error
"Release Unicode\testsi.exe" -u -m -l test1-input.ini > test1-blah.ini
fc test1-expected.ini test1-output.ini
if errorlevel 1 goto error
exit /b 0
:error
echo Failed during test run. Output file doesn't match expected file.
pause
exit /b 1

View file

@ -0,0 +1,85 @@
; testsi-UTF8-std.ini : standard UTF-8 test file for SimpleIni automated testing
;
; The number after a section or key is the order that it is defined in this file
; to make it easier to see if it has been written out correctly. This file should
; be loaded with Unicode / MultiKey / MultiLine turned on.
; This comment should be joined on to the one below it about the key
; with no section.
; Key with no section
lonely-key = nosection
another = nosection either
; This key has no value
empty =
; This should be joined with the comment below about japanese.
; Another line which will be un-indented.
; This is a section of keys showing the word Japanese in different syllabies.
[ordered-1]
a-1 = blah
; this is in kanji
japanese-2 = 日本語
; this is in hiragana
japanese-3 = にほんご
; this is in katakana
japanese-4 = ニホンゴ
; this is in romaji
japanese-5 = nihongo
; kanji as the key
日本語-6 = japanese
[multi-2]
; value a
test = a
; value b
test = b
; value c
test = c
; value d
test = d
[multiline-3]
; This is obviously a multi-line entry
multiline-1 = <<<END_OF_TEXT
This is a multi-line comment. It
will continue until we have the word MULTI
on a line by itself.
日本語も。
END_OF_TEXT
; This looks like multi-line, but because the newline following the last
; line is discarded, it will be converted into a single line entry.
another-2 = This is not a multiline entry.
; If you wanted a multiline entry with a single line, you need to add
; an extra line to it.
another-3 = <<<END_OF_TEXT
This is a multiline entry.
END_OF_TEXT
[integer]
dec = 42
hex = 0x2a

76
simpleini/test1-input.ini Normal file
View file

@ -0,0 +1,76 @@
; testsi-UTF8-std.ini : standard UTF-8 test file for SimpleIni automated testing
;
; The number after a section or key is the order that it is defined in this file
; to make it easier to see if it has been written out correctly. This file should
; be loaded with Unicode / MultiKey / MultiLine turned on.
; This comment should be joined on to the one below it about the key
; with no section.
; Key with no section
lonely-key = nosection
another = nosection either
; This key has no value
empty =
; This should be joined with the comment below about japanese.
; Another line which will be un-indented.
; This is a section of keys showing the word Japanese in different syllabies.
[ordered-1]
a-1 = blah
; this is in kanji
japanese-2 = 日本語
; this is in hiragana
japanese-3 = にほんご
; this is in katakana
japanese-4 = ニホンゴ
; this is in romaji
japanese-5 = nihongo
; kanji as the key
日本語-6 = japanese
[multi-2]
; value a
test = a
; value b
test = b
; value c
test = c
; value d
test = d
[multiline-3]
; This is obviously a multi-line entry
multiline-1 = <<<MULTI
This is a multi-line comment. It
will continue until we have the word MULTI
on a line by itself.
日本語も。
MULTI
; This looks like multi-line, but because the newline following the last
; line is discarded, it will be converted into a single line entry.
another-2 = <<<MULTI
This is not a multiline entry.
MULTI
; If you wanted a multiline entry with a single line, you need to add
; an extra line to it.
another-3 = <<<MULTI
This is a multiline entry.
MULTI
[integer]
dec = 42
hex = 0x2a

166
simpleini/test1.cpp Normal file
View file

@ -0,0 +1,166 @@
// File: test1.cpp
// Library: SimpleIni
// Author: Brodie Thiesfield <code@jellycan.com>
// Source: http://code.jellycan.com/simpleini/
//
// Automated testing for SimpleIni streams
#ifdef _WIN32
# pragma warning(disable: 4786)
#endif
#ifdef _WIN32
# include <windows.h>
# define DELETE_FILE DeleteFileA
#else
# include <unistd.h>
# define DELETE_FILE unlink
#endif
#include <fstream>
#define SI_SUPPORT_IOSTREAMS
#include "SimpleIni.h"
class Test
{
std::string m_strTest;
public:
Test(const char * a_pszName)
: m_strTest(a_pszName)
{
printf("%s: test starting\n", m_strTest.c_str());
}
bool Success()
{
printf("%s: test succeeded\n", m_strTest.c_str());
return false;
}
bool Failure(const char * pszReason)
{
printf("%s: test FAILED (%s)\n", m_strTest.c_str(), pszReason);
return false;
}
};
bool FileComparisonTest(const char * a_pszFile1, const char * a_pszFile2) {
// ensure that the two files are the same
try {
std::string strFile1, strFile2;
char szBuf[1024];
FILE * fp = NULL;
#if __STDC_WANT_SECURE_LIB__
fopen_s(&fp, a_pszFile1, "rb");
#else
fp = fopen(a_pszFile1, "rb");
#endif
if (!fp) throw false;
while (!feof(fp)) {
size_t n = fread(szBuf, 1, sizeof(szBuf), fp);
strFile1.append(szBuf, n);
}
fclose(fp);
fp = NULL;
#if __STDC_WANT_SECURE_LIB__
fopen_s(&fp, a_pszFile2, "rb");
#else
fp = fopen(a_pszFile2, "rb");
#endif
if (!fp) throw false;
while (!feof(fp)) {
size_t n = fread(szBuf, 1, sizeof(szBuf), fp);
strFile2.append(szBuf, n);
}
fclose(fp);
if (strFile1 != strFile2) throw false;
}
catch (...) {
return false;
}
return true;
}
bool FileLoadTest(const char * a_pszFile1, const char * a_pszFile2) {
// ensure that the two files load into simpleini the same
CSimpleIniA ini(true, true, true);
bool b;
try {
ini.Reset();
if (ini.LoadFile(a_pszFile1) < 0) throw "Load failed for file 1";
if (ini.SaveFile("test1.ini") < 0) throw "Save failed for file 1";
ini.Reset();
if (ini.LoadFile(a_pszFile2) < 0) throw "Load failed for file 2";
if (ini.SaveFile("test2.ini") < 0) throw "Save failed for file 2";
b = FileComparisonTest("test1.ini", "test2.ini");
DELETE_FILE("test1.ini");
DELETE_FILE("test2.ini");
if (!b) throw "File comparison failed in FileLoadTest";
}
catch (...) {
return false;
}
return true;
}
bool TestStreams()
{
const char * rgszTestFile[3] = {
"test1-input.ini",
"test1-output.ini",
"test1-expected.ini"
};
Test oTest("TestStreams");
CSimpleIniW ini;
ini.SetUnicode(true);
ini.SetMultiKey(true);
ini.SetMultiLine(true);
// load the file
try {
std::ifstream instream;
instream.open(rgszTestFile[0], std::ifstream::in | std::ifstream::binary);
if (ini.LoadData(instream) < 0) throw false;
instream.close();
}
catch (...) {
return oTest.Failure("Failed to load file");
}
// standard contents test
//if (!StandardContentsTest(ini, oTest)) {
// return false;
//}
// save the file
try {
std::ofstream outfile;
outfile.open(rgszTestFile[1], std::ofstream::out | std::ofstream::binary);
if (ini.Save(outfile, true) < 0) throw false;
outfile.close();
}
catch (...) {
return oTest.Failure("Failed to save file");
}
// file comparison test
if (!FileComparisonTest(rgszTestFile[1], rgszTestFile[2])) {
return oTest.Failure("Failed file comparison");
}
if (!FileLoadTest(rgszTestFile[1], rgszTestFile[2])) {
return oTest.Failure("Failed file load comparison");
}
return oTest.Success();
}

View file

@ -0,0 +1,52 @@
; test file for SimpleIni
nosection=ok
NOSECTION=still ok
whitespace = ok
[standard]
foo=foo1
standard-1=foo
日本語=ok1
[Standard]
Foo=foo2
standard-2=foo
日本語=ok2
[ Whitespace ]
a=
[ whitespace in section name ]
whitespace in key name = whitespace in value name
; comments
; more comments
invalid
=invalid
====invalid
[Japanese]
nihongo = 日本語
日本語 = 日本語
[日本語]
nihongo = 日本語
日本語 = 日本語
[]
more=no section name
[MultiLine]
single = This is a single line.
multi = <<<MULTI
This is a multi-line value. It continues until the MULTI tag is found
on a line by itself with no whitespace before or after it. This value
will be returned to the user with all newlines and whitespace.
MULTI

51
simpleini/testsi-SJIS.ini Normal file
View file

@ -0,0 +1,51 @@
; test file for SimpleIni
nosection=ok
NOSECTION=still ok
whitespace = ok
[standard]
foo=foo1
standard-1=foo
日本語=ok1
[Standard]
Foo=foo2
standard-2=foo
日本語=ok2
[ Whitespace ]
a=
[ whitespace in section name ]
whitespace in key name = whitespace in value name
; comments
; more comments
invalid
=invalid
====invalid
[Japanese]
nihongo = 日本語
日本語 = 日本語
[日本語]
nihongo = 日本語
日本語 = 日本語
[]
more=no section name
[MultiLine]
single = This is a single line.
multi = <<<MULTI
This is a multi-line value. It continues until the MULTI tag is found
on a line by itself with no whitespace before or after it. This value
will be returned to the user with all newlines and whitespace.
MULTI

50
simpleini/testsi-UTF8.ini Normal file
View file

@ -0,0 +1,50 @@
; test file for SimpleIni
whitespace = ok
nosection=ok
NOSECTION=still ok
[standard]
foo=foo1
standard-1=foo
日本語=ok1
[Standard]
Foo=foo2
standard-2=foo
日本語=ok2
[ Whitespace ]
a=
[ whitespace in section name ]
whitespace in key name = whitespace in value name
; comments
; more comments
invalid
=invalid
====invalid
[Japanese]
nihongo = 日本語
日本語 = 日本語
[日本語]
nihongo = 日本語
日本語 = 日本語
[]
more=no section name
[MultiLine]
single = This is a single line.
multi = <<<MULTI
This is a multi-line value. It continues until the MULTI tag is found
on a line by itself with no whitespace before or after it. This value
will be returned to the user with all newlines and whitespace.
MULTI

315
simpleini/testsi.cpp Normal file
View file

@ -0,0 +1,315 @@
// File: testsi.cpp
// Library: SimpleIni
// Author: Brodie Thiesfield <code@jellycan.com>
// Source: http://code.jellycan.com/simpleini/
//
// Demo of usage
#ifdef _WIN32
# pragma warning(disable: 4786)
#endif
#include <locale.h>
#include <stdio.h>
#include <cassert>
#define SI_SUPPORT_IOSTREAMS
#if defined(SI_SUPPORT_IOSTREAMS) && !defined(_UNICODE)
# include <fstream>
#endif
//#define SI_CONVERT_GENERIC
//#define SI_CONVERT_ICU
//#define SI_CONVERT_WIN32
#include "SimpleIni.h"
#ifdef SI_CONVERT_ICU
// if converting using ICU then we need the ICU library
# pragma comment(lib, "icuuc.lib")
#endif
#ifdef _WIN32
# include <tchar.h>
#else // !_WIN32
# define TCHAR char
# define _T(x) x
# define _tprintf printf
# define _tmain main
#endif // _WIN32
static void
Test(
CSimpleIni & ini
)
{
const TCHAR *pszSection = 0;
const TCHAR *pItem = 0;
const TCHAR *pszVal = 0;
// get the value of the key "foo" in section "standard"
bool bHasMulti;
pszVal = ini.GetValue(_T("standard"), _T("foo"), 0, &bHasMulti);
_tprintf(_T("\n-- Value of standard::foo is '%s' (hasMulti = %d)\n"),
pszVal ? pszVal : _T("(null)"), bHasMulti);
// set the value of the key "foo" in section "standard"
ini.SetValue(_T("standard"), _T("foo"), _T("wibble"));
pszVal = ini.GetValue(_T("standard"), _T("foo"), 0, &bHasMulti);
_tprintf(_T("\n-- Value of standard::foo is '%s' (hasMulti = %d)\n"),
pszVal ? pszVal : _T("(null)"), bHasMulti);
// get all values of the key "foo" in section "standard"
CSimpleIni::TNamesDepend values;
if (ini.GetAllValues(_T("standard"), _T("foo"), values)) {
_tprintf(_T("\n-- Values of standard::foo are:\n"));
CSimpleIni::TNamesDepend::const_iterator i = values.begin();
for (; i != values.end(); ++i) {
pszVal = i->pItem;
_tprintf(_T(" -> '%s'\n"), pszVal);
}
}
// get the size of the section [standard]
_tprintf(_T("\n-- Number of keys in section [standard] = %d\n"),
ini.GetSectionSize(_T("standard")));
// delete the key "foo" in section "standard", if it has value "bar"
ini.DeleteValue(_T("standard"), _T("foo"), _T("bar"));
pszVal = ini.GetValue(_T("standard"), _T("foo"), 0);
_tprintf(_T("\n-- Value of standard::foo is now '%s'\n"),
pszVal ? pszVal : _T("(null)"));
// delete the key "foo" in section "standard"
ini.Delete(_T("standard"), _T("foo"));
pszVal = ini.GetValue(_T("standard"), _T("foo"), 0);
_tprintf(_T("\n-- Value of standard::foo is now '%s'\n"),
pszVal ? pszVal : _T("(null)"));
// get the size of the section [standard]
_tprintf(_T("\n-- Number of keys in section [standard] = %d\n"),
ini.GetSectionSize(_T("standard")));
// get the list of all key names for the section "standard"
_tprintf(_T("\n-- Dumping keys of section: [standard]\n"));
CSimpleIni::TNamesDepend keys;
ini.GetAllKeys(_T("standard"), keys);
// dump all of the key names
CSimpleIni::TNamesDepend::const_iterator iKey = keys.begin();
for ( ; iKey != keys.end(); ++iKey ) {
pItem = iKey->pItem;
_tprintf(_T("Key: %s\n"), pItem);
}
// add a decimal value
ini.SetLongValue(_T("integer"), _T("dec"), 42, NULL, false);
ini.SetLongValue(_T("integer"), _T("hex"), 42, NULL, true);
// add some bool values
ini.SetBoolValue(_T("bool"), _T("t"), true);
ini.SetBoolValue(_T("bool"), _T("f"), false);
// get the values back
assert(42 == ini.GetLongValue(_T("integer"), _T("dec")));
assert(42 == ini.GetLongValue(_T("integer"), _T("hex")));
assert(true == ini.GetBoolValue(_T("bool"), _T("t")));
assert(false == ini.GetBoolValue(_T("bool"), _T("f")));
// delete the section "standard"
ini.Delete(_T("standard"), NULL);
_tprintf(_T("\n-- Number of keys in section [standard] = %d\n"),
ini.GetSectionSize(_T("standard")));
// iterate through every section in the file
_tprintf(_T("\n-- Dumping all sections\n"));
CSimpleIni::TNamesDepend sections;
ini.GetAllSections(sections);
CSimpleIni::TNamesDepend::const_iterator iSection = sections.begin();
for ( ; iSection != sections.end(); ++iSection ) {
pszSection = iSection->pItem;
// print the section name
printf("\n");
if (*pszSection) {
_tprintf(_T("[%s]\n"), pszSection);
}
// if there are keys and values...
const CSimpleIni::TKeyVal * pSectionData = ini.GetSection(pszSection);
if (pSectionData) {
// iterate over all keys and dump the key name and value
CSimpleIni::TKeyVal::const_iterator iKeyVal = pSectionData->begin();
for ( ;iKeyVal != pSectionData->end(); ++iKeyVal) {
pItem = iKeyVal->first.pItem;
pszVal = iKeyVal->second;
_tprintf(_T("%s=%s\n"), pItem, pszVal);
}
}
}
}
#if defined(SI_SUPPORT_IOSTREAMS) && !defined(_UNICODE)
static bool
TestStreams(
const TCHAR * a_pszFile,
bool a_bIsUtf8,
bool a_bUseMultiKey,
bool a_bUseMultiLine
)
{
// load the file
CSimpleIni ini(a_bIsUtf8, a_bUseMultiKey, a_bUseMultiLine);
_tprintf(_T("Loading file: %s\n"), a_pszFile);
std::ifstream instream;
instream.open(a_pszFile, std::ifstream::in | std::ifstream::binary);
SI_Error rc = ini.LoadData(instream);
instream.close();
if (rc < 0) {
printf("Failed to open file.\n");
return false;
}
Test(ini);
// save the file (simple)
_tprintf(_T("\n-- Saving file to: testsi-out-streams.ini\n"));
std::ofstream outstream;
outstream.open("testsi-out-streams.ini", std::ofstream::out | std::ofstream::binary);
ini.Save(outstream);
outstream.close();
return true;
}
#endif // SI_SUPPORT_IOSTREAMS
static bool
TestFile(
const TCHAR * a_pszFile,
bool a_bIsUtf8,
bool a_bUseMultiKey,
bool a_bUseMultiLine
)
{
// load the file
CSimpleIni ini(a_bIsUtf8, a_bUseMultiKey, a_bUseMultiLine);
_tprintf(_T("Loading file: %s\n"), a_pszFile);
SI_Error rc = ini.LoadFile(a_pszFile);
if (rc < 0) {
printf("Failed to open file.\n");
return false;
}
// run the tests
Test(ini);
// save the file (simple)
_tprintf(_T("\n-- Saving file to: testsi-out.ini\n"));
ini.SaveFile("testsi-out.ini");
// save the file (with comments)
// Note: to save the file and add a comment to the beginning, use
// code such as the following.
_tprintf(_T("\n-- Saving file to: testsi-out-comment.ini\n"));
FILE * fp = NULL;
#if __STDC_WANT_SECURE_LIB__
fopen_s(&fp, "testsi-out-comment.ini", "wb");
#else
fp = fopen("testsi-out-comment.ini", "wb");
#endif
if (fp) {
CSimpleIni::FileWriter writer(fp);
if (a_bIsUtf8) {
writer.Write(SI_UTF8_SIGNATURE);
}
// add a string to the file in the correct text format
CSimpleIni::Converter convert = ini.GetConverter();
convert.ConvertToStore(_T("; output from testsi.cpp test program")
SI_NEWLINE SI_NEWLINE);
writer.Write(convert.Data());
ini.Save(writer, false);
fclose(fp);
}
return true;
}
static bool
ParseCommandLine(
int argc,
TCHAR * argv[],
const TCHAR * & a_pszFile,
bool & a_bIsUtf8,
bool & a_bUseMultiKey,
bool & a_bUseMultiLine
)
{
a_pszFile = 0;
a_bIsUtf8 = false;
a_bUseMultiKey = false;
a_bUseMultiLine = false;
for (--argc; argc > 0; --argc) {
if (argv[argc][0] == '-') {
switch (argv[argc][1]) {
case TCHAR('u'):
a_bIsUtf8 = true;
break;
case TCHAR('m'):
a_bUseMultiKey = true;
break;
case TCHAR('l'):
a_bUseMultiLine = true;
break;
}
}
else {
a_pszFile = argv[argc];
}
}
if (!a_pszFile) {
_tprintf(
_T("Usage: testsi [-u] [-m] [-l] iniFile\n")
_T(" -u Load file as UTF-8 (Default is to use system locale)\n")
_T(" -m Enable multiple keys\n")
_T(" -l Enable multiple line values\n")
);
return false;
}
return true;
}
extern bool TestStreams();
int
_tmain(
int argc,
TCHAR * argv[]
)
{
setlocale(LC_ALL, "");
// start of automated testing...
TestStreams();
// parse the command line
const TCHAR * pszFile;
bool bIsUtf8, bUseMultiKey, bUseMultiLine;
if (!ParseCommandLine(argc, argv, pszFile, bIsUtf8, bUseMultiKey, bUseMultiLine)) {
return 1;
}
// run the test
if (!TestFile(pszFile, bIsUtf8, bUseMultiKey, bUseMultiLine)) {
return 1;
}
#if defined(SI_SUPPORT_IOSTREAMS) && !defined(_UNICODE)
if (!TestStreams(pszFile, bIsUtf8, bUseMultiKey, bUseMultiLine)) {
return 1;
}
#endif
return 0;
}

1263
src/main.cpp Normal file

File diff suppressed because it is too large Load diff

9
src/palette.inc Normal file
View file

@ -0,0 +1,9 @@
u32 nesRgb[] =
{ 0x7C7C7C, 0x0000FC, 0x0000BC, 0x4428BC, 0x940084, 0xA80020, 0xA81000, 0x881400,
0x503000, 0x007800, 0x006800, 0x005800, 0x004058, 0x000000, 0x000000, 0x000000,
0xBCBCBC, 0x0078F8, 0x0058F8, 0x6844FC, 0xD800CC, 0xE40058, 0xF83800, 0xE45C10,
0xAC7C00, 0x00B800, 0x00A800, 0x00A844, 0x008888, 0x000000, 0x000000, 0x000000,
0xF8F8F8, 0x3CBCFC, 0x6888FC, 0x9878F8, 0xF878F8, 0xF85898, 0xF87858, 0xFCA044,
0xF8B800, 0xB8F818, 0x58D854, 0x58F898, 0x00E8D8, 0x787878, 0x000000, 0x000000,
0xFCFCFC, 0xA4E4FC, 0xB8B8F8, 0xD8B8F8, 0xF8B8F8, 0xF8A4C0, 0xF0D0B0, 0xFCE0A8,
0xF8D878, 0xD8F878, 0xB8F8B8, 0xB8F8D8, 0x00FCFC, 0xF8D8F8, 0x000000, 0x000000 };