Proper APU emulation (no longer using a library for it, except BlipBuffer) - Not finished need to implement save states & reset behavior

This commit is contained in:
Souryo 2015-07-14 21:50:14 -04:00
parent bb52c3f69c
commit e5fe396ffb
38 changed files with 1937 additions and 1636 deletions

View file

@ -0,0 +1,158 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="12.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|Win32">
<Configuration>Debug</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Debug|x64">
<Configuration>Debug</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|Win32">
<Configuration>Release</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|x64">
<Configuration>Release</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
</ItemGroup>
<PropertyGroup Label="Globals">
<ProjectGuid>{CF35D78C-F710-41D2-968F-C46ACCFF6F07}</ProjectGuid>
<Keyword>Win32Proj</Keyword>
<RootNamespace>BlipBuffer</RootNamespace>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v120</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v120</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v120</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v120</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<LinkIncremental>true</LinkIncremental>
<OutDir>$(SolutionDir)\bin\$(PlatformTarget)\$(Configuration)\</OutDir>
<IntDir>obj\$(Platform)\$(Configuration)\</IntDir>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<LinkIncremental>true</LinkIncremental>
<OutDir>$(SolutionDir)\bin\$(PlatformTarget)\$(Configuration)\</OutDir>
<IntDir>obj\$(Platform)\$(Configuration)\</IntDir>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<LinkIncremental>false</LinkIncremental>
<OutDir>$(SolutionDir)\bin\$(PlatformTarget)\$(Configuration)\</OutDir>
<IntDir>obj\$(Platform)\$(Configuration)\</IntDir>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<LinkIncremental>false</LinkIncremental>
<OutDir>$(SolutionDir)\bin\$(PlatformTarget)\$(Configuration)\</OutDir>
<IntDir>obj\$(Platform)\$(Configuration)\</IntDir>
</PropertyGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<ClCompile>
<PrecompiledHeader>
</PrecompiledHeader>
<WarningLevel>Level3</WarningLevel>
<Optimization>Disabled</Optimization>
<PreprocessorDefinitions>WIN32;_DEBUG;_WINDOWS;_USRDLL;BLIPBUFFER_EXPORTS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<ClCompile>
<PrecompiledHeader>
</PrecompiledHeader>
<WarningLevel>Level3</WarningLevel>
<Optimization>Disabled</Optimization>
<PreprocessorDefinitions>WIN32;_DEBUG;_WINDOWS;_USRDLL;BLIPBUFFER_EXPORTS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<PrecompiledHeader>
</PrecompiledHeader>
<Optimization>MaxSpeed</Optimization>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<PreprocessorDefinitions>WIN32;NDEBUG;_WINDOWS;_USRDLL;BLIPBUFFER_EXPORTS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<PrecompiledHeader>
</PrecompiledHeader>
<Optimization>MaxSpeed</Optimization>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<PreprocessorDefinitions>WIN32;NDEBUG;_WINDOWS;_USRDLL;BLIPBUFFER_EXPORTS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
</Link>
</ItemDefinitionGroup>
<ItemGroup>
<ClCompile Include="Blip_Buffer.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="Blip_Buffer.h" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
</Project>

View file

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<Filter Include="Source Files">
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
<Extensions>cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
</Filter>
<Filter Include="Header Files">
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
<Extensions>h;hh;hpp;hxx;hm;inl;inc;xsd</Extensions>
</Filter>
</ItemGroup>
<ItemGroup>
<ClCompile Include="Blip_Buffer.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="Blip_Buffer.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
</Project>

406
BlipBuffer/Blip_Buffer.cpp Normal file
View file

@ -0,0 +1,406 @@
// Blip_Buffer 0.4.0. http://www.slack.net/~ant/
#include "Blip_Buffer.h"
#include <assert.h>
#include <limits.h>
#include <string.h>
#include <stdlib.h>
#include <math.h>
/* Copyright (C) 2003-2006 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 */
int const buffer_extra = blip_widest_impulse_ + 2;
Blip_Buffer::Blip_Buffer()
{
factor_ = LONG_MAX;
offset_ = 0;
buffer_ = 0;
buffer_size_ = 0;
sample_rate_ = 0;
reader_accum = 0;
bass_shift = 0;
clock_rate_ = 0;
bass_freq_ = 16;
length_ = 0;
// assumptions code makes about implementation-defined features
#ifndef NDEBUG
// right shift of negative value preserves sign
int i = INT_MIN;
assert( (i >> 1) == INT_MIN / 2 );
// casting to smaller signed type truncates bits and extends sign
long l = (SHRT_MAX + 1) * 5;
assert( (short) l == SHRT_MIN );
#endif
}
Blip_Buffer::~Blip_Buffer()
{
free( buffer_ );
}
void Blip_Buffer::clear( int entire_buffer )
{
offset_ = 0;
reader_accum = 0;
if ( buffer_ )
{
long count = (entire_buffer ? buffer_size_ : samples_avail());
memset( buffer_, 0, (count + buffer_extra) * sizeof (buf_t_) );
}
}
Blip_Buffer::blargg_err_t Blip_Buffer::set_sample_rate( long new_rate, int msec )
{
// start with maximum length that resampled time can represent
long new_size = (ULONG_MAX >> BLIP_BUFFER_ACCURACY) - buffer_extra - 64;
if ( msec != blip_max_length )
{
long s = (new_rate * (msec + 1) + 999) / 1000;
if ( s < new_size )
new_size = s;
else
assert( 0 ); // fails if requested buffer length exceeds limit
}
if ( buffer_size_ != new_size )
{
void* p = realloc( buffer_, (new_size + buffer_extra) * sizeof *buffer_ );
if ( !p )
return "Out of memory";
buffer_ = (buf_t_*) p;
}
buffer_size_ = new_size;
// update things based on the sample rate
sample_rate_ = new_rate;
length_ = new_size * 1000 / new_rate - 1;
if ( msec )
assert( length_ == msec ); // ensure length is same as that passed in
if ( clock_rate_ )
clock_rate( clock_rate_ );
bass_freq( bass_freq_ );
clear();
return 0; // success
}
blip_resampled_time_t Blip_Buffer::clock_rate_factor( long clock_rate ) const
{
double ratio = (double) sample_rate_ / clock_rate;
long factor = (long) floor( ratio * (1L << BLIP_BUFFER_ACCURACY) + 0.5 );
assert( factor > 0 || !sample_rate_ ); // fails if clock/output ratio is too large
return (blip_resampled_time_t) factor;
}
void Blip_Buffer::bass_freq( int freq )
{
bass_freq_ = freq;
int shift = 31;
if ( freq > 0 )
{
shift = 13;
long f = (freq << 16) / sample_rate_;
while ( (f >>= 1) && --shift ) { }
}
bass_shift = shift;
}
void Blip_Buffer::end_frame( blip_time_t t )
{
offset_ += t * factor_;
assert( samples_avail() <= (long) buffer_size_ ); // time outside buffer length
}
void Blip_Buffer::remove_silence( long count )
{
assert( count <= samples_avail() ); // tried to remove more samples than available
offset_ -= (blip_resampled_time_t) count << BLIP_BUFFER_ACCURACY;
}
long Blip_Buffer::count_samples( blip_time_t t ) const
{
unsigned long last_sample = resampled_time( t ) >> BLIP_BUFFER_ACCURACY;
unsigned long first_sample = offset_ >> BLIP_BUFFER_ACCURACY;
return (long) (last_sample - first_sample);
}
blip_time_t Blip_Buffer::count_clocks( long count ) const
{
if ( count > buffer_size_ )
count = buffer_size_;
blip_resampled_time_t time = (blip_resampled_time_t) count << BLIP_BUFFER_ACCURACY;
return (blip_time_t) ((time - offset_ + factor_ - 1) / factor_);
}
void Blip_Buffer::remove_samples( long count )
{
if ( count )
{
remove_silence( count );
// copy remaining samples to beginning and clear old samples
long remain = samples_avail() + buffer_extra;
memmove( buffer_, buffer_ + count, remain * sizeof *buffer_ );
memset( buffer_ + remain, 0, count * sizeof *buffer_ );
}
}
// Blip_Synth_
Blip_Synth_::Blip_Synth_( short* p, int w ) :
impulses( p ),
width( w )
{
volume_unit_ = 0.0;
kernel_unit = 0;
buf = 0;
last_amp = 0;
delta_factor = 0;
}
static double const pi = 3.1415926535897932384626433832795029;
static void gen_sinc( float* out, int count, double oversample, double treble, double cutoff )
{
if ( cutoff >= 0.999 )
cutoff = 0.999;
if ( treble < -300.0 )
treble = -300.0;
if ( treble > 5.0 )
treble = 5.0;
double const maxh = 4096.0;
double const rolloff = pow( 10.0, 1.0 / (maxh * 20.0) * treble / (1.0 - cutoff) );
double const pow_a_n = pow( rolloff, maxh - maxh * cutoff );
double const to_angle = pi / 2 / maxh / oversample;
for ( int i = 0; i < count; i++ )
{
double angle = ((i - count) * 2 + 1) * to_angle;
double c = rolloff * cos( (maxh - 1.0) * angle ) - cos( maxh * angle );
double cos_nc_angle = cos( maxh * cutoff * angle );
double cos_nc1_angle = cos( (maxh * cutoff - 1.0) * angle );
double cos_angle = cos( angle );
c = c * pow_a_n - rolloff * cos_nc1_angle + cos_nc_angle;
double d = 1.0 + rolloff * (rolloff - cos_angle - cos_angle);
double b = 2.0 - cos_angle - cos_angle;
double a = 1.0 - cos_angle - cos_nc_angle + cos_nc1_angle;
out [i] = (float) ((a * d + c * b) / (b * d)); // a / b + c / d
}
}
void blip_eq_t::generate( float* out, int count ) const
{
// lower cutoff freq for narrow kernels with their wider transition band
// (8 points->1.49, 16 points->1.15)
double oversample = blip_res * 2.25 / count + 0.85;
double half_rate = sample_rate * 0.5;
if ( cutoff_freq )
oversample = half_rate / cutoff_freq;
double cutoff = rolloff_freq * oversample / half_rate;
gen_sinc( out, count, blip_res * oversample, treble, cutoff );
// apply (half of) hamming window
double to_fraction = pi / (count - 1);
for ( int i = count; i--; )
out [i] *= (float)(0.54 - 0.46 * cos( i * to_fraction ));
}
void Blip_Synth_::adjust_impulse()
{
// sum pairs for each phase and add error correction to end of first half
int const size = impulses_size();
for ( int p = blip_res; p-- >= blip_res / 2; )
{
int p2 = blip_res - 2 - p;
long error = kernel_unit;
for ( int i = 1; i < size; i += blip_res )
{
error -= impulses [i + p ];
error -= impulses [i + p2];
}
if ( p == p2 )
error /= 2; // phase = 0.5 impulse uses same half for both sides
impulses [size - blip_res + p] += (short)error;
//printf( "error: %ld\n", error );
}
//for ( int i = blip_res; i--; printf( "\n" ) )
// for ( int j = 0; j < width / 2; j++ )
// printf( "%5ld,", impulses [j * blip_res + i + 1] );
}
void Blip_Synth_::treble_eq( blip_eq_t const& eq )
{
float fimpulse [blip_res / 2 * (blip_widest_impulse_ - 1) + blip_res * 2];
int const half_size = blip_res / 2 * (width - 1);
eq.generate( &fimpulse [blip_res], half_size );
int i;
// need mirror slightly past center for calculation
for ( i = blip_res; i--; )
fimpulse [blip_res + half_size + i] = fimpulse [blip_res + half_size - 1 - i];
// starts at 0
for ( i = 0; i < blip_res; i++ )
fimpulse [i] = 0.0f;
// find rescale factor
double total = 0.0;
for ( i = 0; i < half_size; i++ )
total += fimpulse [blip_res + i];
//double const base_unit = 44800.0 - 128 * 18; // allows treble up to +0 dB
//double const base_unit = 37888.0; // allows treble to +5 dB
double const base_unit = 32768.0; // necessary for blip_unscaled to work
double rescale = base_unit / 2 / total;
kernel_unit = (long) base_unit;
// integrate, first difference, rescale, convert to int
double sum = 0.0;
double next = 0.0;
int const impulses_size = this->impulses_size();
for ( i = 0; i < impulses_size; i++ )
{
impulses [i] = (short) floor( (next - sum) * rescale + 0.5 );
sum += fimpulse [i];
next += fimpulse [i + blip_res];
}
adjust_impulse();
// volume might require rescaling
double vol = volume_unit_;
if ( vol )
{
volume_unit_ = 0.0;
volume_unit( vol );
}
}
void Blip_Synth_::volume_unit( double new_unit )
{
if ( new_unit != volume_unit_ )
{
// use default eq if it hasn't been set yet
if ( !kernel_unit )
treble_eq( -8.0 );
volume_unit_ = new_unit;
double factor = new_unit * (1L << blip_sample_bits) / kernel_unit;
if ( factor > 0.0 )
{
int shift = 0;
// if unit is really small, might need to attenuate kernel
while ( factor < 2.0 )
{
shift++;
factor *= 2.0;
}
if ( shift )
{
kernel_unit >>= shift;
assert( kernel_unit > 0 ); // fails if volume unit is too low
// keep values positive to avoid round-towards-zero of sign-preserving
// right shift for negative values
long offset = 0x8000 + (1 << (shift - 1));
long offset2 = 0x8000 >> shift;
for ( int i = impulses_size(); i--; )
impulses [i] = (short) (((impulses [i] + offset) >> shift) - offset2);
adjust_impulse();
}
}
delta_factor = (int) floor( factor + 0.5 );
//printf( "delta_factor: %d, kernel_unit: %d\n", delta_factor, kernel_unit );
}
}
long Blip_Buffer::read_samples( blip_sample_t* out, long max_samples, int stereo )
{
long count = samples_avail();
if ( count > max_samples )
count = max_samples;
if ( count )
{
int const sample_shift = blip_sample_bits - 16;
int const bass_shift = this->bass_shift;
long accum = reader_accum;
buf_t_* in = buffer_;
if ( !stereo )
{
for ( long n = count; n--; )
{
long s = accum >> sample_shift;
accum -= accum >> bass_shift;
accum += *in++;
*out++ = (blip_sample_t) s;
// clamp sample
if ( (blip_sample_t) s != s )
out [-1] = (blip_sample_t) (0x7FFF - (s >> 24));
}
}
else
{
for ( long n = count; n--; )
{
long s = accum >> sample_shift;
accum -= accum >> bass_shift;
accum += *in++;
*out = (blip_sample_t) s;
out += 2;
// clamp sample
if ( (blip_sample_t) s != s )
out [-2] = (blip_sample_t) (0x7FFF - (s >> 24));
}
}
reader_accum = accum;
remove_samples( count );
}
return count;
}
void Blip_Buffer::mix_samples( blip_sample_t const* in, long count )
{
buf_t_* out = buffer_ + (offset_ >> BLIP_BUFFER_ACCURACY) + blip_widest_impulse_ / 2;
int const sample_shift = blip_sample_bits - 16;
int prev = 0;
while ( count-- )
{
long s = (long) *in++ << sample_shift;
*out += s - prev;
prev = s;
++out;
}
*out -= prev;
}

354
BlipBuffer/Blip_Buffer.h Normal file
View file

@ -0,0 +1,354 @@
// Band-limited sound synthesis and buffering
// Blip_Buffer 0.4.0
#ifndef BLIP_BUFFER_H
#define BLIP_BUFFER_H
// Time unit at source clock rate
typedef long blip_time_t;
// Output samples are 16-bit signed, with a range of -32767 to 32767
typedef short blip_sample_t;
enum { blip_sample_max = 32767 };
class __declspec(dllexport) Blip_Buffer {
public:
typedef const char* blargg_err_t;
// Set output sample rate and buffer length in milliseconds (1/1000 sec, defaults
// to 1/4 second), then clear buffer. Returns NULL on success, otherwise if there
// isn't enough memory, returns error without affecting current buffer setup.
blargg_err_t set_sample_rate( long samples_per_sec, int msec_length = 1000 / 4 );
// Set number of source time units per second
void clock_rate( long );
// End current time frame of specified duration and make its samples available
// (along with any still-unread samples) for reading with read_samples(). Begins
// a new time frame at the end of the current frame.
void end_frame( blip_time_t time );
// Read at most 'max_samples' out of buffer into 'dest', removing them from from
// the buffer. Returns number of samples actually read and removed. If stereo is
// true, increments '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, int stereo = 0 );
// Additional optional features
// Current output sample rate
long sample_rate() const;
// Length of buffer, in milliseconds
int length() const;
// Number of source time units per second
long clock_rate() const;
// Set frequency high-pass filter frequency, where higher values reduce bass more
void bass_freq( int frequency );
// Number of samples delay from synthesis to samples read out
int output_latency() const;
// Remove all available samples and clear buffer to silence. If 'entire_buffer' is
// false, just clears out any samples waiting rather than the entire buffer.
void clear( int entire_buffer = 1 );
// Number of samples available for reading with read_samples()
long samples_avail() const;
// Remove 'count' samples from those waiting to be read
void remove_samples( long count );
// Experimental features
// 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( blip_sample_t const* buf, long count );
// Count number of clocks needed until 'count' samples will be available.
// If buffer can't even hold 'count' samples, returns number of clocks until
// buffer becomes full.
blip_time_t count_clocks( long count ) const;
// not documented yet
typedef unsigned long blip_resampled_time_t;
void remove_silence( long count );
blip_resampled_time_t resampled_duration( int t ) const { return t * factor_; }
blip_resampled_time_t resampled_time( blip_time_t t ) const { return t * factor_ + offset_; }
blip_resampled_time_t clock_rate_factor( long clock_rate ) const;
public:
Blip_Buffer();
~Blip_Buffer();
// Deprecated
typedef blip_resampled_time_t resampled_time_t;
blargg_err_t sample_rate( long r ) { return set_sample_rate( r ); }
blargg_err_t sample_rate( long r, int msec ) { return set_sample_rate( r, msec ); }
private:
// noncopyable
Blip_Buffer( const Blip_Buffer& );
Blip_Buffer& operator = ( const Blip_Buffer& );
public:
typedef long buf_t_;
unsigned long factor_;
blip_resampled_time_t offset_;
buf_t_* buffer_;
long buffer_size_;
private:
long reader_accum;
int bass_shift;
long sample_rate_;
long clock_rate_;
int bass_freq_;
int length_;
friend class Blip_Reader;
};
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
// Number of bits in resample ratio fraction. Higher values give a more accurate ratio
// but reduce maximum buffer size.
#ifndef BLIP_BUFFER_ACCURACY
#define BLIP_BUFFER_ACCURACY 16
#endif
// Number bits in phase offset. Fewer than 6 bits (64 phase offsets) results in
// noticeable broadband noise when synthesizing high frequency square waves.
// Affects size of Blip_Synth objects since they store the waveform directly.
#ifndef BLIP_PHASE_BITS
#define BLIP_PHASE_BITS 6
#endif
// Internal
typedef unsigned long blip_resampled_time_t;
int const blip_widest_impulse_ = 16;
int const blip_res = 1 << BLIP_PHASE_BITS;
class blip_eq_t;
class __declspec(dllexport) Blip_Synth_ {
double volume_unit_;
short* const impulses;
int const width;
long kernel_unit;
int impulses_size() const { return blip_res / 2 * width + 1; }
void adjust_impulse();
public:
Blip_Buffer* buf;
int last_amp;
int delta_factor;
Blip_Synth_( short* impulses, int width );
void treble_eq( blip_eq_t const& );
void volume_unit( double );
};
// Quality level. Start with blip_good_quality.
const int blip_med_quality = 8;
const int blip_good_quality = 12;
const int blip_high_quality = 16;
// Range specifies the greatest expected change in amplitude. Calculate it
// by finding the difference between the maximum and minimum expected
// amplitudes (max - min).
template<int quality,int range>
class __declspec(dllexport) Blip_Synth {
public:
// Set overall volume of waveform
void volume( double v ) { impl.volume_unit( v * (1.0 / (range < 0 ? -range : range)) ); }
// Configure low-pass filter (see notes.txt)
void treble_eq( blip_eq_t const& eq ) { impl.treble_eq( eq ); }
// Get/set Blip_Buffer used for output
Blip_Buffer* output() const { return impl.buf; }
void output( Blip_Buffer* b ) { impl.buf = b; impl.last_amp = 0; }
// Update amplitude of waveform at given time. Using this requires a separate
// Blip_Synth for each waveform.
void update( blip_time_t time, int amplitude );
// Low-level interface
// Add an amplitude transition of specified delta, optionally into specified buffer
// rather than the one set with output(). Delta can be positive or negative.
// The actual change in amplitude is delta * (volume / range)
void offset( blip_time_t, int delta, Blip_Buffer* ) const;
void offset( blip_time_t t, int delta ) const { offset( t, delta, impl.buf ); }
// Works directly in terms of fractional output samples. Contact author for more.
void offset_resampled( blip_resampled_time_t, int delta, Blip_Buffer* ) const;
// Same as offset(), except code is inlined for higher performance
void offset_inline( blip_time_t t, int delta, Blip_Buffer* buf ) const {
offset_resampled( t * buf->factor_ + buf->offset_, delta, buf );
}
void offset_inline( blip_time_t t, int delta ) const {
offset_resampled( t * impl.buf->factor_ + impl.buf->offset_, delta, impl.buf );
}
public:
Blip_Synth() : impl( impulses, quality ) { }
private:
typedef short imp_t;
imp_t impulses [blip_res * (quality / 2) + 1];
Blip_Synth_ impl;
};
// Low-pass equalization parameters
class blip_eq_t {
public:
// Logarithmic rolloff to treble dB at half sampling rate. Negative values reduce
// treble, small positive values (0 to 5.0) increase treble.
blip_eq_t( double treble_db = 0 );
// See notes.txt
blip_eq_t( double treble, long rolloff_freq, long sample_rate, long cutoff_freq = 0 );
private:
double treble;
long rolloff_freq;
long sample_rate;
long cutoff_freq;
void generate( float* out, int count ) const;
friend class Blip_Synth_;
};
int const blip_sample_bits = 30;
// Optimized inline sample reader for custom sample formats and mixing of Blip_Buffer samples
class Blip_Reader {
public:
// Begin reading samples from buffer. Returns value to pass to next() (can
// be ignored if default bass_freq is acceptable).
int begin( Blip_Buffer& );
// Current sample
long read() const { return accum >> (blip_sample_bits - 16); }
// Current raw sample in full internal resolution
long read_raw() const { return accum; }
// Advance to next sample
void next( int bass_shift = 9 ) { accum += *buf++ - (accum >> bass_shift); }
// End reading samples from buffer. The number of samples read must now be removed
// using Blip_Buffer::remove_samples().
void end( Blip_Buffer& b ) { b.reader_accum = accum; }
private:
const Blip_Buffer::buf_t_* buf;
long accum;
};
// End of public interface
#include <assert.h>
// Compatibility with older version
const long blip_unscaled = 65535;
const int blip_low_quality = blip_med_quality;
const int blip_best_quality = blip_high_quality;
#define BLIP_FWD( i ) { \
long t0 = i0 * delta + buf [fwd + i]; \
long t1 = imp [blip_res * (i + 1)] * delta + buf [fwd + 1 + i]; \
i0 = imp [blip_res * (i + 2)]; \
buf [fwd + i] = t0; \
buf [fwd + 1 + i] = t1; }
#define BLIP_REV( r ) { \
long t0 = i0 * delta + buf [rev - r]; \
long t1 = imp [blip_res * r] * delta + buf [rev + 1 - r]; \
i0 = imp [blip_res * (r - 1)]; \
buf [rev - r] = t0; \
buf [rev + 1 - r] = t1; }
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
{
// Fails if time is beyond end of Blip_Buffer, due to a bug in caller code or the
// need for a longer buffer as set by set_sample_rate().
assert( (long) (time >> BLIP_BUFFER_ACCURACY) < blip_buf->buffer_size_ );
delta *= impl.delta_factor;
int phase = (int) (time >> (BLIP_BUFFER_ACCURACY - BLIP_PHASE_BITS) & (blip_res - 1));
imp_t const* imp = impulses + blip_res - phase;
long* buf = blip_buf->buffer_ + (time >> BLIP_BUFFER_ACCURACY);
long i0 = *imp;
int const fwd = (blip_widest_impulse_ - quality) / 2;
int const rev = fwd + quality - 2;
BLIP_FWD( 0 )
if ( quality > 8 ) BLIP_FWD( 2 )
if ( quality > 12 ) BLIP_FWD( 4 )
{
int const mid = quality / 2 - 1;
long t0 = i0 * delta + buf [fwd + mid - 1];
long t1 = imp [blip_res * mid] * delta + buf [fwd + mid];
imp = impulses + phase;
i0 = imp [blip_res * mid];
buf [fwd + mid - 1] = t0;
buf [fwd + mid] = t1;
}
if ( quality > 12 ) BLIP_REV( 6 )
if ( quality > 8 ) BLIP_REV( 4 )
BLIP_REV( 2 )
long t0 = i0 * delta + buf [rev];
long t1 = *imp * delta + buf [rev + 1];
buf [rev] = t0;
buf [rev + 1] = t1;
}
#undef BLIP_FWD
#undef BLIP_REV
template<int quality,int range>
void Blip_Synth<quality,range>::offset( blip_time_t t, int delta, Blip_Buffer* buf ) const
{
offset_resampled( t * buf->factor_ + buf->offset_, delta, buf );
}
template<int quality,int range>
void Blip_Synth<quality,range>::update( blip_time_t t, int amp )
{
int delta = amp - impl.last_amp;
impl.last_amp = amp;
offset_resampled( t * impl.buf->factor_ + impl.buf->offset_, delta, impl.buf );
}
inline blip_eq_t::blip_eq_t( double t ) :
treble( t ), rolloff_freq( 0 ), sample_rate( 44100 ), cutoff_freq( 0 ) { }
inline blip_eq_t::blip_eq_t( double t, long rf, long sr, long cf ) :
treble( t ), rolloff_freq( rf ), sample_rate( sr ), cutoff_freq( cf ) { }
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 sample_rate_; }
inline int Blip_Buffer::output_latency() const { return blip_widest_impulse_ / 2; }
inline long Blip_Buffer::clock_rate() const { return clock_rate_; }
inline void Blip_Buffer::clock_rate( long cps ) { factor_ = clock_rate_factor( clock_rate_ = cps ); }
inline int Blip_Reader::begin( Blip_Buffer& blip_buf )
{
buf = blip_buf.buffer_;
accum = blip_buf.reader_accum;
return blip_buf.bass_shift;
}
int const blip_max_length = 0;
int const blip_default_length = 250;
#endif

View file

@ -1,7 +1,12 @@
#include "stdafx.h"
#include "../BlipBuffer/Blip_Buffer.h"
#include "APU.h"
#include "CPU.h"
#include "Nes_Apu\apu_snapshot.h"
#include "SquareChannel.h"
#include "TriangleChannel.h"
#include "NoiseChannel.h"
#include "DeltaModulationChannel.h"
#include "ApuFrameCounter.h"
APU* APU::Instance = nullptr;
IAudioDevice* APU::AudioDevice = nullptr;
@ -11,15 +16,31 @@ APU::APU(MemoryManager* memoryManager)
APU::Instance = this;
_memoryManager = memoryManager;
_buf.sample_rate(APU::SampleRate);
_buf.clock_rate(CPU::ClockRate);
_apu.output(&_buf);
_apu.dmc_reader(&APU::DMCRead);
//_apu.irq_notifier(&APU::IRQChanged);
_blipBuffer = new Blip_Buffer();
_blipBuffer->sample_rate(APU::SampleRate);
_blipBuffer->clock_rate(CPU::ClockRate);
_outputBuffer = new int16_t[APU::SamplesPerFrame];
_squareChannel.push_back(unique_ptr<SquareChannel>(new SquareChannel(true)));
_squareChannel.push_back(unique_ptr<SquareChannel>(new SquareChannel(false)));
_triangleChannel.reset(new TriangleChannel());
_noiseChannel.reset(new NoiseChannel());
_deltaModulationChannel.reset(new DeltaModulationChannel(_memoryManager));
_frameCounter.reset(new ApuFrameCounter(&APU::FrameCounterTick));
_squareChannel[0]->SetBuffer(_blipBuffer);
_squareChannel[1]->SetBuffer(_blipBuffer);
_triangleChannel->SetBuffer(_blipBuffer);
_noiseChannel->SetBuffer(_blipBuffer);
_deltaModulationChannel->SetBuffer(_blipBuffer);
_memoryManager->RegisterIODevice(_squareChannel[0].get());
_memoryManager->RegisterIODevice(_squareChannel[1].get());
_memoryManager->RegisterIODevice(_frameCounter.get());
_memoryManager->RegisterIODevice(_triangleChannel.get());
_memoryManager->RegisterIODevice(_noiseChannel.get());
_memoryManager->RegisterIODevice(_deltaModulationChannel.get());
}
APU::~APU()
@ -29,52 +50,137 @@ APU::~APU()
void APU::Reset()
{
_apu.reset();
//_apu.reset();
}
int APU::DMCRead(void*, cpu_addr_t addr)
void APU::FrameCounterTick(FrameType type)
{
return APU::Instance->_memoryManager->Read(addr);
//Quarter & half frame clock envelope & linear counter
Instance->_squareChannel[0]->TickEnvelope();
Instance->_squareChannel[1]->TickEnvelope();
Instance->_triangleChannel->TickLinearCounter();
Instance->_noiseChannel->TickEnvelope();
if(type == FrameType::HalfFrame) {
//Half frames clock length counter & sweep
Instance->_squareChannel[0]->TickLengthCounter();
Instance->_squareChannel[1]->TickLengthCounter();
Instance->_triangleChannel->TickLengthCounter();
Instance->_noiseChannel->TickLengthCounter();
Instance->_squareChannel[0]->TickSweep();
Instance->_squareChannel[1]->TickSweep();
}
}
uint8_t APU::ReadRAM(uint16_t addr)
{
switch(addr) {
case 0x4015:
CPU::ClearIRQSource(IRQSource::FrameCounter);
return _apu.read_status(_currentClock + 4);
}
//$4015 read
Run();
return 0;
uint8_t status = 0;
status |= _squareChannel[0]->GetStatus() ? 0x01 : 0x00;
status |= _squareChannel[1]->GetStatus() ? 0x02 : 0x00;
status |= _triangleChannel->GetStatus() ? 0x04 : 0x00;
status |= _noiseChannel->GetStatus() ? 0x08 : 0x00;
status |= _deltaModulationChannel->GetStatus() ? 0x10 : 0x00;
status |= CPU::HasIRQSource(IRQSource::FrameCounter) ? 0x40 : 0x00;
status |= CPU::HasIRQSource(IRQSource::DMC) ? 0x80 : 0x00;
//Reading $4015 clears the Frame Counter interrupt flag.
CPU::ClearIRQSource(IRQSource::FrameCounter);
return status;
}
void APU::WriteRAM(uint16_t addr, uint8_t value)
{
_apu.write_register(_currentClock + 4, addr, value);
if(addr == 0x4017 && (value & 0x40) == 0x40) {
//Disable frame interrupts
CPU::ClearIRQSource(IRQSource::FrameCounter);
}
//$4015 write
Run();
_squareChannel[0]->SetEnabled((value & 0x01) == 0x01);
_squareChannel[1]->SetEnabled((value & 0x02) == 0x02);
_triangleChannel->SetEnabled((value & 0x04) == 0x04);
_noiseChannel->SetEnabled((value & 0x08) == 0x08);
_deltaModulationChannel->SetEnabled((value & 0x10) == 0x10);
//Writing to $4015 clears the DMC interrupt flag.
CPU::ClearIRQSource(IRQSource::DMC);
}
bool APU::Exec(uint32_t currentCPUCycle)
void APU::GetMemoryRanges(MemoryRanges &ranges)
{
_currentClock = currentCPUCycle;
ranges.AddHandler(MemoryType::RAM, MemoryOperation::Read, 0x4015);
ranges.AddHandler(MemoryType::RAM, MemoryOperation::Write, 0x4015);
}
if(_currentClock >= 29780) {
_apu.end_frame(_currentClock);
_buf.end_frame(_currentClock);
void APU::Run()
{
//Update framecounter and all channels
//This is called:
//-At the end of a frame
//-Before APU registers are read/written to
//-When a DMC or FrameCounter interrupt needs to be fired
uint32_t targetCycle = CPU::GetCycleCount();
uint32_t currentCycle = _previousCycle;
uint32_t cyclesToRun = targetCycle - _previousCycle;
_currentClock = 0;
while(currentCycle < targetCycle) {
currentCycle += _frameCounter->Run(cyclesToRun);
if(APU::Instance->_apu.earliest_irq() == Nes_Apu::irq_waiting) {
CPU::SetIRQSource(IRQSource::FrameCounter);
}
_squareChannel[0]->Run(currentCycle);
_squareChannel[1]->Run(currentCycle);
_noiseChannel->Run(currentCycle);
_triangleChannel->Run(currentCycle);
_deltaModulationChannel->Run(currentCycle);
}
_previousCycle = targetCycle;
}
void APU::StaticRun()
{
Instance->Run();
}
bool APU::IrqPending(uint32_t currentCycle)
{
uint32_t cyclesToRun = currentCycle - _previousCycle;
if(_frameCounter->IrqPending(cyclesToRun)) {
return true;
} else if(_deltaModulationChannel->IrqPending(cyclesToRun)) {
return true;
}
return false;
}
void APU::ExecStatic(uint32_t currentCpuCycle)
{
Instance->Exec(currentCpuCycle);
}
bool APU::Exec(uint32_t currentCpuCycle)
{
if(IrqPending(currentCpuCycle)) {
Run();
}
if(currentCpuCycle >= 29780) {
Run();
_previousCycle = 0;
_squareChannel[0]->EndFrame();
_squareChannel[1]->EndFrame();
_triangleChannel->EndFrame();
_noiseChannel->EndFrame();
_deltaModulationChannel->EndFrame();
_blipBuffer->end_frame(currentCpuCycle);
// Read some samples out of Blip_Buffer if there are enough to fill our output buffer
uint32_t availableSampleCount = _buf.samples_avail();
uint32_t availableSampleCount = _blipBuffer->samples_avail();
if(availableSampleCount >= APU::SamplesPerFrame) {
size_t sampleCount = _buf.read_samples(_outputBuffer, APU::SamplesPerFrame);
size_t sampleCount = _blipBuffer->read_samples(_outputBuffer, APU::SamplesPerFrame);
if(APU::AudioDevice) {
APU::AudioDevice->PlayBuffer(_outputBuffer, (uint32_t)(sampleCount * BitsPerSample / 8));
}
@ -93,9 +199,9 @@ void APU::StopAudio()
void APU::StreamState(bool saving)
{
apu_snapshot_t snapshot;
/*apu_snapshot_t snapshot;
if(saving) {
_apu.save_snapshot(&snapshot);
//_apu.save_snapshot(&snapshot);
}
Stream<uint32_t>(_currentClock);
@ -145,6 +251,6 @@ void APU::StreamState(bool saving)
Stream<uint8_t>(snapshot.dmc.irq_flag);
if(!saving) {
_apu.load_snapshot(snapshot);
}
//_apu.load_snapshot(snapshot);
}*/
}

View file

@ -1,28 +1,44 @@
#pragma once
#include "stdafx.h"
#include "MemoryManager.h"
#include "IMemoryHandler.h"
#include "IAudioDevice.h"
#include "Snapshotable.h"
#include "Nes_Apu/Nes_Apu.h"
class APU : public IMemoryHandler, public Snapshotable
class MemoryManager;
class SquareChannel;
class TriangleChannel;
class NoiseChannel;
class DeltaModulationChannel;
class ApuFrameCounter;
class Blip_Buffer;
enum class FrameType;
class APU : public Snapshotable, public IMemoryHandler
{
private:
static IAudioDevice* AudioDevice;
static APU* Instance;
uint32_t _currentClock = 0;
uint32_t _previousCycle = 0;
Nes_Apu _apu;
Blip_Buffer _buf;
vector<unique_ptr<SquareChannel>> _squareChannel;
unique_ptr<TriangleChannel> _triangleChannel;
unique_ptr<NoiseChannel> _noiseChannel;
unique_ptr<DeltaModulationChannel> _deltaModulationChannel;
unique_ptr<ApuFrameCounter> _frameCounter;
Blip_Buffer* _blipBuffer;
int16_t* _outputBuffer;
MemoryManager* _memoryManager;
private:
static int DMCRead(void*, cpu_addr_t addr);
static void IRQChanged(void* data);
bool IrqPending(uint32_t currentCycle);
void Run();
static void FrameCounterTick(FrameType type);
protected:
void StreamState(bool saving);
@ -38,14 +54,6 @@ class APU : public IMemoryHandler, public Snapshotable
void Reset();
void GetMemoryRanges(MemoryRanges &ranges)
{
ranges.AddHandler(MemoryType::RAM, MemoryOperation::Read, 0x4015);
ranges.AddHandler(MemoryType::RAM, MemoryOperation::Write, 0x4000, 0x4013);
ranges.AddHandler(MemoryType::RAM, MemoryOperation::Write, 0x4015);
ranges.AddHandler(MemoryType::RAM, MemoryOperation::Write, 0x4017);
}
static void RegisterAudioDevice(IAudioDevice *audioDevice)
{
APU::AudioDevice = audioDevice;
@ -53,7 +61,11 @@ class APU : public IMemoryHandler, public Snapshotable
uint8_t ReadRAM(uint16_t addr);
void WriteRAM(uint16_t addr, uint8_t value);
void GetMemoryRanges(MemoryRanges &ranges);
bool Exec(uint32_t executedCycles);
bool Exec(uint32_t currentCpuCycle);
static void ExecStatic(uint32_t currentCpuCycle);
static void StaticRun();
static void StopAudio();
};

62
Core/ApuEnvelope.h Normal file
View file

@ -0,0 +1,62 @@
#pragma once
#include "stdafx.h"
#include "ApuLengthCounter.h"
class ApuEnvelope : public ApuLengthCounter
{
private:
bool _constantVolume = false;
uint8_t _volume = 0;
uint8_t _envelope = 0;
uint8_t _envelopeCounter = 0;
bool _start = false;
int8_t _divider = 0;
uint8_t _counter = 0;
protected:
void InitializeEnvelope(uint8_t regValue)
{
_constantVolume = (regValue & 0x10) == 0x10;
_volume = regValue & 0x0F;
}
void ResetEnvelope()
{
_start = true;
}
uint32_t GetVolume()
{
if(_lengthCounter > 0) {
if(_constantVolume) {
return _volume;
} else {
return _counter;
}
} else {
return 0;
}
}
public:
void TickEnvelope()
{
if(!_start) {
_divider--;
if(_divider < 0) {
_divider = _volume;
if(_counter > 0) {
_counter--;
} else if(_lengthCounterHalt) {
_counter = 15;
}
}
} else {
_start = false;
_counter = 15;
_divider = _volume;
}
}
};

115
Core/ApuFrameCounter.h Normal file
View file

@ -0,0 +1,115 @@
#pragma once
#include "stdafx.h"
#include "IMemoryHandler.h"
#include "CPU.h"
enum class FrameType
{
None = 0,
QuarterFrame = 1,
HalfFrame = 2,
};
class ApuFrameCounter : public IMemoryHandler
{
private:
int32_t _nextIrqCycle = 29828;
uint32_t _previousCycle = 0;
const vector<vector<uint32_t>> _stepCycles = { { { 7457, 14913, 22371, 29828, 29829, 29830},
{ 7457, 14913, 22371, 29829, 37281, 37282} } };
const vector<vector<FrameType>> _frameType = { { { FrameType::QuarterFrame, FrameType::HalfFrame, FrameType::QuarterFrame, FrameType::None, FrameType::HalfFrame, FrameType::None },
{ FrameType::QuarterFrame, FrameType::HalfFrame, FrameType::QuarterFrame, FrameType::None, FrameType::HalfFrame, FrameType::None } } };
void (*_callback)(FrameType);
uint32_t _currentStep = 0;
uint32_t _stepMode = 0; //0: 4-step mode, 1: 5-step mode
bool _inhibitIRQ = false;
public:
ApuFrameCounter(void (*frameCounterTickCallback)(FrameType))
{
_callback = frameCounterTickCallback;
}
uint32_t Run(uint32_t &cyclesToRun)
{
uint32_t cyclesRan;
if(_previousCycle + cyclesToRun >= _stepCycles[_stepMode][_currentStep]) {
if(!_inhibitIRQ && _stepMode == 0 && _currentStep >= 3) {
//Set irq on the last 3 cycles for 4-step mode
CPU::SetIRQSource(IRQSource::FrameCounter);
_nextIrqCycle++;
}
FrameType type = _frameType[_stepMode][_currentStep];
if(type != FrameType::None) {
_callback(type);
}
cyclesRan = _stepCycles[_stepMode][_currentStep] - _previousCycle;
cyclesToRun -= cyclesRan;
_currentStep++;
if(_currentStep == 6) {
_currentStep = 0;
_previousCycle = 0;
if(_stepMode == 0 && !_inhibitIRQ) {
_nextIrqCycle = 29828;
}
} else {
_previousCycle += cyclesRan;
}
} else {
cyclesRan = cyclesToRun;
cyclesToRun = 0;
_previousCycle += cyclesRan;
}
return cyclesRan;
}
bool IrqPending(uint32_t cyclesToRun)
{
if(_nextIrqCycle != -1) {
if(_previousCycle + cyclesToRun >= (uint32_t)_nextIrqCycle) {
return true;
}
}
return false;
}
void GetMemoryRanges(MemoryRanges &ranges)
{
ranges.AddHandler(MemoryType::RAM, MemoryOperation::Write, 0x4017);
}
uint8_t ReadRAM(uint16_t addr)
{
return 0;
}
void WriteRAM(uint16_t addr, uint8_t value)
{
APU::StaticRun();
_stepMode = ((value & 0x80) == 0x80) ? 1 : 0;
_inhibitIRQ = (value & 0x40) == 0x40;
_nextIrqCycle = -1;
if(_inhibitIRQ) {
CPU::ClearIRQSource(IRQSource::FrameCounter);
} else if(_stepMode == 0) {
_nextIrqCycle = 29828;
}
//Reset sequence when $4017 is written to
_previousCycle = 0;
_currentStep = 0;
if(_stepMode == 1) {
//Writing to $4017 with bit 7 set will immediately generate a clock for both the quarter frame and the half frame units, regardless of what the sequencer is doing.
_callback(FrameType::HalfFrame);
}
}
};

47
Core/ApuLengthCounter.h Normal file
View file

@ -0,0 +1,47 @@
#pragma once
#include "stdafx.h"
#include "BaseApuChannel.h"
class ApuLengthCounter : public BaseApuChannel<15>
{
private:
const vector<uint8_t> _lcLookupTable = { { 10, 254, 20, 2, 40, 4, 80, 6, 160, 8, 60, 10, 14, 12, 26, 14, 12, 16, 24, 18, 48, 20, 96, 22, 192, 24, 72, 26, 16, 28, 32, 30 } };
bool _enabled = false;
protected:
bool _lengthCounterHalt = false;
uint8_t _lengthCounter = 0;
void InitializeLengthCounter(bool haltFlag)
{
_lengthCounterHalt = haltFlag;
}
void LoadLengthCounter(uint8_t value)
{
if(_enabled) {
_lengthCounter = _lcLookupTable[value];
}
}
public:
bool GetStatus()
{
return _lengthCounter > 0;
}
void TickLengthCounter()
{
if(_lengthCounter > 0 && !_lengthCounterHalt) {
_lengthCounter--;
}
}
void SetEnabled(bool enabled)
{
if(!enabled) {
_lengthCounter = 0;
}
_enabled = enabled;
}
};

76
Core/BaseApuChannel.h Normal file
View file

@ -0,0 +1,76 @@
#pragma once
#include "stdafx.h"
#include "IMemoryHandler.h"
#include "../BlipBuffer/Blip_Buffer.h"
template<int range>
class BaseApuChannel : public IMemoryHandler
{
private:
unique_ptr<Blip_Synth<blip_good_quality, range>> _synth;
uint16_t _lastOutput = 0;
uint32_t _previousCycle = 0;
Blip_Buffer *_buffer;
protected:
uint16_t _timer = 0;
uint16_t _period = 0;
uint32_t _clockDivider = 2; //All channels except triangle clock overy other cpu clock
public:
virtual void Clock() = 0;
virtual bool GetStatus() = 0;
BaseApuChannel()
{
_synth.reset(new Blip_Synth<blip_good_quality, range>());
}
void SetBuffer(Blip_Buffer *buffer)
{
_buffer = buffer;
}
void SetVolume(double volume)
{
_synth->volume(volume);
}
virtual void Run(uint32_t targetCycle)
{
while(_previousCycle < targetCycle) {
if(_timer == 0) {
Clock();
_timer = _period;
_previousCycle += _clockDivider;
} else {
uint32_t cyclesToRun = (targetCycle - _previousCycle) / _clockDivider;
uint16_t skipCount = _timer > cyclesToRun ? cyclesToRun : _timer;
_timer -= skipCount;
_previousCycle += skipCount * _clockDivider;
if(cyclesToRun == 0) {
break;
}
}
}
}
uint8_t ReadRAM(uint16_t addr)
{
return 0;
}
void AddOutput(uint16_t output)
{
if(output != _lastOutput) {
_synth->offset_inline(_previousCycle, output - _lastOutput, _buffer);
}
_lastOutput = output;
}
void EndFrame()
{
_previousCycle = 0;
}
};

View file

@ -667,6 +667,7 @@ public:
static void SetNMIFlag() { CPU::Instance->_state.NMIFlag = true; }
static void ClearNMIFlag() { CPU::Instance->_state.NMIFlag = false; }
static void SetIRQSource(IRQSource source) { CPU::Instance->_state.IRQFlag |= (int)source; }
static bool HasIRQSource(IRQSource source) { return (CPU::Instance->_state.IRQFlag & (int)source) != 0; }
static void ClearIRQSource(IRQSource source) { CPU::Instance->_state.IRQFlag &= ~(int)source; }
static void RunDMATransfer(uint8_t* spriteRAM, uint32_t &spriteRamAddr, uint8_t offsetValue);

View file

@ -266,7 +266,12 @@
</ItemDefinitionGroup>
<ItemGroup>
<ClInclude Include="APU.h" />
<ClInclude Include="DeltaModulationChannel.h" />
<ClInclude Include="ApuEnvelope.h" />
<ClInclude Include="ApuFrameCounter.h" />
<ClInclude Include="ApuLengthCounter.h" />
<ClInclude Include="AXROM.h" />
<ClInclude Include="BaseApuChannel.h" />
<ClInclude Include="BaseMapper.h" />
<ClInclude Include="Breakpoint.h" />
<ClInclude Include="CheatManager.h" />
@ -293,6 +298,8 @@
<ClInclude Include="IMessageManager.h" />
<ClInclude Include="INotificationListener.h" />
<ClInclude Include="InputDataMessage.h" />
<ClInclude Include="NoiseChannel.h" />
<ClInclude Include="SquareChannel.h" />
<ClInclude Include="StandardController.h" />
<ClInclude Include="MessageManager.h" />
<ClInclude Include="MessageType.h" />
@ -309,23 +316,13 @@
<ClInclude Include="MapperFactory.h" />
<ClInclude Include="MMC1.h" />
<ClInclude Include="MMC3.h" />
<ClInclude Include="Nes_Apu\apu_snapshot.h" />
<ClInclude Include="Nes_Apu\blargg_common.h" />
<ClInclude Include="Nes_Apu\blargg_source.h" />
<ClInclude Include="Nes_Apu\Blip_Buffer.h" />
<ClInclude Include="Nes_Apu\Blip_Synth.h" />
<ClInclude Include="Nes_Apu\Multi_Buffer.h" />
<ClInclude Include="Nes_Apu\Nes_Apu.h" />
<ClInclude Include="Nes_Apu\Nes_Namco.h" />
<ClInclude Include="Nes_Apu\Nes_Oscs.h" />
<ClInclude Include="Nes_Apu\Nes_Vrc6.h" />
<ClInclude Include="Nes_Apu\Nonlinear_Buffer.h" />
<ClInclude Include="NROM.h" />
<ClInclude Include="PPU.h" />
<ClInclude Include="CPU.h" />
<ClInclude Include="MemoryManager.h" />
<ClInclude Include="ROMLoader.h" />
<ClInclude Include="stdafx.h" />
<ClInclude Include="TriangleChannel.h" />
<ClInclude Include="UNROM.h" />
<ClInclude Include="VirtualController.h" />
<ClInclude Include="VRC2_4.h" />

View file

@ -9,9 +9,6 @@
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
<Extensions>h;hh;hpp;hxx;hm;inl;inc;xsd</Extensions>
</Filter>
<Filter Include="Header Files\Nes_Apu">
<UniqueIdentifier>{c6dc2048-98f6-4551-89dc-830f12f1bb2e}</UniqueIdentifier>
</Filter>
<Filter Include="Header Files\Interfaces">
<UniqueIdentifier>{ca661408-b52a-4378-aef4-80fda1d64cd6}</UniqueIdentifier>
</Filter>
@ -30,6 +27,12 @@
<Filter Include="Debugger">
<UniqueIdentifier>{ff3c6e48-3987-41d2-8916-b588a457ff30}</UniqueIdentifier>
</Filter>
<Filter Include="Header Files\APU">
<UniqueIdentifier>{d9dec4ba-97e7-4a80-85e1-6b53f5ed7218}</UniqueIdentifier>
</Filter>
<Filter Include="Source Files\APU">
<UniqueIdentifier>{b99fc308-b28a-48b7-9ca8-6e8005bbc2bf}</UniqueIdentifier>
</Filter>
</ItemGroup>
<ItemGroup>
<ClInclude Include="stdafx.h">
@ -53,42 +56,6 @@
<ClInclude Include="ControlManager.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="APU.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="Nes_Apu\apu_snapshot.h">
<Filter>Header Files\Nes_Apu</Filter>
</ClInclude>
<ClInclude Include="Nes_Apu\blargg_common.h">
<Filter>Header Files\Nes_Apu</Filter>
</ClInclude>
<ClInclude Include="Nes_Apu\blargg_source.h">
<Filter>Header Files\Nes_Apu</Filter>
</ClInclude>
<ClInclude Include="Nes_Apu\Blip_Buffer.h">
<Filter>Header Files\Nes_Apu</Filter>
</ClInclude>
<ClInclude Include="Nes_Apu\Blip_Synth.h">
<Filter>Header Files\Nes_Apu</Filter>
</ClInclude>
<ClInclude Include="Nes_Apu\Multi_Buffer.h">
<Filter>Header Files\Nes_Apu</Filter>
</ClInclude>
<ClInclude Include="Nes_Apu\Nes_Apu.h">
<Filter>Header Files\Nes_Apu</Filter>
</ClInclude>
<ClInclude Include="Nes_Apu\Nes_Namco.h">
<Filter>Header Files\Nes_Apu</Filter>
</ClInclude>
<ClInclude Include="Nes_Apu\Nes_Oscs.h">
<Filter>Header Files\Nes_Apu</Filter>
</ClInclude>
<ClInclude Include="Nes_Apu\Nes_Vrc6.h">
<Filter>Header Files\Nes_Apu</Filter>
</ClInclude>
<ClInclude Include="Nes_Apu\Nonlinear_Buffer.h">
<Filter>Header Files\Nes_Apu</Filter>
</ClInclude>
<ClInclude Include="IAudioDevice.h">
<Filter>Header Files\Interfaces</Filter>
</ClInclude>
@ -224,6 +191,33 @@
<ClInclude Include="StandardController.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="SquareChannel.h">
<Filter>Header Files\APU</Filter>
</ClInclude>
<ClInclude Include="APU.h">
<Filter>Header Files\APU</Filter>
</ClInclude>
<ClInclude Include="ApuLengthCounter.h">
<Filter>Header Files\APU</Filter>
</ClInclude>
<ClInclude Include="ApuEnvelope.h">
<Filter>Header Files\APU</Filter>
</ClInclude>
<ClInclude Include="ApuFrameCounter.h">
<Filter>Header Files\APU</Filter>
</ClInclude>
<ClInclude Include="TriangleChannel.h">
<Filter>Header Files\APU</Filter>
</ClInclude>
<ClInclude Include="NoiseChannel.h">
<Filter>Header Files\APU</Filter>
</ClInclude>
<ClInclude Include="BaseApuChannel.h">
<Filter>Header Files\APU</Filter>
</ClInclude>
<ClInclude Include="DeltaModulationChannel.h">
<Filter>Header Files\APU</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="CPU.cpp">
@ -244,9 +238,6 @@
<ClCompile Include="ControlManager.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="APU.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="Movie.cpp">
<Filter>Source Files</Filter>
</ClCompile>
@ -295,5 +286,8 @@
<ClCompile Include="StandardController.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="APU.cpp">
<Filter>Source Files\APU</Filter>
</ClCompile>
</ItemGroup>
</Project>

View file

@ -0,0 +1,154 @@
#pragma once
#include "stdafx.h"
#include "../BlipBuffer/Blip_Buffer.h"
#include "APU.h"
#include "IMemoryHandler.h"
#include "ApuEnvelope.h"
class DeltaModulationChannel : public BaseApuChannel<127>
{
private:
const vector<uint16_t> _dmcPeriodLookupTable = { { 428, 380, 340, 320, 286, 254, 226, 214, 190, 160, 142, 128, 106, 84, 72, 54 } };
MemoryManager *_memoryManager = nullptr;
uint16_t _sampleAddr = 0;
uint16_t _sampleLength = 0;
uint8_t _outputLevel = 0;
bool _irqEnabled = false;
bool _loopFlag = false;
uint16_t _currentAddr = 0;
uint16_t _bytesRemaining = 0;
uint8_t _readBuffer = 0;
bool _bufferEmpty = true;
uint8_t _shiftRegister = 0;
uint8_t _bitsRemaining = 0;
bool _silenceFlag = true;
public:
DeltaModulationChannel(MemoryManager* memoryManager)
{
_memoryManager = memoryManager;
_clockDivider = 1;
SetVolume(0.42545);
}
bool IrqPending(uint32_t cyclesToRun)
{
if(_irqEnabled && _bytesRemaining > 0) {
uint32_t cyclesToEmptyBuffer = (_bitsRemaining + (_bytesRemaining-1)* 8) * _period;
if(cyclesToRun >= cyclesToEmptyBuffer) {
return true;
}
}
return false;
}
bool GetStatus()
{
return _bytesRemaining > 0;
}
void GetMemoryRanges(MemoryRanges &ranges)
{
ranges.AddHandler(MemoryType::RAM, MemoryOperation::Write, 0x4010, 0x4013);
}
void WriteRAM(uint16_t addr, uint8_t value)
{
APU::StaticRun();
switch(addr & 0x03) {
case 0: //4010
_irqEnabled = (value & 0x80) == 0x80;
_loopFlag = (value & 0x40) == 0x40;
_period = _dmcPeriodLookupTable[value & 0x0F];
if(!_irqEnabled) {
CPU::ClearIRQSource(IRQSource::DMC);
}
break;
case 1: //4011
_outputLevel = value & 0x7F;
_shiftRegister = value & 0x7F;
break;
case 2: //4012
_sampleAddr = 0xC000 | ((uint32_t)value << 6);
break;
case 3: //4013
_sampleLength = (value << 4) | 0x0001;
break;
}
}
void SetEnabled(bool enabled)
{
if(!enabled) {
_bytesRemaining = 0;
} else if(_bytesRemaining == 0) {
InitSample();
FillReadBuffer();
}
}
void InitSample()
{
_currentAddr = _sampleAddr;
_bytesRemaining = _sampleLength;
}
void FillReadBuffer()
{
if(_bufferEmpty && _bytesRemaining > 0) {
_readBuffer = _memoryManager->Read(_currentAddr);
_bufferEmpty = false;
_currentAddr++;
_bytesRemaining--;
if(_bytesRemaining == 0) {
if(_loopFlag) {
//Looped sample should never set IRQ flag
InitSample();
} else if(_irqEnabled) {
CPU::SetIRQSource(IRQSource::DMC);
}
}
}
}
void Clock()
{
if(!_silenceFlag) {
if(_shiftRegister & 0x01) {
if(_outputLevel <= 125) {
_outputLevel += 2;
}
} else {
if(_outputLevel >= 2) {
_outputLevel -= 2;
}
}
_shiftRegister >>= 1;
}
_bitsRemaining--;
if(_bitsRemaining == 0) {
_bitsRemaining = 8;
if(_bufferEmpty) {
_silenceFlag = true;
} else {
_silenceFlag = false;
_shiftRegister = _readBuffer;
_bufferEmpty = true;
FillReadBuffer();
}
}
AddOutput(_outputLevel);
}
};

View file

@ -1,253 +0,0 @@
// 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

View file

@ -1,203 +0,0 @@
// 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

View file

@ -1,157 +0,0 @@
// 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 );
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 );
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

View file

@ -1,162 +0,0 @@
// 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

View file

@ -1,86 +0,0 @@
// 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

View file

@ -1,142 +0,0 @@
// 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

View file

@ -1,85 +0,0 @@
// 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

@ -1,65 +0,0 @@
// 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 );
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

View file

@ -1,75 +0,0 @@
// 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

View file

@ -1,181 +0,0 @@
// 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)
#define BLARGG_COMPILER_HAS_NAMESPACE 1
// 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

@ -1,43 +0,0 @@
// 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

@ -1,13 +0,0 @@
// 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

@ -1,42 +0,0 @@
// 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

@ -1,22 +0,0 @@
// 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

70
Core/NoiseChannel.h Normal file
View file

@ -0,0 +1,70 @@
#pragma once
#include "stdafx.h"
#include "../BlipBuffer/Blip_Buffer.h"
#include "APU.h"
#include "IMemoryHandler.h"
#include "ApuEnvelope.h"
class NoiseChannel : public ApuEnvelope
{
private:
const vector<uint16_t> _noisePeriodLookupTable = { { 4, 8, 16, 32, 64, 96, 128, 160, 202, 254, 380, 508, 762, 1016, 2034, 4068 } };
//On power-up, the shift register is loaded with the value 1.
uint16_t _shiftRegister = 1;
bool _modeFlag = false;
bool IsMuted()
{
//The mixer receives the current envelope volume except when Bit 0 of the shift register is set, or The length counter is zero
return (_shiftRegister & 0x01) == 0x01;
}
public:
NoiseChannel()
{
SetVolume(0.0741);
}
void GetMemoryRanges(MemoryRanges &ranges)
{
ranges.AddHandler(MemoryType::RAM, MemoryOperation::Write, 0x400C, 0x400F);
}
void WriteRAM(uint16_t addr, uint8_t value)
{
APU::StaticRun();
switch(addr & 0x03) {
case 0: //400C
InitializeLengthCounter((value & 0x20) == 0x20);
InitializeEnvelope(value);
break;
case 2: //400E
_period = _noisePeriodLookupTable[value & 0x0F];
break;
case 3: //400F
LoadLengthCounter(value >> 3);
//The envelope is also restarted.
ResetEnvelope();
break;
}
}
void Clock()
{
uint32_t volume = GetVolume();
//Feedback is calculated as the exclusive-OR of bit 0 and one other bit: bit 6 if Mode flag is set, otherwise bit 1.
uint16_t feedback = (_shiftRegister & 0x01) ^ ((_shiftRegister >> (_modeFlag ? 6 : 1)) & 0x01);
_shiftRegister >>= 1;
_shiftRegister |= (feedback << 14);
if(IsMuted()) {
AddOutput(0);
} else {
AddOutput(GetVolume());
}
}
};

154
Core/SquareChannel.h Normal file
View file

@ -0,0 +1,154 @@
#pragma once
#include "stdafx.h"
#include "../BlipBuffer/Blip_Buffer.h"
#include "APU.h"
#include "IMemoryHandler.h"
#include "ApuEnvelope.h"
class SquareChannel : public ApuEnvelope
{
private:
const vector<const vector<uint8_t>> _dutySequences = { {
{ 0, 1, 0, 0, 0, 0, 0, 0 },
{ 0, 1, 1, 0, 0, 0, 0, 0 },
{ 0, 1, 1, 1, 1, 0, 0, 0 },
{ 1, 0, 0, 1, 1, 1, 1, 1 }
} };
bool _isChannel1 = false;
uint8_t _duty = 0;
uint8_t _dutyPos = 0;
bool _sweepEnabled = false;
uint8_t _sweepPeriod = 0;
bool _sweepNegate = false;
uint8_t _sweepShift = 0;
bool _reloadSweep = false;
uint8_t _sweepDivider = 0;
uint32_t _sweepTargetPeriod = 0;
bool IsMuted()
{
//A period of t < 8, either set explicitly or via a sweep period update, silences the corresponding pulse channel.
return _period < 8 || _sweepTargetPeriod > 0x7FF;
}
public:
SquareChannel(bool isChannel1)
{
SetVolume(0.1128);
_isChannel1 = isChannel1;
}
void GetMemoryRanges(MemoryRanges &ranges)
{
if(_isChannel1) {
ranges.AddHandler(MemoryType::RAM, MemoryOperation::Write, 0x4000, 0x4003);
} else {
ranges.AddHandler(MemoryType::RAM, MemoryOperation::Write, 0x4004, 0x4007);
}
}
void WriteRAM(uint16_t addr, uint8_t value)
{
APU::StaticRun();
switch(addr & 0x03) {
case 0: //4000 & 4004
InitializeLengthCounter((value & 0x20) == 0x20);
InitializeEnvelope(value);
_duty = (value & 0xC0) >> 6;
break;
case 1: //4001 & 4005
InitializeSweep(value);
break;
case 2: //4002 & 4006
_period &= ~0x00FF;
_period |= value;
break;
case 3: //4003 & 4007
LoadLengthCounter(value >> 3);
_period &= ~0x0700;
_period |= (value & 0x07) << 8;
//The sequencer is restarted at the first value of the current sequence.
_timer = _period + 1;
_dutyPos = 0;
//The envelope is also restarted.
ResetEnvelope();
break;
}
}
void InitializeSweep(uint8_t regValue)
{
_sweepEnabled = (regValue & 0x80) == 0x80;
_sweepNegate = (regValue & 0x08) == 0x08;
//The divider's period is set to P + 1
_sweepPeriod = ((regValue & 0x70) >> 4) + 1;
_sweepShift = (regValue & 0x07);
//Side effects: Sets the reload flag
_reloadSweep = true;
}
void UpdateTargetPeriod(bool setPeriod)
{
uint16_t shiftResult = (_period >> _sweepShift);
if(_sweepNegate) {
_sweepTargetPeriod = _period - shiftResult;
if(_isChannel1) {
// As a result, a negative sweep on pulse channel 1 will subtract the shifted period value minus 1
_sweepTargetPeriod++;
}
} else {
_sweepTargetPeriod = _period + shiftResult;
}
if(setPeriod && _sweepShift > 0 && _period >= 8 && _sweepTargetPeriod <= 0x7FF) {
_period = _sweepTargetPeriod;
}
}
void TickSweep()
{
if(_reloadSweep) {
if(_sweepDivider == 0 && _sweepEnabled) {
//If the divider's counter was zero before the reload and the sweep is enabled, the pulse's period is also adjusted
UpdateTargetPeriod(true);
}
_sweepDivider = _sweepPeriod;
_reloadSweep = false;
} else {
if(_sweepDivider > 0) {
_sweepDivider--;
} else if(_sweepEnabled) {
UpdateTargetPeriod(true);
_sweepDivider = _sweepPeriod;
}
}
}
void Run(uint32_t targetCycle)
{
UpdateTargetPeriod(false);
BaseApuChannel::Run(targetCycle);
}
void Clock()
{
_dutyPos = (_dutyPos - 1) & 0x07;
if(IsMuted()) {
AddOutput(0);
} else {
AddOutput(_dutySequences[_duty][_dutyPos] * GetVolume());
}
}
};

82
Core/TriangleChannel.h Normal file
View file

@ -0,0 +1,82 @@
#pragma once
#include "stdafx.h"
#include "../BlipBuffer/Blip_Buffer.h"
#include "APU.h"
#include "IMemoryHandler.h"
#include "ApuEnvelope.h"
class TriangleChannel : public ApuLengthCounter
{
private:
const vector<uint8_t> _sequence = { { 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 } };
uint8_t _linearCounter = 0;
uint8_t _linearCounterReload = 0;
bool _linearReloadFlag = false;
bool _linearControlFlag = false;
uint8_t _sequencePosition = 0;
public:
TriangleChannel()
{
_clockDivider = 1; //Triangle clocks at the same speed as the cpu
SetVolume(0.12765);
}
void GetMemoryRanges(MemoryRanges &ranges)
{
ranges.AddHandler(MemoryType::RAM, MemoryOperation::Write, 0x4008, 0x400B);
}
void WriteRAM(uint16_t addr, uint8_t value)
{
APU::StaticRun();
switch(addr & 0x03) {
case 0: //4008
_linearControlFlag = (value & 0x80) == 0x80;
_linearCounterReload = value & 0x7F;
InitializeLengthCounter(_linearControlFlag);
break;
case 2: //400A
_period &= ~0x00FF;
_period |= value;
break;
case 3: //400B
LoadLengthCounter(value >> 3);
_period &= ~0xFF00;
_period |= (value & 0x03) << 8;
//Side effects Sets the linear counter reload flag
_linearReloadFlag = true;
break;
}
}
void Clock()
{
//The sequencer is clocked by the timer as long as both the linear counter and the length counter are nonzero.
if(_lengthCounter > 0 && _linearCounter > 0) {
_sequencePosition = (_sequencePosition + 1) & 0x1F;
AddOutput(_sequence[_sequencePosition]);
}
}
void TickLinearCounter()
{
if(_linearReloadFlag) {
_linearCounter = _linearCounterReload;
} else if(_linearCounter > 0) {
_linearCounter--;
}
if(!_linearControlFlag) {
_linearReloadFlag = false;
}
}
};

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -254,6 +254,16 @@
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release x64|x64'">Create</PrecompiledHeader>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\BlipBuffer\BlipBuffer.vcxproj">
<Project>{cf35d78c-f710-41d2-968f-c46accff6f07}</Project>
<Private>false</Private>
<ReferenceOutputAssembly>true</ReferenceOutputAssembly>
<CopyLocalSatelliteAssemblies>false</CopyLocalSatelliteAssemblies>
<LinkLibraryDependencies>true</LinkLibraryDependencies>
<UseLibraryDependencyInputs>false</UseLibraryDependencyInputs>
</ProjectReference>
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>

View file

@ -45,6 +45,5 @@
#pragma comment(lib, MESEN_LIBRARY_PATH"Utilities.lib")
#pragma comment(lib, MESEN_LIBRARY_PATH"Windows.lib")
#pragma comment(lib, "../Dependencies/DirectXTK"MESEN_LIBRARY_SUFFIX)
#pragma comment(lib, "../Dependencies/Nes_Apu"MESEN_LIBRARY_SUFFIX)
#define DllExport __declspec(dllexport)

10
NES.sln
View file

@ -6,6 +6,7 @@ MinimumVisualStudioVersion = 10.0.40219.1
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Core", "Core\Core.vcxproj", "{78FEF1A1-6DF1-4CBB-A373-AE6FA7CE5CE0}"
ProjectSection(ProjectDependencies) = postProject
{B5330148-E8C7-46BA-B54E-69BE59EA337D} = {B5330148-E8C7-46BA-B54E-69BE59EA337D}
{CF35D78C-F710-41D2-968F-C46ACCFF6F07} = {CF35D78C-F710-41D2-968F-C46ACCFF6F07}
{7761E790-B42C-4179-8550-8365FF9EB23E} = {7761E790-B42C-4179-8550-8365FF9EB23E}
EndProjectSection
EndProject
@ -29,6 +30,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "PGOHelper", "PGOHelper\PGOH
{37749BB2-FA78-4EC9-8990-5628FC0BBA19} = {37749BB2-FA78-4EC9-8990-5628FC0BBA19}
EndProjectSection
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "BlipBuffer", "BlipBuffer\BlipBuffer.vcxproj", "{CF35D78C-F710-41D2-968F-C46ACCFF6F07}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|x64 = Debug|x64
@ -85,6 +88,13 @@ Global
{38D74EE1-5276-4D24-AABC-104B912A27D2}.Release|x64.Build.0 = Release|x64
{38D74EE1-5276-4D24-AABC-104B912A27D2}.Release|x86.ActiveCfg = Release|Win32
{38D74EE1-5276-4D24-AABC-104B912A27D2}.Release|x86.Build.0 = Release|Win32
{CF35D78C-F710-41D2-968F-C46ACCFF6F07}.Debug|x64.ActiveCfg = Debug|x64
{CF35D78C-F710-41D2-968F-C46ACCFF6F07}.Debug|x64.Build.0 = Debug|x64
{CF35D78C-F710-41D2-968F-C46ACCFF6F07}.Debug|x86.ActiveCfg = Debug|Win32
{CF35D78C-F710-41D2-968F-C46ACCFF6F07}.Debug|x86.Build.0 = Debug|Win32
{CF35D78C-F710-41D2-968F-C46ACCFF6F07}.Release|x64.ActiveCfg = Release|x64
{CF35D78C-F710-41D2-968F-C46ACCFF6F07}.Release|x86.ActiveCfg = Release|Win32
{CF35D78C-F710-41D2-968F-C46ACCFF6F07}.Release|x86.Build.0 = Release|Win32
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE