diff --git a/Frameworks/GME/GME.xcodeproj/xcuserdata/Chris.xcuserdatad/xcschemes/GME Framework.xcscheme b/Frameworks/GME/GME.xcodeproj/xcuserdata/Chris.xcuserdatad/xcschemes/GME Framework.xcscheme new file mode 100644 index 000000000..265fe4847 --- /dev/null +++ b/Frameworks/GME/GME.xcodeproj/xcuserdata/Chris.xcuserdatad/xcschemes/GME Framework.xcscheme @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Frameworks/GME/GME.xcodeproj/xcuserdata/Chris.xcuserdatad/xcschemes/xcschememanagement.plist b/Frameworks/GME/GME.xcodeproj/xcuserdata/Chris.xcuserdatad/xcschemes/xcschememanagement.plist new file mode 100644 index 000000000..958883c18 --- /dev/null +++ b/Frameworks/GME/GME.xcodeproj/xcuserdata/Chris.xcuserdatad/xcschemes/xcschememanagement.plist @@ -0,0 +1,22 @@ + + + + + SchemeUserState + + GME Framework.xcscheme + + orderHint + 12 + + + SuppressBuildableAutocreation + + 8DC2EF4F0486A6940098B216 + + primary + + + + + diff --git a/Frameworks/GME/gme/Ay_Core.cpp b/Frameworks/GME/gme/Ay_Core.cpp new file mode 100644 index 000000000..c54a73e9a --- /dev/null +++ b/Frameworks/GME/gme/Ay_Core.cpp @@ -0,0 +1,190 @@ +// Game_Music_Emu $vers. http://www.slack.net/~ant/ + +#include "Ay_Core.h" + +/* Copyright (C) 2006-2009 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +#include "blargg_source.h" + +inline void Ay_Core::disable_beeper() +{ + beeper_mask = 0; + last_beeper = 0; +} + +Ay_Core::Ay_Core() +{ + beeper_output = NULL; + disable_beeper(); +} + +Ay_Core::~Ay_Core() { } + +void Ay_Core::set_beeper_output( Blip_Buffer* b ) +{ + beeper_output = b; + if ( b && !cpc_mode ) + beeper_mask = 0x10; + else + disable_beeper(); +} + +void Ay_Core::start_track( registers_t const& r, addr_t play ) +{ + play_addr = play; + + memset( mem_.padding1, 0xFF, sizeof mem_.padding1 ); + + int const mirrored = 0x80; // this much is mirrored after end of memory + memset( mem_.ram + mem_size + mirrored, 0xFF, sizeof mem_.ram - mem_size - mirrored ); + memcpy( mem_.ram + mem_size, mem_.ram, mirrored ); // some code wraps around (ugh) + + cpu.reset( mem_.padding1, mem_.padding1 ); + cpu.map_mem( 0, mem_size, mem_.ram, mem_.ram ); + cpu.r = r; + + beeper_delta = (int) (apu_.amp_range * 0.8); + last_beeper = 0; + next_play = play_period; + spectrum_mode = false; + cpc_mode = false; + cpc_latch = 0; + set_beeper_output( beeper_output ); + apu_.reset(); + + // a few tunes rely on channels having tone enabled at the beginning + apu_.write_addr( 7 ); + apu_.write_data( 0, 0x38 ); + +} + +// Emulation + +void Ay_Core::cpu_out_( time_t time, addr_t addr, int data ) +{ + // Spectrum + if ( !cpc_mode ) + { + switch ( addr & 0xFEFF ) + { + case 0xFEFD: + spectrum_mode = true; + apu_.write_addr( data ); + return; + + case 0xBEFD: + spectrum_mode = true; + apu_.write_data( time, data ); + return; + } + } + + // CPC + if ( !spectrum_mode ) + { + switch ( addr >> 8 ) + { + case 0xF6: + switch ( data & 0xC0 ) + { + case 0xC0: + apu_.write_addr( cpc_latch ); + goto enable_cpc; + + case 0x80: + apu_.write_data( time, cpc_latch ); + goto enable_cpc; + } + break; + + case 0xF4: + cpc_latch = data; + goto enable_cpc; + } + } + + dprintf( "Unmapped OUT: $%04X <- $%02X\n", addr, data ); + return; + +enable_cpc: + if ( !cpc_mode ) + { + cpc_mode = true; + disable_beeper(); + set_cpc_callback.f( set_cpc_callback.data ); + } +} + +int Ay_Core::cpu_in( addr_t addr ) +{ + // keyboard read and other things + if ( (addr & 0xFF) == 0xFE ) + return 0xFF; // other values break some beeper tunes + + dprintf( "Unmapped IN : $%04X\n", addr ); + return 0xFF; +} + +void Ay_Core::end_frame( time_t* end ) +{ + cpu.set_time( 0 ); + + // Since detection of CPC mode will halve clock rate during the frame + // and thus generate up to twice as much sound, we must generate half + // as much until mode is known. + if ( !(spectrum_mode | cpc_mode) ) + *end /= 2; + + while ( cpu.time() < *end ) + { + run_cpu( min( *end, next_play ) ); + + if ( cpu.time() >= next_play ) + { + // next frame + next_play += play_period; + + if ( cpu.r.iff1 ) + { + // interrupt enabled + + if ( mem_.ram [cpu.r.pc] == 0x76 ) + cpu.r.pc++; // advance past HALT instruction + + cpu.r.iff1 = 0; + cpu.r.iff2 = 0; + + mem_.ram [--cpu.r.sp] = byte (cpu.r.pc >> 8); + mem_.ram [--cpu.r.sp] = byte (cpu.r.pc); + + // fixed interrupt + cpu.r.pc = 0x38; + cpu.adjust_time( 12 ); + + if ( cpu.r.im == 2 ) + { + // vectored interrupt + addr_t addr = cpu.r.i * 0x100 + 0xFF; + cpu.r.pc = mem_.ram [(addr + 1) & 0xFFFF] * 0x100 + mem_.ram [addr]; + cpu.adjust_time( 6 ); + } + } + } + } + + // End time frame + *end = cpu.time(); + next_play -= *end; + check( next_play >= 0 ); + cpu.adjust_time( -*end ); + apu_.end_frame( *end ); +} diff --git a/Frameworks/GME/gme/Ay_Core.h b/Frameworks/GME/gme/Ay_Core.h new file mode 100644 index 000000000..96c57c0e0 --- /dev/null +++ b/Frameworks/GME/gme/Ay_Core.h @@ -0,0 +1,81 @@ +// Sinclair Spectrum AY music emulator core + +// Game_Music_Emu $vers +#ifndef AY_CORE_H +#define AY_CORE_H + +#include "Z80_Cpu.h" +#include "Ay_Apu.h" + +class Ay_Core { +public: + + // Clock count + typedef int time_t; + + // Sound chip access, to assign it to Blip_Buffer etc. + Ay_Apu& apu() { return apu_; } + + // Sets beeper sound buffer, or NULL to mute it. Volume and treble EQ of + // beeper are set by APU. + void set_beeper_output( Blip_Buffer* ); + + // Sets time between calls to play routine. Can be changed while playing. + void set_play_period( time_t p ) { play_period = p; } + + // 64K memory to load code and data into before starting track. Caller + // must parse the AY file. + BOOST::uint8_t* mem() { return mem_.ram; } + enum { mem_size = 0x10000 }; + enum { ram_addr = 0x4000 }; // where official RAM starts + + // Starts track using specified register values, and sets play routine that + // is called periodically + typedef Z80_Cpu::registers_t registers_t; + typedef int addr_t; + void start_track( registers_t const&, addr_t play ); + + // Ends time frame of at most *end clocks and sets *end to number of clocks + // emulated. Until Spectrum/CPC mode is determined, *end is HALVED. + void end_frame( time_t* end ); + + // Called when CPC hardware is first accessed. AY file format doesn't specify + // which sound hardware is used, so it must be determined during playback + // based on which sound port is first used. + blargg_callback set_cpc_callback; + +// Implementation +public: + Ay_Core(); + ~Ay_Core(); + +private: + Blip_Buffer* beeper_output; + int beeper_delta; + int last_beeper; + int beeper_mask; + + addr_t play_addr; + time_t play_period; + time_t next_play; + + int cpc_latch; + bool spectrum_mode; + bool cpc_mode; + + // large items + Z80_Cpu cpu; + struct { + BOOST::uint8_t padding1 [0x100]; + BOOST::uint8_t ram [mem_size + 0x100]; + } mem_; + Ay_Apu apu_; + + int cpu_in( addr_t ); + void cpu_out( time_t, addr_t, int data ); + void cpu_out_( time_t, addr_t, int data ); + bool run_cpu( time_t end ); + void disable_beeper(); +}; + +#endif diff --git a/Frameworks/GME/gme/Blip_Buffer_impl.h b/Frameworks/GME/gme/Blip_Buffer_impl.h new file mode 100644 index 000000000..aebaa1af0 --- /dev/null +++ b/Frameworks/GME/gme/Blip_Buffer_impl.h @@ -0,0 +1,135 @@ +// Internal stuff here to keep public header uncluttered + +// Blip_Buffer $vers +#ifndef BLIP_BUFFER_IMPL_H +#define BLIP_BUFFER_IMPL_H + +typedef unsigned blip_resampled_time_t; + +#ifndef BLIP_MAX_QUALITY + #define BLIP_MAX_QUALITY 32 +#endif + +#ifndef BLIP_BUFFER_ACCURACY + #define BLIP_BUFFER_ACCURACY 16 +#endif + +#ifndef BLIP_PHASE_BITS + #define BLIP_PHASE_BITS 6 +#endif + +class blip_eq_t; +class Blip_Buffer; + +#if BLIP_BUFFER_FAST + // linear interpolation needs 8 bits + #undef BLIP_PHASE_BITS + #define BLIP_PHASE_BITS 8 + + #undef BLIP_MAX_QUALITY + #define BLIP_MAX_QUALITY 2 +#endif + +int const blip_res = 1 << BLIP_PHASE_BITS; +int const blip_buffer_extra_ = BLIP_MAX_QUALITY + 2; + +class Blip_Buffer_ { +public: +// Writer + + typedef int clocks_t; + + // Properties of fixed-point sample position + typedef unsigned fixed_t; // unsigned for more range, optimized shifts + enum { fixed_bits = BLIP_BUFFER_ACCURACY }; // bits in fraction + enum { fixed_unit = 1 << fixed_bits }; // 1.0 samples + + // Converts clock count to fixed-point sample position + fixed_t to_fixed( clocks_t t ) const { return t * factor_ + offset_; } + + // Deltas in buffer are fixed-point with this many fraction bits. + // Less than 16 for extra range. + enum { delta_bits = 14 }; + + // Pointer to first committed delta sample + typedef int delta_t; + + // Pointer to delta corresponding to fixed-point sample position + delta_t* delta_at( fixed_t ); + +// Reader + + delta_t* read_pos() { return buffer_; } + + void clear_modified() { modified_ = false; } + int highpass_shift() const { return bass_shift_; } + int integrator() const { return reader_accum_; } + void set_integrator( int n ) { reader_accum_ = n; } + +public: //friend class Tracked_Blip_Buffer; private: + bool modified() const { return modified_; } + void remove_silence( int count ); + +private: + unsigned factor_; + fixed_t offset_; + delta_t* buffer_center_; + int buffer_size_; + int reader_accum_; + int bass_shift_; + delta_t* buffer_; + int sample_rate_; + int clock_rate_; + int bass_freq_; + int length_; + bool modified_; + + friend class Blip_Buffer; +}; + +class Blip_Synth_Fast_ { +public: + int delta_factor; + int last_amp; + Blip_Buffer* buf; + + void volume_unit( double ); + void treble_eq( blip_eq_t const& ) { } + Blip_Synth_Fast_(); +}; + +class Blip_Synth_ { +public: + int delta_factor; + int last_amp; + Blip_Buffer* buf; + + void volume_unit( double ); + void treble_eq( blip_eq_t const& ); + Blip_Synth_( short phases [], int width ); +private: + double volume_unit_; + short* const phases; + int const width; + int kernel_unit; + + void adjust_impulse(); + void rescale_kernel( int shift ); + int impulses_size() const { return blip_res / 2 * width; } +}; + +class blip_buffer_state_t +{ + blip_resampled_time_t offset_; + int reader_accum_; + int buf [blip_buffer_extra_]; + friend class Blip_Buffer; +}; + +inline Blip_Buffer_::delta_t* Blip_Buffer_::delta_at( fixed_t f ) +{ + assert( (f >> fixed_bits) < (unsigned) buffer_size_ ); + return buffer_center_ + (f >> fixed_bits); +} + +#endif diff --git a/Frameworks/GME/gme/Blip_Buffer_impl2.h b/Frameworks/GME/gme/Blip_Buffer_impl2.h new file mode 100644 index 000000000..0775ca637 --- /dev/null +++ b/Frameworks/GME/gme/Blip_Buffer_impl2.h @@ -0,0 +1,282 @@ +// Internal stuff here to keep public header uncluttered + +// Blip_Buffer $vers +#ifndef BLIP_BUFFER_IMPL2_H +#define BLIP_BUFFER_IMPL2_H + +//// Compatibility + +BLARGG_DEPRECATED( int const blip_low_quality = 8; ) +BLARGG_DEPRECATED( int const blip_med_quality = 8; ) +BLARGG_DEPRECATED( int const blip_good_quality = 12; ) +BLARGG_DEPRECATED( int const blip_high_quality = 16; ) + +BLARGG_DEPRECATED( int const blip_sample_max = 32767; ) + +// Number of bits in raw sample that covers normal output range. Less than 32 bits to give +// extra amplitude range. That is, +// +1 << (blip_sample_bits-1) = +1.0 +// -1 << (blip_sample_bits-1) = -1.0 +int const blip_sample_bits = 30; + +//// BLIP_READER_ + +//// Optimized reading from Blip_Buffer, for use in custom sample buffer or mixer + +// Begins reading from buffer. Name should be unique to the current {} block. +#define BLIP_READER_BEGIN( name, blip_buffer ) \ + const Blip_Buffer::delta_t* BLARGG_RESTRICT name##_reader_buf = (blip_buffer).read_pos();\ + int name##_reader_accum = (blip_buffer).integrator() + +// Gets value to pass to BLIP_READER_NEXT() +#define BLIP_READER_BASS( blip_buffer ) (blip_buffer).highpass_shift() + +// Constant value to use instead of BLIP_READER_BASS(), for slightly more optimal +// code at the cost of having no bass_freq() functionality +int const blip_reader_default_bass = 9; + +// Current sample as 16-bit signed value +#define BLIP_READER_READ( name ) (name##_reader_accum >> (blip_sample_bits - 16)) + +// Current raw sample in full internal resolution +#define BLIP_READER_READ_RAW( name ) (name##_reader_accum) + +// Advances to next sample +#define BLIP_READER_NEXT( name, bass ) \ + (void) (name##_reader_accum += *name##_reader_buf++ - (name##_reader_accum >> (bass))) + +// Ends reading samples from buffer. The number of samples read must now be removed +// using Blip_Buffer::remove_samples(). +#define BLIP_READER_END( name, blip_buffer ) \ + (void) ((blip_buffer).set_integrator( name##_reader_accum )) + +#define BLIP_READER_ADJ_( name, offset ) (name##_reader_buf += offset) + +int const blip_reader_idx_factor = sizeof (Blip_Buffer::delta_t); + +#define BLIP_READER_NEXT_IDX_( name, bass, idx ) {\ + name##_reader_accum -= name##_reader_accum >> (bass);\ + name##_reader_accum += name##_reader_buf [(idx)];\ +} + +#define BLIP_READER_NEXT_RAW_IDX_( name, bass, idx ) {\ + name##_reader_accum -= name##_reader_accum >> (bass);\ + name##_reader_accum +=\ + *(Blip_Buffer::delta_t const*) ((char const*) name##_reader_buf + (idx));\ +} + +//// BLIP_CLAMP + +#if defined (_M_IX86) || defined (_M_IA64) || defined (__i486__) || \ + defined (__x86_64__) || defined (__ia64__) || defined (__i386__) + #define BLIP_X86 1 + #define BLIP_CLAMP_( in ) in < -0x8000 || 0x7FFF < in +#else + #define BLIP_CLAMP_( in ) (blip_sample_t) in != in +#endif + +// Clamp sample to blip_sample_t range +#define BLIP_CLAMP( sample, out )\ + { if ( BLIP_CLAMP_( (sample) ) ) (out) = ((sample) >> 31) ^ 0x7FFF; } + + +//// Blip_Synth + +// (in >> sh & mask) * mul +#define BLIP_SH_AND_MUL( in, sh, mask, mul ) \ +((int) (in) / ((1U << (sh)) / (mul)) & (unsigned) ((mask) * (mul))) + +// (T*) ptr + (off >> sh) +#define BLIP_PTR_OFF_SH( T, ptr, off, sh ) \ + ((T*) (BLIP_SH_AND_MUL( off, sh, -1, sizeof (T) ) + (char*) (ptr))) + +template +inline void Blip_Synth::offset_resampled( blip_resampled_time_t time, + int delta, Blip_Buffer* blip_buf ) const +{ +#if BLIP_BUFFER_FAST + int const half_width = 1; +#else + int const half_width = quality / 2; +#endif + + Blip_Buffer::delta_t* BLARGG_RESTRICT buf = blip_buf->delta_at( time ); + + delta *= impl.delta_factor; + + int const phase_shift = BLIP_BUFFER_ACCURACY - BLIP_PHASE_BITS; + int const phase = (half_width & (half_width - 1)) ? + (int) BLIP_SH_AND_MUL( time, phase_shift, blip_res - 1, sizeof (coeff_t) ) * half_width : + (int) BLIP_SH_AND_MUL( time, phase_shift, blip_res - 1, sizeof (coeff_t) * half_width ); + +#if BLIP_BUFFER_FAST + int left = buf [0] + delta; + + // Kind of crappy, but doing shift after multiply results in overflow. + // Alternate way of delaying multiply by delta_factor results in worse + // sub-sample resolution. + int right = (delta >> BLIP_PHASE_BITS) * phase; + #if BLIP_BUFFER_NOINTERP + // TODO: remove? (just a hack to see how it sounds) + right = 0; + #endif + left -= right; + right += buf [1]; + + buf [0] = left; + buf [1] = right; +#else + + int const fwd = -quality / 2; + int const rev = fwd + quality - 2; + + coeff_t const* BLARGG_RESTRICT imp = (coeff_t const*) ((char const*) phases + phase); + int const phase2 = phase + phase - (blip_res - 1) * half_width * sizeof (coeff_t); + + #define BLIP_MID_IMP imp = (coeff_t const*) ((char const*) imp - phase2); + + #if BLIP_MAX_QUALITY > 16 + // General version for any quality + if ( quality != 8 && quality != 12 && quality != 16 ) + { + buf += fwd; + + // left half + for ( int n = half_width / 2; --n >= 0; ) + { + buf [0] += imp [0] * delta; + buf [1] += imp [1] * delta; + imp += 2; + buf += 2; + } + + // mirrored right half + BLIP_MID_IMP + for ( int n = half_width / 2; --n >= 0; ) + { + buf [0] += imp [-1] * delta; + buf [1] += *(imp -= 2) * delta; + buf += 2; + } + + return; + } + #endif + + // Unrolled versions for qualities 8, 12, and 16 + + #if BLIP_X86 + // This gives better code for x86 + #define BLIP_ADD( out, in ) \ + buf [out] += imp [in] * delta + + #define BLIP_FWD( i ) {\ + BLIP_ADD( fwd + i, i );\ + BLIP_ADD( fwd + 1 + i, i + 1 );\ + } + + #define BLIP_REV( r ) {\ + BLIP_ADD( rev - r, r + 1 );\ + BLIP_ADD( rev + 1 - r, r );\ + } + + BLIP_FWD( 0 ) + BLIP_FWD( 2 ) + if ( quality > 8 ) BLIP_FWD( 4 ) + if ( quality > 12 ) BLIP_FWD( 6 ) + BLIP_MID_IMP + if ( quality > 12 ) BLIP_REV( 6 ) + if ( quality > 8 ) BLIP_REV( 4 ) + BLIP_REV( 2 ) + BLIP_REV( 0 ) + + #else + // Help RISC processors and simplistic compilers by reading ahead of writes + #define BLIP_FWD( i ) {\ + int t0 = i0 * delta + buf [fwd + i];\ + int t1 = imp [i + 1] * delta + buf [fwd + 1 + i];\ + i0 = imp [i + 2];\ + buf [fwd + i] = t0;\ + buf [fwd + 1 + i] = t1;\ + } + + #define BLIP_REV( r ) {\ + int t0 = i0 * delta + buf [rev - r];\ + int t1 = imp [r] * delta + buf [rev + 1 - r];\ + i0 = imp [r - 1];\ + buf [rev - r] = t0;\ + buf [rev + 1 - r] = t1;\ + } + + int i0 = *imp; + BLIP_FWD( 0 ) + if ( quality > 8 ) BLIP_FWD( 2 ) + if ( quality > 12 ) BLIP_FWD( 4 ) + { + int const mid = half_width - 1; + int t0 = i0 * delta + buf [fwd + mid - 1]; + int t1 = imp [mid] * delta + buf [fwd + mid ]; + BLIP_MID_IMP + i0 = imp [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 ) + + int t0 = i0 * delta + buf [rev ]; + int t1 = *imp * delta + buf [rev + 1]; + buf [rev ] = t0; + buf [rev + 1] = t1; + #endif + +#endif +} + +template +#if BLIP_BUFFER_FAST + inline +#endif +void Blip_Synth::offset( blip_time_t t, int delta, Blip_Buffer* buf ) const +{ + offset_resampled( buf->to_fixed( t ), delta, buf ); +} + +template +#if BLIP_BUFFER_FAST + inline +#endif +void Blip_Synth::update( blip_time_t t, int amp ) +{ + int delta = amp - impl.last_amp; + impl.last_amp = amp; + offset_resampled( impl.buf->to_fixed( t ), delta, impl.buf ); +} + + +//// blip_eq_t + +inline blip_eq_t::blip_eq_t( double t ) : + treble( t ), kaiser( 5.2 ), rolloff_freq( 0 ), sample_rate( 44100 ), cutoff_freq( 0 ) { } +inline blip_eq_t::blip_eq_t( double t, int rf, int sr, int cf, double k ) : + treble( t ), kaiser( k ), rolloff_freq( rf ), sample_rate( sr ), cutoff_freq( cf ) { } + + +//// Blip_Buffer + +inline int Blip_Buffer::length() const { return length_; } +inline int Blip_Buffer::samples_avail() const { return (int) (offset_ >> BLIP_BUFFER_ACCURACY); } +inline int Blip_Buffer::sample_rate() const { return sample_rate_; } +inline int Blip_Buffer::output_latency() const { return BLIP_MAX_QUALITY / 2; } +inline int Blip_Buffer::clock_rate() const { return clock_rate_; } +inline void Blip_Buffer::clock_rate( int cps ) { factor_ = clock_rate_factor( clock_rate_ = cps ); } + +inline void Blip_Buffer::remove_silence( int count ) +{ + // fails if you try to remove more samples than available + assert( count <= samples_avail() ); + offset_ -= (blip_resampled_time_t) count << BLIP_BUFFER_ACCURACY; +} + +#endif diff --git a/Frameworks/GME/gme/Bml_Parser.cpp b/Frameworks/GME/gme/Bml_Parser.cpp new file mode 100644 index 000000000..1735ec47a --- /dev/null +++ b/Frameworks/GME/gme/Bml_Parser.cpp @@ -0,0 +1,268 @@ +#include +#include +#include +#include + +#include "Bml_Parser.h" + +Bml_Node Bml_Node::emptyNode; + +Bml_Node::Bml_Node() +{ + name = 0; + value = 0; +} + +Bml_Node::Bml_Node(const Bml_Node &in) +{ + size_t length; + name = 0; + if (in.name) + { + length = strlen(in.name); + name = new char[length + 1]; + memcpy(name, in.name, length + 1); + } + value = 0; + if (in.value) + { + length = strlen(in.value); + value = new char[length + 1]; + memcpy(value, in.value, length + 1); + } + children = in.children; +} + +Bml_Node::~Bml_Node() +{ + delete [] name; + delete [] value; +} + +void Bml_Node::clear() +{ + delete [] name; + delete [] value; + + name = 0; + value = 0; + children.resize( 0 ); +} + +void Bml_Node::setLine(const char *line) +{ + delete [] name; + delete [] value; + + name = 0; + value = 0; + + const char * line_end = strchr(line, '\n'); + if ( !line_end ) line_end = line + strlen(line); + + const char * first_letter = line; + while ( first_letter < line_end && *first_letter <= 0x20 ) first_letter++; + + const char * colon = strchr(first_letter, ':'); + if (colon >= line_end) colon = 0; + const char * last_letter = line_end - 1; + + if (colon) + { + const char * first_value_letter = colon + 1; + while (first_value_letter < line_end && *first_value_letter <= 0x20) first_value_letter++; + last_letter = line_end - 1; + while (last_letter > first_value_letter && *last_letter <= 0x20) last_letter--; + + value = new char[last_letter - first_value_letter + 2]; + memcpy(value, first_value_letter, last_letter - first_value_letter + 1); + value[last_letter - first_value_letter + 1] = '\0'; + + last_letter = colon - 1; + } + + while (last_letter > first_letter && *last_letter <= 0x20) last_letter--; + + name = new char[last_letter - first_letter + 2]; + memcpy(name, first_letter, last_letter - first_letter + 1); + name[last_letter - first_letter + 1] = '\0'; +} + +void Bml_Node::addChild(const Bml_Node &child) +{ + children.push_back(child); +} + +const char * Bml_Node::getName() const +{ + return name; +} + +const char * Bml_Node::getValue() const +{ + return value; +} + +size_t Bml_Node::getChildCount() const +{ + return children.size(); +} + +Bml_Node const& Bml_Node::getChild(size_t index) const +{ + return children[index]; +} + +Bml_Node & Bml_Node::walkToNode(const char *path) +{ + Bml_Node * node = this; + while ( *path ) + { + bool item_found = false; + const char * next_separator = strchr( path, ':' ); + if ( !next_separator ) next_separator = path + strlen(path); + for ( std::vector::iterator it = node->children.end(); it != node->children.begin(); ) + { + --it; + if ( next_separator - path == strlen(it->name) && + strncmp( it->name, path, next_separator - path ) == 0 ) + { + node = &(*it); + item_found = true; + break; + } + } + if ( !item_found ) return emptyNode; + if ( *next_separator ) + { + path = next_separator + 1; + } + else break; + } + return *node; +} + +Bml_Node const& Bml_Node::walkToNode(const char *path) const +{ + Bml_Node const* next_node; + Bml_Node const* node = this; + while ( *path ) + { + bool item_found = false; + size_t array_index = ~0; + const char * array_index_start = strchr( path, '[' ); + const char * next_separator = strchr( path, ':' ); + if ( !next_separator ) next_separator = path + strlen(path); + if ( array_index_start && array_index_start < next_separator ) + { + char * temp; + array_index = strtoul( array_index_start + 1, &temp, 10 ); + } + else + { + array_index_start = next_separator; + } + for ( std::vector::const_iterator it = node->children.begin(), ite = node->children.end(); it != ite; ++it ) + { + if ( array_index_start - path == strlen(it->name) && + strncmp( it->name, path, array_index_start - path ) == 0 ) + { + next_node = &(*it); + item_found = true; + if ( array_index == 0 ) break; + --array_index; + } + } + if ( !item_found ) return emptyNode; + node = next_node; + if ( *next_separator ) + { + path = next_separator + 1; + } + else break; + } + return *node; +} + +void Bml_Parser::parseDocument( const char * source ) +{ + std::vector indents; + std::string last_name; + std::string current_path; + + document.clear(); + + size_t last_indent = ~0; + + Bml_Node node; + + while ( *source ) + { + const char * line_end = strchr( source, '\n' ); + if ( !line_end ) line_end = source + strlen( source ); + + if ( node.getName() ) last_name = node.getName(); + + node.setLine( source ); + + size_t indent = 0; + while ( source < line_end && *source <= 0x20 ) + { + source++; + indent++; + } + + if ( last_indent == ~0 ) last_indent = indent; + + if ( indent > last_indent ) + { + indents.push_back( last_indent ); + last_indent = indent; + if ( current_path.length() ) current_path += ":"; + current_path += last_name; + } + else if ( indent < last_indent ) + { + while ( last_indent > indent && indents.size() ) + { + last_indent = *(indents.end() - 1); + indents.pop_back(); + size_t colon = current_path.find_last_of( ':' ); + if ( colon != ~0 ) current_path.resize( colon ); + else current_path.resize( 0 ); + } + last_indent = indent; + } + + document.walkToNode( current_path.c_str() ).addChild( node ); + + source = line_end; + while ( *source && *source == '\n' ) source++; + } +} + +const char * Bml_Parser::enumValue(const char *path) const +{ + return document.walkToNode(path).getValue(); +} + +#if 0 +void Bml_Parser::print(Bml_Node const* node, unsigned int indent) const +{ + if (node == 0) node = &document; + + for (unsigned i = 0; i < indent; ++i) printf(" "); + + printf("%s", node->getName()); + if (node->getValue()) printf(":%s", node->getValue()); + printf("\n"); + + indent++; + + for (unsigned i = 0, j = node->getChildCount(); i < j; ++i) + { + Bml_Node const& child = node->getChild(i); + print( &child, indent ); + } +} +#endif diff --git a/Frameworks/GME/gme/Bml_Parser.h b/Frameworks/GME/gme/Bml_Parser.h new file mode 100644 index 000000000..c46c51146 --- /dev/null +++ b/Frameworks/GME/gme/Bml_Parser.h @@ -0,0 +1,52 @@ +#ifndef BML_PARSER_H +#define BML_PARSER_H + +#include + +class Bml_Node +{ + char * name; + char * value; + + std::vector children; + + static Bml_Node emptyNode; + +public: + Bml_Node(); + Bml_Node(Bml_Node const& in); + + ~Bml_Node(); + + void clear(); + + void setLine(const char * line); + void addChild(Bml_Node const& child); + + const char * getName() const; + const char * getValue() const; + + size_t getChildCount() const; + Bml_Node const& getChild(size_t index) const; + + Bml_Node & walkToNode( const char * path ); + Bml_Node const& walkToNode( const char * path ) const; +}; + +class Bml_Parser +{ + Bml_Node document; + +public: + Bml_Parser() { } + + void parseDocument(const char * document); + + const char * enumValue(const char * path) const; + +#if 0 + void print(Bml_Node const* node = 0, unsigned int indent = 0) const; +#endif +}; + +#endif // BML_PARSER_H diff --git a/Frameworks/GME/gme/C140_Emu.cpp b/Frameworks/GME/gme/C140_Emu.cpp new file mode 100644 index 000000000..c34c09b6f --- /dev/null +++ b/Frameworks/GME/gme/C140_Emu.cpp @@ -0,0 +1,77 @@ +// Game_Music_Emu $vers. http://www.slack.net/~ant/ + +#include "C140_Emu.h" +#include "c140.h" + +C140_Emu::C140_Emu() { chip = 0; } + +C140_Emu::~C140_Emu() +{ + if ( chip ) device_stop_c140( chip ); +} + +int C140_Emu::set_rate( int type, double sample_rate, double clock_rate ) +{ + if ( chip ) + { + device_stop_c140( chip ); + chip = 0; + } + + chip = device_start_c140( sample_rate, clock_rate, type ); + if ( !chip ) + return 1; + + reset(); + return 0; +} + +void C140_Emu::reset() +{ + device_reset_c140( chip ); + c140_set_mute_mask( chip, 0 ); +} + +void C140_Emu::write( int addr, int data ) +{ + c140_w( chip, addr, data ); +} + +void C140_Emu::write_rom( int size, int start, int length, void * data ) +{ + c140_write_rom( chip, size, start, length, (const UINT8 *) data ); +} + +void C140_Emu::mute_voices( int mask ) +{ + c140_set_mute_mask( chip, mask ); +} + +void C140_Emu::run( int pair_count, sample_t* out ) +{ + stream_sample_t bufL[ 1024 ]; + stream_sample_t bufR[ 1024 ]; + stream_sample_t * buffers[2] = { bufL, bufR }; + + while (pair_count > 0) + { + int todo = pair_count; + if (todo > 1024) todo = 1024; + c140_update( chip, buffers, todo ); + + for (int i = 0; i < todo; i++) + { + int output_l = bufL [i]; + int output_r = bufR [i]; + output_l += out [0]; + output_r += out [1]; + if ( (short)output_l != output_l ) output_l = 0x7FFF ^ ( output_l >> 31 ); + if ( (short)output_r != output_r ) output_r = 0x7FFF ^ ( output_r >> 31 ); + out [0] = output_l; + out [1] = output_r; + out += 2; + } + + pair_count -= todo; + } +} diff --git a/Frameworks/GME/gme/C140_Emu.h b/Frameworks/GME/gme/C140_Emu.h new file mode 100644 index 000000000..8011e91a6 --- /dev/null +++ b/Frameworks/GME/gme/C140_Emu.h @@ -0,0 +1,36 @@ +// C140 sound chip emulator interface + +// Game_Music_Emu $vers +#ifndef C140_EMU_H +#define C140_EMU_H + +class C140_Emu { + void* chip; +public: + C140_Emu(); + ~C140_Emu(); + + // Sets output sample rate and chip clock rates, in Hz. Returns non-zero + // if error. + int set_rate( int type, double sample_rate, double clock_rate ); + + // Resets to power-up state + void reset(); + + // Mutes voice n if bit n (1 << n) of mask is set + enum { channel_count = 24 }; + void mute_voices( int mask ); + + // Writes data to addr + void write( int addr, int data ); + + // Scales ROM size, then writes length bytes from data at start offset + void write_rom( int size, int start, int length, void * data ); + + // Runs and writes pair_count*2 samples to output + typedef short sample_t; + enum { out_chan_count = 2 }; // stereo + void run( int pair_count, sample_t* out ); +}; + +#endif diff --git a/Frameworks/GME/gme/Chip_Resampler.h b/Frameworks/GME/gme/Chip_Resampler.h new file mode 100644 index 000000000..276a50379 --- /dev/null +++ b/Frameworks/GME/gme/Chip_Resampler.h @@ -0,0 +1,147 @@ +// Fir_Resampler chip emulator container that mixes into the output buffer + +// Game_Music_Emu $vers +#ifndef CHIP_RESAMPLER_H +#define CHIP_RESAMPLER_H + +#include "blargg_source.h" + +#include "Fir_Resampler.h" +typedef Fir_Resampler_Norm Chip_Resampler_Downsampler; + +int const resampler_extra = 0; //34; + +template +class Chip_Resampler_Emu : public Emu { + int last_time; + short* out; + typedef short dsample_t; + enum { disabled_time = -1 }; + enum { gain_bits = 14 }; + blargg_vector sample_buf; + int sample_buf_size; + int oversamples_per_frame; + int buf_pos; + int buffered; + int resampler_size; + int gain_; + + Chip_Resampler_Downsampler resampler; + + void mix_samples( short * buf, int count ) + { + dsample_t * inptr = sample_buf.begin(); + for ( unsigned i = 0; i < count * 2; i++ ) + { + int sample = inptr[i]; + sample += buf[i]; + if ((short)sample != sample) sample = 0x7FFF ^ (sample >> 31); + buf[i] = sample; + } + } + +public: + Chip_Resampler_Emu() { last_time = disabled_time; out = NULL; } + blargg_err_t setup( double oversample, double rolloff, double gain ) + { + gain_ = (int) ((1 << gain_bits) * gain); + RETURN_ERR( resampler.set_rate( oversample ) ); + return reset_resampler(); + } + + blargg_err_t reset() + { + Emu::reset(); + resampler.clear(); + return blargg_ok; + } + + blargg_err_t reset_resampler() + { + unsigned int pairs; + double rate = resampler.rate(); + if ( rate >= 1.0 ) pairs = 64.0 * rate; + else pairs = 64.0 / rate; + RETURN_ERR( sample_buf.resize( (pairs + (pairs >> 2)) * 2 ) ); + resize( pairs ); + resampler_size = oversamples_per_frame + (oversamples_per_frame >> 2); + RETURN_ERR( resampler.resize_buffer( resampler_size ) ); + return blargg_ok; + } + + void resize( int pairs ) + { + int new_sample_buf_size = pairs * 2; + //new_sample_buf_size = new_sample_buf_size / 4 * 4; // TODO: needed only for 3:2 downsampler + if ( sample_buf_size != new_sample_buf_size ) + { + if ( (unsigned) new_sample_buf_size > sample_buf.size() ) + { + check( false ); + return; + } + sample_buf_size = new_sample_buf_size; + oversamples_per_frame = int (pairs * resampler.rate()) * 2 + 2; + clear(); + } + } + + void clear() + { + buf_pos = buffered = 0; + resampler.clear(); + } + + void enable( bool b = true ) { last_time = b ? 0 : disabled_time; } + bool enabled() const { return last_time != disabled_time; } + void begin_frame( short* buf ) { out = buf; last_time = 0; } + + int run_until( int time ) + { + int count = time - last_time; + while ( count > 0 ) + { + if ( last_time < 0 ) + return false; + last_time = time; + if ( buffered ) + { + int samples_to_copy = buffered; + if ( samples_to_copy > count ) samples_to_copy = count; + memcpy( out, sample_buf.begin(), samples_to_copy * sizeof(short) * 2 ); + memcpy( sample_buf.begin(), sample_buf.begin() + samples_to_copy * 2, ( buffered - samples_to_copy ) * 2 * sizeof(short) ); + buffered -= samples_to_copy; + count -= samples_to_copy; + continue; + } + int sample_count = oversamples_per_frame - resampler.written() + resampler_extra; + memset( resampler.buffer(), 0, sample_count * sizeof(*resampler.buffer()) ); + Emu::run( sample_count >> 1, resampler.buffer() ); + for ( unsigned i = 0; i < sample_count; i++ ) + { + dsample_t * ptr = resampler.buffer() + i; + *ptr = ( *ptr * gain_ ) >> gain_bits; + } + short* p = out; + resampler.write( sample_count ); + sample_count = resampler.read( sample_buf.begin(), count * 2 > sample_buf_size ? sample_buf_size : count * 2 ) >> 1; + if ( sample_count > count ) + { + out += count * Emu::out_chan_count; + mix_samples( p, count ); + memmove( sample_buf.begin(), sample_buf.begin() + count * 2, (sample_count - count) * 2 * sizeof(short) ); + buffered = sample_count - count; + return true; + } + else if (!sample_count) return true; + out += sample_count * Emu::out_chan_count; + mix_samples( p, sample_count ); + count -= sample_count; + } + return true; + } +}; + + + +#endif diff --git a/Frameworks/GME/gme/Downsampler.cpp b/Frameworks/GME/gme/Downsampler.cpp new file mode 100644 index 000000000..9df5eb2be --- /dev/null +++ b/Frameworks/GME/gme/Downsampler.cpp @@ -0,0 +1,74 @@ +// $package. http://www.slack.net/~ant/ + +#include "Downsampler.h" + +/* Copyright (C) 2004-2008 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +#include "blargg_source.h" + +int const shift = 14; +int const unit = 1 << shift; + +void Downsampler::clear_() +{ + pos = 0; + Resampler::clear_(); +} + +Downsampler::Downsampler() +{ + clear(); +} + +blargg_err_t Downsampler::set_rate_( double new_factor ) +{ + step = (int) (new_factor * unit + 0.5); + return Resampler::set_rate_( 1.0 / unit * step ); +} + +Resampler::sample_t const* Downsampler::resample_( sample_t** out_, + sample_t const* out_end, sample_t const in [], int in_size ) +{ + in_size -= write_offset; + if ( in_size > 0 ) + { + sample_t* BLARGG_RESTRICT out = *out_; + sample_t const* const in_end = in + in_size; + + int const step = this->step; + int pos = this->pos; + + // TODO: IIR filter, then linear resample + // TODO: detect skipped sample, allowing merging of IIR and resample? + + do + { + #define INTERP( i, out )\ + out = (in [0 + i] * (unit - pos) + ((in [2 + i] + in [4 + i] + in [6 + i]) << shift) +\ + in [8 + i] * pos) >> (shift + 2); + + int out_0; + INTERP( 0, out_0 ) + INTERP( 1, out [0] = out_0; out [1] ) + out += stereo; + + pos += step; + in += ((unsigned) pos >> shift) * stereo; + pos &= unit - 1; + } + while ( in < in_end && out < out_end ); + + this->pos = pos; + *out_ = out; + } + return in; +} diff --git a/Frameworks/GME/gme/Downsampler.h b/Frameworks/GME/gme/Downsampler.h new file mode 100644 index 000000000..a26dec466 --- /dev/null +++ b/Frameworks/GME/gme/Downsampler.h @@ -0,0 +1,25 @@ +// Linear downsampler with pre-low-pass + +// $package +#ifndef DOWNSAMPLER_H +#define DOWNSAMPLER_H + +#include "Resampler.h" + +class Downsampler : public Resampler { +public: + Downsampler(); + +protected: + virtual blargg_err_t set_rate_( double ); + virtual void clear_(); + virtual sample_t const* resample_( sample_t**, sample_t const*, sample_t const [], int ); + +private: + enum { stereo = 2 }; + enum { write_offset = 8 * stereo }; + int pos; + int step; +}; + +#endif diff --git a/Frameworks/GME/gme/Gb_Cpu_run.h b/Frameworks/GME/gme/Gb_Cpu_run.h new file mode 100644 index 000000000..69ac8f68f --- /dev/null +++ b/Frameworks/GME/gme/Gb_Cpu_run.h @@ -0,0 +1,1183 @@ +// Game_Music_Emu $vers. http://www.slack.net/~ant/ + +#if 0 +/* Define these macros in the source file before #including this file. +- Parameters might be expressions, so they are best evaluated only once, +though they NEVER have side-effects, so multiple evaluation is OK. +- Output parameters might be a multiple-assignment expression like "a=x", +so they must NOT be parenthesized. +- Macros "returning" void may use a {} statement block. */ + + // 0 <= addr <= 0xFFFF + page_size + // time functions can be used + int READ_MEM( addr_t ); + void WRITE_MEM( addr_t, int data ); + + // Access of 0xFF00 + offset + // 0 <= offset <= 0xFF + int READ_IO( int offset ); + void WRITE_IO( int offset, int data ); + + // Often-used instructions use this instead of READ_MEM + void READ_FAST( addr_t, int& out ); + +// The following can be used within macros: + + // Current time + cpu_time_t TIME(); +#endif + +/* Copyright (C) 2003-2009 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +// Common instructions: +// +// 365880 FA LD A,(nn) +// 355863 20 JR NZ +// 313655 21 LD HL,nn +// 274580 28 JR Z +// 252878 FE CP n +// 230541 7E LD A,(HL) +// 226209 2A LD A,(HL+) +// 217467 CD CALL +// 212034 C9 RET +// 208376 CB CB prefix +// +// 27486 CB 7E BIT 7,(HL) +// 15925 CB 76 BIT 6,(HL) +// 13035 CB 19 RR C +// 11557 CB 7F BIT 7,A +// 10898 CB 37 SWAP A +// 10208 CB 66 BIT 4,(HL) + +// Allows MWCW debugger to step through code properly +#ifdef CPU_BEGIN + CPU_BEGIN +#endif + +#define TIME() s.time + +#define CODE_PAGE( addr ) s.code_map [GB_CPU_PAGE( addr )] +#define READ_CODE( addr ) (CODE_PAGE( addr ) [GB_CPU_OFFSET( addr )]) + +// Flags with hex value for clarity when used as mask. +// Stored in indicated variable during emulation. +int const z80 = 0x80; // cz +int const n40 = 0x40; // ph +int const h20 = 0x20; // ph +int const c10 = 0x10; // cz + +#define SET_FLAGS( in )\ +{\ + cz = ((in) << 4 & 0x100) + (~(in) >> 7 & 1);\ + ph = (~(in) << 2 & 0x100) + ((in) >> 1 & 0x10);\ +} + +// random bits in cz to catch misuse of them +#define SET_FLAGS_DEBUG( in )\ +{\ + cz = ((in) << 4 & 0x100) | (rand() & ~0x1FF) | ((in) & 0x80 ? 0 : (rand() & 0xFF) | 1);\ + ph = (~(in) << 2 & 0x100) | (((in) >> 1 & 0x10) ^ BYTE( cz ));\ +} + +#define GET_FLAGS( out )\ +{\ + out = (cz >> 4 & c10);\ + out += ~ph >> 2 & n40;\ + out += (ph ^ cz) << 1 & h20;\ + if ( !BYTE( cz ) )\ + out += z80;\ +} + +#define CC_NZ() ( BYTE( cz )) +#define CC_Z() (!BYTE( cz )) +#define CC_NC() (!(cz & 0x100)) +#define CC_C() ( cz & 0x100 ) + +// Truncation +#define BYTE( n ) ((BOOST::uint8_t ) (n)) /* (unsigned) n & 0xFF */ +#define SBYTE( n ) ((BOOST::int8_t ) (n)) /* (BYTE( n ) ^ 0x80) - 0x80 */ +#define WORD( n ) ((BOOST::uint16_t) (n)) /* (unsigned) n & 0xFFFF */ + +{ + Gb_Cpu::cpu_state_t s; + CPU.cpu_state = &s; + memcpy( &s, &CPU.cpu_state_, sizeof s ); + + union { + struct { + #if BLARGG_BIG_ENDIAN + byte b, c, d, e, h, l, flags, a; + #else + byte c, b, e, d, l, h, a, flags; + #endif + } rg; // individual registers + Gb_Cpu::core_regs_t rp; // pairs + + byte r8_ [8]; // indexed registers (use R8 macro due to endian dependence) + BOOST::uint16_t r16 [4]; // indexed pairs + }; + BLARGG_STATIC_ASSERT( sizeof rg == 8 && sizeof rp == 8 ); + + #if BLARGG_BIG_ENDIAN + #define R8( n ) (r8_ [n]) + #elif BLARGG_LITTLE_ENDIAN + #define R8( n ) (r8_ [(n) ^ 1]) + #else + // Be sure "blargg_endian.h" has been #included in the file that #includes this + #error "Byte order of CPU must be known" + #endif + + rp = CPU.r; + int pc = CPU.r.pc; + int sp = CPU.r.sp; + int ph; + int cz; + SET_FLAGS( rg.flags ); + + int time = s.time; + +loop: + + check( (unsigned) pc < 0x10000 + 1 ); // +1 so emulator can catch wrap-around + check( (unsigned) sp < 0x10000 ); + + byte const* instr = CODE_PAGE( pc ); + int op; + + if ( GB_CPU_OFFSET(~0) == ~0 ) + { + op = instr [pc]; + pc++; + instr += pc; + } + else + { + instr += GB_CPU_OFFSET( pc ); + op = *instr++; + pc++; + } + +#define GET_ADDR() GET_LE16( instr ) + + static byte const instr_times [256*2] = { + // 0 1 2 3 4 5 6 7 8 9 A B C D E F + 4,12, 8, 8, 4, 4, 8, 4,20, 8, 8, 8, 4, 4, 8, 4,// 0 + 4,12, 8, 8, 4, 4, 8, 4,12, 8, 8, 8, 4, 4, 8, 4,// 1 + 8,12, 8, 8, 4, 4, 8, 4, 8, 8, 8, 8, 4, 4, 8, 4,// 2 + 8,12, 8, 8,12,12,12, 4, 8, 8, 8, 8, 4, 4, 8, 4,// 3 + 4, 4, 4, 4, 4, 4, 8, 4, 4, 4, 4, 4, 4, 4, 8, 4,// 4 + 4, 4, 4, 4, 4, 4, 8, 4, 4, 4, 4, 4, 4, 4, 8, 4,// 5 + 4, 4, 4, 4, 4, 4, 8, 4, 4, 4, 4, 4, 4, 4, 8, 4,// 6 + 8, 8, 8, 8, 8, 8, 0, 8, 4, 4, 4, 4, 4, 4, 8, 4,// 7 + 4, 4, 4, 4, 4, 4, 8, 4, 4, 4, 4, 4, 4, 4, 8, 4,// 8 + 4, 4, 4, 4, 4, 4, 8, 4, 4, 4, 4, 4, 4, 4, 8, 4,// 9 + 4, 4, 4, 4, 4, 4, 8, 4, 4, 4, 4, 4, 4, 4, 8, 4,// A + 4, 4, 4, 4, 4, 4, 8, 4, 4, 4, 4, 4, 4, 4, 8, 4,// B + 8,12,16,16,12,16, 8,16, 8,16,16, 0,12,24, 8,16,// C + 8,12,16, 0,12,16, 8,16, 8,16,16, 0,12, 0, 8,16,// D + 12,12, 8, 0, 0,16, 8,16,16, 4,16, 0, 0, 0, 8,16,// E + 12,12, 8, 4, 0,16, 8,16,12, 8,16, 4, 0, 0, 8,16,// F + + // CB prefixed + // 0 1 2 3 4 5 6 7 8 9 A B C D E F + 8, 8, 8, 8, 8, 8,16, 8, 8, 8, 8, 8, 8, 8,16, 8,// 0 + 8, 8, 8, 8, 8, 8,16, 8, 8, 8, 8, 8, 8, 8,16, 8,// 1 + 8, 8, 8, 8, 8, 8,16, 8, 8, 8, 8, 8, 8, 8,16, 8,// 2 + 8, 8, 8, 8, 8, 8,16, 8, 8, 8, 8, 8, 8, 8,16, 8,// 3 + 8, 8, 8, 8, 8, 8,12, 8, 8, 8, 8, 8, 8, 8,12, 8,// 4 + 8, 8, 8, 8, 8, 8,12, 8, 8, 8, 8, 8, 8, 8,12, 8,// 5 + 8, 8, 8, 8, 8, 8,12, 8, 8, 8, 8, 8, 8, 8,12, 8,// 6 + 8, 8, 8, 8, 8, 8,12, 8, 8, 8, 8, 8, 8, 8,12, 8,// 7 + 8, 8, 8, 8, 8, 8,16, 8, 8, 8, 8, 8, 8, 8,16, 8,// 8 + 8, 8, 8, 8, 8, 8,16, 8, 8, 8, 8, 8, 8, 8,16, 8,// 9 + 8, 8, 8, 8, 8, 8,16, 8, 8, 8, 8, 8, 8, 8,16, 8,// A + 8, 8, 8, 8, 8, 8,16, 8, 8, 8, 8, 8, 8, 8,16, 8,// B + 8, 8, 8, 8, 8, 8,16, 8, 8, 8, 8, 8, 8, 8,16, 8,// C + 8, 8, 8, 8, 8, 8,16, 8, 8, 8, 8, 8, 8, 8,16, 8,// D + 8, 8, 8, 8, 8, 8,16, 8, 8, 8, 8, 8, 8, 8,16, 8,// E + 8, 8, 8, 8, 8, 8,16, 8, 8, 8, 8, 8, 8, 8,16, 8,// F + }; + + if ( time >= 0 ) + goto stop; + + time += instr_times [op]; + + int data; + data = *instr; + s.time = time; + + #ifdef CPU_INSTR_HOOK + { CPU_INSTR_HOOK( (pc-1), (instr-1), rg.a, rp.bc, rp.de, rp.hl, sp ); } + #endif + + switch ( op ) + { + +// TODO: more efficient way to handle negative branch that wraps PC around +#define BRANCH_( cond, clocks )\ +{\ + pc++;\ + if ( !(cond) )\ + goto loop;\ + pc = WORD( pc + SBYTE( data ) );\ + time += clocks;\ + goto loop;\ +} + +#define BRANCH( cond ) BRANCH_( cond, 4 ) + +// Most Common + + case 0x20: // JR NZ + BRANCH( CC_NZ() ) + + case 0x21: // LD HL,IMM (common) + rp.hl = GET_ADDR(); + pc += 2; + goto loop; + + case 0x28: // JR Z + BRANCH( CC_Z() ) + + case 0xF2: // LD A,(0xFF00+C) + READ_IO( rg.c, rg.a ); + goto loop; + + case 0xF0: // LD A,(0xFF00+imm) + pc++; + READ_IO( data, rg.a ); + goto loop; + + { + int temp; + case 0x0A: // LD A,(BC) + temp = rp.bc; + goto ld_a_ind_comm; + + case 0x3A: // LD A,(HL-) + temp = rp.hl; + rp.hl = temp - 1; + goto ld_a_ind_comm; + + case 0x1A: // LD A,(DE) + temp = rp.de; + goto ld_a_ind_comm; + + case 0x2A: // LD A,(HL+) (common) + temp = rp.hl; + rp.hl = temp + 1; + goto ld_a_ind_comm; + + case 0xFA: // LD A,IND16 (common) + temp = GET_ADDR(); + pc += 2; + ld_a_ind_comm: + READ_FAST( temp, rg.a ); + goto loop; + } + + { + int temp; + case 0xBE: // CP (HL) + temp = READ_MEM( rp.hl ); + goto cmp_comm; + + case 0xB8: // CP B + case 0xB9: // CP C + case 0xBA: // CP D + case 0xBB: // CP E + case 0xBC: // CP H + case 0xBD: // CP L + case 0xBF: // CP A + temp = R8( op & 7 ); + cmp_comm: + ph = rg.a ^ temp; // N=1 H=* + cz = rg.a - temp; // C=* Z=* + goto loop; + } + + case 0xFE: // CP IMM + pc++; + ph = rg.a ^ data; // N=1 H=* + cz = rg.a - data; // C=* Z=* + goto loop; + + case 0x46: // LD B,(HL) + case 0x4E: // LD C,(HL) + case 0x56: // LD D,(HL) + case 0x5E: // LD E,(HL) + case 0x66: // LD H,(HL) + case 0x6E: // LD L,(HL) + case 0x7E:{// LD A,(HL) + int addr = rp.hl; + READ_FAST( addr, R8( op >> 3 & 7 ) ); + goto loop; + } + + case 0xC4: // CNZ (next-most-common) + pc += 2; + if ( CC_Z() ) + goto loop; + call: + time += 12; + pc -= 2; + case 0xCD: // CALL (most-common) + data = pc + 2; + pc = GET_ADDR(); + push: { + int addr = WORD( sp - 1 ); + WRITE_MEM( addr, (data >> 8) ); + sp = WORD( sp - 2 ); + WRITE_MEM( sp, data ); + goto loop; + } + + case 0xC8: // RET Z (next-most-common) + if ( CC_NZ() ) + goto loop; + ret: + time += 12; + case 0xD9: // RETI + case 0xC9:{// RET (most common) + pc = READ_MEM( sp ); + int addr = sp + 1; + sp = WORD( sp + 2 ); + pc += 0x100 * READ_MEM( addr ); + goto loop; + } + + case 0x00: // NOP + case 0x40: // LD B,B + case 0x49: // LD C,C + case 0x52: // LD D,D + case 0x5B: // LD E,E + case 0x64: // LD H,H + case 0x6D: // LD L,L + case 0x7F: // LD A,A + goto loop; + +// CB Instructions + + case 0xCB: + time += (instr_times + 256) [data]; + pc++; + // now data is the opcode + switch ( data ) { + + case 0x46: // BIT b,(HL) + case 0x4E: + case 0x56: + case 0x5E: + case 0x66: + case 0x6E: + case 0x76: + case 0x7E: { + int addr = rp.hl; + READ_FAST( addr, op ); + goto bit_comm; + } + + case 0x40: case 0x41: case 0x42: case 0x43: // BIT b,r + case 0x44: case 0x45: case 0x47: case 0x48: + case 0x49: case 0x4A: case 0x4B: case 0x4C: + case 0x4D: case 0x4F: case 0x50: case 0x51: + case 0x52: case 0x53: case 0x54: case 0x55: + case 0x57: case 0x58: case 0x59: case 0x5A: + case 0x5B: case 0x5C: case 0x5D: case 0x5F: + case 0x60: case 0x61: case 0x62: case 0x63: + case 0x64: case 0x65: case 0x67: case 0x68: + case 0x69: case 0x6A: case 0x6B: case 0x6C: + case 0x6D: case 0x6F: case 0x70: case 0x71: + case 0x72: case 0x73: case 0x74: case 0x75: + case 0x77: case 0x78: case 0x79: case 0x7A: + case 0x7B: case 0x7C: case 0x7D: case 0x7F: + op = R8( data & 7 ); + bit_comm: + ph = op >> (data >> 3 & 7) & 1; + cz = (cz & 0x100) + ph; + ph ^= 0x110; // N=0 H=1 + goto loop; + + case 0x86: // RES b,(HL) + case 0x8E: + case 0x96: + case 0x9E: + case 0xA6: + case 0xAE: + case 0xB6: + case 0xBE: { + int temp = READ_MEM( rp.hl ); + temp &= ~(1 << (data >> 3 & 7)); + WRITE_MEM( rp.hl, temp ); + goto loop; + } + + case 0xC6: // SET b,(HL) + case 0xCE: + case 0xD6: + case 0xDE: + case 0xE6: + case 0xEE: + case 0xF6: + case 0xFE: { + int temp = READ_MEM( rp.hl ); + temp |= 1 << (data >> 3 & 7); + WRITE_MEM( rp.hl, temp ); + goto loop; + } + + case 0xC0: case 0xC1: case 0xC2: case 0xC3: // SET b,r + case 0xC4: case 0xC5: case 0xC7: case 0xC8: + case 0xC9: case 0xCA: case 0xCB: case 0xCC: + case 0xCD: case 0xCF: case 0xD0: case 0xD1: + case 0xD2: case 0xD3: case 0xD4: case 0xD5: + case 0xD7: case 0xD8: case 0xD9: case 0xDA: + case 0xDB: case 0xDC: case 0xDD: case 0xDF: + case 0xE0: case 0xE1: case 0xE2: case 0xE3: + case 0xE4: case 0xE5: case 0xE7: case 0xE8: + case 0xE9: case 0xEA: case 0xEB: case 0xEC: + case 0xED: case 0xEF: case 0xF0: case 0xF1: + case 0xF2: case 0xF3: case 0xF4: case 0xF5: + case 0xF7: case 0xF8: case 0xF9: case 0xFA: + case 0xFB: case 0xFC: case 0xFD: case 0xFF: + R8( data & 7 ) |= 1 << (data >> 3 & 7); + goto loop; + + case 0x80: case 0x81: case 0x82: case 0x83: // RES b,r + case 0x84: case 0x85: case 0x87: case 0x88: + case 0x89: case 0x8A: case 0x8B: case 0x8C: + case 0x8D: case 0x8F: case 0x90: case 0x91: + case 0x92: case 0x93: case 0x94: case 0x95: + case 0x97: case 0x98: case 0x99: case 0x9A: + case 0x9B: case 0x9C: case 0x9D: case 0x9F: + case 0xA0: case 0xA1: case 0xA2: case 0xA3: + case 0xA4: case 0xA5: case 0xA7: case 0xA8: + case 0xA9: case 0xAA: case 0xAB: case 0xAC: + case 0xAD: case 0xAF: case 0xB0: case 0xB1: + case 0xB2: case 0xB3: case 0xB4: case 0xB5: + case 0xB7: case 0xB8: case 0xB9: case 0xBA: + case 0xBB: case 0xBC: case 0xBD: case 0xBF: + R8( data & 7 ) &= ~(1 << (data >> 3 & 7)); + goto loop; + + case 0x36: // SWAP (HL) + op = READ_MEM( rp.hl ); + goto swap_comm; + + case 0x30: // SWAP B + case 0x31: // SWAP C + case 0x32: // SWAP D + case 0x33: // SWAP E + case 0x34: // SWAP H + case 0x35: // SWAP L + case 0x37: // SWAP A + op = R8( data & 7 ); + swap_comm: + op = (op >> 4) + (op << 4); + cz = BYTE( op ); + ph = cz + 0x100; + if ( data == 0x36 ) + goto write_hl_op_ff; + R8( data & 7 ) = op; + goto loop; + +// Shift/Rotate + + case 0x26: // SLA (HL) + cz = 0; + case 0x16: // RL (HL) + cz = (cz >> 8 & 1) + (READ_MEM( rp.hl ) << 1); + goto rl_hl_common; + + case 0x06: // RLC (HL) + cz = READ_MEM( rp.hl ); + cz = (cz << 1) + (cz >> 7 & 1); + rl_hl_common: + // Z=* C=* + ph = cz | 0x100; // N=0 H=0 + WRITE_MEM( rp.hl, cz ); + goto loop; + + case 0x20: case 0x21: case 0x22: case 0x23: case 0x24: case 0x25: case 0x27: // SLA r + cz = 0; + case 0x10: case 0x11: case 0x12: case 0x13: case 0x14: case 0x15: case 0x17: // RL r + cz = (cz >> 8 & 1) + (R8( data & 7 ) << 1); + goto rl_common; + + case 0x00: case 0x01: case 0x02: case 0x03: case 0x04: case 0x05: case 0x07: // RLC r + cz = R8( data & 7 ); + cz = (cz << 1) + (cz >> 7 & 1); + rl_common: + // Z=* C=* + ph = cz | 0x100; // N=0 H=0 + R8( data & 7 ) = cz; + goto loop; + + case 0x0E: // RRC (HL) + cz = READ_MEM( rp.hl ); + cz += cz << 8 & 0x100; + goto rr_hl_common; + + case 0x2E: // SRA (HL) + cz = READ_MEM( rp.hl ); + cz += cz << 1 & 0x100; + goto rr_hl_common; + + case 0x3E: // SRL (HL) + cz = 0; + case 0x1E: // RR (HL) + cz = (cz & 0x100) + READ_MEM( rp.hl ); + rr_hl_common: + cz = (cz << 8) + (cz >> 1); // Z=* C=* + ph = cz | 0x100; // N=0 H=0 + WRITE_MEM( rp.hl, cz ); + goto loop; + + case 0x08: case 0x09: case 0x0A: case 0x0B: case 0x0C: case 0x0D: case 0x0F: // RRC r + cz = R8( data & 7 ); + cz += cz << 8 & 0x100; + goto rr_common; + + case 0x28: case 0x29: case 0x2A: case 0x2B: case 0x2C: case 0x2D: case 0x2F: // SRA r + cz = R8( data & 7 ); + cz += cz << 1 & 0x100; + goto rr_common; + + case 0x38: case 0x39: case 0x3A: case 0x3B: case 0x3C: case 0x3D: case 0x3F: // SRL r + cz = 0; + case 0x18: case 0x19: case 0x1A: case 0x1B: case 0x1C: case 0x1D: case 0x1F: // RR r + cz = (cz & 0x100) + R8( data & 7 ); + rr_common: + cz = (cz << 8) + (cz >> 1); // Z=* C=* + ph = cz | 0x100; // N=0 H=0 + R8( data & 7 ) = cz; + goto loop; + + } // CB op + assert( false ); // unhandled CB op + + case 0x07: // RLCA + cz = rg.a >> 7; + goto rlc_common; + case 0x17: // RLA + cz = cz >> 8 & 1; + rlc_common: + cz += rg.a << 1; + ph = cz | 0x100; + rg.a = BYTE( cz ); + cz |= 1; + goto loop; + + case 0x0F: // RRCA + ph = rg.a << 8; + goto rrc_common; + case 0x1F: // RRA + ph = cz; + rrc_common: + cz = (rg.a << 8) + 1; // Z=0 C=* + rg.a = ((ph & 0x100) + rg.a) >> 1; + ph = 0x100; // N=0 H=0 + goto loop; + +// Load + + case 0x70: // LD (HL),B + case 0x71: // LD (HL),C + case 0x72: // LD (HL),D + case 0x73: // LD (HL),E + case 0x74: // LD (HL),H + case 0x75: // LD (HL),L + case 0x77: // LD (HL),A + op = R8( op & 7 ); + write_hl_op_ff: + WRITE_MEM( rp.hl, op ); + goto loop; + + case 0x41: case 0x42: case 0x43: case 0x44: case 0x45: case 0x47: // LD r,r + case 0x48: case 0x4A: case 0x4B: case 0x4C: case 0x4D: case 0x4F: + case 0x50: case 0x51: case 0x53: case 0x54: case 0x55: case 0x57: + case 0x58: case 0x59: case 0x5A: case 0x5C: case 0x5D: case 0x5F: + case 0x60: case 0x61: case 0x62: case 0x63: case 0x65: case 0x67: + case 0x68: case 0x69: case 0x6A: case 0x6B: case 0x6C: case 0x6F: + case 0x78: case 0x79: case 0x7A: case 0x7B: case 0x7C: case 0x7D: + R8( op >> 3 & 7 ) = R8( op & 7 ); + goto loop; + + case 0x08: // LD IND16,SP + data = GET_ADDR(); + pc += 2; + WRITE_MEM( data, sp ); + data++; + WRITE_MEM( data, (sp >> 8) ); + goto loop; + + case 0xF9: // LD SP,HL + sp = rp.hl; + goto loop; + + case 0x31: // LD SP,IMM + sp = GET_ADDR(); + pc += 2; + goto loop; + + case 0x01: // LD BC,IMM + case 0x11: // LD DE,IMM + r16 [(unsigned) op >> 4] = GET_ADDR(); + pc += 2; + goto loop; + + case 0xE2: // LD (0xFF00+C),A + WRITE_IO( rg.c, rg.a ); + goto loop; + + case 0xE0: // LD (0xFF00+imm),A + pc++; + WRITE_IO( data, rg.a ); + goto loop; + + { + int temp; + case 0x32: // LD (HL-),A + temp = rp.hl; + rp.hl = temp - 1; + goto write_data_rg_a; + + case 0x02: // LD (BC),A + temp = rp.bc; + goto write_data_rg_a; + + case 0x12: // LD (DE),A + temp = rp.de; + goto write_data_rg_a; + + case 0x22: // LD (HL+),A + temp = rp.hl; + rp.hl = temp + 1; + goto write_data_rg_a; + + case 0xEA: // LD IND16,A (common) + temp = GET_ADDR(); + pc += 2; + write_data_rg_a: + WRITE_MEM( temp, rg.a ); + goto loop; + } + + case 0x06: // LD B,IMM + rg.b = data; + pc++; + goto loop; + + case 0x0E: // LD C,IMM + rg.c = data; + pc++; + goto loop; + + case 0x16: // LD D,IMM + rg.d = data; + pc++; + goto loop; + + case 0x1E: // LD E,IMM + rg.e = data; + pc++; + goto loop; + + case 0x26: // LD H,IMM + rg.h = data; + pc++; + goto loop; + + case 0x2E: // LD L,IMM + rg.l = data; + pc++; + goto loop; + + case 0x36: // LD (HL),IMM + WRITE_MEM( rp.hl, data ); + pc++; + goto loop; + + case 0x3E: // LD A,IMM + rg.a = data; + pc++; + goto loop; + +// Increment/decrement + + case 0x03: // INC BC + case 0x13: // INC DE + case 0x23: // INC HL + r16 [(unsigned) op >> 4]++; + goto loop; + + case 0x33: // INC SP + sp = WORD( sp + 1 ); + goto loop; + + case 0x0B: // DEC BC + case 0x1B: // DEC DE + case 0x2B: // DEC HL + r16 [(unsigned) op >> 4]--; + goto loop; + + case 0x3B: // DEC SP + sp = WORD( sp - 1 ); + goto loop; + + case 0x34: // INC (HL) + op = rp.hl; + data = READ_MEM( op ); + data++; + WRITE_MEM( op, data ); + goto inc_comm; + + case 0x04: // INC B + case 0x0C: // INC C (common) + case 0x14: // INC D + case 0x1C: // INC E + case 0x24: // INC H + case 0x2C: // INC L + case 0x3C: // INC A + op = op >> 3 & 7; + data = R8( op ) + 1; + R8( op ) = data; + inc_comm: + ph = data - 0x101; // N=0 H=* + cz = (cz & 0x100) + BYTE( data ); // C=- Z=* + goto loop; + + case 0x35: // DEC (HL) + op = rp.hl; + data = READ_MEM( op ); + data--; + WRITE_MEM( op, data ); + goto dec_comm; + + case 0x05: // DEC B + case 0x0D: // DEC C + case 0x15: // DEC D + case 0x1D: // DEC E + case 0x25: // DEC H + case 0x2D: // DEC L + case 0x3D: // DEC A + op = op >> 3 & 7; + data = R8( op ) - 1; + R8( op ) = data; + dec_comm: + ph = data + 1; // N=1 H=* + cz = (cz & 0x100) + BYTE( data ); // C=- Z=* + goto loop; + +// Add 16-bit + + case 0xF8: // LD HL,SP+n + case 0xE8:{// ADD SP,n + pc++; + int t = WORD( sp + SBYTE( data ) ); + cz = ((BYTE( sp ) + data) & 0x100) + 1; // Z=0 C=* + ph = (sp ^ data ^ t) | 0x100; // N=0 H=* + if ( op == 0xF8 ) + { + rp.hl = t; + goto loop; + } + sp = t; + goto loop; + } + + case 0x39: // ADD HL,SP + data = sp; + goto add_hl_comm; + + case 0x09: // ADD HL,BC + case 0x19: // ADD HL,DE + case 0x29: // ADD HL,HL + data = r16 [(unsigned) op >> 4]; + add_hl_comm: + ph = rp.hl ^ data; + data += rp.hl; + rp.hl = WORD( data ); + ph ^= data; + cz = BYTE( cz ) + (data >> 8 & 0x100); // C=* Z=- + ph = ((ph >> 8) ^ cz) | 0x100; // N=0 H=* + goto loop; + + case 0x86: // ADD (HL) + data = READ_MEM( rp.hl ); + goto add_comm; + + case 0x80: // ADD B + case 0x81: // ADD C + case 0x82: // ADD D + case 0x83: // ADD E + case 0x84: // ADD H + case 0x85: // ADD L + case 0x87: // ADD A + data = R8( op & 7 ); + goto add_comm; + + case 0xC6: // ADD IMM + pc++; + add_comm: + ph = (rg.a ^ data) | 0x100; // N=1 H=* + cz = rg.a + data; // C=* Z=* + rg.a = cz; + goto loop; + +// Add/Subtract + + case 0x8E: // ADC (HL) + data = READ_MEM( rp.hl ); + goto adc_comm; + + case 0x88: // ADC B + case 0x89: // ADC C + case 0x8A: // ADC D + case 0x8B: // ADC E + case 0x8C: // ADC H + case 0x8D: // ADC L + case 0x8F: // ADC A + data = R8( op & 7 ); + goto adc_comm; + + case 0xCE: // ADC IMM + pc++; + adc_comm: + ph = (rg.a ^ data) | 0x100; // N=1 H=* + cz = rg.a + data + (cz >> 8 & 1); // C=* Z=* + rg.a = cz; + goto loop; + + case 0x96: // SUB (HL) + data = READ_MEM( rp.hl ); + goto sub_comm; + + case 0x90: // SUB B + case 0x91: // SUB C + case 0x92: // SUB D + case 0x93: // SUB E + case 0x94: // SUB H + case 0x95: // SUB L + case 0x97: // SUB A + data = R8( op & 7 ); + goto sub_comm; + + case 0xD6: // SUB IMM + pc++; + sub_comm: + ph = rg.a ^ data; // N=1 H=* + cz = rg.a - data; // C=* Z=* + rg.a = cz; + goto loop; + + case 0x9E: // SBC (HL) + data = READ_MEM( rp.hl ); + goto sbc_comm; + + case 0x98: // SBC B + case 0x99: // SBC C + case 0x9A: // SBC D + case 0x9B: // SBC E + case 0x9C: // SBC H + case 0x9D: // SBC L + case 0x9F: // SBC A + data = R8( op & 7 ); + goto sbc_comm; + + case 0xDE: // SBC IMM + pc++; + sbc_comm: + ph = rg.a ^ data; // N=1 H=* + cz = rg.a - data - (cz >> 8 & 1); // C=* Z=* + rg.a = cz; + goto loop; + +// Logical + + case 0xA0: // AND B + case 0xA1: // AND C + case 0xA2: // AND D + case 0xA3: // AND E + case 0xA4: // AND H + case 0xA5: // AND L + data = R8( op & 7 ); + goto and_comm; + + case 0xA6: // AND (HL) + data = READ_MEM( rp.hl ); + goto and_comm; + case 0xE6: // AND IMM + pc++; + and_comm: + cz = rg.a & data; // C=0 Z=* + ph = ~cz; // N=0 H=1 + rg.a = cz; + goto loop; + + case 0xA7: // AND A + cz = rg.a; // C=0 Z=* + ph = ~rg.a; // N=0 H=1 + goto loop; + + case 0xB0: // OR B + case 0xB1: // OR C + case 0xB2: // OR D + case 0xB3: // OR E + case 0xB4: // OR H + case 0xB5: // OR L + data = R8( op & 7 ); + goto or_comm; + + case 0xB6: // OR (HL) + data = READ_MEM( rp.hl ); + goto or_comm; + case 0xF6: // OR IMM + pc++; + or_comm: + cz = rg.a | data; // C=0 Z=* + ph = cz | 0x100; // N=0 H=0 + rg.a = cz; + goto loop; + + case 0xB7: // OR A + cz = rg.a; // C=0 Z=* + ph = rg.a + 0x100; // N=0 H=0 + goto loop; + + case 0xA8: // XOR B + case 0xA9: // XOR C + case 0xAA: // XOR D + case 0xAB: // XOR E + case 0xAC: // XOR H + case 0xAD: // XOR L + data = R8( op & 7 ); + goto xor_comm; + + case 0xAE: // XOR (HL) + data = READ_MEM( rp.hl ); + pc--; + case 0xEE: // XOR IMM + pc++; + xor_comm: + cz = rg.a ^ data; // C=0 Z=* + ph = cz + 0x100; // N=0 H=0 + rg.a = cz; + goto loop; + + case 0xAF: // XOR A + rg.a = 0; + cz = 0; // C=0 Z=* + ph = 0x100; // N=0 H=0 + goto loop; + +// Stack + + case 0xF1: // POP AF + case 0xC1: // POP BC + case 0xD1: // POP DE + case 0xE1: // POP HL (common) + data = READ_MEM( sp ); + r16 [op >> 4 & 3] = data + 0x100 * READ_MEM( (sp + 1) ); + sp = WORD( sp + 2 ); + if ( op != 0xF1 ) + goto loop; + + SET_FLAGS( rg.a ); + rg.a = rg.flags; + goto loop; + + case 0xC5: // PUSH BC + data = rp.bc; + goto push; + + case 0xD5: // PUSH DE + data = rp.de; + goto push; + + case 0xE5: // PUSH HL + data = rp.hl; + goto push; + + case 0xF5: // PUSH AF + GET_FLAGS( data ); + data += rg.a << 8; + goto push; + +// Flow control + + case 0xFF: case 0xC7: case 0xCF: case 0xD7: // RST + case 0xDF: case 0xE7: case 0xEF: case 0xF7: + data = pc; + pc = (op & 0x38) + CPU.rst_base; + goto push; + + case 0xCC: // CALL Z + pc += 2; + if ( CC_Z() ) + goto call; + goto loop; + + case 0xD4: // CALL NC + pc += 2; + if ( CC_NC() ) + goto call; + goto loop; + + case 0xDC: // CALL C + pc += 2; + if ( CC_C() ) + goto call; + goto loop; + + case 0xC0: // RET NZ + if ( CC_NZ() ) + goto ret; + goto loop; + + case 0xD0: // RET NC + if ( CC_NC() ) + goto ret; + goto loop; + + case 0xD8: // RET C + if ( CC_C() ) + goto ret; + goto loop; + + case 0x18: // JR + BRANCH_( true, 0 ) + + case 0x30: // JR NC + BRANCH( CC_NC() ) + + case 0x38: // JR C + BRANCH( CC_C() ) + + case 0xE9: // LD PC,HL + pc = rp.hl; + goto loop; + + case 0xC3: // JP (next-most-common) + pc = GET_ADDR(); + goto loop; + + case 0xC2: // JP NZ + pc += 2; + if ( CC_NZ() ) + goto jp_taken; + time -= 4; + goto loop; + + case 0xCA: // JP Z (most common) + pc += 2; + if ( CC_Z() ) + goto jp_taken; + time -= 4; + goto loop; + + jp_taken: + pc -= 2; + pc = GET_ADDR(); + goto loop; + + case 0xD2: // JP NC + pc += 2; + if ( CC_NC() ) + goto jp_taken; + time -= 4; + goto loop; + + case 0xDA: // JP C + pc += 2; + if ( CC_C() ) + goto jp_taken; + time -= 4; + goto loop; + +// Flags + + case 0x2F: // CPL + rg.a = ~rg.a; + ph = BYTE( ~cz ); // N=1 H=1 + goto loop; + + case 0x3F: // CCF + ph = cz | 0x100; // N=0 H=0 + cz ^= 0x100; // C=* Z=- + goto loop; + + case 0x37: // SCF + ph = cz | 0x100; // N=0 H=0 + cz |= 0x100; // C=1 Z=- + goto loop; + + case 0xF3: // DI + goto loop; + + case 0xFB: // EI + goto loop; + + case 0x27:{// DAA + unsigned a = rg.a; + int h = ph ^ cz; + if ( ph & 0x100 ) + { + if ( (h & 0x10) || (a & 0x0F) > 9 ) + a += 6; + + if ( (cz & 0x100) || a > 0x9F ) + a += 0x60; + } + else + { + if ( h & 0x10 ) + a = (a - 6) & 0xFF; + + if ( cz & 0x100 ) + a -= 0x60; + } + cz = (cz & 0x100) | a; // C=- Z=* + rg.a = a; + ph = (ph & 0x100) + BYTE( a ); // N=- H=0 + goto loop; + } + +// Special + + case 0x76: // HALT + case 0x10: // STOP + case 0xD3: case 0xDB: case 0xDD: // Illegal + case 0xE3: case 0xE4: case 0xEB: case 0xEC: case 0xED: // (all freeze CPU) + case 0xF4: case 0xFC: case 0xFD: + goto stop; + } + + // If this fails then an opcode isn't handled above + assert( false ); + +stop: + pc--; + + // copy state back + CPU.cpu_state_.time = time; + CPU.r.pc = pc; + CPU.r.sp = sp; + { + int t; + GET_FLAGS( t ); + rg.flags = t; + } + CPU.cpu_state = &CPU.cpu_state_; + STATIC_CAST(Gb_Cpu::core_regs_t&,CPU.r) = rp; +} diff --git a/Frameworks/GME/gme/Gbs_Core.cpp b/Frameworks/GME/gme/Gbs_Core.cpp new file mode 100644 index 000000000..5703db69b --- /dev/null +++ b/Frameworks/GME/gme/Gbs_Core.cpp @@ -0,0 +1,208 @@ +// Game_Music_Emu $vers. http://www.slack.net/~ant/ + +#include "Gbs_Core.h" + +#include "blargg_endian.h" + +/* Copyright (C) 2003-2009 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +#include "blargg_source.h" + +int const tempo_unit = 16; +int const idle_addr = 0xF00D; +int const bank_size = 0x4000; + +Gbs_Core::Gbs_Core() : rom( bank_size ) +{ + tempo = tempo_unit; + assert( offsetof (header_t,copyright [32]) == header_t::size ); +} + +Gbs_Core::~Gbs_Core() { } + +void Gbs_Core::unload() +{ + header_.timer_mode = 0; // set_tempo() reads this + rom.clear(); + Gme_Loader::unload(); +} + +bool Gbs_Core::header_t::valid_tag() const +{ + return 0 == memcmp( tag, "GBS", 3 ); +} + +blargg_err_t Gbs_Core::load_( Data_Reader& in ) +{ + RETURN_ERR( rom.load( in, header_.size, &header_, 0 ) ); + + if ( !header_.valid_tag() ) + return blargg_err_file_type; + + if ( header_.vers != 1 ) + set_warning( "Unknown file version" ); + + if ( header_.timer_mode & 0x78 ) + set_warning( "Invalid timer mode" ); + + addr_t load_addr = get_le16( header_.load_addr ); + if ( (header_.load_addr [1] | header_.init_addr [1] | header_.play_addr [1]) > 0x7F || + load_addr < 0x400 ) + set_warning( "Invalid load/init/play address" ); + + cpu.rst_base = load_addr; + rom.set_addr( load_addr ); + + return blargg_ok; +} + +void Gbs_Core::set_bank( int n ) +{ + addr_t addr = rom.mask_addr( n * bank_size ); + if ( addr == 0 && rom.size() > bank_size ) + addr = bank_size; // MBC1&2 behavior, bank 0 acts like bank 1 + cpu.map_code( bank_size, bank_size, rom.at_addr( addr ) ); +} + +void Gbs_Core::update_timer() +{ + play_period_ = 70224 / tempo_unit; // 59.73 Hz + + if ( header_.timer_mode & 0x04 ) + { + // Using custom rate + static byte const rates [4] = { 6, 0, 2, 4 }; + // TODO: emulate double speed CPU mode rather than halving timer rate + int double_speed = header_.timer_mode >> 7; + int shift = rates [ram [hi_page + 7] & 3] - double_speed; + play_period_ = (256 - ram [hi_page + 6]) << shift; + } + + play_period_ *= tempo; +} + +void Gbs_Core::set_tempo( double t ) +{ + tempo = (int) (tempo_unit / t + 0.5); + apu_.set_tempo( t ); + update_timer(); +} + +// Jumps to routine, given pointer to address in file header. Pushes idle_addr +// as return address, NOT old PC. +void Gbs_Core::jsr_then_stop( byte const addr [] ) +{ + check( cpu.r.sp == get_le16( header_.stack_ptr ) ); + cpu.r.pc = get_le16( addr ); + write_mem( --cpu.r.sp, idle_addr >> 8 ); + write_mem( --cpu.r.sp, idle_addr ); +} + +blargg_err_t Gbs_Core::start_track( int track, Gb_Apu::mode_t mode ) +{ + // Reset APU to state expected by most rips + static byte const sound_data [] = { + 0x80, 0xBF, 0x00, 0x00, 0xB8, // square 1 DAC disabled + 0x00, 0x3F, 0x00, 0x00, 0xB8, // square 2 DAC disabled + 0x7F, 0xFF, 0x9F, 0x00, 0xB8, // wave DAC disabled + 0x00, 0xFF, 0x00, 0x00, 0xB8, // noise DAC disabled + 0x77, 0xFF, 0x80, // max volume, all chans in center, power on + }; + apu_.reset( mode ); + apu_.write_register( 0, 0xFF26, 0x80 ); // power on + for ( int i = 0; i < (int) sizeof sound_data; i++ ) + apu_.write_register( 0, i + apu_.io_addr, sound_data [i] ); + apu_.end_frame( 1 ); // necessary to get click out of the way + + // Init memory and I/O registers + memset( ram, 0, 0x4000 ); + memset( ram + 0x4000, 0xFF, 0x1F80 ); + memset( ram + 0x5F80, 0, sizeof ram - 0x5F80 ); + ram [hi_page] = 0; // joypad reads back as 0 + ram [idle_addr - ram_addr] = 0xED; // illegal instruction + ram [hi_page + 6] = header_.timer_modulo; + ram [hi_page + 7] = header_.timer_mode; + + // Map memory + cpu.reset( rom.unmapped() ); + cpu.map_code( ram_addr, 0x10000 - ram_addr, ram ); + cpu.map_code( 0, bank_size, rom.at_addr( 0 ) ); + set_bank( rom.size() > bank_size ); + + // CPU registers, timing + update_timer(); + next_play = play_period_; + cpu.r.fa = track; + cpu.r.sp = get_le16( header_.stack_ptr ); + jsr_then_stop( header_.init_addr ); + + return blargg_ok; +} + +blargg_err_t Gbs_Core::run_until( int end ) +{ + end_time = end; + cpu.set_time( cpu.time() - end ); + while ( true ) + { + run_cpu(); + if ( cpu.time() >= 0 ) + break; + + if ( cpu.r.pc == idle_addr ) + { + if ( next_play > end_time ) + { + cpu.set_time( 0 ); + break; + } + + if ( cpu.time() < next_play - end_time ) + cpu.set_time( next_play - end_time ); + next_play += play_period_; + jsr_then_stop( header_.play_addr ); + } + else if ( cpu.r.pc > 0xFFFF ) + { + dprintf( "PC wrapped around\n" ); + cpu.r.pc &= 0xFFFF; + } + else + { + set_warning( "Emulation error (illegal/unsupported instruction)" ); + dprintf( "Bad opcode $%02X at $%04X\n", + (int) *cpu.get_code( cpu.r.pc ), (int) cpu.r.pc ); + cpu.r.pc = (cpu.r.pc + 1) & 0xFFFF; + cpu.set_time( cpu.time() + 6 ); + } + } + + return blargg_ok; +} + +blargg_err_t Gbs_Core::end_frame( int end ) +{ + RETURN_ERR( run_until( end ) ); + + next_play -= end; + if ( next_play < 0 ) // happens when play routine takes too long + { + #if !GBS_IGNORE_STARVED_PLAY + check( false ); + #endif + next_play = 0; + } + + apu_.end_frame( end ); + + return blargg_ok; +} diff --git a/Frameworks/GME/gme/Gbs_Core.h b/Frameworks/GME/gme/Gbs_Core.h new file mode 100644 index 000000000..833f807e6 --- /dev/null +++ b/Frameworks/GME/gme/Gbs_Core.h @@ -0,0 +1,109 @@ +// Nintendo Game Boy GBS music file emulator core + +// Game_Music_Emu $vers +#ifndef GBS_CORE_H +#define GBS_CORE_H + +#include "Gme_Loader.h" +#include "Rom_Data.h" +#include "Gb_Cpu.h" +#include "Gb_Apu.h" + +class Gbs_Core : public Gme_Loader { +public: + + // GBS file header + struct header_t + { + enum { size = 112 }; + + char tag [ 3]; + byte vers; + byte track_count; + byte first_track; + byte load_addr [ 2]; + byte init_addr [ 2]; + byte play_addr [ 2]; + byte stack_ptr [ 2]; + byte timer_modulo; + byte timer_mode; + char game [32]; // strings can be 32 chars, NOT terminated + char author [32]; + char copyright [32]; + + // True if header has valid file signature + bool valid_tag() const; + }; + + // Header for currently loaded file + header_t const& header() const { return header_; } + + // Sound chip + Gb_Apu& apu() { return apu_; } + + // ROM data + Rom_Data const& rom_() const { return rom; } + + // Adjusts music tempo, where 1.0 is normal. Can be changed while playing. + void set_tempo( double ); + + // Starts track, where 0 is the first. Uses specified APU mode. + blargg_err_t start_track( int, Gb_Apu::mode_t = Gb_Apu::mode_cgb ); + + // Ends time frame at time t + typedef int time_t; // clock count + blargg_err_t end_frame( time_t t ); + + // Clocks between calls to play routine + time_t play_period() const { return play_period_; } + +protected: + typedef int addr_t; + + // Current time + time_t time() const { return cpu.time() + end_time; } + + // Runs emulator to time t + blargg_err_t run_until( time_t t ); + + // Runs CPU until time becomes >= 0 + void run_cpu(); + + // Reads/writes memory and I/O + int read_mem( addr_t ); + void write_mem( addr_t, int ); + +// Implementation +public: + Gbs_Core(); + ~Gbs_Core(); + virtual void unload(); + +protected: + virtual blargg_err_t load_( Data_Reader& ); + +private: + enum { ram_addr = 0xA000 }; + enum { io_base = 0xFF00 }; + enum { hi_page = io_base - ram_addr }; + + Rom_Data rom; + int tempo; + time_t end_time; + time_t play_period_; + time_t next_play; + header_t header_; + Gb_Cpu cpu; + Gb_Apu apu_; + byte ram [0x4000 + 0x2000 + Gb_Cpu::cpu_padding]; + + void update_timer(); + void jsr_then_stop( byte const [] ); + void set_bank( int n ); + void write_io_inline( int offset, int data, int base ); + void write_io_( int offset, int data ); + int read_io( int offset ); + void write_io( int offset, int data ); +}; + +#endif diff --git a/Frameworks/GME/gme/Gbs_Cpu.cpp b/Frameworks/GME/gme/Gbs_Cpu.cpp new file mode 100644 index 000000000..4f5751aa0 --- /dev/null +++ b/Frameworks/GME/gme/Gbs_Cpu.cpp @@ -0,0 +1,134 @@ +// Game_Music_Emu $vers. http://www.slack.net/~ant/ + +#include "Gbs_Core.h" + +#include "blargg_endian.h" + +//#include "gb_cpu_log.h" + +/* Copyright (C) 2003-2009 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +#include "blargg_source.h" + +#ifndef LOG_MEM + #define LOG_MEM( addr, str, data ) data +#endif + +int Gbs_Core::read_mem( addr_t addr ) +{ + int result = *cpu.get_code( addr ); + if ( (unsigned) (addr - apu_.io_addr) < apu_.io_size ) + result = apu_.read_register( time(), addr ); + +#ifndef NDEBUG + else if ( unsigned (addr - 0x8000) < 0x2000 || unsigned (addr - 0xE000) < 0x1F00 ) + dprintf( "Unmapped read $%04X\n", (unsigned) addr ); + else if ( unsigned (addr - 0xFF01) < 0xFF80 - 0xFF01 && addr != 0xFF70 && addr != 0xFF05 ) + dprintf( "Unmapped read $%04X\n", (unsigned) addr ); +#endif + + return LOG_MEM( addr, ">", result ); +} + +inline void Gbs_Core::write_io_inline( int offset, int data, int base ) +{ + if ( (unsigned) (offset - (apu_.io_addr - base)) < apu_.io_size ) + apu_.write_register( time(), offset + base, data & 0xFF ); + else if ( (unsigned) (offset - (0xFF06 - base)) < 2 ) + update_timer(); + else if ( offset == io_base - base ) + ram [base - ram_addr + offset] = 0; // keep joypad return value 0 + else + ram [base - ram_addr + offset] = 0xFF; + + //if ( offset == 0xFFFF - base ) + // dprintf( "Wrote interrupt mask\n" ); +} + +void Gbs_Core::write_mem( addr_t addr, int data ) +{ + (void) LOG_MEM( addr, "<", data ); + + int offset = addr - ram_addr; + if ( (unsigned) offset < 0x10000 - ram_addr ) + { + ram [offset] = data; + + offset -= 0xE000 - ram_addr; + if ( (unsigned) offset < 0x1F80 ) + write_io_inline( offset, data, 0xE000 ); + } + else if ( (unsigned) (offset - (0x2000 - ram_addr)) < 0x2000 ) + { + set_bank( data & 0xFF ); + } +#ifndef NDEBUG + else if ( unsigned (addr - 0x8000) < 0x2000 || unsigned (addr - 0xE000) < 0x1F00 ) + { + dprintf( "Unmapped write $%04X\n", (unsigned) addr ); + } +#endif +} + +void Gbs_Core::write_io_( int offset, int data ) +{ + write_io_inline( offset, data, io_base ); +} + +inline void Gbs_Core::write_io( int offset, int data ) +{ + (void) LOG_MEM( offset + io_base, "<", data ); + + ram [io_base - ram_addr + offset] = data; + if ( (unsigned) offset < 0x80 ) + write_io_( offset, data ); +} + +int Gbs_Core::read_io( int offset ) +{ + int const io_base = 0xFF00; + int result = ram [io_base - ram_addr + offset]; + + if ( (unsigned) (offset - (apu_.io_addr - io_base)) < apu_.io_size ) + { + result = apu_.read_register( time(), offset + io_base ); + (void) LOG_MEM( offset + io_base, ">", result ); + } + else + { + check( result == read_mem( offset + io_base ) ); + } + return result; +} + +#define READ_FAST( addr, out ) \ +{\ + out = READ_CODE( addr );\ + if ( (unsigned) (addr - apu_.io_addr) < apu_.io_size )\ + out = LOG_MEM( addr, ">", apu_.read_register( TIME() + end_time, addr ) );\ + else\ + check( out == read_mem( addr ) );\ +} + +#define READ_MEM( addr ) read_mem( addr ) +#define WRITE_MEM( addr, data ) write_mem( addr, data ) + +#define WRITE_IO( addr, data ) write_io( addr, data ) +#define READ_IO( addr, out ) out = read_io( addr ) + +#define CPU cpu + +#define CPU_BEGIN \ +void Gbs_Core::run_cpu()\ +{ + #include "Gb_Cpu_run.h" +} diff --git a/Frameworks/GME/gme/Gme_Loader.cpp b/Frameworks/GME/gme/Gme_Loader.cpp new file mode 100644 index 000000000..8f340a138 --- /dev/null +++ b/Frameworks/GME/gme/Gme_Loader.cpp @@ -0,0 +1,86 @@ +// Game_Music_Emu $vers. http://www.slack.net/~ant/ + +#include "Gme_Loader.h" + +#include "blargg_endian.h" + +/* Copyright (C) 2003-2008 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +#include "blargg_source.h" + +void Gme_Loader::unload() +{ + file_begin_ = NULL; + file_end_ = NULL; + file_data.clear(); +} + +Gme_Loader::Gme_Loader() +{ + warning_ = NULL; + Gme_Loader::unload(); + blargg_verify_byte_order(); // used by most emulator types, so save them the trouble +} + +Gme_Loader::~Gme_Loader() { } + +blargg_err_t Gme_Loader::load_mem_( byte const data [], int size ) +{ + require( data != file_data.begin() ); // load_mem_() or load_() must be overridden + Mem_File_Reader in( data, size ); + return load_( in ); +} + +inline blargg_err_t Gme_Loader::load_mem_wrapper( byte const data [], int size ) +{ + file_begin_ = data; + file_end_ = data + size; + return load_mem_( data, size ); +} + +blargg_err_t Gme_Loader::load_( Data_Reader& in ) +{ + RETURN_ERR( file_data.resize( in.remain() ) ); + RETURN_ERR( in.read( file_data.begin(), file_data.size() ) ); + return load_mem_wrapper( file_data.begin(), file_data.size() ); +} + +blargg_err_t Gme_Loader::post_load_( blargg_err_t err ) +{ + if ( err ) + { + unload(); + return err; + } + + return post_load(); +} + +blargg_err_t Gme_Loader::load_mem( void const* in, long size ) +{ + pre_load(); + return post_load_( load_mem_wrapper( (byte const*) in, (int) size ) ); +} + +blargg_err_t Gme_Loader::load( Data_Reader& in ) +{ + pre_load(); + return post_load_( load_( in ) ); +} + +blargg_err_t Gme_Loader::load_file( const char path [] ) +{ + pre_load(); + GME_FILE_READER in; + RETURN_ERR( in.open( path ) ); + return post_load_( load_( in ) ); +} diff --git a/Frameworks/GME/gme/Gme_Loader.h b/Frameworks/GME/gme/Gme_Loader.h new file mode 100644 index 000000000..e59431c9a --- /dev/null +++ b/Frameworks/GME/gme/Gme_Loader.h @@ -0,0 +1,92 @@ +// Common interface for loading file data from various sources + +// Game_Music_Emu $vers +#ifndef GME_LOADER_H +#define GME_LOADER_H + +#include "blargg_common.h" +#include "Data_Reader.h" + +class Gme_Loader { +public: + + // Each loads game music data from a file and returns an error if + // file is wrong type or is seriously corrupt. Minor problems are + // reported using warning(). + + // Loads from file on disk + blargg_err_t load_file( const char path [] ); + + // Loads from custom data source (see Data_Reader.h) + blargg_err_t load( Data_Reader& ); + + // Loads from file already read into memory. Object might keep pointer to + // data; if it does, you MUST NOT free it until you're done with the file. + blargg_err_t load_mem( void const* data, long size ); + + // Most recent warning string, or NULL if none. Clears current warning after + // returning. + const char* warning(); + + // Unloads file from memory + virtual void unload(); + + virtual ~Gme_Loader(); + +protected: + typedef BOOST::uint8_t byte; + + // File data in memory, or 0 if data was loaded with load_() + byte const* file_begin() const { return file_begin_; } + byte const* file_end() const { return file_end_; } + int file_size() const { return (int) (file_end_ - file_begin_); } + + // Sets warning string + void set_warning( const char s [] ) { warning_ = s; } + + // At least one must be overridden + virtual blargg_err_t load_( Data_Reader& ); // default loads then calls load_mem_() + virtual blargg_err_t load_mem_( byte const data [], int size ); // use data in memory + + // Optionally overridden + virtual void pre_load() { unload(); } // called before load_()/load_mem_() + virtual blargg_err_t post_load() { return blargg_ok; } // called after load_()/load_mem_() succeeds + +private: + // noncopyable + Gme_Loader( const Gme_Loader& ); + Gme_Loader& operator = ( const Gme_Loader& ); + +// Implementation +public: + Gme_Loader(); + BLARGG_DISABLE_NOTHROW + + blargg_vector file_data; // used only when loading from file to load_mem_() + byte const* file_begin_; + byte const* file_end_; + const char* warning_; + + blargg_err_t load_mem_wrapper( byte const [], int ); + blargg_err_t post_load_( blargg_err_t err ); +}; + +// Files are read with GME_FILE_READER. Default supports gzip if zlib is available. +#ifndef GME_FILE_READER + #ifdef HAVE_ZLIB_H + #define GME_FILE_READER Gzip_File_Reader + #else + #define GME_FILE_READER Std_File_Reader + #endif +#elif defined (GME_FILE_READER_INCLUDE) + #include GME_FILE_READER_INCLUDE +#endif + +inline const char* Gme_Loader::warning() +{ + const char* s = warning_; + warning_ = NULL; + return s; +} + +#endif diff --git a/Frameworks/GME/gme/Hes_Apu_Adpcm.cpp b/Frameworks/GME/gme/Hes_Apu_Adpcm.cpp new file mode 100644 index 000000000..edd955054 --- /dev/null +++ b/Frameworks/GME/gme/Hes_Apu_Adpcm.cpp @@ -0,0 +1,309 @@ +// Game_Music_Emu $vers. http://www.slack.net/~ant/ + +#include "Hes_Apu_Adpcm.h" + +/* Copyright (C) 2006-2008 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +#include "blargg_source.h" + +Hes_Apu_Adpcm::Hes_Apu_Adpcm() +{ + output = NULL; + + memset( &state, 0, sizeof( state ) ); + + reset(); +} + +void Hes_Apu_Adpcm::reset() +{ + last_time = 0; + next_timer = 0; + last_amp = 0; + + memset( &state.pcmbuf, 0, sizeof(state.pcmbuf) ); + memset( &state.port, 0, sizeof(state.port) ); + + state.ad_sample = 0; + state.ad_ref_index = 0; + + state.addr = 0; + state.freq = 0; + state.writeptr = 0; + state.readptr = 0; + state.playflag = 0; + state.repeatflag = 0; + state.length = 0; + state.volume = 0xFF; + state.fadetimer = 0; + state.fadecount = 0; +} + +void Hes_Apu_Adpcm::set_output( int i, Blip_Buffer* center, Blip_Buffer* left, Blip_Buffer* right ) +{ + // Must be silent (all NULL), mono (left and right NULL), or stereo (none NULL) + require( !center || (center && !left && !right) || (center && left && right) ); + require( (unsigned) i < osc_count ); // fails if you pass invalid osc index + + if ( !center || !left || !right ) + { + left = center; + right = center; + } + + output = center; +} + +void Hes_Apu_Adpcm::run_until( blip_time_t end_time ) +{ + int volume = state.volume; + int fadetimer = state.fadetimer; + int fadecount = state.fadecount; + int last_time = this->last_time; + double next_timer = this->next_timer; + int last_amp = this->last_amp; + + Blip_Buffer* output = this->output; // cache often-used values + + while ( state.playflag && last_time < end_time ) + { + while ( last_time >= next_timer ) + { + if ( fadetimer ) + { + if ( fadecount > 0 ) + { + fadecount--; + volume = 0xFF * fadecount / fadetimer; + } + else if ( fadecount < 0 ) + { + fadecount++; + volume = 0xFF - ( 0xFF * fadecount / fadetimer ); + } + } + next_timer += 7159.091; + } + int amp; + if ( state.ad_low_nibble ) + { + amp = adpcm_decode( state.pcmbuf[ state.playptr ] & 0x0F ); + state.ad_low_nibble = false; + state.playptr++; + state.playedsamplecount++; + if ( state.playedsamplecount == state.playlength ) + { + state.playflag = 0; + } + } + else + { + amp = adpcm_decode( state.pcmbuf[ state.playptr ] >> 4 ); + state.ad_low_nibble = true; + } + amp = amp * volume / 0xFF; + int delta = amp - last_amp; + if ( output && delta ) + { + last_amp = amp; + synth.offset_inline( last_time, delta, output ); + } + last_time += state.freq; + } + + if ( !state.playflag ) + { + while ( next_timer <= end_time ) next_timer += 7159.091; + last_time = end_time; + } + + this->last_time = last_time; + this->next_timer = next_timer; + this->last_amp = last_amp; + state.volume = volume; + state.fadetimer = fadetimer; + state.fadecount = fadecount; +} + +void Hes_Apu_Adpcm::write_data( blip_time_t time, int addr, int data ) +{ + if ( time > last_time ) run_until( time ); + + data &= 0xFF; + state.port[ addr & 15 ] = data; + switch ( addr & 15 ) + { + case 8: + state.addr &= 0xFF00; + state.addr |= data; + break; + case 9: + state.addr &= 0xFF; + state.addr |= data << 8; + break; + case 10: + state.pcmbuf[ state.writeptr++ ] = data; + state.playlength ++; + break; + case 11: + dprintf("ADPCM DMA 0x%02X", data); + break; + case 13: + if ( data & 0x80 ) + { + state.addr = 0; + state.freq = 0; + state.writeptr = 0; + state.readptr = 0; + state.playflag = 0; + state.repeatflag = 0; + state.length = 0; + state.volume = 0xFF; + } + if ( ( data & 3 ) == 3 ) + { + state.writeptr = state.addr; + } + if ( data & 8 ) + { + state.readptr = state.addr ? state.addr - 1 : state.addr; + } + if ( data & 0x10 ) + { + state.length = state.addr; + } + state.repeatflag = data & 0x20; + state.playflag = data & 0x40; + if ( state.playflag ) + { + state.playptr = state.readptr; + state.playlength = state.length + 1; + state.playedsamplecount = 0; + state.ad_sample = 0; + state.ad_low_nibble = false; + } + break; + case 14: + state.freq = 7159091 / ( 32000 / ( 16 - ( data & 15 ) ) ); + break; + case 15: + switch ( data & 15 ) + { + case 0: + case 8: + case 12: + state.fadetimer = -100; + state.fadecount = state.fadetimer; + break; + case 10: + state.fadetimer = 5000; + state.fadecount = state.fadetimer; + break; + case 14: + state.fadetimer = 1500; + state.fadecount = state.fadetimer; + break; + } + break; + } +} + +int Hes_Apu_Adpcm::read_data( blip_time_t time, int addr ) +{ + if ( time > last_time ) run_until( time ); + + switch ( addr & 15 ) + { + case 10: + return state.pcmbuf [state.readptr++]; + case 11: + return state.port [11] & ~1; + case 12: + if (!state.playflag) + { + state.port [12] |= 1; + state.port [12] &= ~8; + } + else + { + state.port [12] &= ~1; + state.port [12] |= 8; + } + return state.port [12]; + case 13: + return state.port [13]; + } + + return 0xFF; +} + +void Hes_Apu_Adpcm::end_frame( blip_time_t end_time ) +{ + run_until( end_time ); + last_time -= end_time; + next_timer -= (double)end_time; + check( last_time >= 0 ); + if ( output ) + output->set_modified(); +} + +static short stepsize[49] = { + 16, 17, 19, 21, 23, 25, 28, + 31, 34, 37, 41, 45, 50, 55, + 60, 66, 73, 80, 88, 97, 107, + 118, 130, 143, 157, 173, 190, 209, + 230, 253, 279, 307, 337, 371, 408, + 449, 494, 544, 598, 658, 724, 796, + 876, 963,1060,1166,1282,1411,1552 +}; + +int Hes_Apu_Adpcm::adpcm_decode( int code ) +{ + int step = stepsize[state.ad_ref_index]; + int delta; + int c = code & 7; +#if 1 + delta = 0; + if ( c & 4 ) delta += step; + step >>= 1; + if ( c & 2 ) delta += step; + step >>= 1; + if ( c & 1 ) delta += step; + step >>= 1; + delta += step; +#else + delta = ( ( c + c + 1 ) * step ) / 8; // maybe faster, but introduces rounding +#endif + if ( c != code ) + { + state.ad_sample -= delta; + if ( state.ad_sample < -2048 ) + state.ad_sample = -2048; + } + else + { + state.ad_sample += delta; + if ( state.ad_sample > 2047 ) + state.ad_sample = 2047; + } + + static int const steps [8] = { + -1, -1, -1, -1, 2, 4, 6, 8 + }; + state.ad_ref_index += steps [c]; + if ( state.ad_ref_index < 0 ) + state.ad_ref_index = 0; + else if ( state.ad_ref_index > 48 ) + state.ad_ref_index = 48; + + return state.ad_sample; +} diff --git a/Frameworks/GME/gme/Hes_Apu_Adpcm.h b/Frameworks/GME/gme/Hes_Apu_Adpcm.h new file mode 100644 index 000000000..6abd80869 --- /dev/null +++ b/Frameworks/GME/gme/Hes_Apu_Adpcm.h @@ -0,0 +1,94 @@ +// Turbo Grafx 16 (PC Engine) ADPCM sound chip emulator + +// Game_Music_Emu $vers +#ifndef HES_APU_ADPCM_H +#define HES_APU_ADPCM_H + +#include "blargg_common.h" +#include "Blip_Buffer.h" + +class Hes_Apu_Adpcm { +public: +// Basics + + // Sets buffer(s) to generate sound into, or 0 to mute. If only center is not 0, + // output is mono. + void set_output( Blip_Buffer* center, Blip_Buffer* left = NULL, Blip_Buffer* right = NULL ); + + // Emulates to time t, then writes data to addr + void write_data( blip_time_t t, int addr, int data ); + + // Emulates to time t, then reads from addr + int read_data( blip_time_t t, int addr ); + + // Emulates to time t, then subtracts t from the current time. + // OK if previous write call had time slightly after t. + void end_frame( blip_time_t t ); + +// More features + + // Resets sound chip + void reset(); + + // Same as set_output(), but for a particular channel + enum { osc_count = 1 }; // 0 <= chan < osc_count + void set_output( int chan, Blip_Buffer* center, Blip_Buffer* left = NULL, Blip_Buffer* right = NULL ); + + // Sets treble equalization + void treble_eq( blip_eq_t const& eq ) { synth.treble_eq( eq ); } + + // Sets overall volume, where 1.0 is normal + void volume( double v ) { synth.volume( 0.6 / osc_count / amp_range * v ); } + + // Registers are at io_addr to io_addr+io_size-1 + enum { io_addr = 0x1800 }; + enum { io_size = 0x400 }; + +// Implementation +public: + Hes_Apu_Adpcm(); + typedef BOOST::uint8_t byte; + +private: + enum { amp_range = 2048 }; + + struct State + { + byte pcmbuf [0x10000]; + byte port [0x10]; + int ad_sample; + int ad_ref_index; + bool ad_low_nibble; + int freq; + unsigned short addr; + unsigned short writeptr; + unsigned short readptr; + unsigned short playptr; + byte playflag; + byte repeatflag; + int length; + int playlength; + int playedsamplecount; + int volume; + int fadetimer; + int fadecount; + }; + State state; + Blip_Synth_Fast synth; + + Blip_Buffer* output; + blip_time_t last_time; + double next_timer; + int last_amp; + + void run_until( blip_time_t ); + + int adpcm_decode( int ); +}; + +inline void Hes_Apu_Adpcm::set_output( Blip_Buffer* c, Blip_Buffer* l, Blip_Buffer* r ) +{ + set_output( 0, c, l, r ); +} + +#endif diff --git a/Frameworks/GME/gme/Hes_Core.cpp b/Frameworks/GME/gme/Hes_Core.cpp new file mode 100644 index 000000000..273787ca9 --- /dev/null +++ b/Frameworks/GME/gme/Hes_Core.cpp @@ -0,0 +1,408 @@ +// Game_Music_Emu $vers. http://www.slack.net/~ant/ + +#include "Hes_Core.h" + +#include "blargg_endian.h" + +/* Copyright (C) 2006-2008 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +#include "blargg_source.h" + +int const timer_mask = 0x04; +int const vdp_mask = 0x02; +int const i_flag_mask = 0x04; +int const unmapped = 0xFF; + +int const period_60hz = 262 * 455; // scanlines * clocks per scanline + +Hes_Core::Hes_Core() : rom( Hes_Cpu::page_size ) +{ + timer.raw_load = 0; +} + +Hes_Core::~Hes_Core() { } + +void Hes_Core::unload() +{ + rom.clear(); + Gme_Loader::unload(); +} + +bool Hes_Core::header_t::valid_tag() const +{ + return 0 == memcmp( tag, "HESM", 4 ); +} + +blargg_err_t Hes_Core::load_( Data_Reader& in ) +{ + assert( offsetof (header_t,unused [4]) == header_t::size ); + RETURN_ERR( rom.load( in, header_t::size, &header_, unmapped ) ); + + if ( !header_.valid_tag() ) + return blargg_err_file_type; + + if ( header_.vers != 0 ) + set_warning( "Unknown file version" ); + + if ( memcmp( header_.data_tag, "DATA", 4 ) ) + set_warning( "Data header missing" ); + + if ( memcmp( header_.unused, "\0\0\0\0", 4 ) ) + set_warning( "Unknown header data" ); + + // File spec supports multiple blocks, but I haven't found any, and + // many files have bad sizes in the only block, so it's simpler to + // just try to load the damn data as best as possible. + + int addr = get_le32( header_.addr ); + int size = get_le32( header_.data_size ); + int const rom_max = 0x100000; + if ( (unsigned) addr >= (unsigned) rom_max ) + { + set_warning( "Invalid address" ); + addr &= rom_max - 1; + } + if ( (unsigned) (addr + size) > (unsigned) rom_max ) + set_warning( "Invalid size" ); + + if ( size != rom.file_size() ) + { + if ( size <= rom.file_size() - 4 && !memcmp( rom.begin() + size, "DATA", 4 ) ) + set_warning( "Multiple DATA not supported" ); + else if ( size < rom.file_size() ) + set_warning( "Extra file data" ); + else + set_warning( "Missing file data" ); + } + + rom.set_addr( addr ); + + return blargg_ok; +} + +void Hes_Core::recalc_timer_load() +{ + timer.load = timer.raw_load * timer_base + 1; +} + +void Hes_Core::set_tempo( double t ) +{ + play_period = (time_t) (period_60hz / t); + timer_base = (int) (1024 / t); + recalc_timer_load(); +} + +blargg_err_t Hes_Core::start_track( int track ) +{ + memset( ram, 0, sizeof ram ); // some HES music relies on zero fill + memset( sgx, 0, sizeof sgx ); + + apu_.reset(); + adpcm_.reset(); + cpu.reset(); + + for ( int i = 0; i < (int) sizeof header_.banks; i++ ) + set_mmr( i, header_.banks [i] ); + set_mmr( cpu.page_count, 0xFF ); // unmapped beyond end of address space + + irq.disables = timer_mask | vdp_mask; + irq.timer = cpu.future_time; + irq.vdp = cpu.future_time; + + timer.enabled = false; + timer.raw_load = 0x80; + timer.count = timer.load; + timer.fired = false; + timer.last_time = 0; + + vdp.latch = 0; + vdp.control = 0; + vdp.next_vbl = 0; + + ram [0x1FF] = (idle_addr - 1) >> 8; + ram [0x1FE] = (idle_addr - 1) & 0xFF; + cpu.r.sp = 0xFD; + cpu.r.pc = get_le16( header_.init_addr ); + cpu.r.a = track; + + recalc_timer_load(); + + return blargg_ok; +} + +// Hardware + +void Hes_Core::run_until( time_t present ) +{ + while ( vdp.next_vbl < present ) + vdp.next_vbl += play_period; + + time_t elapsed = present - timer.last_time; + if ( elapsed > 0 ) + { + if ( timer.enabled ) + { + timer.count -= elapsed; + if ( timer.count <= 0 ) + timer.count += timer.load; + } + timer.last_time = present; + } +} + +void Hes_Core::write_vdp( int addr, int data ) +{ + switch ( addr ) + { + case 0: + vdp.latch = data & 0x1F; + break; + + case 2: + if ( vdp.latch == 5 ) + { + if ( data & 0x04 ) + set_warning( "Scanline interrupt unsupported" ); + run_until( cpu.time() ); + vdp.control = data; + irq_changed(); + } + else + { + dprintf( "VDP not supported: $%02X <- $%02X\n", vdp.latch, data ); + } + break; + + case 3: + dprintf( "VDP MSB not supported: $%02X <- $%02X\n", vdp.latch, data ); + break; + } +} + +void Hes_Core::write_mem_( addr_t addr, int data ) +{ + time_t time = cpu.time(); + if ( (unsigned) (addr - apu_.io_addr) < apu_.io_size ) + { + // Avoid going way past end when a long block xfer is writing to I/O space. + // Not a problem for other registers below because they don't write to + // Blip_Buffer. + time_t t = min( time, cpu.end_time() + 8 ); + apu_.write_data( t, addr, data ); + return; + } + if ( (unsigned) (addr - adpcm_.io_addr) < adpcm_.io_size ) + { + time_t t = min( time, cpu.end_time() + 6 ); + adpcm_.write_data( t, addr, data ); + return; + } + + switch ( addr ) + { + case 0x0000: + case 0x0002: + case 0x0003: + write_vdp( addr, data ); + return; + + case 0x0C00: { + run_until( time ); + timer.raw_load = (data & 0x7F) + 1; + recalc_timer_load(); + timer.count = timer.load; + break; + } + + case 0x0C01: + data &= 1; + if ( timer.enabled == data ) + return; + run_until( time ); + timer.enabled = data; + if ( data ) + timer.count = timer.load; + break; + + case 0x1402: + run_until( time ); + irq.disables = data; + if ( (data & 0xF8) && (data & 0xF8) != 0xF8 ) // flag questionable values + dprintf( "Int mask: $%02X\n", data ); + break; + + case 0x1403: + run_until( time ); + if ( timer.enabled ) + timer.count = timer.load; + timer.fired = false; + break; + +#ifndef NDEBUG + case 0x1000: // I/O port + case 0x0402: // palette + case 0x0403: + case 0x0404: + case 0x0405: + return; + + default: + dprintf( "unmapped write $%04X <- $%02X\n", addr, data ); + return; +#endif + } + + irq_changed(); +} + +int Hes_Core::read_mem_( addr_t addr ) +{ + time_t time = cpu.time(); + addr &= cpu.page_size - 1; + switch ( addr ) + { + case 0x0000: + if ( irq.vdp > time ) + return 0; + irq.vdp = cpu.future_time; + run_until( time ); + irq_changed(); + return 0x20; + + case 0x0002: + case 0x0003: + dprintf( "VDP read not supported: %d\n", addr ); + return 0; + + case 0x0C01: + //return timer.enabled; // TODO: remove? + case 0x0C00: + run_until( time ); + dprintf( "Timer count read\n" ); + return (unsigned) (timer.count - 1) / timer_base; + + case 0x1402: + return irq.disables; + + case 0x1403: + { + int status = 0; + if ( irq.timer <= time ) status |= timer_mask; + if ( irq.vdp <= time ) status |= vdp_mask; + return status; + } + + case 0x180A: + case 0x180B: + case 0x180C: + case 0x180D: + return adpcm_.read_data( time, addr ); + + #ifndef NDEBUG + case 0x1000: // I/O port + //case 0x180C: // CD-ROM + //case 0x180D: + break; + + default: + dprintf( "unmapped read $%04X\n", addr ); + #endif + } + + return unmapped; +} + +void Hes_Core::irq_changed() +{ + time_t present = cpu.time(); + + if ( irq.timer > present ) + { + irq.timer = cpu.future_time; + if ( timer.enabled && !timer.fired ) + irq.timer = present + timer.count; + } + + if ( irq.vdp > present ) + { + irq.vdp = cpu.future_time; + if ( vdp.control & 0x08 ) + irq.vdp = vdp.next_vbl; + } + + time_t time = cpu.future_time; + if ( !(irq.disables & timer_mask) ) time = irq.timer; + if ( !(irq.disables & vdp_mask) ) time = min( time, irq.vdp ); + + cpu.set_irq_time( time ); +} + +int Hes_Core::cpu_done() +{ + check( cpu.time() >= cpu.end_time() || + (!(cpu.r.flags & i_flag_mask) && cpu.time() >= cpu.irq_time()) ); + + if ( !(cpu.r.flags & i_flag_mask) ) + { + time_t present = cpu.time(); + + if ( irq.timer <= present && !(irq.disables & timer_mask) ) + { + timer.fired = true; + irq.timer = cpu.future_time; + irq_changed(); // overkill, but not worth writing custom code + return 0x0A; + } + + if ( irq.vdp <= present && !(irq.disables & vdp_mask) ) + { + // work around for bugs with music not acknowledging VDP + //run_until( present ); + //irq.vdp = cpu.future_time; + //irq_changed(); + return 0x08; + } + } + return -1; +} + +static void adjust_time( Hes_Core::time_t& time, Hes_Core::time_t delta ) +{ + if ( time < Hes_Cpu::future_time ) + { + time -= delta; + if ( time < 0 ) + time = 0; + } +} + +blargg_err_t Hes_Core::end_frame( time_t duration ) +{ + if ( run_cpu( duration ) ) + set_warning( "Emulation error (illegal instruction)" ); + + check( cpu.time() >= duration ); + //check( time() - duration < 20 ); // Txx instruction could cause going way over + + run_until( duration ); + + // end time frame + timer.last_time -= duration; + vdp.next_vbl -= duration; + cpu.end_frame( duration ); + ::adjust_time( irq.timer, duration ); + ::adjust_time( irq.vdp, duration ); + apu_.end_frame( duration ); + adpcm_.end_frame( duration ); + + return blargg_ok; +} diff --git a/Frameworks/GME/gme/Hes_Core.h b/Frameworks/GME/gme/Hes_Core.h new file mode 100644 index 000000000..80b0a5fd5 --- /dev/null +++ b/Frameworks/GME/gme/Hes_Core.h @@ -0,0 +1,120 @@ +// TurboGrafx-16/PC Engine HES music file emulator core + +// Game_Music_Emu $vers +#ifndef HES_CORE_H +#define HES_CORE_H + +#include "Gme_Loader.h" +#include "Rom_Data.h" +#include "Hes_Apu.h" +#include "Hes_Apu_Adpcm.h" +#include "Hes_Cpu.h" + +class Hes_Core : public Gme_Loader { +public: + + // HES file header + enum { info_offset = 0x20 }; + struct header_t + { + enum { size = 0x20 }; + + byte tag [4]; + byte vers; + byte first_track; + byte init_addr [2]; + byte banks [8]; + byte data_tag [4]; + byte data_size [4]; + byte addr [4]; + byte unused [4]; + + // True if header has valid file signature + bool valid_tag() const; + }; + + // Header for currently loaded file + header_t const& header() const { return header_; } + + // Pointer to ROM data, for getting track information from + byte const* data() const { return rom.begin(); } + int data_size() const { return rom.file_size(); } + + // Adjusts rate play routine is called at, where 1.0 is normal. + // Can be changed while track is playing. + void set_tempo( double ); + + // Sound chip + Hes_Apu& apu() { return apu_; } + + Hes_Apu_Adpcm& adpcm() { return adpcm_; } + + // Starts track + blargg_err_t start_track( int ); + + // Ends time frame at time t + typedef int time_t; + blargg_err_t end_frame( time_t ); + +// Implementation +public: + Hes_Core(); + ~Hes_Core(); + virtual void unload(); + +protected: + virtual blargg_err_t load_( Data_Reader& ); + +private: + enum { idle_addr = 0x1FFF }; + + typedef int addr_t; + Hes_Cpu cpu; + Rom_Data rom; + header_t header_; + time_t play_period; + int timer_base; + + struct { + time_t last_time; + int count; + int load; + int raw_load; + byte enabled; + byte fired; + } timer; + + struct { + time_t next_vbl; + byte latch; + byte control; + } vdp; + + struct { + time_t timer; + time_t vdp; + byte disables; + } irq; + + void recalc_timer_load(); + + // large items + byte* write_pages [Hes_Cpu::page_count + 1]; // 0 if unmapped or I/O space + Hes_Apu apu_; + Hes_Apu_Adpcm adpcm_; + byte ram [Hes_Cpu::page_size]; + byte sgx [3 * Hes_Cpu::page_size + Hes_Cpu::cpu_padding]; + + void irq_changed(); + void run_until( time_t ); + bool run_cpu( time_t end ); + int read_mem_( addr_t ); + int read_mem( addr_t ); + void write_mem_( addr_t, int data ); + void write_mem( addr_t, int ); + void write_vdp( int addr, int data ); + void set_mmr( int reg, int bank ); + int cpu_done(); +}; + +#endif diff --git a/Frameworks/GME/gme/Hes_Cpu_run.h b/Frameworks/GME/gme/Hes_Cpu_run.h new file mode 100644 index 000000000..2b9c2ae06 --- /dev/null +++ b/Frameworks/GME/gme/Hes_Cpu_run.h @@ -0,0 +1,1342 @@ +// $package. http://www.slack.net/~ant/ + +#if 0 +/* Define these macros in the source file before #including this file. +- Parameters might be expressions, so they are best evaluated only once, +though they NEVER have side-effects, so multiple evaluation is OK. +- Output parameters might be a multiple-assignment expression like "a=x", +so they must NOT be parenthesized. +- Except where noted, time() and related functions will NOT work +correctly inside a macro. TIME() is always correct, and FLUSH_TIME() and +CACHE_TIME() allow the time changing functions to work. +- Macros "returning" void may use a {} statement block. */ + + // 0 <= addr <= 0xFFFF + page_size + // time functions can be used + int READ_MEM( addr_t ); + void WRITE_MEM( addr_t, int data ); + + // 0 <= addr <= 0x1FF + int READ_LOW( addr_t ); + void WRITE_LOW( addr_t, int data ); + + // 0 <= addr <= 0xFFFF + page_size + // Used by common instructions. + int READ_FAST( addr_t, int& out ); + void WRITE_FAST( addr_t, int data ); + + // 0 <= addr <= 2 + // ST0, ST1, ST2 instructions + void WRITE_VDP( int addr, int data ); + +// The following can be used within macros: + + // Current time + time_t TIME(); + + // Allows use of time functions + void FLUSH_TIME(); + + // Must be used before end of macro if FLUSH_TIME() was used earlier + void CACHE_TIME(); + +// Configuration (optional; commented behavior if defined) + + // Expanded just before beginning of code, to help debugger + #define CPU_BEGIN void my_run_cpu() { +#endif + +/* Copyright (C) 2003-2008 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +// TODO: support T flag, including clearing it at appropriate times? + +// all zero-page should really use whatever is at page 1, but that would +// reduce efficiency quite a bit +int const ram_addr = 0x2000; + +void Hes_Cpu::reset() +{ + check( cpu_state == &cpu_state_ ); + cpu_state = &cpu_state_; + + cpu_state_.time = 0; + cpu_state_.base = 0; + irq_time_ = future_time; + end_time_ = future_time; + + r.flags = 0x04; + r.sp = 0; + r.pc = 0; + r.a = 0; + r.x = 0; + r.y = 0; + + // Be sure "blargg_endian.h" has been #included + blargg_verify_byte_order(); +} + +// Allows MWCW debugger to step through code properly +#ifdef CPU_BEGIN + CPU_BEGIN +#endif + +// Time +#define TIME() (s_time + s.base) +#define FLUSH_TIME() {s.time = s_time;} +#define CACHE_TIME() {s_time = s.time;} + +// Memory +#define READ_STACK READ_LOW +#define WRITE_STACK WRITE_LOW + +#define CODE_PAGE( addr ) s.code_map [HES_CPU_PAGE( addr )] +#define CODE_OFFSET( addr ) HES_CPU_OFFSET( addr ) +#define READ_CODE( addr ) CODE_PAGE( addr ) [CODE_OFFSET( addr )] + +// Stack +#define SET_SP( v ) (sp = ((v) + 1) | 0x100) +#define GET_SP() ((sp - 1) & 0xFF) +#define SP( o ) ((sp + (o - (o>0)*0x100)) | 0x100) + +// Truncation +#define BYTE( n ) ((BOOST::uint8_t ) (n)) /* (unsigned) n & 0xFF */ +#define SBYTE( n ) ((BOOST::int8_t ) (n)) /* (BYTE( n ) ^ 0x80) - 0x80 */ +#define WORD( n ) ((BOOST::uint16_t) (n)) /* (unsigned) n & 0xFFFF */ + +// Flags with hex value for clarity when used as mask. +// Stored in indicated variable during emulation. +int const n80 = 0x80; // nz +int const v40 = 0x40; // flags +//int const t20 = 0x20; +int const b10 = 0x10; +int const d08 = 0x08; // flags +int const i04 = 0x04; // flags +int const z02 = 0x02; // nz +int const c01 = 0x01; // c + +#define IS_NEG (nz & 0x8080) + +#define GET_FLAGS( out ) \ +{\ + out = flags & (v40 | d08 | i04);\ + out += ((nz >> 8) | nz) & n80;\ + out += c >> 8 & c01;\ + if ( !BYTE( nz ) )\ + out += z02;\ +} + +#define SET_FLAGS( in ) \ +{\ + flags = in & (v40 | d08 | i04);\ + c = nz = in << 8;\ + nz += ~in & z02;\ +} + +bool illegal_encountered = false; +{ + Hes_Cpu::cpu_state_t s = CPU.cpu_state_; + CPU.cpu_state = &s; + // even on x86, using s.time in place of s_time was slower + int s_time = s.time; + + // registers + int pc = CPU.r.pc; + int a = CPU.r.a; + int x = CPU.r.x; + int y = CPU.r.y; + int sp; + SET_SP( CPU.r.sp ); + + // Flags + int flags; + int c; // carry set if (c & 0x100) != 0 + int nz; // Z set if (nz & 0xFF) == 0, N set if (nz & 0x8080) != 0 + { + int temp = CPU.r.flags; + SET_FLAGS( temp ); + } + +loop: + + #ifndef NDEBUG + { + time_t correct = CPU.end_time_; + if ( !(flags & i04) && correct > CPU.irq_time_ ) + correct = CPU.irq_time_; + check( s.base == correct ); + /* + static int count; + if ( count == 1844 ) Debugger(); + if ( s.base != correct ) dprintf( "%ld\n", count ); + count++; + */ + } + #endif + + // Check all values + check( (unsigned) sp - 0x100 < 0x100 ); + check( (unsigned) pc < 0x10000 + 0x100 ); // +0x100 so emulator can catch wrap-around + check( (unsigned) a < 0x100 ); + check( (unsigned) x < 0x100 ); + check( (unsigned) y < 0x100 ); + + // Read instruction + byte const* instr = CODE_PAGE( pc ); + int opcode; + + if ( CODE_OFFSET(~0) == ~0 ) + { + opcode = instr [pc]; + pc++; + instr += pc; + } + else + { + instr += CODE_OFFSET( pc ); + opcode = *instr++; + pc++; + } + + // TODO: each reference lists slightly different timing values, ugh + static byte const clock_table [256] = + {// 0 1 2 3 4 5 6 7 8 9 A B C D E F + 1,7,3, 4,6,4,6,7,3,2,2,2,7,5,7,4,// 0 + 2,7,7, 4,6,4,6,7,2,5,2,2,7,5,7,4,// 1 + 7,7,3, 4,4,4,6,7,4,2,2,2,5,5,7,4,// 2 + 2,7,7, 2,4,4,6,7,2,5,2,2,5,5,7,4,// 3 + 7,7,3, 4,8,4,6,7,3,2,2,2,4,5,7,4,// 4 + 2,7,7, 5,2,4,6,7,2,5,3,2,2,5,7,4,// 5 + 7,7,2, 2,4,4,6,7,4,2,2,2,7,5,7,4,// 6 + 2,7,7,17,4,4,6,7,2,5,4,2,7,5,7,4,// 7 + 4,7,2, 7,4,4,4,7,2,2,2,2,5,5,5,4,// 8 + 2,7,7, 8,4,4,4,7,2,5,2,2,5,5,5,4,// 9 + 2,7,2, 7,4,4,4,7,2,2,2,2,5,5,5,4,// A + 2,7,7, 8,4,4,4,7,2,5,2,2,5,5,5,4,// B + 2,7,2,17,4,4,6,7,2,2,2,2,5,5,7,4,// C + 2,7,7,17,2,4,6,7,2,5,3,2,2,5,7,4,// D + 2,7,2,17,4,4,6,7,2,2,2,2,5,5,7,4,// E + 2,7,7,17,2,4,6,7,2,5,4,2,2,5,7,4 // F + }; // 0x00 was 8 + + // Update time + if ( s_time >= 0 ) + goto out_of_time; + + #ifdef HES_CPU_LOG_H + log_cpu( "new", pc - 1, opcode, instr [0], instr [1], instr [2], + instr [3], instr [4], instr [5], a, x, y ); + //log_opcode( opcode ); + #endif + + s_time += clock_table [opcode]; + + int data; + data = *instr; + + switch ( opcode ) + { +// Macros + +#define GET_MSB() (instr [1]) +#define ADD_PAGE( out ) (pc++, out = data + 0x100 * GET_MSB()); +#define GET_ADDR() GET_LE16( instr ) + +// TODO: is the penalty really always added? the original 6502 was much better +//#define PAGE_PENALTY( lsb ) (void) (s_time += (lsb) >> 8) +#define PAGE_PENALTY( lsb ) + +// Branch + +// TODO: more efficient way to handle negative branch that wraps PC around +#define BRANCH_( cond, adj )\ +{\ + pc++;\ + if ( !(cond) ) goto loop;\ + pc = (BOOST::uint16_t) (pc + SBYTE( data ));\ + s_time += adj;\ + goto loop;\ +} + +#define BRANCH( cond ) BRANCH_( cond, 2 ) + + case 0xF0: // BEQ + BRANCH( !BYTE( nz ) ); + + case 0xD0: // BNE + BRANCH( BYTE( nz ) ); + + case 0x10: // BPL + BRANCH( !IS_NEG ); + + case 0x90: // BCC + BRANCH( !(c & 0x100) ) + + case 0x30: // BMI + BRANCH( IS_NEG ) + + case 0x50: // BVC + BRANCH( !(flags & v40) ) + + case 0x70: // BVS + BRANCH( flags & v40 ) + + case 0xB0: // BCS + BRANCH( c & 0x100 ) + + case 0x80: // BRA + branch_taken: + BRANCH_( true, 0 ); + + case 0xFF: + #ifdef IDLE_ADDR + if ( pc == IDLE_ADDR + 1 ) + goto idle_done; + #endif + + pc = (BOOST::uint16_t) pc; + + case 0x0F: // BBRn + case 0x1F: + case 0x2F: + case 0x3F: + case 0x4F: + case 0x5F: + case 0x6F: + case 0x7F: + case 0x8F: // BBSn + case 0x9F: + case 0xAF: + case 0xBF: + case 0xCF: + case 0xDF: + case 0xEF: { + // Make two copies of bits, one negated + int t = 0x101 * READ_LOW( data ); + t ^= 0xFF; + pc++; + data = GET_MSB(); + BRANCH( t & (1 << (opcode >> 4)) ) + } + + case 0x4C: // JMP abs + pc = GET_ADDR(); + goto loop; + + case 0x7C: // JMP (ind+X) + data += x; + case 0x6C:{// JMP (ind) + data += 0x100 * GET_MSB(); + pc = GET_LE16( &READ_CODE( data ) ); + goto loop; + } + +// Subroutine + + case 0x44: // BSR + WRITE_STACK( SP( -1 ), pc >> 8 ); + sp = SP( -2 ); + WRITE_STACK( sp, pc ); + goto branch_taken; + + case 0x20: { // JSR + int temp = pc + 1; + pc = GET_ADDR(); + WRITE_STACK( SP( -1 ), temp >> 8 ); + sp = SP( -2 ); + WRITE_STACK( sp, temp ); + goto loop; + } + + case 0x60: // RTS + pc = 1 + READ_STACK( sp ); + pc += 0x100 * READ_STACK( SP( 1 ) ); + sp = SP( 2 ); + goto loop; + + case 0x00: // BRK + goto handle_brk; + +// Common + + case 0xBD:{// LDA abs,X + PAGE_PENALTY( data + x ); + int addr = GET_ADDR() + x; + pc += 2; + READ_FAST( addr, nz ); + a = nz; + goto loop; + } + + case 0x9D:{// STA abs,X + int addr = GET_ADDR() + x; + pc += 2; + WRITE_FAST( addr, a ); + goto loop; + } + + case 0x95: // STA zp,x + data = BYTE( data + x ); + case 0x85: // STA zp + pc++; + WRITE_LOW( data, a ); + goto loop; + + case 0xAE:{// LDX abs + int addr = GET_ADDR(); + pc += 2; + READ_FAST( addr, nz ); + x = nz; + goto loop; + } + + case 0xA5: // LDA zp + a = nz = READ_LOW( data ); + pc++; + goto loop; + +// Load/store + + { + int addr; + case 0x91: // STA (ind),Y + addr = 0x100 * READ_LOW( BYTE( data + 1 ) ); + addr += READ_LOW( data ) + y; + pc++; + goto sta_ptr; + + case 0x81: // STA (ind,X) + data = BYTE( data + x ); + case 0x92: // STA (ind) + addr = 0x100 * READ_LOW( BYTE( data + 1 ) ); + addr += READ_LOW( data ); + pc++; + goto sta_ptr; + + case 0x99: // STA abs,Y + data += y; + case 0x8D: // STA abs + addr = data + 0x100 * GET_MSB(); + pc += 2; + sta_ptr: + WRITE_FAST( addr, a ); + goto loop; + } + + { + int addr; + case 0xA1: // LDA (ind,X) + data = BYTE( data + x ); + case 0xB2: // LDA (ind) + addr = 0x100 * READ_LOW( BYTE( data + 1 ) ); + addr += READ_LOW( data ); + pc++; + goto a_nz_read_addr; + + case 0xB1:// LDA (ind),Y + addr = READ_LOW( data ) + y; + PAGE_PENALTY( addr ); + addr += 0x100 * READ_LOW( BYTE( data + 1 ) ); + pc++; + goto a_nz_read_addr; + + case 0xB9: // LDA abs,Y + data += y; + PAGE_PENALTY( data ); + case 0xAD: // LDA abs + addr = data + 0x100 * GET_MSB(); + pc += 2; + a_nz_read_addr: + READ_FAST( addr, nz ); + a = nz; + goto loop; + } + + case 0xBE:{// LDX abs,y + PAGE_PENALTY( data + y ); + int addr = GET_ADDR() + y; + pc += 2; + FLUSH_TIME(); + x = nz = READ_MEM( addr ); + CACHE_TIME(); + goto loop; + } + + case 0xB5: // LDA zp,x + a = nz = READ_LOW( BYTE( data + x ) ); + pc++; + goto loop; + + case 0xA9: // LDA #imm + pc++; + a = data; + nz = data; + goto loop; + +// Bit operations + + case 0x3C: // BIT abs,x + data += x; + case 0x2C:{// BIT abs + int addr; + ADD_PAGE( addr ); + FLUSH_TIME(); + nz = READ_MEM( addr ); + CACHE_TIME(); + goto bit_common; + } + case 0x34: // BIT zp,x + data = BYTE( data + x ); + case 0x24: // BIT zp + data = READ_LOW( data ); + case 0x89: // BIT imm + nz = data; + bit_common: + pc++; + flags = (flags & ~v40) + (nz & v40); + if ( nz & a ) + goto loop; // Z should be clear, and nz must be non-zero if nz & a is + nz <<= 8; // set Z flag without affecting N flag + goto loop; + + { + int addr; + + case 0xB3: // TST abs,x + addr = GET_MSB() + x; + goto tst_abs; + + case 0x93: // TST abs + addr = GET_MSB(); + tst_abs: + addr += 0x100 * instr [2]; + pc++; + FLUSH_TIME(); + nz = READ_MEM( addr ); + CACHE_TIME(); + goto tst_common; + } + + case 0xA3: // TST zp,x + nz = READ_LOW( BYTE( GET_MSB() + x ) ); + goto tst_common; + + case 0x83: // TST zp + nz = READ_LOW( GET_MSB() ); + tst_common: + pc += 2; + flags = (flags & ~v40) + (nz & v40); + if ( nz & data ) + goto loop; // Z should be clear, and nz must be non-zero if nz & data is + nz <<= 8; // set Z flag without affecting N flag + goto loop; + + { + int addr; + case 0x0C: // TSB abs + case 0x1C: // TRB abs + addr = GET_ADDR(); + pc++; + goto txb_addr; + + // TODO: everyone lists different behaviors for the flags flags, ugh + case 0x04: // TSB zp + case 0x14: // TRB zp + addr = data + ram_addr; + txb_addr: + FLUSH_TIME(); + nz = a | READ_MEM( addr ); + if ( opcode & 0x10 ) + nz ^= a; // bits from a will already be set, so this clears them + flags = (flags & ~v40) + (nz & v40); + pc++; + WRITE_MEM( addr, nz ); + CACHE_TIME(); + goto loop; + } + + case 0x07: // RMBn + case 0x17: + case 0x27: + case 0x37: + case 0x47: + case 0x57: + case 0x67: + case 0x77: + pc++; + READ_LOW( data ) &= ~(1 << (opcode >> 4)); + goto loop; + + case 0x87: // SMBn + case 0x97: + case 0xA7: + case 0xB7: + case 0xC7: + case 0xD7: + case 0xE7: + case 0xF7: + pc++; + READ_LOW( data ) |= 1 << ((opcode >> 4) - 8); + goto loop; + +// Load/store + + case 0x9E: // STZ abs,x + data += x; + case 0x9C: // STZ abs + ADD_PAGE( data ); + pc++; + FLUSH_TIME(); + WRITE_MEM( data, 0 ); + CACHE_TIME(); + goto loop; + + case 0x74: // STZ zp,x + data = BYTE( data + x ); + case 0x64: // STZ zp + pc++; + WRITE_LOW( data, 0 ); + goto loop; + + case 0x94: // STY zp,x + data = BYTE( data + x ); + case 0x84: // STY zp + pc++; + WRITE_LOW( data, y ); + goto loop; + + case 0x96: // STX zp,y + data = BYTE( data + y ); + case 0x86: // STX zp + pc++; + WRITE_LOW( data, x ); + goto loop; + + case 0xB6: // LDX zp,y + data = BYTE( data + y ); + case 0xA6: // LDX zp + data = READ_LOW( data ); + case 0xA2: // LDX #imm + pc++; + x = data; + nz = data; + goto loop; + + case 0xB4: // LDY zp,x + data = BYTE( data + x ); + case 0xA4: // LDY zp + data = READ_LOW( data ); + case 0xA0: // LDY #imm + pc++; + y = data; + nz = data; + goto loop; + + case 0xBC: // LDY abs,X + data += x; + PAGE_PENALTY( data ); + case 0xAC:{// LDY abs + int addr = data + 0x100 * GET_MSB(); + pc += 2; + FLUSH_TIME(); + y = nz = READ_MEM( addr ); + CACHE_TIME(); + goto loop; + } + + { + int temp; + case 0x8C: // STY abs + temp = y; + if ( 0 ) + case 0x8E: // STX abs + temp = x; + int addr = GET_ADDR(); + pc += 2; + FLUSH_TIME(); + WRITE_MEM( addr, temp ); + CACHE_TIME(); + goto loop; + } + +// Compare + + case 0xEC:{// CPX abs + int addr = GET_ADDR(); + pc++; + FLUSH_TIME(); + data = READ_MEM( addr ); + CACHE_TIME(); + goto cpx_data; + } + + case 0xE4: // CPX zp + data = READ_LOW( data ); + case 0xE0: // CPX #imm + cpx_data: + nz = x - data; + pc++; + c = ~nz; + nz = BYTE( nz ); + goto loop; + + case 0xCC:{// CPY abs + int addr = GET_ADDR(); + pc++; + FLUSH_TIME(); + data = READ_MEM( addr ); + CACHE_TIME(); + goto cpy_data; + } + + case 0xC4: // CPY zp + data = READ_LOW( data ); + case 0xC0: // CPY #imm + cpy_data: + nz = y - data; + pc++; + c = ~nz; + nz = BYTE( nz ); + goto loop; + +// Logical + +#define ARITH_ADDR_MODES( op )\ + case op - 0x04: /* (ind,x) */\ + data = BYTE( data + x );\ + case op + 0x0D: /* (ind) */\ + data = 0x100 * READ_LOW( BYTE( data + 1 ) ) + READ_LOW( data );\ + goto ptr##op;\ + case op + 0x0C:{/* (ind),y */\ + int temp = READ_LOW( data ) + y;\ + PAGE_PENALTY( temp );\ + data = temp + 0x100 * READ_LOW( BYTE( data + 1 ) );\ + goto ptr##op;\ + }\ + case op + 0x10: /* zp,X */\ + data = BYTE( data + x );\ + case op + 0x00: /* zp */\ + data = READ_LOW( data );\ + goto imm##op;\ + case op + 0x14: /* abs,Y */\ + data += y;\ + goto ind##op;\ + case op + 0x18: /* abs,X */\ + data += x;\ + ind##op:\ + PAGE_PENALTY( data );\ + case op + 0x08: /* abs */\ + ADD_PAGE( data );\ + ptr##op:\ + FLUSH_TIME();\ + data = READ_MEM( data );\ + CACHE_TIME();\ + case op + 0x04: /* imm */\ + imm##op: + + ARITH_ADDR_MODES( 0xC5 ) // CMP + nz = a - data; + pc++; + c = ~nz; + nz = BYTE( nz ); + goto loop; + + ARITH_ADDR_MODES( 0x25 ) // AND + nz = (a &= data); + pc++; + goto loop; + + ARITH_ADDR_MODES( 0x45 ) // EOR + nz = (a ^= data); + pc++; + goto loop; + + ARITH_ADDR_MODES( 0x05 ) // ORA + nz = (a |= data); + pc++; + goto loop; + +// Add/subtract + + ARITH_ADDR_MODES( 0xE5 ) // SBC + data ^= 0xFF; + goto adc_imm; + + ARITH_ADDR_MODES( 0x65 ) // ADC + adc_imm: { + if ( flags & d08 ) + dprintf( "Decimal mode not supported\n" ); + int carry = c >> 8 & 1; + int ov = (a ^ 0x80) + carry + SBYTE( data ); + flags = (flags & ~v40) + (ov >> 2 & v40); + c = nz = a + data + carry; + pc++; + a = BYTE( nz ); + goto loop; + } + +// Shift/rotate + + case 0x4A: // LSR A + c = 0; + case 0x6A: // ROR A + nz = c >> 1 & 0x80; + c = a << 8; + nz += a >> 1; + a = nz; + goto loop; + + case 0x0A: // ASL A + nz = a << 1; + c = nz; + a = BYTE( nz ); + goto loop; + + case 0x2A: { // ROL A + nz = a << 1; + int temp = c >> 8 & 1; + c = nz; + nz += temp; + a = BYTE( nz ); + goto loop; + } + + case 0x5E: // LSR abs,X + data += x; + case 0x4E: // LSR abs + c = 0; + case 0x6E: // ROR abs + ror_abs: { + ADD_PAGE( data ); + FLUSH_TIME(); + int temp = READ_MEM( data ); + nz = (c >> 1 & 0x80) + (temp >> 1); + c = temp << 8; + goto rotate_common; + } + + case 0x3E: // ROL abs,X + data += x; + goto rol_abs; + + case 0x1E: // ASL abs,X + data += x; + case 0x0E: // ASL abs + c = 0; + case 0x2E: // ROL abs + rol_abs: + ADD_PAGE( data ); + nz = c >> 8 & 1; + FLUSH_TIME(); + nz += (c = READ_MEM( data ) << 1); + rotate_common: + pc++; + WRITE_MEM( data, BYTE( nz ) ); + CACHE_TIME(); + goto loop; + + case 0x7E: // ROR abs,X + data += x; + goto ror_abs; + + case 0x76: // ROR zp,x + data = BYTE( data + x ); + goto ror_zp; + + case 0x56: // LSR zp,x + data = BYTE( data + x ); + case 0x46: // LSR zp + c = 0; + case 0x66: // ROR zp + ror_zp: { + int temp = READ_LOW( data ); + nz = (c >> 1 & 0x80) + (temp >> 1); + c = temp << 8; + goto write_nz_zp; + } + + case 0x36: // ROL zp,x + data = BYTE( data + x ); + goto rol_zp; + + case 0x16: // ASL zp,x + data = BYTE( data + x ); + case 0x06: // ASL zp + c = 0; + case 0x26: // ROL zp + rol_zp: + nz = c >> 8 & 1; + nz += (c = READ_LOW( data ) << 1); + goto write_nz_zp; + +// Increment/decrement + +#define INC_DEC( reg, n ) reg = BYTE( nz = reg + n ); goto loop; + + case 0x1A: // INA + INC_DEC( a, +1 ) + + case 0xE8: // INX + INC_DEC( x, +1 ) + + case 0xC8: // INY + INC_DEC( y, +1 ) + + case 0x3A: // DEA + INC_DEC( a, -1 ) + + case 0xCA: // DEX + INC_DEC( x, -1 ) + + case 0x88: // DEY + INC_DEC( y, -1 ) + + case 0xF6: // INC zp,x + data = BYTE( data + x ); + case 0xE6: // INC zp + nz = 1; + goto add_nz_zp; + + case 0xD6: // DEC zp,x + data = BYTE( data + x ); + case 0xC6: // DEC zp + nz = -1; + add_nz_zp: + nz += READ_LOW( data ); + write_nz_zp: + pc++; + WRITE_LOW( data, nz ); + goto loop; + + case 0xFE: // INC abs,x + data = x + GET_ADDR(); + goto inc_ptr; + + case 0xEE: // INC abs + data = GET_ADDR(); + inc_ptr: + nz = 1; + goto inc_common; + + case 0xDE: // DEC abs,x + data = x + GET_ADDR(); + goto dec_ptr; + + case 0xCE: // DEC abs + data = GET_ADDR(); + dec_ptr: + nz = -1; + inc_common: + FLUSH_TIME(); + pc += 2; + nz += READ_MEM( data ); + WRITE_MEM( data, BYTE( nz ) ); + CACHE_TIME(); + goto loop; + +// Transfer + + case 0xA8: // TAY + y = nz = a; + goto loop; + + case 0x98: // TYA + a = nz = y; + goto loop; + + case 0xAA: // TAX + x = nz = a; + goto loop; + + case 0x8A: // TXA + a = nz = x; + goto loop; + + case 0x9A: // TXS + SET_SP( x ); // verified (no flag change) + goto loop; + + case 0xBA: // TSX + x = nz = GET_SP(); + goto loop; + + #define SWAP_REGS( r1, r2 ) {\ + int t = r1;\ + r1 = r2;\ + r2 = t;\ + goto loop;\ + } + + case 0x02: // SXY + SWAP_REGS( x, y ); + + case 0x22: // SAX + SWAP_REGS( a, x ); + + case 0x42: // SAY + SWAP_REGS( a, y ); + + case 0x62: // CLA + a = 0; + goto loop; + + case 0x82: // CLX + x = 0; + goto loop; + + case 0xC2: // CLY + y = 0; + goto loop; + +// Stack + + case 0x48: // PHA + sp = SP( -1 ); + WRITE_STACK( sp, a ); + goto loop; + + case 0x68: // PLA + a = nz = READ_STACK( sp ); + sp = SP( 1 ); + goto loop; + + case 0xDA: // PHX + sp = SP( -1 ); + WRITE_STACK( sp, x ); + goto loop; + + case 0x5A: // PHY + sp = SP( -1 ); + WRITE_STACK( sp, y ); + goto loop; + + case 0x40:{// RTI + pc = READ_STACK( SP( 1 ) ); + pc += READ_STACK( SP( 2 ) ) * 0x100; + int temp = READ_STACK( sp ); + sp = SP( 3 ); + data = flags; + SET_FLAGS( temp ); + CPU.r.flags = flags; // update externally-visible I flag + if ( (data ^ flags) & i04 ) + { + time_t new_time = CPU.end_time_; + if ( !(flags & i04) && new_time > CPU.irq_time_ ) + new_time = CPU.irq_time_; + int delta = s.base - new_time; + s.base = new_time; + s_time += delta; + } + goto loop; + } + + case 0xFA: // PLX + x = nz = READ_STACK( sp ); + sp = SP( 1 ); + goto loop; + + case 0x7A: // PLY + y = nz = READ_STACK( sp ); + sp = SP( 1 ); + goto loop; + + case 0x28:{// PLP + int temp = READ_STACK( sp ); + sp = SP( 1 ); + int changed = flags ^ temp; + SET_FLAGS( temp ); + if ( !(changed & i04) ) + goto loop; // I flag didn't change + if ( flags & i04 ) + goto handle_sei; + goto handle_cli; + } + + case 0x08:{// PHP + int temp; + GET_FLAGS( temp ); + sp = SP( -1 ); + WRITE_STACK( sp, temp | b10 ); + goto loop; + } + +// Flags + + case 0x38: // SEC + c = 0x100; + goto loop; + + case 0x18: // CLC + c = 0; + goto loop; + + case 0xB8: // CLV + flags &= ~v40; + goto loop; + + case 0xD8: // CLD + flags &= ~d08; + goto loop; + + case 0xF8: // SED + flags |= d08; + goto loop; + + case 0x58: // CLI + if ( !(flags & i04) ) + goto loop; + flags &= ~i04; + handle_cli: { + //dprintf( "CLI at %d\n", TIME ); + CPU.r.flags = flags; // update externally-visible I flag + int delta = s.base - CPU.irq_time_; + if ( delta <= 0 ) + { + if ( TIME() < CPU.irq_time_ ) + goto loop; + goto delayed_cli; + } + s.base = CPU.irq_time_; + s_time += delta; + if ( s_time < 0 ) + goto loop; + + if ( delta >= s_time + 1 ) + { + // delayed irq until after next instruction + s.base += s_time + 1; + s_time = -1; + CPU.irq_time_ = s.base; // TODO: remove, as only to satisfy debug check in loop + goto loop; + } + + // TODO: implement + delayed_cli: + dprintf( "Delayed CLI not supported\n" ); + goto loop; + } + + case 0x78: // SEI + if ( flags & i04 ) + goto loop; + flags |= i04; + handle_sei: { + CPU.r.flags = flags; // update externally-visible I flag + int delta = s.base - CPU.end_time_; + s.base = CPU.end_time_; + s_time += delta; + if ( s_time < 0 ) + goto loop; + + dprintf( "Delayed SEI not supported\n" ); + goto loop; + } + +// Special + + case 0x53:{// TAM + int bits = data; // avoid using data across function call + pc++; + for ( int i = 0; i < 8; i++ ) + if ( bits & (1 << i) ) + SET_MMR( i, a ); + goto loop; + } + + case 0x43:{// TMA + pc++; + byte const* in = CPU.mmr; + do + { + if ( data & 1 ) + a = *in; + in++; + } + while ( (data >>= 1) != 0 ); + goto loop; + } + + case 0x03: // ST0 + case 0x13: // ST1 + case 0x23:{// ST2 + int addr = opcode >> 4; + if ( addr ) + addr++; + pc++; + FLUSH_TIME(); + WRITE_VDP( addr, data ); + CACHE_TIME(); + goto loop; + } + + case 0xEA: // NOP + goto loop; + + case 0x54: // CSL + dprintf( "CSL not supported\n" ); + illegal_encountered = true; + goto loop; + + case 0xD4: // CSH + goto loop; + + case 0xF4: { // SET + //int operand = GET_MSB(); + dprintf( "SET not handled\n" ); + //switch ( data ) + //{ + //} + illegal_encountered = true; + goto loop; + } + +// Block transfer + + { + int in_alt; + int in_inc; + int out_alt; + int out_inc; + + case 0xE3: // TIA + in_alt = 0; + goto bxfer_alt; + + case 0xF3: // TAI + in_alt = 1; + bxfer_alt: + in_inc = in_alt ^ 1; + out_alt = in_inc; + out_inc = in_alt; + goto bxfer; + + case 0xD3: // TIN + in_inc = 1; + out_inc = 0; + goto bxfer_no_alt; + + case 0xC3: // TDD + in_inc = -1; + out_inc = -1; + goto bxfer_no_alt; + + case 0x73: // TII + in_inc = 1; + out_inc = 1; + bxfer_no_alt: + in_alt = 0; + out_alt = 0; + bxfer: + int in = GET_LE16( instr + 0 ); + int out = GET_LE16( instr + 2 ); + int count = GET_LE16( instr + 4 ); + if ( !count ) + count = 0x10000; + pc += 6; + WRITE_STACK( SP( -1 ), y ); + WRITE_STACK( SP( -2 ), a ); + WRITE_STACK( SP( -3 ), x ); + FLUSH_TIME(); + do + { + // TODO: reads from $0800-$1400 in I/O page should do I/O + int t = READ_MEM( in ); + in = WORD( in + in_inc ); + s.time += 6; + if ( in_alt ) + in_inc = -in_inc; + WRITE_MEM( out, t ); + out = WORD( out + out_inc ); + if ( out_alt ) + out_inc = -out_inc; + } + while ( --count ); + CACHE_TIME(); + goto loop; + } + +// Illegal + + default: + check( (unsigned) opcode <= 0xFF ); + dprintf( "Illegal opcode $%02X at $%04X\n", (int) opcode, (int) pc - 1 ); + illegal_encountered = true; + goto loop; + } + assert( false ); // catch missing 'goto loop' or accidental 'break' + + int result_; +handle_brk: + pc++; + result_ = 6; + +interrupt: + { + s_time += 7; + + // Save PC and read vector + WRITE_STACK( SP( -1 ), pc >> 8 ); + WRITE_STACK( SP( -2 ), pc ); + pc = GET_LE16( &READ_CODE( 0xFFF0 ) + result_ ); + + // Save flags + int temp; + GET_FLAGS( temp ); + if ( result_ == 6 ) + temp |= b10; // BRK sets B bit + sp = SP( -3 ); + WRITE_STACK( sp, temp ); + + // Update I flag in externally-visible flags + flags &= ~d08; + CPU.r.flags = (flags |= i04); + + // Update time + int delta = s.base - CPU.end_time_; + if ( delta >= 0 ) + goto loop; + s_time += delta; + s.base = CPU.end_time_; + goto loop; + } + +idle_done: + s_time = 0; + +out_of_time: + pc--; + + // Optional action that triggers interrupt or changes irq/end time + #ifdef CPU_DONE + { + CPU_DONE( result_ ); + if ( result_ >= 0 ) + goto interrupt; + if ( s_time < 0 ) + goto loop; + } + #endif + + // Flush cached state + CPU.r.pc = pc; + CPU.r.sp = GET_SP(); + CPU.r.a = a; + CPU.r.x = x; + CPU.r.y = y; + + int temp; + GET_FLAGS( temp ); + CPU.r.flags = temp; + + CPU.cpu_state_.base = s.base; + CPU.cpu_state_.time = s_time; + CPU.cpu_state = &CPU.cpu_state_; +} diff --git a/Frameworks/GME/gme/K051649_Emu.cpp b/Frameworks/GME/gme/K051649_Emu.cpp new file mode 100644 index 000000000..26aaa6794 --- /dev/null +++ b/Frameworks/GME/gme/K051649_Emu.cpp @@ -0,0 +1,73 @@ +// Game_Music_Emu $vers. http://www.slack.net/~ant/ + +#include "K051649_Emu.h" +#include "k051649.h" + +K051649_Emu::K051649_Emu() { SCC = 0; } + +K051649_Emu::~K051649_Emu() +{ + if ( SCC ) device_stop_k051649( SCC ); +} + +int K051649_Emu::set_rate( int clock_rate ) +{ + if ( SCC ) + { + device_stop_k051649( SCC ); + SCC = 0; + } + + SCC = device_start_k051649( clock_rate ); + if ( !SCC ) + return 1; + + reset(); + return 0; +} + +void K051649_Emu::reset() +{ + device_reset_k051649( SCC ); + k051649_set_mute_mask( SCC, 0 ); +} + +void K051649_Emu::write( int port, int offset, int data ) +{ + k051649_w( SCC, (port << 1) | 0x00, offset); + k051649_w( SCC, (port << 1) | 0x01, data); +} + +void K051649_Emu::mute_voices( int mask ) +{ + k051649_set_mute_mask( SCC, mask ); +} + +void K051649_Emu::run( int pair_count, sample_t* out ) +{ + stream_sample_t bufL[ 1024 ]; + stream_sample_t bufR[ 1024 ]; + stream_sample_t * buffers[2] = { bufL, bufR }; + + while (pair_count > 0) + { + int todo = pair_count; + if (todo > 1024) todo = 1024; + k051649_update( SCC, buffers, todo ); + + for (int i = 0; i < todo; i++) + { + int output_l = bufL [i]; + int output_r = bufR [i]; + output_l += out [0]; + output_r += out [1]; + if ( (short)output_l != output_l ) output_l = 0x7FFF ^ ( output_l >> 31 ); + if ( (short)output_r != output_r ) output_r = 0x7FFF ^ ( output_r >> 31 ); + out [0] = output_l; + out [1] = output_r; + out += 2; + } + + pair_count -= todo; + } +} diff --git a/Frameworks/GME/gme/K051649_Emu.h b/Frameworks/GME/gme/K051649_Emu.h new file mode 100644 index 000000000..f45214db6 --- /dev/null +++ b/Frameworks/GME/gme/K051649_Emu.h @@ -0,0 +1,33 @@ +// K051649 sound chip emulator interface + +// Game_Music_Emu $vers +#ifndef K051649_EMU_H +#define K051649_EMU_H + +class K051649_Emu { + void* SCC; +public: + K051649_Emu(); + ~K051649_Emu(); + + // Sets output sample rate and chip clock rates, in Hz. Returns non-zero + // if error. + int set_rate( int clock_rate ); + + // Resets to power-up state + void reset(); + + // Mutes voice n if bit n (1 << n) of mask is set + enum { channel_count = 5 }; + void mute_voices( int mask ); + + // Writes data to addr + void write( int port, int offset, int data ); + + // Runs and writes pair_count*2 samples to output + typedef short sample_t; + enum { out_chan_count = 2 }; // stereo + void run( int pair_count, sample_t* out ); +}; + +#endif diff --git a/Frameworks/GME/gme/K053260_Emu.cpp b/Frameworks/GME/gme/K053260_Emu.cpp new file mode 100644 index 000000000..8444b8c4d --- /dev/null +++ b/Frameworks/GME/gme/K053260_Emu.cpp @@ -0,0 +1,77 @@ +// Game_Music_Emu $vers. http://www.slack.net/~ant/ + +#include "K053260_Emu.h" +#include "k053260.h" + +K053260_Emu::K053260_Emu() { chip = 0; } + +K053260_Emu::~K053260_Emu() +{ + if ( chip ) device_stop_k053260( chip ); +} + +int K053260_Emu::set_rate( int clock_rate ) +{ + if ( chip ) + { + device_stop_k053260( chip ); + chip = 0; + } + + chip = device_start_k053260( clock_rate ); + if ( !chip ) + return 1; + + reset(); + return 0; +} + +void K053260_Emu::reset() +{ + device_reset_k053260( chip ); + k053260_set_mute_mask( chip, 0 ); +} + +void K053260_Emu::write( int addr, int data ) +{ + k053260_w( chip, addr, data); +} + +void K053260_Emu::write_rom( int size, int start, int length, void * data ) +{ + k053260_write_rom( chip, size, start, length, (const UINT8 *) data ); +} + +void K053260_Emu::mute_voices( int mask ) +{ + k053260_set_mute_mask( chip, mask ); +} + +void K053260_Emu::run( int pair_count, sample_t* out ) +{ + stream_sample_t bufL[ 1024 ]; + stream_sample_t bufR[ 1024 ]; + stream_sample_t * buffers[2] = { bufL, bufR }; + + while (pair_count > 0) + { + int todo = pair_count; + if (todo > 1024) todo = 1024; + k053260_update( chip, buffers, todo ); + + for (int i = 0; i < todo; i++) + { + int output_l = bufL [i]; + int output_r = bufR [i]; + output_l += out [0]; + output_r += out [1]; + if ( (short)output_l != output_l ) output_l = 0x7FFF ^ ( output_l >> 31 ); + if ( (short)output_r != output_r ) output_r = 0x7FFF ^ ( output_r >> 31 ); + out [0] = output_l; + out [1] = output_r; + out += 2; + } + + pair_count -= todo; + } +} diff --git a/Frameworks/GME/gme/K053260_Emu.h b/Frameworks/GME/gme/K053260_Emu.h new file mode 100644 index 000000000..af346c1cc --- /dev/null +++ b/Frameworks/GME/gme/K053260_Emu.h @@ -0,0 +1,36 @@ +// K053260 sound chip emulator interface + +// Game_Music_Emu $vers +#ifndef K053260_EMU_H +#define K053260_EMU_H + +class K053260_Emu { + void* chip; +public: + K053260_Emu(); + ~K053260_Emu(); + + // Sets output sample rate and chip clock rates, in Hz. Returns non-zero + // if error. + int set_rate( int clock_rate ); + + // Resets to power-up state + void reset(); + + // Mutes voice n if bit n (1 << n) of mask is set + enum { channel_count = 5 }; + void mute_voices( int mask ); + + // Writes data to addr + void write( int addr, int data ); + + // Scales ROM size, then writes length bytes from data at start offset + void write_rom( int size, int start, int length, void * data ); + + // Runs and writes pair_count*2 samples to output + typedef short sample_t; + enum { out_chan_count = 2 }; // stereo + void run( int pair_count, sample_t* out ); +}; + +#endif diff --git a/Frameworks/GME/gme/K054539_Emu.cpp b/Frameworks/GME/gme/K054539_Emu.cpp new file mode 100644 index 000000000..3c25eb633 --- /dev/null +++ b/Frameworks/GME/gme/K054539_Emu.cpp @@ -0,0 +1,79 @@ +// Game_Music_Emu $vers. http://www.slack.net/~ant/ + +#include "K054539_Emu.h" +#include "k054539.h" + +K054539_Emu::K054539_Emu() { chip = 0; } + +K054539_Emu::~K054539_Emu() +{ + if ( chip ) device_stop_k054539( chip ); +} + +int K054539_Emu::set_rate( int clock_rate, int flags ) +{ + if ( chip ) + { + device_stop_k054539( chip ); + chip = 0; + } + + chip = device_start_k054539( clock_rate ); + if ( !chip ) + return 1; + + k054539_init_flags( chip, flags ); + + reset(); + return 0; +} + +void K054539_Emu::reset() +{ + device_reset_k054539( chip ); + k054539_set_mute_mask( chip, 0 ); +} + +void K054539_Emu::write( int addr, int data ) +{ + k054539_w( chip, addr, data); +} + +void K054539_Emu::write_rom( int size, int start, int length, void * data ) +{ + k054539_write_rom( chip, size, start, length, (const UINT8 *) data ); +} + +void K054539_Emu::mute_voices( int mask ) +{ + k054539_set_mute_mask( chip, mask ); +} + +void K054539_Emu::run( int pair_count, sample_t* out ) +{ + stream_sample_t bufL[ 1024 ]; + stream_sample_t bufR[ 1024 ]; + stream_sample_t * buffers[2] = { bufL, bufR }; + + while (pair_count > 0) + { + int todo = pair_count; + if (todo > 1024) todo = 1024; + k054539_update( chip, buffers, todo ); + + for (int i = 0; i < todo; i++) + { + int output_l = bufL [i]; + int output_r = bufR [i]; + output_l += out [0]; + output_r += out [1]; + if ( (short)output_l != output_l ) output_l = 0x7FFF ^ ( output_l >> 31 ); + if ( (short)output_r != output_r ) output_r = 0x7FFF ^ ( output_r >> 31 ); + out [0] = output_l; + out [1] = output_r; + out += 2; + } + + pair_count -= todo; + } +} diff --git a/Frameworks/GME/gme/K054539_Emu.h b/Frameworks/GME/gme/K054539_Emu.h new file mode 100644 index 000000000..1af976212 --- /dev/null +++ b/Frameworks/GME/gme/K054539_Emu.h @@ -0,0 +1,36 @@ +// K054539 sound chip emulator interface + +// Game_Music_Emu $vers +#ifndef K054539_EMU_H +#define K054539_EMU_H + +class K054539_Emu { + void* chip; +public: + K054539_Emu(); + ~K054539_Emu(); + + // Sets output sample rate and chip clock rates, in Hz. Returns non-zero + // if error. + int set_rate( int clock_rate, int flags ); + + // Resets to power-up state + void reset(); + + // Mutes voice n if bit n (1 << n) of mask is set + enum { channel_count = 5 }; + void mute_voices( int mask ); + + // Writes data to addr + void write( int addr, int data ); + + // Scales ROM size, then writes length bytes from data at start offset + void write_rom( int size, int start, int length, void * data ); + + // Runs and writes pair_count*2 samples to output + typedef short sample_t; + enum { out_chan_count = 2 }; // stereo + void run( int pair_count, sample_t* out ); +}; + +#endif diff --git a/Frameworks/GME/gme/Kss_Core.cpp b/Frameworks/GME/gme/Kss_Core.cpp new file mode 100644 index 000000000..97ac2ded5 --- /dev/null +++ b/Frameworks/GME/gme/Kss_Core.cpp @@ -0,0 +1,214 @@ +// Game_Music_Emu $vers. http://www.slack.net/~ant/ + +#include "Kss_Core.h" + +#include "blargg_endian.h" + +/* Copyright (C) 2006-2009 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +#include "blargg_source.h" + +Kss_Core::Kss_Core() : rom( Kss_Cpu::page_size ) +{ + memset( unmapped_read, 0xFF, sizeof unmapped_read ); +} + +Kss_Core::~Kss_Core() { } + +void Kss_Core::unload() +{ + rom.clear(); +} + +static blargg_err_t check_kss_header( void const* header ) +{ + if ( memcmp( header, "KSCC", 4 ) && memcmp( header, "KSSX", 4 ) ) + return blargg_err_file_type; + return blargg_ok; +} + +blargg_err_t Kss_Core::load_( Data_Reader& in ) +{ + memset( &header_, 0, sizeof header_ ); + assert( offsetof (header_t,msx_audio_vol) == header_t::size - 1 ); + RETURN_ERR( rom.load( in, header_t::base_size, &header_, 0 ) ); + + RETURN_ERR( check_kss_header( header_.tag ) ); + + header_.last_track [0] = 255; + if ( header_.tag [3] == 'C' ) + { + if ( header_.extra_header ) + { + header_.extra_header = 0; + set_warning( "Unknown data in header" ); + } + if ( header_.device_flags & ~0x0F ) + { + header_.device_flags &= 0x0F; + set_warning( "Unknown data in header" ); + } + } + else if ( header_.extra_header ) + { + if ( header_.extra_header != header_.ext_size ) + { + header_.extra_header = 0; + set_warning( "Invalid extra_header_size" ); + } + else + { + memcpy( header_.data_size, rom.begin(), header_.ext_size ); + } + } + + #ifndef NDEBUG + { + int ram_mode = header_.device_flags & 0x84; // MSX + if ( header_.device_flags & 0x02 ) // SMS + ram_mode = (header_.device_flags & 0x88); + + if ( ram_mode ) + dprintf( "RAM not supported\n" ); // TODO: support + } + #endif + + return blargg_ok; +} + +void Kss_Core::jsr( byte const (&addr) [2] ) +{ + ram [--cpu.r.sp] = idle_addr >> 8; + ram [--cpu.r.sp] = idle_addr & 0xFF; + cpu.r.pc = get_le16( addr ); +} + +blargg_err_t Kss_Core::start_track( int track ) +{ + memset( ram, 0xC9, 0x4000 ); + memset( ram + 0x4000, 0, sizeof ram - 0x4000 ); + + // copy driver code to lo RAM + static byte const bios [] = { + 0xD3, 0xA0, 0xF5, 0x7B, 0xD3, 0xA1, 0xF1, 0xC9, // $0001: WRTPSG + 0xD3, 0xA0, 0xDB, 0xA2, 0xC9 // $0009: RDPSG + }; + static byte const vectors [] = { + 0xC3, 0x01, 0x00, // $0093: WRTPSG vector + 0xC3, 0x09, 0x00, // $0096: RDPSG vector + }; + memcpy( ram + 0x01, bios, sizeof bios ); + memcpy( ram + 0x93, vectors, sizeof vectors ); + + // copy non-banked data into RAM + int load_addr = get_le16( header_.load_addr ); + int orig_load_size = get_le16( header_.load_size ); + int load_size = min( orig_load_size, rom.file_size() ); + load_size = min( load_size, (int) mem_size - load_addr ); + if ( load_size != orig_load_size ) + set_warning( "Excessive data size" ); + memcpy( ram + load_addr, rom.begin() + header_.extra_header, load_size ); + + rom.set_addr( -load_size - header_.extra_header ); + + // check available bank data + int const bank_size = this->bank_size(); + int max_banks = (rom.file_size() - load_size + bank_size - 1) / bank_size; + bank_count = header_.bank_mode & 0x7F; + if ( bank_count > max_banks ) + { + bank_count = max_banks; + set_warning( "Bank data missing" ); + } + //dprintf( "load_size : $%X\n", load_size ); + //dprintf( "bank_size : $%X\n", bank_size ); + //dprintf( "bank_count: %d (%d claimed)\n", bank_count, header_.bank_mode & 0x7F ); + + ram [idle_addr] = 0xFF; + cpu.reset( unmapped_write, unmapped_read ); + cpu.map_mem( 0, mem_size, ram, ram ); + + cpu.r.sp = 0xF380; + cpu.r.b.a = track; + cpu.r.b.h = 0; + next_play = play_period; + gain_updated = false; + jsr( header_.init_addr ); + + return blargg_ok; +} + +void Kss_Core::set_bank( int logical, int physical ) +{ + int const bank_size = this->bank_size(); + + int addr = 0x8000; + if ( logical && bank_size == 8 * 1024 ) + addr = 0xA000; + + physical -= header_.first_bank; + if ( (unsigned) physical >= (unsigned) bank_count ) + { + byte* data = ram + addr; + cpu.map_mem( addr, bank_size, data, data ); + } + else + { + int phys = physical * bank_size; + for ( int offset = 0; offset < bank_size; offset += cpu.page_size ) + cpu.map_mem( addr + offset, cpu.page_size, + unmapped_write, rom.at_addr( phys + offset ) ); + } +} + +void Kss_Core::cpu_out( time_t, addr_t addr, int data ) +{ + dprintf( "OUT $%04X,$%02X\n", addr, data ); +} + +int Kss_Core::cpu_in( time_t, addr_t addr ) +{ + dprintf( "IN $%04X\n", addr ); + return 0xFF; +} + +blargg_err_t Kss_Core::end_frame( time_t end ) +{ + while ( cpu.time() < end ) + { + time_t next = min( end, next_play ); + run_cpu( next ); + if ( cpu.r.pc == idle_addr ) + cpu.set_time( next ); + + if ( cpu.time() >= next_play ) + { + next_play += play_period; + if ( cpu.r.pc == idle_addr ) + { + if ( !gain_updated ) + { + gain_updated = true; + update_gain(); + } + + jsr( header_.play_addr ); + } + } + } + + next_play -= end; + check( next_play >= 0 ); + cpu.adjust_time( -end ); + + return blargg_ok; +} diff --git a/Frameworks/GME/gme/Kss_Core.h b/Frameworks/GME/gme/Kss_Core.h new file mode 100644 index 000000000..67ec94e20 --- /dev/null +++ b/Frameworks/GME/gme/Kss_Core.h @@ -0,0 +1,100 @@ +// MSX computer KSS music file emulator + +// Game_Music_Emu $vers +#ifndef KSS_CORE_H +#define KSS_CORE_H + +#include "Gme_Loader.h" +#include "Rom_Data.h" +#include "Z80_Cpu.h" + +class Kss_Core : public Gme_Loader { +public: + // KSS file header + struct header_t + { + enum { size = 0x20 }; + enum { base_size = 0x10 }; + enum { ext_size = size - base_size }; + + byte tag [4]; + byte load_addr [2]; + byte load_size [2]; + byte init_addr [2]; + byte play_addr [2]; + byte first_bank; + byte bank_mode; + byte extra_header; + byte device_flags; + + // KSSX extended data, if extra_header==0x10 + byte data_size [4]; + byte unused [4]; + byte first_track [2]; + byte last_track [2]; // if no extended data, we set this to 0xFF + byte psg_vol; + byte scc_vol; + byte msx_music_vol; + byte msx_audio_vol; + }; + + // Header for currently loaded file + header_t const& header() const { return header_; } + + // ROM data + Rom_Data const& rom_() const { return rom; } + + typedef int time_t; + void set_play_period( time_t p ) { play_period = p; } + + blargg_err_t start_track( int ); + + blargg_err_t end_frame( time_t ); + +protected: + typedef Z80_Cpu Kss_Cpu; + Kss_Cpu cpu; + + void set_bank( int logical, int physical ); + + typedef int addr_t; + virtual void cpu_write( addr_t, int ) = 0; + virtual int cpu_in( time_t, addr_t ); + virtual void cpu_out( time_t, addr_t, int ); + + // Called after one frame of emulation + virtual void update_gain() = 0; + +// Implementation +public: + Kss_Core(); + virtual ~Kss_Core(); + +protected: + virtual blargg_err_t load_( Data_Reader& ); + virtual void unload(); + +private: + enum { idle_addr = 0xFFFF }; + + Rom_Data rom; + header_t header_; + bool gain_updated; + int bank_count; + time_t play_period; + time_t next_play; + + // large items + enum { mem_size = 0x10000 }; + byte ram [mem_size + Kss_Cpu::cpu_padding]; + byte unmapped_read [0x100]; // TODO: why isn't this page_size? + // because CPU can't read beyond this in last page? or because it will spill into unmapped_write? + + byte unmapped_write [Kss_Cpu::page_size]; + + int bank_size() const { return (16 * 1024) >> (header_.bank_mode >> 7 & 1); } + bool run_cpu( time_t end ); + void jsr( byte const (&addr) [2] ); +}; + +#endif diff --git a/Frameworks/GME/gme/Nes_Cpu_run.h b/Frameworks/GME/gme/Nes_Cpu_run.h new file mode 100644 index 000000000..0973b1852 --- /dev/null +++ b/Frameworks/GME/gme/Nes_Cpu_run.h @@ -0,0 +1,1121 @@ +// NES 6502 CPU emulator run function + +#if 0 +/* Define these macros in the source file before #including this file. +- Parameters might be expressions, so they are best evaluated only once, +though they NEVER have side-effects, so multiple evaluation is OK. +- Output parameters might be a multiple-assignment expression like "a=x", +so they must NOT be parenthesized. +- Except where noted, time() and related functions will NOT work +correctly inside a macro. TIME() is always correct, and FLUSH_TIME() and +CACHE_TIME() allow the time changing functions to work. +- Macros "returning" void may use a {} statement block. */ + + // 0 <= addr <= 0xFFFF + page_size + // time functions can be used + int READ_MEM( addr_t ); + void WRITE_MEM( addr_t, int data ); + // 0 <= READ_MEM() <= 0xFF + + // 0 <= addr <= 0x1FF + int READ_LOW( addr_t ); + void WRITE_LOW( addr_t, int data ); + // 0 <= READ_LOW() <= 0xFF + + // Often-used instructions attempt these before using a normal memory access. + // Optional; defaults to READ_MEM() and WRITE_MEM() + bool CAN_READ_FAST( addr_t ); // if true, uses result of READ_FAST + void READ_FAST( addr_t, int& out ); // ALWAYS called BEFORE CAN_READ_FAST + bool CAN_WRITE_FAST( addr_t ); // if true, uses WRITE_FAST instead of WRITE_MEM + void WRITE_FAST( addr_t, int data ); + + // Used by instructions most often used to access the NES PPU (LDA abs and BIT abs). + // Optional; defaults to READ_MEM. + void READ_PPU( addr_t, int& out ); + // 0 <= out <= 0xFF + +// The following can be used within macros: + + // Current time + time_t TIME(); + + // Allows use of time functions + void FLUSH_TIME(); + + // Must be used before end of macro if FLUSH_TIME() was used earlier + void CACHE_TIME(); + +// Configuration (optional; commented behavior if defined) + + // Emulates dummy reads for indexed instructions + #define NES_CPU_DUMMY_READS 1 + + // Optimizes as if map_code( 0, 0x10000 + cpu_padding, FLAT_MEM ) is always in effect + #define FLAT_MEM my_mem_array + + // Expanded just before beginning of code, to help debugger + #define CPU_BEGIN void my_run_cpu() { + +#endif + +/* Copyright (C) 2003-2008 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +// Allows MWCW debugger to step through code properly +#ifdef CPU_BEGIN + CPU_BEGIN +#endif + +// Time +#define TIME() (s_time + s.base) +#define FLUSH_TIME() {s.time = s_time - time_offset;} +#define CACHE_TIME() {s_time = s.time + time_offset;} + +// Defaults +#ifndef CAN_WRITE_FAST + #define CAN_WRITE_FAST( addr ) 0 + #define WRITE_FAST( addr, data ) +#endif + +#ifndef CAN_READ_FAST + #define CAN_READ_FAST( addr ) 0 + #define READ_FAST( addr, out ) +#endif + +#ifndef READ_PPU + #define READ_PPU( addr, out )\ + {\ + FLUSH_TIME();\ + out = READ_MEM( addr );\ + CACHE_TIME();\ + } +#endif + +#define READ_STACK READ_LOW +#define WRITE_STACK WRITE_LOW + +// Dummy reads +#if NES_CPU_DUMMY_READS + // TODO: optimize time handling + #define DUMMY_READ( addr, idx ) \ + if ( (addr & 0xFF) < idx )\ + {\ + int const time_offset = 1;\ + FLUSH_TIME();\ + READ_MEM( (addr - 0x100) );\ + CACHE_TIME();\ + } +#else + #define DUMMY_READ( addr, idx ) +#endif + +// Code +#ifdef FLAT_MEM + #define CODE_PAGE( addr ) (FLAT_MEM) + #define CODE_OFFSET( addr ) (addr) +#else + #define CODE_PAGE( addr ) (s.code_map [NES_CPU_PAGE( addr )]) + #define CODE_OFFSET( addr ) NES_CPU_OFFSET( addr ) +#endif +#define READ_CODE( addr ) (CODE_PAGE( addr ) [CODE_OFFSET( addr )]) + +// Stack +#define SET_SP( v ) (sp = ((v) + 1) | 0x100) +#define GET_SP() ((sp - 1) & 0xFF) +#define SP( o ) ((sp + (o - (o>0)*0x100)) | 0x100) + +// Truncation +#define BYTE( n ) ((BOOST::uint8_t ) (n)) /* (unsigned) n & 0xFF */ +#define SBYTE( n ) ((BOOST::int8_t ) (n)) /* (BYTE( n ) ^ 0x80) - 0x80 */ +#define WORD( n ) ((BOOST::uint16_t) (n)) /* (unsigned) n & 0xFFFF */ + +// Flags with hex value for clarity when used as mask. +// Stored in indicated variable during emulation. +int const n80 = 0x80; // nz +int const v40 = 0x40; // flags +int const r20 = 0x20; +int const b10 = 0x10; +int const d08 = 0x08; // flags +int const i04 = 0x04; // flags +int const z02 = 0x02; // nz +int const c01 = 0x01; // c + +#define IS_NEG (nz & 0x8080) + +#define GET_FLAGS( out ) \ +{\ + out = flags & (v40 | d08 | i04);\ + out += ((nz >> 8) | nz) & n80;\ + out += c >> 8 & c01;\ + if ( !BYTE( nz ) )\ + out += z02;\ +} + +#define SET_FLAGS( in ) \ +{\ + flags = in & (v40 | d08 | i04);\ + c = nz = in << 8;\ + nz += ~in & z02;\ +} + +{ + int const time_offset = 0; + + // Local state + Nes_Cpu::cpu_state_t s; + #ifdef FLAT_MEM + s.base = CPU.cpu_state_.base; + #else + s = CPU.cpu_state_; + #endif + CPU.cpu_state = &s; + int s_time = CPU.cpu_state_.time; // helps even on x86 + + // Registers + int pc = CPU.r.pc; + int a = CPU.r.a; + int x = CPU.r.x; + int y = CPU.r.y; + int sp; + SET_SP( CPU.r.sp ); + + // Flags + int flags; + int c; // carry set if (c & 0x100) != 0 + int nz; // Z set if (nz & 0xFF) == 0, N set if (nz & 0x8080) != 0 + { + int temp = CPU.r.flags; + SET_FLAGS( temp ); + } + +loop: + + // Check all values + check( (unsigned) sp - 0x100 < 0x100 ); + check( (unsigned) pc < 0x10000 ); + check( (unsigned) a < 0x100 ); + check( (unsigned) x < 0x100 ); + check( (unsigned) y < 0x100 ); + + // Read instruction + byte const* instr = CODE_PAGE( pc ); + int opcode; + + if ( CODE_OFFSET(~0) == ~0 ) + { + opcode = instr [pc]; + pc++; + instr += pc; + } + else + { + instr += CODE_OFFSET( pc ); + opcode = *instr++; + pc++; + } + + // local to function in case it helps optimizer + static byte const clock_table [256] = + {// 0 1 2 3 4 5 6 7 8 9 A B C D E F + 0,6,2,8,3,3,5,5,3,2,2,2,4,4,6,6,// 0 + 2,5,2,8,4,4,6,6,2,4,2,7,4,4,7,7,// 1 + 6,6,0,8,3,3,5,5,4,2,2,2,4,4,6,6,// 2 + 2,5,2,8,4,4,6,6,2,4,2,7,4,4,7,7,// 3 + 6,6,2,8,3,3,5,5,3,2,2,2,3,4,6,6,// 4 + 2,5,2,8,4,4,6,6,2,4,2,7,4,4,7,7,// 5 + 6,6,2,8,3,3,5,5,4,2,2,2,5,4,6,6,// 6 + 2,5,2,8,4,4,6,6,2,4,2,7,4,4,7,7,// 7 + 2,6,2,6,3,3,3,3,2,2,2,2,4,4,4,4,// 8 + 2,6,2,6,4,4,4,4,2,5,2,5,5,5,5,5,// 9 + 2,6,2,6,3,3,3,3,2,2,2,2,4,4,4,4,// A + 2,5,2,5,4,4,4,4,2,4,2,4,4,4,4,4,// B + 2,6,2,8,3,3,5,5,2,2,2,2,4,4,6,6,// C + 2,5,2,8,4,4,6,6,2,4,2,7,4,4,7,7,// D + 2,6,2,8,3,3,5,5,2,2,2,2,4,4,6,6,// E + 2,5,2,8,4,4,6,6,2,4,2,7,4,4,7,7 // F + }; // 0x00 was 7 and 0x22 was 2 + + // Update time + if ( s_time >= 0 ) + goto out_of_time; + + #ifdef CPU_INSTR_HOOK + { CPU_INSTR_HOOK( (pc-1), (&instr [-1]), a, x, y, GET_SP(), TIME() ); } + #endif + + s_time += clock_table [opcode]; + + int data; + data = *instr; + + switch ( opcode ) + { + +// Macros + +#define GET_MSB() (instr [1]) +#define ADD_PAGE( out ) (pc++, out = data + 0x100 * GET_MSB()) +#define GET_ADDR() GET_LE16( instr ) + +#define PAGE_PENALTY( lsb ) s_time += (lsb) >> 8; + +#define INC_DEC( reg, n ) reg = BYTE( nz = reg + n ); goto loop; + +#define IND_Y( cross, out ) {\ + int temp = READ_LOW( data ) + y;\ + out = temp + 0x100 * READ_LOW( BYTE( data + 1 ) );\ + cross( temp );\ + } + +#define IND_X( out ) {\ + int temp = data + x;\ + out = 0x100 * READ_LOW( BYTE( temp + 1 ) ) + READ_LOW( BYTE( temp ) );\ + } + +#define ARITH_ADDR_MODES( op )\ +case op - 0x04: /* (ind,x) */\ + IND_X( data )\ + goto ptr##op;\ +case op + 0x0C: /* (ind),y */\ + IND_Y( PAGE_PENALTY, data )\ + goto ptr##op;\ +case op + 0x10: /* zp,X */\ + data = BYTE( data + x );\ +case op + 0x00: /* zp */\ + data = READ_LOW( data );\ + goto imm##op;\ +case op + 0x14: /* abs,Y */\ + data += y;\ + goto ind##op;\ +case op + 0x18: /* abs,X */\ + data += x;\ +ind##op:\ + PAGE_PENALTY( data );\ +case op + 0x08: /* abs */\ + ADD_PAGE( data );\ +ptr##op:\ + FLUSH_TIME();\ + data = READ_MEM( data );\ + CACHE_TIME();\ +case op + 0x04: /* imm */\ +imm##op: + +// TODO: more efficient way to handle negative branch that wraps PC around +#define BRANCH( cond )\ +{\ + ++pc;\ + if ( !(cond) ) goto loop;\ + s_time++;\ + int offset = SBYTE( data );\ + s_time += (BYTE(pc) + offset) >> 8 & 1;\ + pc = WORD( pc + offset );\ + goto loop;\ +} + +// Often-Used + + case 0xB5: // LDA zp,x + a = nz = READ_LOW( BYTE( data + x ) ); + pc++; + goto loop; + + case 0xA5: // LDA zp + a = nz = READ_LOW( data ); + pc++; + goto loop; + + case 0xD0: // BNE + BRANCH( BYTE( nz ) ); + + case 0x20: { // JSR + int temp = pc + 1; + pc = GET_ADDR(); + WRITE_STACK( SP( -1 ), temp >> 8 ); + sp = SP( -2 ); + WRITE_STACK( sp, temp ); + goto loop; + } + + case 0x4C: // JMP abs + pc = GET_ADDR(); + goto loop; + + case 0xE8: // INX + INC_DEC( x, 1 ) + + case 0x10: // BPL + BRANCH( !IS_NEG ) + + ARITH_ADDR_MODES( 0xC5 ) // CMP + nz = a - data; + pc++; + c = ~nz; + nz &= 0xFF; + goto loop; + + case 0x30: // BMI + BRANCH( IS_NEG ) + + case 0xF0: // BEQ + BRANCH( !BYTE( nz ) ); + + case 0x95: // STA zp,x + data = BYTE( data + x ); + case 0x85: // STA zp + pc++; + WRITE_LOW( data, a ); + goto loop; + + case 0xC8: // INY + INC_DEC( y, 1 ) + + case 0xA8: // TAY + y = a; + nz = a; + goto loop; + + case 0x98: // TYA + a = y; + nz = y; + goto loop; + + case 0xAD:{// LDA abs + int addr = GET_ADDR(); + pc += 2; + READ_PPU( addr, a = nz ); + goto loop; + } + + case 0x60: // RTS + pc = 1 + READ_STACK( sp ); + pc += 0x100 * READ_STACK( SP( 1 ) ); + sp = SP( 2 ); + goto loop; + + { + int addr; + + case 0x8D: // STA abs + addr = GET_ADDR(); + pc += 2; + if ( CAN_WRITE_FAST( addr ) ) + { + WRITE_FAST( addr, a ); + goto loop; + } + sta_ptr: + FLUSH_TIME(); + WRITE_MEM( addr, a ); + CACHE_TIME(); + goto loop; + + case 0x99: // STA abs,Y + addr = y + GET_ADDR(); + pc += 2; + if ( CAN_WRITE_FAST( addr ) ) + { + WRITE_FAST( addr, a ); + goto loop; + } + goto sta_abs_x; + + case 0x9D: // STA abs,X (slightly more common than STA abs) + addr = x + GET_ADDR(); + pc += 2; + if ( CAN_WRITE_FAST( addr ) ) + { + WRITE_FAST( addr, a ); + goto loop; + } + DUMMY_READ( addr, x ); + sta_abs_x: + FLUSH_TIME(); + WRITE_MEM( addr, a ); + CACHE_TIME(); + goto loop; + + case 0x91: // STA (ind),Y + #define NO_PAGE_PENALTY( lsb ) + IND_Y( NO_PAGE_PENALTY, addr ) + pc++; + DUMMY_READ( addr, y ); + goto sta_ptr; + + case 0x81: // STA (ind,X) + IND_X( addr ) + pc++; + goto sta_ptr; + + } + + case 0xA9: // LDA #imm + pc++; + a = data; + nz = data; + goto loop; + + // common read instructions + { + int addr; + + case 0xA1: // LDA (ind,X) + IND_X( addr ) + pc++; + goto a_nz_read_addr; + + case 0xB1:// LDA (ind),Y + addr = READ_LOW( data ) + y; + PAGE_PENALTY( addr ); + addr += 0x100 * READ_LOW( BYTE( data + 1 ) ); + pc++; + READ_FAST( addr, a = nz ); + if ( CAN_READ_FAST( addr ) ) + goto loop; + DUMMY_READ( addr, y ); + goto a_nz_read_addr; + + case 0xB9: // LDA abs,Y + PAGE_PENALTY( data + y ); + addr = GET_ADDR() + y; + pc += 2; + READ_FAST( addr, a = nz ); + if ( CAN_READ_FAST( addr ) ) + goto loop; + goto a_nz_read_addr; + + case 0xBD: // LDA abs,X + PAGE_PENALTY( data + x ); + addr = GET_ADDR() + x; + pc += 2; + READ_FAST( addr, a = nz ); + if ( CAN_READ_FAST( addr ) ) + goto loop; + DUMMY_READ( addr, x ); + a_nz_read_addr: + FLUSH_TIME(); + a = nz = READ_MEM( addr ); + CACHE_TIME(); + goto loop; + + } + +// Branch + + case 0x50: // BVC + BRANCH( !(flags & v40) ) + + case 0x70: // BVS + BRANCH( flags & v40 ) + + case 0xB0: // BCS + BRANCH( c & 0x100 ) + + case 0x90: // BCC + BRANCH( !(c & 0x100) ) + +// Load/store + + case 0x94: // STY zp,x + data = BYTE( data + x ); + case 0x84: // STY zp + pc++; + WRITE_LOW( data, y ); + goto loop; + + case 0x96: // STX zp,y + data = BYTE( data + y ); + case 0x86: // STX zp + pc++; + WRITE_LOW( data, x ); + goto loop; + + case 0xB6: // LDX zp,y + data = BYTE( data + y ); + case 0xA6: // LDX zp + data = READ_LOW( data ); + case 0xA2: // LDX #imm + pc++; + x = data; + nz = data; + goto loop; + + case 0xB4: // LDY zp,x + data = BYTE( data + x ); + case 0xA4: // LDY zp + data = READ_LOW( data ); + case 0xA0: // LDY #imm + pc++; + y = data; + nz = data; + goto loop; + + case 0xBC: // LDY abs,X + data += x; + PAGE_PENALTY( data ); + case 0xAC:{// LDY abs + int addr = data + 0x100 * GET_MSB(); + pc += 2; + FLUSH_TIME(); + y = nz = READ_MEM( addr ); + CACHE_TIME(); + goto loop; + } + + case 0xBE: // LDX abs,y + data += y; + PAGE_PENALTY( data ); + case 0xAE:{// LDX abs + int addr = data + 0x100 * GET_MSB(); + pc += 2; + FLUSH_TIME(); + x = nz = READ_MEM( addr ); + CACHE_TIME(); + goto loop; + } + + { + int temp; + case 0x8C: // STY abs + temp = y; + goto store_abs; + + case 0x8E: // STX abs + temp = x; + store_abs: + int addr = GET_ADDR(); + pc += 2; + if ( CAN_WRITE_FAST( addr ) ) + { + WRITE_FAST( addr, temp ); + goto loop; + } + FLUSH_TIME(); + WRITE_MEM( addr, temp ); + CACHE_TIME(); + goto loop; + } + +// Compare + + case 0xEC:{// CPX abs + int addr = GET_ADDR(); + pc++; + FLUSH_TIME(); + data = READ_MEM( addr ); + CACHE_TIME(); + goto cpx_data; + } + + case 0xE4: // CPX zp + data = READ_LOW( data ); + case 0xE0: // CPX #imm + cpx_data: + nz = x - data; + pc++; + c = ~nz; + nz &= 0xFF; + goto loop; + + case 0xCC:{// CPY abs + int addr = GET_ADDR(); + pc++; + FLUSH_TIME(); + data = READ_MEM( addr ); + CACHE_TIME(); + goto cpy_data; + } + + case 0xC4: // CPY zp + data = READ_LOW( data ); + case 0xC0: // CPY #imm + cpy_data: + nz = y - data; + pc++; + c = ~nz; + nz &= 0xFF; + goto loop; + +// Logical + + ARITH_ADDR_MODES( 0x25 ) // AND + nz = (a &= data); + pc++; + goto loop; + + ARITH_ADDR_MODES( 0x45 ) // EOR + nz = (a ^= data); + pc++; + goto loop; + + ARITH_ADDR_MODES( 0x05 ) // ORA + nz = (a |= data); + pc++; + goto loop; + + case 0x2C:{// BIT abs + int addr = GET_ADDR(); + pc += 2; + READ_PPU( addr, nz ); + flags = (flags & ~v40) + (nz & v40); + if ( a & nz ) + goto loop; + nz <<= 8; // result must be zero, even if N bit is set + goto loop; + } + + case 0x24: // BIT zp + nz = READ_LOW( data ); + pc++; + flags = (flags & ~v40) + (nz & v40); + if ( a & nz ) + goto loop; // Z should be clear, and nz must be non-zero if nz & a is + nz <<= 8; // set Z flag without affecting N flag + goto loop; + +// Add/subtract + + ARITH_ADDR_MODES( 0xE5 ) // SBC + case 0xEB: // unofficial equivalent + data ^= 0xFF; + goto adc_imm; + + ARITH_ADDR_MODES( 0x65 ) // ADC + adc_imm: { + int carry = c >> 8 & 1; + int ov = (a ^ 0x80) + carry + SBYTE( data ); + flags = (flags & ~v40) + (ov >> 2 & v40); + c = nz = a + data + carry; + pc++; + a = BYTE( nz ); + goto loop; + } + +// Shift/rotate + + case 0x4A: // LSR A + c = 0; + case 0x6A: // ROR A + nz = c >> 1 & 0x80; + c = a << 8; + nz += a >> 1; + a = nz; + goto loop; + + case 0x0A: // ASL A + nz = a << 1; + c = nz; + a = BYTE( nz ); + goto loop; + + case 0x2A: { // ROL A + nz = a << 1; + int temp = c >> 8 & 1; + c = nz; + nz += temp; + a = BYTE( nz ); + goto loop; + } + + case 0x5E: // LSR abs,X + data += x; + case 0x4E: // LSR abs + c = 0; + case 0x6E: // ROR abs + ror_abs: { + ADD_PAGE( data ); + FLUSH_TIME(); + int temp = READ_MEM( data ); + nz = (c >> 1 & 0x80) + (temp >> 1); + c = temp << 8; + goto rotate_common; + } + + case 0x3E: // ROL abs,X + data += x; + goto rol_abs; + + case 0x1E: // ASL abs,X + data += x; + case 0x0E: // ASL abs + c = 0; + case 0x2E: // ROL abs + rol_abs: + ADD_PAGE( data ); + nz = c >> 8 & 1; + FLUSH_TIME(); + nz += (c = READ_MEM( data ) << 1); + rotate_common: + pc++; + WRITE_MEM( data, BYTE( nz ) ); + CACHE_TIME(); + goto loop; + + case 0x7E: // ROR abs,X + data += x; + goto ror_abs; + + case 0x76: // ROR zp,x + data = BYTE( data + x ); + goto ror_zp; + + case 0x56: // LSR zp,x + data = BYTE( data + x ); + case 0x46: // LSR zp + c = 0; + case 0x66: // ROR zp + ror_zp: { + int temp = READ_LOW( data ); + nz = (c >> 1 & 0x80) + (temp >> 1); + c = temp << 8; + goto write_nz_zp; + } + + case 0x36: // ROL zp,x + data = BYTE( data + x ); + goto rol_zp; + + case 0x16: // ASL zp,x + data = BYTE( data + x ); + case 0x06: // ASL zp + c = 0; + case 0x26: // ROL zp + rol_zp: + nz = c >> 8 & 1; + nz += (c = READ_LOW( data ) << 1); + goto write_nz_zp; + +// Increment/decrement + + case 0xCA: // DEX + INC_DEC( x, -1 ) + + case 0x88: // DEY + INC_DEC( y, -1 ) + + case 0xF6: // INC zp,x + data = BYTE( data + x ); + case 0xE6: // INC zp + nz = 1; + goto add_nz_zp; + + case 0xD6: // DEC zp,x + data = BYTE( data + x ); + case 0xC6: // DEC zp + nz = -1; + add_nz_zp: + nz += READ_LOW( data ); + write_nz_zp: + pc++; + WRITE_LOW( data, nz ); + goto loop; + + case 0xFE: // INC abs,x + data = x + GET_ADDR(); + goto inc_ptr; + + case 0xEE: // INC abs + data = GET_ADDR(); + inc_ptr: + nz = 1; + goto inc_common; + + case 0xDE: // DEC abs,x + data = x + GET_ADDR(); + goto dec_ptr; + + case 0xCE: // DEC abs + data = GET_ADDR(); + dec_ptr: + nz = -1; + inc_common: + FLUSH_TIME(); + pc += 2; + nz += READ_MEM( data ); + WRITE_MEM( data, BYTE( nz ) ); + CACHE_TIME(); + goto loop; + +// Transfer + + case 0xAA: // TAX + x = nz = a; + goto loop; + + case 0x8A: // TXA + a = nz = x; + goto loop; + + case 0x9A: // TXS + SET_SP( x ); // verified (no flag change) + goto loop; + + case 0xBA: // TSX + x = nz = GET_SP(); + goto loop; + +// Stack + + case 0x48: // PHA + sp = SP( -1 ); + WRITE_STACK( sp, a ); + goto loop; + + case 0x68: // PLA + a = nz = READ_STACK( sp ); + sp = SP( 1 ); + goto loop; + + case 0x40:{// RTI + pc = READ_STACK( SP( 1 ) ); + pc += READ_STACK( SP( 2 ) ) * 0x100; + int temp = READ_STACK( sp ); + sp = SP( 3 ); + data = flags; + SET_FLAGS( temp ); + CPU.r.flags = flags; // update externally-visible I flag + int delta = s.base - CPU.irq_time_; + if ( delta <= 0 ) goto loop; // end_time < irq_time + if ( flags & i04 ) goto loop; + s_time += delta; + s.base = CPU.irq_time_; + goto loop; + } + + case 0x28:{// PLP + int temp = READ_STACK( sp ); + sp = SP( 1 ); + int changed = flags ^ temp; + SET_FLAGS( temp ); + if ( !(changed & i04) ) + goto loop; // I flag didn't change + if ( flags & i04 ) + goto handle_sei; + goto handle_cli; + } + + case 0x08:{// PHP + int temp; + GET_FLAGS( temp ); + sp = SP( -1 ); + WRITE_STACK( sp, temp | (b10 | r20) ); + goto loop; + } + + case 0x6C:{// JMP (ind) + data = GET_ADDR(); + byte const* page = CODE_PAGE( data ); + pc = page [CODE_OFFSET( data )]; + data = (data & 0xFF00) + ((data + 1) & 0xFF); + pc += page [CODE_OFFSET( data )] * 0x100; + goto loop; + } + + case 0x00: // BRK + goto handle_brk; + +// Flags + + case 0x38: // SEC + c = 0x100; + goto loop; + + case 0x18: // CLC + c = 0; + goto loop; + + case 0xB8: // CLV + flags &= ~v40; + goto loop; + + case 0xD8: // CLD + flags &= ~d08; + goto loop; + + case 0xF8: // SED + flags |= d08; + goto loop; + + case 0x58: // CLI + if ( !(flags & i04) ) + goto loop; + flags &= ~i04; + handle_cli: { + //dprintf( "CLI at %d\n", TIME ); + CPU.r.flags = flags; // update externally-visible I flag + int delta = s.base - CPU.irq_time_; + if ( delta <= 0 ) + { + if ( TIME() < CPU.irq_time_ ) + goto loop; + goto delayed_cli; + } + s.base = CPU.irq_time_; + s_time += delta; + if ( s_time < 0 ) + goto loop; + + if ( delta >= s_time + 1 ) + { + // delayed irq until after next instruction + s.base += s_time + 1; + s_time = -1; + goto loop; + } + + // TODO: implement + delayed_cli: + dprintf( "Delayed CLI not emulated\n" ); + goto loop; + } + + case 0x78: // SEI + if ( flags & i04 ) + goto loop; + flags |= i04; + handle_sei: { + CPU.r.flags = flags; // update externally-visible I flag + int delta = s.base - CPU.end_time_; + s.base = CPU.end_time_; + s_time += delta; + if ( s_time < 0 ) + goto loop; + + dprintf( "Delayed SEI not emulated\n" ); + goto loop; + } + +// Unofficial + + // SKW - skip word + case 0x1C: case 0x3C: case 0x5C: case 0x7C: case 0xDC: case 0xFC: + PAGE_PENALTY( data + x ); + case 0x0C: + pc++; + // SKB - skip byte + case 0x74: case 0x04: case 0x14: case 0x34: case 0x44: case 0x54: case 0x64: + case 0x80: case 0x82: case 0x89: case 0xC2: case 0xD4: case 0xE2: case 0xF4: + pc++; + goto loop; + + // NOP + case 0xEA: case 0x1A: case 0x3A: case 0x5A: case 0x7A: case 0xDA: case 0xFA: + goto loop; + + case Nes_Cpu::halt_opcode: // HLT - halt processor + if ( pc-- > 0x10000 ) + { + // handle wrap-around (assumes caller has put page of HLT at 0x10000) + pc = WORD( pc ); + goto loop; + } + case 0x02: case 0x12: case 0x32: case 0x42: case 0x52: + case 0x62: case 0x72: case 0x92: case 0xB2: case 0xD2: case 0xF2: + goto stop; + +// Unimplemented + + case 0xFF: // force 256-entry jump table for optimization purposes + c |= 1; // compiler doesn't know that this won't affect anything + default: + check( (unsigned) opcode < 0x100 ); + + #ifdef UNIMPL_INSTR + UNIMPL_INSTR(); + #endif + + // At least skip over proper number of bytes instruction uses + static unsigned char const illop_lens [8] = { + 0x40, 0x40, 0x40, 0x80, 0x40, 0x40, 0x80, 0xA0 + }; + int opcode = instr [-1]; + int len = illop_lens [opcode >> 2 & 7] >> (opcode << 1 & 6) & 3; + if ( opcode == 0x9C ) + len = 2; + pc += len; + CPU.error_count_++; + + // Account for extra clock + if ( (opcode >> 4) == 0x0B ) + { + if ( opcode == 0xB3 ) + data = READ_LOW( data ); + if ( opcode != 0xB7 ) + PAGE_PENALTY( data + y ); + } + goto loop; + } + assert( false ); // catch missing 'goto loop' or accidental 'break' + + int result_; +handle_brk: + pc++; + result_ = b10 | 4; + +#ifdef CPU_DONE +interrupt: +#endif + { + s_time += 7; + + // Save PC and read vector + WRITE_STACK( SP( -1 ), pc >> 8 ); + WRITE_STACK( SP( -2 ), pc ); + pc = GET_LE16( &READ_CODE( 0xFFFA ) + (result_ & 4) ); + + // Save flags + int temp; + GET_FLAGS( temp ); + temp |= r20 + (result_ & b10); // B flag set for BRK + sp = SP( -3 ); + WRITE_STACK( sp, temp ); + + // Update I flag in externally-visible flags + CPU.r.flags = (flags |= i04); + + // Update time + int delta = s.base - CPU.end_time_; + if ( delta >= 0 ) + goto loop; + s_time += delta; + s.base = CPU.end_time_; + goto loop; + } + +out_of_time: + pc--; + + // Optional action that triggers interrupt or changes irq/end time + #ifdef CPU_DONE + { + CPU_DONE( result_ ); + if ( result_ >= 0 ) + goto interrupt; + if ( s_time < 0 ) + goto loop; + } + #endif +stop: + + // Flush cached state + CPU.r.pc = pc; + CPU.r.sp = GET_SP(); + CPU.r.a = a; + CPU.r.x = x; + CPU.r.y = y; + + int temp; + GET_FLAGS( temp ); + CPU.r.flags = temp; + + CPU.cpu_state_.base = s.base; + CPU.cpu_state_.time = s_time; + CPU.cpu_state = &CPU.cpu_state_; +} diff --git a/Frameworks/GME/gme/Nes_Fds_Apu.cpp b/Frameworks/GME/gme/Nes_Fds_Apu.cpp new file mode 100644 index 000000000..69fe2c082 --- /dev/null +++ b/Frameworks/GME/gme/Nes_Fds_Apu.cpp @@ -0,0 +1,280 @@ +// Game_Music_Emu $vers. http://www.slack.net/~ant/ + +#include "Nes_Fds_Apu.h" + +/* Copyright (C) 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +#include "blargg_source.h" + +int const fract_range = 65536; + +void Nes_Fds_Apu::reset() +{ + memset( regs_, 0, sizeof regs_ ); + memset( mod_wave, 0, sizeof mod_wave ); + + last_time = 0; + env_delay = 0; + sweep_delay = 0; + wave_pos = 0; + last_amp = 0; + wave_fract = fract_range; + mod_fract = fract_range; + mod_pos = 0; + mod_write_pos = 0; + + static byte const initial_regs [0x0B] = { + 0x80, // disable envelope + 0, 0, 0xC0, // disable wave and lfo + 0x80, // disable sweep + 0, 0, 0x80, // disable modulation + 0, 0, 0xFF // LFO period // TODO: use 0xE8 as FDS ROM does? + }; + for ( int i = 0; i < (int) sizeof initial_regs; i++ ) + { + // two writes to set both gain and period for envelope registers + write_( io_addr + wave_size + i, 0 ); + write_( io_addr + wave_size + i, initial_regs [i] ); + } +} + +void Nes_Fds_Apu::write_( unsigned addr, int data ) +{ + unsigned reg = addr - io_addr; + if ( reg < io_size ) + { + if ( reg < wave_size ) + { + if ( regs (0x4089) & 0x80 ) + regs_ [reg] = data & wave_sample_max; + } + else + { + regs_ [reg] = data; + switch ( addr ) + { + case 0x4080: + if ( data & 0x80 ) + env_gain = data & 0x3F; + else + env_speed = (data & 0x3F) + 1; + break; + + case 0x4084: + if ( data & 0x80 ) + sweep_gain = data & 0x3F; + else + sweep_speed = (data & 0x3F) + 1; + break; + + case 0x4085: + mod_pos = mod_write_pos; + regs (0x4085) = data & 0x7F; + break; + + case 0x4088: + if ( regs (0x4087) & 0x80 ) + { + int pos = mod_write_pos; + data &= 0x07; + mod_wave [pos ] = data; + mod_wave [pos + 1] = data; + mod_write_pos = (pos + 2) & (wave_size - 1); + mod_pos = (mod_pos + 2) & (wave_size - 1); + } + break; + } + } + } +} + +void Nes_Fds_Apu::set_tempo( double t ) +{ + lfo_tempo = lfo_base_tempo; + if ( t != 1.0 ) + { + lfo_tempo = int ((double) lfo_base_tempo / t + 0.5); + if ( lfo_tempo <= 0 ) + lfo_tempo = 1; + } +} + +void Nes_Fds_Apu::run_until( blip_time_t final_end_time ) +{ + int const wave_freq = (regs (0x4083) & 0x0F) * 0x100 + regs (0x4082); + Blip_Buffer* const output_ = this->output_; + if ( wave_freq && output_ && !((regs (0x4089) | regs (0x4083)) & 0x80) ) + { + output_->set_modified(); + + // master_volume + #define MVOL_ENTRY( percent ) (master_vol_max * percent + 50) / 100 + static unsigned char const master_volumes [4] = { + MVOL_ENTRY( 100 ), MVOL_ENTRY( 67 ), MVOL_ENTRY( 50 ), MVOL_ENTRY( 40 ) + }; + int const master_volume = master_volumes [regs (0x4089) & 0x03]; + + // lfo_period + blip_time_t lfo_period = regs (0x408A) * lfo_tempo; + if ( regs (0x4083) & 0x40 ) + lfo_period = 0; + + // sweep setup + blip_time_t sweep_time = last_time + sweep_delay; + blip_time_t const sweep_period = lfo_period * sweep_speed; + if ( !sweep_period || regs (0x4084) & 0x80 ) + sweep_time = final_end_time; + + // envelope setup + blip_time_t env_time = last_time + env_delay; + blip_time_t const env_period = lfo_period * env_speed; + if ( !env_period || regs (0x4080) & 0x80 ) + env_time = final_end_time; + + // modulation + int mod_freq = 0; + if ( !(regs (0x4087) & 0x80) ) + mod_freq = (regs (0x4087) & 0x0F) * 0x100 + regs (0x4086); + + blip_time_t end_time = last_time; + do + { + // sweep + if ( sweep_time <= end_time ) + { + sweep_time += sweep_period; + int mode = regs (0x4084) >> 5 & 2; + int new_sweep_gain = sweep_gain + mode - 1; + if ( (unsigned) new_sweep_gain <= (unsigned) 0x80 >> mode ) + sweep_gain = new_sweep_gain; + else + regs (0x4084) |= 0x80; // optimization only + } + + // envelope + if ( env_time <= end_time ) + { + env_time += env_period; + int mode = regs (0x4080) >> 5 & 2; + int new_env_gain = env_gain + mode - 1; + if ( (unsigned) new_env_gain <= (unsigned) 0x80 >> mode ) + env_gain = new_env_gain; + else + regs (0x4080) |= 0x80; // optimization only + } + + // new end_time + blip_time_t const start_time = end_time; + end_time = final_end_time; + if ( end_time > env_time ) end_time = env_time; + if ( end_time > sweep_time ) end_time = sweep_time; + + // frequency modulation + int freq = wave_freq; + if ( mod_freq ) + { + // time of next modulation clock + blip_time_t mod_time = start_time + (mod_fract + mod_freq - 1) / mod_freq; + if ( end_time > mod_time ) + end_time = mod_time; + + // run modulator up to next clock and save old sweep_bias + int sweep_bias = regs (0x4085); + mod_fract -= (end_time - start_time) * mod_freq; + if ( mod_fract <= 0 ) + { + mod_fract += fract_range; + check( (unsigned) mod_fract <= fract_range ); + + static short const mod_table [8] = { 0, +1, +2, +4, 0, -4, -2, -1 }; + int mod = mod_wave [mod_pos]; + mod_pos = (mod_pos + 1) & (wave_size - 1); + int new_sweep_bias = (sweep_bias + mod_table [mod]) & 0x7F; + if ( mod == 4 ) + new_sweep_bias = 0; + regs (0x4085) = new_sweep_bias; + } + + // apply frequency modulation + sweep_bias = (sweep_bias ^ 0x40) - 0x40; + int factor = sweep_bias * sweep_gain; + int extra = factor & 0x0F; + factor >>= 4; + if ( extra ) + { + factor--; + if ( sweep_bias >= 0 ) + factor += 3; + } + if ( factor > 193 ) factor -= 258; + if ( factor < -64 ) factor += 256; + freq += (freq * factor) >> 6; + if ( freq <= 0 ) + continue; + } + + // wave + int wave_fract = this->wave_fract; + blip_time_t delay = (wave_fract + freq - 1) / freq; + blip_time_t time = start_time + delay; + + if ( time <= end_time ) + { + // at least one wave clock within start_time...end_time + + blip_time_t const min_delay = fract_range / freq; + int wave_pos = this->wave_pos; + + int volume = env_gain; + if ( volume > vol_max ) + volume = vol_max; + volume *= master_volume; + + int const min_fract = min_delay * freq; + + do + { + // clock wave + int amp = regs_ [wave_pos] * volume; + wave_pos = (wave_pos + 1) & (wave_size - 1); + int delta = amp - last_amp; + if ( delta ) + { + last_amp = amp; + synth.offset_inline( time, delta, output_ ); + } + + wave_fract += fract_range - delay * freq; + check( unsigned (fract_range - wave_fract) < freq ); + + // delay until next clock + delay = min_delay; + if ( wave_fract > min_fract ) + delay++; + check( delay && delay == (wave_fract + freq - 1) / freq ); + + time += delay; + } + while ( time <= end_time ); // TODO: using < breaks things, but <= is wrong + + this->wave_pos = wave_pos; + } + this->wave_fract = wave_fract - (end_time - (time - delay)) * freq; + check( this->wave_fract > 0 ); + } + while ( end_time < final_end_time ); + + env_delay = env_time - final_end_time; check( env_delay >= 0 ); + sweep_delay = sweep_time - final_end_time; check( sweep_delay >= 0 ); + } + last_time = final_end_time; +} diff --git a/Frameworks/GME/gme/Nes_Fds_Apu.h b/Frameworks/GME/gme/Nes_Fds_Apu.h new file mode 100644 index 000000000..21176b2db --- /dev/null +++ b/Frameworks/GME/gme/Nes_Fds_Apu.h @@ -0,0 +1,139 @@ +// NES FDS sound chip emulator + +// $package +#ifndef NES_FDS_APU_H +#define NES_FDS_APU_H + +#include "blargg_common.h" +#include "Blip_Buffer.h" + +class Nes_Fds_Apu { +public: + // setup + void set_tempo( double ); + enum { osc_count = 1 }; + void set_output( Blip_Buffer* buf ); + void volume( double ); + void treble_eq( blip_eq_t const& eq ) { synth.treble_eq( eq ); } + + // emulation + void reset(); + enum { io_addr = 0x4040 }; + enum { io_size = 0x53 }; + void write( blip_time_t time, unsigned addr, int data ); + int read( blip_time_t time, unsigned addr ); + void end_frame( blip_time_t ); + +public: + Nes_Fds_Apu(); + void write_( unsigned addr, int data ); + BLARGG_DISABLE_NOTHROW + + void set_output( int index, Blip_Buffer* center, + Blip_Buffer* left_ignored = NULL, Blip_Buffer* right_ignored = NULL ); + BLARGG_DEPRECATED_TEXT( enum { start_addr = 0x4040 }; ) + BLARGG_DEPRECATED_TEXT( enum { end_addr = 0x4092 }; ) + BLARGG_DEPRECATED_TEXT( enum { reg_count = end_addr - start_addr + 1 }; ) + void osc_output( int, Blip_Buffer* ); +private: + enum { wave_size = 0x40 }; + enum { master_vol_max = 10 }; + enum { vol_max = 0x20 }; + enum { wave_sample_max = 0x3F }; + + unsigned char regs_ [io_size];// last written value to registers + + enum { lfo_base_tempo = 8 }; + int lfo_tempo; // normally 8; adjusted by set_tempo() + + int env_delay; + int env_speed; + int env_gain; + + int sweep_delay; + int sweep_speed; + int sweep_gain; + + int wave_pos; + int last_amp; + blip_time_t wave_fract; + + int mod_fract; + int mod_pos; + int mod_write_pos; + unsigned char mod_wave [wave_size]; + + // synthesis + blip_time_t last_time; + Blip_Buffer* output_; + Blip_Synth_Fast synth; + + // allow access to registers by absolute address (i.e. 0x4080) + unsigned char& regs( unsigned addr ) { return regs_ [addr - io_addr]; } + + void run_until( blip_time_t ); +}; + +inline void Nes_Fds_Apu::volume( double v ) +{ + synth.volume( 0.14 / master_vol_max / vol_max / wave_sample_max * v ); +} + +inline void Nes_Fds_Apu::set_output( Blip_Buffer* b ) +{ + output_ = b; +} + +inline void Nes_Fds_Apu::set_output( int i, Blip_Buffer* buf, Blip_Buffer*, Blip_Buffer* ) +{ + assert( (unsigned) i < osc_count ); + output_ = buf; +} + +inline void Nes_Fds_Apu::end_frame( blip_time_t end_time ) +{ + if ( end_time > last_time ) + run_until( end_time ); + last_time -= end_time; + assert( last_time >= 0 ); +} + +inline void Nes_Fds_Apu::write( blip_time_t time, unsigned addr, int data ) +{ + run_until( time ); + write_( addr, data ); +} + +inline int Nes_Fds_Apu::read( blip_time_t time, unsigned addr ) +{ + run_until( time ); + + int result = 0xFF; + switch ( addr ) + { + case 0x4090: + result = env_gain; + break; + + case 0x4092: + result = sweep_gain; + break; + + default: + unsigned i = addr - io_addr; + if ( i < wave_size ) + result = regs_ [i]; + } + + return result | 0x40; +} + +inline Nes_Fds_Apu::Nes_Fds_Apu() +{ + lfo_tempo = lfo_base_tempo; + set_output( NULL ); + volume( 1.0 ); + reset(); +} + +#endif diff --git a/Frameworks/GME/gme/Nes_Mmc5_Apu.h b/Frameworks/GME/gme/Nes_Mmc5_Apu.h new file mode 100644 index 000000000..717658d3e --- /dev/null +++ b/Frameworks/GME/gme/Nes_Mmc5_Apu.h @@ -0,0 +1,70 @@ +// NES MMC5 sound chip emulator + +// Nes_Snd_Emu $vers +#ifndef NES_MMC5_APU_H +#define NES_MMC5_APU_H + +#include "blargg_common.h" +#include "Nes_Apu.h" + +class Nes_Mmc5_Apu : public Nes_Apu { +public: + enum { regs_addr = 0x5000 }; + enum { regs_size = 0x16 }; + + enum { osc_count = 3 }; + void write_register( blip_time_t, unsigned addr, int data ); + void set_output( Blip_Buffer* ); + void set_output( int index, Blip_Buffer* ); + + enum { exram_size = 1024 }; + unsigned char exram [exram_size]; + + BLARGG_DEPRECATED_TEXT( enum { start_addr = 0x5000 }; ) + BLARGG_DEPRECATED_TEXT( enum { end_addr = 0x5015 }; ) +}; + +inline void Nes_Mmc5_Apu::set_output( int i, Blip_Buffer* b ) +{ + // in: square 1, square 2, PCM + // out: square 1, square 2, skipped, skipped, PCM + if ( i > 1 ) + i += 2; + Nes_Apu::set_output( i, b ); +} + +inline void Nes_Mmc5_Apu::set_output( Blip_Buffer* b ) +{ + set_output( 0, b ); + set_output( 1, b ); + set_output( 2, b ); +} + +inline void Nes_Mmc5_Apu::write_register( blip_time_t time, unsigned addr, int data ) +{ + switch ( addr ) + { + case 0x5015: // channel enables + data &= 0x03; // enable the square waves only + // fall through + case 0x5000: // Square 1 + case 0x5002: + case 0x5003: + case 0x5004: // Square 2 + case 0x5006: + case 0x5007: + case 0x5011: // DAC + Nes_Apu::write_register( time, addr - 0x1000, data ); + break; + + case 0x5010: // some things write to this for some reason + break; + +#ifdef BLARGG_DEBUG_H + default: + dprintf( "Unmapped MMC5 APU write: $%04X <- $%02X\n", addr, data ); +#endif + } +} + +#endif diff --git a/Frameworks/GME/gme/Nes_Vrc7_Apu.cpp b/Frameworks/GME/gme/Nes_Vrc7_Apu.cpp new file mode 100644 index 000000000..033061d6a --- /dev/null +++ b/Frameworks/GME/gme/Nes_Vrc7_Apu.cpp @@ -0,0 +1,206 @@ +#include "Nes_Vrc7_Apu.h" + +#include "ym2413.h" +#include + +#include "blargg_source.h" + +int const period = 36; // NES CPU clocks per FM clock + +Nes_Vrc7_Apu::Nes_Vrc7_Apu() +{ + opll = 0; +} + +blargg_err_t Nes_Vrc7_Apu::init() +{ + CHECK_ALLOC( opll = ym2413_init( 3579545, 3579545 / 72, 1 ) ); + + set_output( 0 ); + volume( 1.0 ); + reset(); + return 0; +} + +Nes_Vrc7_Apu::~Nes_Vrc7_Apu() +{ + if ( opll ) + ym2413_shutdown( opll ); +} + +void Nes_Vrc7_Apu::set_output( Blip_Buffer* buf ) +{ + for ( int i = 0; i < osc_count; ++i ) + oscs [i].output = buf; + output_changed(); +} + +void Nes_Vrc7_Apu::output_changed() +{ + mono.output = oscs [0].output; + for ( int i = osc_count; --i; ) + { + if ( mono.output != oscs [i].output ) + { + mono.output = 0; + break; + } + } + + if ( mono.output ) + { + for ( int i = osc_count; --i; ) + { + mono.last_amp += oscs [i].last_amp; + oscs [i].last_amp = 0; + } + } +} + +void Nes_Vrc7_Apu::reset() +{ + addr = 0; + next_time = 0; + mono.last_amp = 0; + + for ( int i = osc_count; --i >= 0; ) + { + Vrc7_Osc& osc = oscs [i]; + osc.last_amp = 0; + for ( int j = 0; j < 3; ++j ) + osc.regs [j] = 0; + } + + ym2413_reset_chip( opll ); +} + +void Nes_Vrc7_Apu::write_reg( int data ) +{ + addr = data; +} + +void Nes_Vrc7_Apu::write_data( blip_time_t time, int data ) +{ + int type = (addr >> 4) - 1; + int chan = addr & 15; + if ( (unsigned) type < 3 && chan < osc_count ) + oscs [chan].regs [type] = data; + + if ( time > next_time ) + run_until( time ); + ym2413_write( opll, 0, addr ); + ym2413_write( opll, 1, data ); +} + +void Nes_Vrc7_Apu::end_frame( blip_time_t time ) +{ + if ( time > next_time ) + run_until( time ); + + next_time -= time; + assert( next_time >= 0 ); + + for ( int i = osc_count; --i >= 0; ) + { + Blip_Buffer* output = oscs [i].output; + if ( output ) + output->set_modified(); + } +} + +void Nes_Vrc7_Apu::save_snapshot( vrc7_snapshot_t* out ) const +{ + out->latch = addr; + out->delay = next_time; + for ( int i = osc_count; --i >= 0; ) + { + for ( int j = 0; j < 3; ++j ) + out->regs [i] [j] = oscs [i].regs [j]; + } + memcpy( out->inst, ym2413_get_inst0( opll ), 8 ); +} + +void Nes_Vrc7_Apu::load_snapshot( vrc7_snapshot_t const& in ) +{ + assert( offsetof (vrc7_snapshot_t,delay) == 28 - 1 ); + + reset(); + next_time = in.delay; + write_reg( in.latch ); + int i; + for ( i = 0; i < osc_count; ++i ) + { + for ( int j = 0; j < 3; ++j ) + oscs [i].regs [j] = in.regs [i] [j]; + } + + for ( i = 0; i < 8; ++i ) + { + ym2413_write( opll, 0, i ); + ym2413_write( opll, 1, in.inst [i] ); + } + + for ( i = 0; i < 3; ++i ) + { + for ( int j = 0; j < 6; ++j ) + { + ym2413_write( opll, 0, 0x10 + i * 0x10 + j ); + ym2413_write( opll, 1, oscs [j].regs [i] ); + } + } +} + +void Nes_Vrc7_Apu::run_until( blip_time_t end_time ) +{ + require( end_time > next_time ); + + blip_time_t time = next_time; + void* opll = this->opll; // cache + Blip_Buffer* const mono_output = mono.output; + if ( mono_output ) + { + // optimal case + do + { + ym2413_advance_lfo( opll ); + int amp = 0; + for ( int i = 0; i < osc_count; i++ ) + amp += ym2413_calcch( opll, i ); + ym2413_advance( opll ); + int delta = amp - mono.last_amp; + if ( delta ) + { + mono.last_amp = amp; + synth.offset_inline( time, delta, mono_output ); + } + time += period; + } + while ( time < end_time ); + } + else + { + mono.last_amp = 0; + do + { + ym2413_advance_lfo( opll ); + for ( int i = 0; i < osc_count; ++i ) + { + Vrc7_Osc& osc = oscs [i]; + if ( osc.output ) + { + int amp = ym2413_calcch( opll, i ); + int delta = amp - osc.last_amp; + if ( delta ) + { + osc.last_amp = amp; + synth.offset( time, delta, osc.output ); + } + } + } + ym2413_advance( opll ); + time += period; + } + while ( time < end_time ); + } + next_time = time; +} diff --git a/Frameworks/GME/gme/Nes_Vrc7_Apu.h b/Frameworks/GME/gme/Nes_Vrc7_Apu.h new file mode 100644 index 000000000..60ea16312 --- /dev/null +++ b/Frameworks/GME/gme/Nes_Vrc7_Apu.h @@ -0,0 +1,80 @@ +// Konami VRC7 sound chip emulator + +#ifndef NES_VRC7_APU_H +#define NES_VRC7_APU_H + +#include "blargg_common.h" +#include "Blip_Buffer.h" + +struct vrc7_snapshot_t; + +class Nes_Vrc7_Apu { +public: + blargg_err_t init(); + + // See Nes_Apu.h for reference + void reset(); + void volume( double ); + void treble_eq( blip_eq_t const& ); + void set_output( Blip_Buffer* ); + enum { osc_count = 6 }; + void set_output( int index, Blip_Buffer* ); + void end_frame( blip_time_t ); + void save_snapshot( vrc7_snapshot_t* ) const; + void load_snapshot( vrc7_snapshot_t const& ); + + void write_reg( int reg ); + void write_data( blip_time_t, int data ); + +public: + Nes_Vrc7_Apu(); + ~Nes_Vrc7_Apu(); + BLARGG_DISABLE_NOTHROW +private: + // noncopyable + Nes_Vrc7_Apu( const Nes_Vrc7_Apu& ); + Nes_Vrc7_Apu& operator = ( const Nes_Vrc7_Apu& ); + + struct Vrc7_Osc + { + BOOST::uint8_t regs [3]; + Blip_Buffer* output; + int last_amp; + }; + + Vrc7_Osc oscs [osc_count]; + void* opll; + int addr; + blip_time_t next_time; + struct { + Blip_Buffer* output; + int last_amp; + } mono; + + Blip_Synth_Fast synth; + + void run_until( blip_time_t ); + void output_changed(); +}; + +struct vrc7_snapshot_t +{ + BOOST::uint8_t latch; + BOOST::uint8_t inst [8]; + BOOST::uint8_t regs [6] [3]; + BOOST::uint8_t delay; +}; + +inline void Nes_Vrc7_Apu::set_output( int i, Blip_Buffer* buf ) +{ + assert( (unsigned) i < osc_count ); + oscs [i].output = buf; + output_changed(); +} + +// DB2LIN_AMP_BITS == 11, * 2 +inline void Nes_Vrc7_Apu::volume( double v ) { synth.volume( 1.0 / 3 / 4096 * v ); } + +inline void Nes_Vrc7_Apu::treble_eq( blip_eq_t const& eq ) { synth.treble_eq( eq ); } + +#endif diff --git a/Frameworks/GME/gme/Nsf_Core.cpp b/Frameworks/GME/gme/Nsf_Core.cpp new file mode 100644 index 000000000..4fa732bf6 --- /dev/null +++ b/Frameworks/GME/gme/Nsf_Core.cpp @@ -0,0 +1,302 @@ +// Game_Music_Emu $vers. http://www.slack.net/~ant/ + +#include "Nsf_Core.h" + +#include "blargg_endian.h" + +#if !NSF_EMU_APU_ONLY + #include "Nes_Namco_Apu.h" + #include "Nes_Vrc6_Apu.h" + #include "Nes_Fme7_Apu.h" + #include "Nes_Fds_Apu.h" + #include "Nes_Mmc5_Apu.h" + #include "Nes_Vrc7_Apu.h" +#endif + +/* Copyright (C) 2003-2008 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +#include "blargg_source.h" + +Nsf_Core::Nsf_Core() +{ + fds = NULL; + fme7 = NULL; + mmc5 = NULL; + namco = NULL; + vrc6 = NULL; + vrc7 = NULL; +} + +Nsf_Core::~Nsf_Core() +{ + unload(); +} + +void Nsf_Core::unload() +{ +#if !NSF_EMU_APU_ONLY + delete fds; + fds = NULL; + + delete fme7; + fme7 = NULL; + + delete namco; + namco = NULL; + + delete mmc5; + mmc5 = NULL; + + delete vrc6; + vrc6 = NULL; + + delete vrc7; + vrc7 = NULL; +#endif + + Nsf_Impl::unload(); +} + +void Nsf_Core::set_tempo( double t ) +{ + set_play_period( (int) (header().play_period() / t) ); + nes_apu()->set_tempo( t ); +#if !NSF_EMU_APU_ONLY + if ( fds ) + fds->set_tempo( t ); +#endif +} + +blargg_err_t Nsf_Core::post_load() +{ + int chip_flags = header().chip_flags; + + #if !NSF_EMU_APU_ONLY + if ( chip_flags & header_t::fds_mask ) + CHECK_ALLOC( fds = BLARGG_NEW Nes_Fds_Apu ); + + if ( chip_flags & header_t::fme7_mask ) + CHECK_ALLOC( fme7 = BLARGG_NEW Nes_Fme7_Apu ); + + if ( chip_flags & header_t::mmc5_mask ) + CHECK_ALLOC( mmc5 = BLARGG_NEW Nes_Mmc5_Apu ); + + if ( chip_flags & header_t::namco_mask ) + CHECK_ALLOC( namco = BLARGG_NEW Nes_Namco_Apu ); + + if ( chip_flags & header_t::vrc6_mask ) + CHECK_ALLOC( vrc6 = BLARGG_NEW Nes_Vrc6_Apu ); + + if ( chip_flags & header_t::vrc7_mask ) + { + #if NSF_EMU_NO_VRC7 + chip_flags = ~chips_mask; // give warning rather than error + #else + CHECK_ALLOC( vrc7 = BLARGG_NEW Nes_Vrc7_Apu ); + RETURN_ERR( vrc7->init() ); + #endif + } + #endif + + set_tempo( 1.0 ); + + if ( chip_flags & ~chips_mask ) + set_warning( "Uses unsupported audio expansion hardware" ); + + return Nsf_Impl::post_load(); +} + +int Nsf_Core::cpu_read( addr_t addr ) +{ + #if !NSF_EMU_APU_ONLY + { + if ( addr == Nes_Namco_Apu::data_reg_addr && namco ) + return namco->read_data(); + + if ( (unsigned) (addr - Nes_Fds_Apu::io_addr) < Nes_Fds_Apu::io_size && fds ) + return fds->read( time(), addr ); + + int i = addr - 0x5C00; + if ( (unsigned) i < mmc5->exram_size && mmc5 ) + return mmc5->exram [i]; + + int m = addr - 0x5205; + if ( (unsigned) m < 2 && mmc5 ) + return (mmc5_mul [0] * mmc5_mul [1]) >> (m * 8) & 0xFF; + } + #endif + + return Nsf_Impl::cpu_read( addr ); +} + +int Nsf_Core::unmapped_read( addr_t addr ) +{ + switch ( addr ) + { + case 0x2002: + case 0x4016: + case 0x4017: + return addr >> 8; + } + + return Nsf_Impl::unmapped_read( addr ); +} + +void Nsf_Core::cpu_write( addr_t addr, int data ) +{ + #if !NSF_EMU_APU_ONLY + { + if ( (unsigned) (addr - fds->io_addr) < fds->io_size && fds ) + { + fds->write( time(), addr, data ); + return; + } + + if ( namco ) + { + if ( addr == namco->addr_reg_addr ) + { + namco->write_addr( data ); + return; + } + + if ( addr == namco->data_reg_addr ) + { + namco->write_data( time(), data ); + return; + } + } + + if ( vrc6 ) + { + int reg = addr & (vrc6->addr_step - 1); + int osc = (unsigned) (addr - vrc6->base_addr) / vrc6->addr_step; + if ( (unsigned) osc < vrc6->osc_count && (unsigned) reg < vrc6->reg_count ) + { + vrc6->write_osc( time(), osc, reg, data ); + return; + } + } + + if ( addr >= fme7->latch_addr && fme7 ) + { + switch ( addr & fme7->addr_mask ) + { + case Nes_Fme7_Apu::latch_addr: + fme7->write_latch( data ); + return; + + case Nes_Fme7_Apu::data_addr: + fme7->write_data( time(), data ); + return; + } + } + + if ( mmc5 ) + { + if ( (unsigned) (addr - mmc5->regs_addr) < mmc5->regs_size ) + { + mmc5->write_register( time(), addr, data ); + return; + } + + int m = addr - 0x5205; + if ( (unsigned) m < 2 ) + { + mmc5_mul [m] = data; + return; + } + + int i = addr - 0x5C00; + if ( (unsigned) i < mmc5->exram_size ) + { + mmc5->exram [i] = data; + return; + } + } + + if ( vrc7 ) + { + if ( addr == 0x9010 ) + { + vrc7->write_reg( data ); + return; + } + + if ( (unsigned) (addr - 0x9028) <= 0x08 ) + { + vrc7->write_data( time(), data ); + return; + } + } + } + #endif + + return Nsf_Impl::cpu_write( addr, data ); +} + +void Nsf_Core::unmapped_write( addr_t addr, int data ) +{ + switch ( addr ) + { + case 0x8000: // some write to $8000 and $8001 repeatedly + case 0x8001: + case 0x4800: // probably namco sound mistakenly turned on in MCK + case 0xF800: + case 0xFFF8: // memory mapper? + return; + } + + if ( mmc5 && addr == 0x5115 ) return; + + // FDS memory + if ( fds && (unsigned) (addr - 0x8000) < 0x6000 ) return; + + Nsf_Impl::unmapped_write( addr, data ); +} + +blargg_err_t Nsf_Core::start_track( int track ) +{ + #if !NSF_EMU_APU_ONLY + if ( mmc5 ) + { + mmc5_mul [0] = 0; + mmc5_mul [1] = 0; + memset( mmc5->exram, 0, mmc5->exram_size ); + } + #endif + + #if !NSF_EMU_APU_ONLY + if ( fds ) fds ->reset(); + if ( fme7 ) fme7 ->reset(); + if ( mmc5 ) mmc5 ->reset(); + if ( namco ) namco->reset(); + if ( vrc6 ) vrc6 ->reset(); + if ( vrc7 ) vrc7 ->reset(); + #endif + + return Nsf_Impl::start_track( track ); +} + +void Nsf_Core::end_frame( time_t end ) +{ + Nsf_Impl::end_frame( end ); + + #if !NSF_EMU_APU_ONLY + if ( fds ) fds ->end_frame( end ); + if ( fme7 ) fme7 ->end_frame( end ); + if ( mmc5 ) mmc5 ->end_frame( end ); + if ( namco ) namco->end_frame( end ); + if ( vrc6 ) vrc6 ->end_frame( end ); + if ( vrc7 ) vrc7 ->end_frame( end ); + #endif +} diff --git a/Frameworks/GME/gme/Nsf_Core.h b/Frameworks/GME/gme/Nsf_Core.h new file mode 100644 index 000000000..4e5fd9e5a --- /dev/null +++ b/Frameworks/GME/gme/Nsf_Core.h @@ -0,0 +1,68 @@ +// Loads NSF file and emulates CPU and sound chips + +// Game_Music_Emu $vers +#ifndef NSF_CORE_H +#define NSF_CORE_H + +#include "Nsf_Impl.h" + +class Nes_Namco_Apu; +class Nes_Vrc6_Apu; +class Nes_Fme7_Apu; +class Nes_Mmc5_Apu; +class Nes_Vrc7_Apu; +class Nes_Fds_Apu; + +class Nsf_Core : public Nsf_Impl { +public: + + // Adjusts music tempo, where 1.0 is normal. Can be changed while playing. + // Loading a file resets tempo to 1.0. + void set_tempo( double ); + + // Pointer to sound chip, or NULL if not used by current file. + // Must be assigned to a Blip_Buffer to get any sound. + Nes_Fds_Apu * fds_apu () { return fds; } + Nes_Fme7_Apu * fme7_apu () { return fme7; } + Nes_Mmc5_Apu * mmc5_apu () { return mmc5; } + Nes_Namco_Apu* namco_apu() { return namco; } + Nes_Vrc6_Apu * vrc6_apu () { return vrc6; } + Nes_Vrc7_Apu * vrc7_apu () { return vrc7; } + + // Mask for which chips are supported + #if NSF_EMU_APU_ONLY + enum { chips_mask = 0 }; + #else + enum { chips_mask = header_t::all_mask }; + #endif + +protected: + virtual int unmapped_read( addr_t ); + virtual void unmapped_write( addr_t, int data ); + + +// Implementation +public: + Nsf_Core(); + ~Nsf_Core(); + virtual void unload(); + virtual blargg_err_t start_track( int ); + virtual void end_frame( time_t ); + +protected: + virtual blargg_err_t post_load(); + virtual int cpu_read( addr_t ); + virtual void cpu_write( addr_t, int ); + +private: + byte mmc5_mul [2]; + + Nes_Fds_Apu* fds; + Nes_Fme7_Apu* fme7; + Nes_Mmc5_Apu* mmc5; + Nes_Namco_Apu* namco; + Nes_Vrc6_Apu* vrc6; + Nes_Vrc7_Apu* vrc7; +}; + +#endif diff --git a/Frameworks/GME/gme/Nsf_Cpu.cpp b/Frameworks/GME/gme/Nsf_Cpu.cpp new file mode 100644 index 000000000..3bb5984b2 --- /dev/null +++ b/Frameworks/GME/gme/Nsf_Cpu.cpp @@ -0,0 +1,116 @@ +// Normal CPU for NSF emulator + +// $package. http://www.slack.net/~ant/ + +#include "Nsf_Impl.h" + +#include "blargg_endian.h" + +#ifdef BLARGG_DEBUG_H + //#define CPU_LOG_START 1000000 + //#include "nes_cpu_log.h" + #undef LOG_MEM +#endif + +/* Copyright (C) 2003-2008 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +#include "blargg_source.h" + +#ifndef LOG_MEM + #define LOG_MEM( addr, str, data ) data +#endif + +int Nsf_Impl::read_mem( addr_t addr ) +{ + int result = low_ram [addr & (low_ram_size-1)]; // also handles wrap-around + if ( addr & 0xE000 ) + { + result = *cpu.get_code( addr ); + if ( addr < sram_addr ) + { + if ( addr == apu.status_addr ) + result = apu.read_status( time() ); + else + result = cpu_read( addr ); + } + } + return LOG_MEM( addr, ">", result ); +} + +void Nsf_Impl::write_mem( addr_t addr, int data ) +{ + (void) LOG_MEM( addr, "<", data ); + + int offset = addr - sram_addr; + if ( (unsigned) offset < sram_size ) + { + sram() [offset] = data; + } + else + { + // after sram because CPU handles most low_ram accesses internally already + int temp = addr & (low_ram_size-1); // also handles wrap-around + if ( !(addr & 0xE000) ) + { + low_ram [temp] = data; + } + else + { + int bank = addr - banks_addr; + if ( (unsigned) bank < bank_count ) + { + write_bank( bank, data ); + } + else if ( (unsigned) (addr - apu.io_addr) < apu.io_size ) + { + apu.write_register( time(), addr, data ); + } + else + { + #if !NSF_EMU_APU_ONLY + // 0x8000-0xDFFF is writable + int i = addr - 0x8000; + if ( (unsigned) i < fdsram_size && fds_enabled() ) + fdsram() [i] = data; + else + #endif + cpu_write( addr, data ); + } + } + } +} + +#define READ_LOW( addr ) (LOG_MEM( addr, ">", low_ram [addr] )) +#define WRITE_LOW( addr, data ) (LOG_MEM( addr, "<", low_ram [addr] = data )) + +#define CAN_WRITE_FAST( addr ) (addr < low_ram_size) +#define WRITE_FAST WRITE_LOW + +// addr < 0x2000 || addr >= 0x8000 +#define CAN_READ_FAST( addr ) ((addr ^ 0x8000) < 0xA000) +#define READ_FAST( addr, out ) (LOG_MEM( addr, ">", out = READ_CODE( addr ) )) + +#define READ_MEM( addr ) read_mem( addr ) +#define WRITE_MEM( addr, data ) write_mem( addr, data ) + +#define CPU cpu + +#define CPU_BEGIN \ +bool Nsf_Impl::run_cpu_until( time_t end )\ +{\ + cpu.set_end_time( end );\ + if ( *cpu.get_code( cpu.r.pc ) != cpu.halt_opcode )\ + { + #include "Nes_Cpu_run.h" + } + return cpu.time_past_end() < 0; +} diff --git a/Frameworks/GME/gme/Nsf_Impl.cpp b/Frameworks/GME/gme/Nsf_Impl.cpp new file mode 100644 index 000000000..62c255152 --- /dev/null +++ b/Frameworks/GME/gme/Nsf_Impl.cpp @@ -0,0 +1,328 @@ +// Game_Music_Emu $vers. http://www.slack.net/~ant/ + +#include "Nsf_Impl.h" + +#include "blargg_endian.h" + +/* Copyright (C) 2003-2008 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +#include "blargg_source.h" + +// number of frames until play interrupts init +int const initial_play_delay = 7; // KikiKaikai needed this to work +int const bank_size = 0x1000; +int const rom_addr = 0x8000; + +int Nsf_Impl::read_code( addr_t addr ) const +{ + return *cpu.get_code( addr ); +} + +int Nsf_Impl::pcm_read( void* self, int addr ) +{ + return STATIC_CAST(Nsf_Impl*,self)->read_code( addr ); +} + +Nsf_Impl::Nsf_Impl() : rom( bank_size ), enable_w4011( true ) +{ + apu.dmc_reader( pcm_read, this ); + assert( offsetof (header_t,unused [4]) == header_t::size ); +} + +void Nsf_Impl::unload() +{ + rom.clear(); + high_ram.clear(); + Gme_Loader::unload(); +} + +Nsf_Impl::~Nsf_Impl() { unload(); } + +bool nsf_header_t::valid_tag() const +{ + return 0 == memcmp( tag, "NESM\x1A", 5 ); +} + +double nsf_header_t::clock_rate() const +{ + return pal_only() ? 1662607.125 : 1789772.727272727; +} + +int nsf_header_t::play_period() const +{ + // NTSC + int clocks = 29780; + int value = 0x411A; + byte const* rate_ptr = ntsc_speed; + + // PAL + if ( pal_only() ) + { + clocks = 33247; + value = 0x4E20; + rate_ptr = pal_speed; + } + + // Default rate + int rate = get_le16( rate_ptr ); + if ( rate == 0 ) + rate = value; + + // Custom rate + if ( rate != value ) + clocks = (int) (rate * clock_rate() * (1.0/1000000.0)); + + return clocks; +} + +// Gets address, given pointer to it in file header. If zero, returns rom_addr. +Nsf_Impl::addr_t Nsf_Impl::get_addr( byte const in [] ) +{ + addr_t addr = get_le16( in ); + if ( addr == 0 ) + addr = rom_addr; + return addr; +} + +blargg_err_t Nsf_Impl::load_( Data_Reader& in ) +{ + // pad ROM data with 0 + RETURN_ERR( rom.load( in, header_.size, &header_, 0 ) ); + + if ( !header_.valid_tag() ) + return blargg_err_file_type; + + RETURN_ERR( high_ram.resize( (fds_enabled() ? fdsram_offset + fdsram_size : fdsram_offset) ) ); + + addr_t load_addr = get_addr( header_.load_addr ); + if ( load_addr < (fds_enabled() ? sram_addr : rom_addr) ) + set_warning( "Load address is too low" ); + + rom.set_addr( load_addr % bank_size ); + + if ( header_.vers != 1 ) + set_warning( "Unknown file version" ); + + set_play_period( header_.play_period() ); + + return blargg_ok; +} + +void Nsf_Impl::write_bank( int bank, int data ) +{ + // Find bank in ROM + int offset = rom.mask_addr( data * bank_size ); + if ( offset >= rom.size() ) + special_event( "invalid bank" ); + void const* rom_data = rom.at_addr( offset ); + + #if !NSF_EMU_APU_ONLY + if ( bank < bank_count - fds_banks && fds_enabled() ) + { + // TODO: FDS bank switching is kind of hacky, might need to + // treat ROM as RAM so changes won't get lost when switching. + byte* out = sram(); + if ( bank >= fds_banks ) + { + out = fdsram(); + bank -= fds_banks; + } + memcpy( &out [bank * bank_size], rom_data, bank_size ); + return; + } + #endif + + if ( bank >= fds_banks ) + cpu.map_code( (bank + 6) * bank_size, bank_size, rom_data ); +} + +void Nsf_Impl::map_memory() +{ + // Map standard things + cpu.reset( unmapped_code() ); + cpu.map_code( 0, 0x2000, low_ram, low_ram_size ); // mirrored four times + cpu.map_code( sram_addr, sram_size, sram() ); + + // Determine initial banks + byte banks [bank_count]; + static byte const zero_banks [sizeof header_.banks] = { 0 }; + if ( memcmp( header_.banks, zero_banks, sizeof zero_banks ) ) + { + banks [0] = header_.banks [6]; + banks [1] = header_.banks [7]; + memcpy( banks + fds_banks, header_.banks, sizeof header_.banks ); + } + else + { + // No initial banks, so assign them based on load_addr + int first_bank = (get_addr( header_.load_addr ) - sram_addr) / bank_size; + unsigned total_banks = rom.size() / bank_size; + for ( int i = bank_count; --i >= 0; ) + { + int bank = i - first_bank; + if ( (unsigned) bank >= total_banks ) + bank = 0; + banks [i] = bank; + } + } + + // Map banks + for ( int i = (fds_enabled() ? 0 : fds_banks); i < bank_count; ++i ) + write_bank( i, banks [i] ); + + // Map FDS RAM + if ( fds_enabled() ) + cpu.map_code( rom_addr, fdsram_size, fdsram() ); +} + +inline void Nsf_Impl::push_byte( int b ) +{ + low_ram [0x100 + cpu.r.sp--] = b; +} + +// Jumps to routine, given pointer to address in file header. Pushes idle_addr +// as return address, NOT old PC. +void Nsf_Impl::jsr_then_stop( byte const addr [] ) +{ + cpu.r.pc = get_addr( addr ); + push_byte( (idle_addr - 1) >> 8 ); + push_byte( (idle_addr - 1) ); +} + +blargg_err_t Nsf_Impl::start_track( int track ) +{ + int speed_flags = 0; + #if NSF_EMU_EXTRA_FLAGS + speed_flags = header().speed_flags; + #endif + + apu.reset( header().pal_only(), (speed_flags & 0x20) ? 0x3F : 0 ); + apu.enable_w4011_( enable_w4011 ); + apu.write_register( 0, 0x4015, 0x0F ); + apu.write_register( 0, 0x4017, (speed_flags & 0x10) ? 0x80 : 0 ); + + // Clear memory + memset( unmapped_code(), Nes_Cpu::halt_opcode, unmapped_size ); + memset( low_ram, 0, low_ram_size ); + memset( sram(), 0, sram_size ); + + map_memory(); + + // Arrange time of first call to play routine + play_extra = 0; + next_play = play_period; + + play_delay = initial_play_delay; + saved_state.pc = idle_addr; + + // Setup for call to init routine + cpu.r.a = track; + cpu.r.x = header_.pal_only(); + cpu.r.sp = 0xFF; + jsr_then_stop( header_.init_addr ); + if ( cpu.r.pc < get_addr( header_.load_addr ) ) + set_warning( "Init address < load address" ); + + return blargg_ok; +} + +void Nsf_Impl::unmapped_write( addr_t addr, int data ) +{ + dprintf( "Unmapped write $%04X <- %02X\n", (int) addr, data ); +} + +int Nsf_Impl::unmapped_read( addr_t addr ) +{ + dprintf( "Unmapped read $%04X\n", (int) addr ); + return addr >> 8; +} + +void Nsf_Impl::special_event( const char str [] ) +{ + dprintf( "%s\n", str ); +} + +void Nsf_Impl::run_once( time_t end ) +{ + // Emulate until next play call if possible + if ( run_cpu_until( min( next_play, end ) ) ) + { + // Halt instruction encountered + + if ( cpu.r.pc != idle_addr ) + { + special_event( "illegal instruction" ); + cpu.count_error(); + cpu.set_time( cpu.end_time() ); + return; + } + + // Init/play routine returned + play_delay = 1; // play can now be called regularly + + if ( saved_state.pc == idle_addr ) + { + // nothing to run + time_t t = cpu.end_time(); + if ( cpu.time() < t ) + cpu.set_time( t ); + } + else + { + // continue init routine that was interrupted by play routine + cpu.r = saved_state; + saved_state.pc = idle_addr; + } + } + + if ( time() >= next_play ) + { + // Calculate time of next call to play routine + play_extra ^= 1; // extra clock every other call + next_play += play_period + play_extra; + + // Call routine if ready + if ( play_delay && !--play_delay ) + { + // Save state if init routine is still running + if ( cpu.r.pc != idle_addr ) + { + check( saved_state.pc == idle_addr ); + saved_state = cpu.r; + special_event( "play called during init" ); + } + + jsr_then_stop( header_.play_addr ); + } + } +} + +void Nsf_Impl::run_until( time_t end ) +{ + while ( time() < end ) + run_once( end ); +} + +void Nsf_Impl::end_frame( time_t end ) +{ + if ( time() < end ) + run_until( end ); + cpu.adjust_time( -end ); + + // Localize to new time frame + next_play -= end; + check( next_play >= 0 ); + if ( next_play < 0 ) + next_play = 0; + + apu.end_frame( end ); +} diff --git a/Frameworks/GME/gme/Nsf_Impl.h b/Frameworks/GME/gme/Nsf_Impl.h new file mode 100644 index 000000000..fcb28f7f7 --- /dev/null +++ b/Frameworks/GME/gme/Nsf_Impl.h @@ -0,0 +1,194 @@ +// Loads NSF file and emulates CPU and RAM, no sound chips + +// Game_Music_Emu $vers +#ifndef NSF_IMPL_H +#define NSF_IMPL_H + +#include "Gme_Loader.h" +#include "Nes_Cpu.h" +#include "Rom_Data.h" +#include "Nes_Apu.h" + +// NSF file header +struct nsf_header_t +{ + typedef unsigned char byte; + enum { size = 0x80 }; + + char tag [ 5]; + byte vers; + byte track_count; + byte first_track; + byte load_addr [ 2]; + byte init_addr [ 2]; + byte play_addr [ 2]; + char game [32]; // NOT null-terminated if 32 chars in length + char author [32]; + char copyright [32]; + byte ntsc_speed [ 2]; + byte banks [ 8]; + byte pal_speed [ 2]; + byte speed_flags; + byte chip_flags; + byte unused [ 4]; + + // Sound chip masks + enum { + vrc6_mask = 1 << 0, + vrc7_mask = 1 << 1, + fds_mask = 1 << 2, + mmc5_mask = 1 << 3, + namco_mask = 1 << 4, + fme7_mask = 1 << 5, + all_mask = (1 << 6) - 1 + }; + + // True if header has proper NSF file signature + bool valid_tag() const; + + // True if file supports only PAL speed + bool pal_only() const { return (speed_flags & 3) == 1; } + + // Clocks per second + double clock_rate() const; + + // Clocks between calls to play routine + int play_period() const; +}; + +/* Loads NSF file into memory, then emulates CPU, RAM, and ROM. +Non-memory accesses are routed through cpu_read() and cpu_write(). */ +class Nsf_Impl : public Gme_Loader { +public: + + // Sound chip + Nes_Apu* nes_apu() { return &apu; } + + // Starts track, where 0 is the first + virtual blargg_err_t start_track( int ); + + // Emulates to at least time t, then begins new time frame at + // time t. Might emulate a few clocks extra, so after returning, + // time() may not be zero. + typedef int time_t; // clock count + virtual void end_frame( time_t n ); + +// Finer control + + // Header for currently loaded file + typedef nsf_header_t header_t; + header_t const& header() const { return header_; } + + // Sets clocks between calls to play routine to p + 1/2 clock + void set_play_period( int p ) { play_period = p; } + + // Time play routine will next be called + time_t play_time() const { return next_play; } + + // Emulates to at least time t. Might emulate a few clocks extra. + virtual void run_until( time_t t ); + + // Time emulated to + time_t time() const { return cpu.time(); } + + void enable_w4011_(bool enable = true) { enable_w4011 = enable; } + + Rom_Data const& rom_() const { return rom; } + +protected: +// Nsf_Core use + + typedef int addr_t; + + // Called for unmapped accesses. Default just prints info if debugging. + virtual void unmapped_write( addr_t, int data ); + virtual int unmapped_read( addr_t ); + + // Override in derived class + // Bank writes and RAM at 0-$7FF and $6000-$7FFF are handled internally + virtual int cpu_read( addr_t a ) { return unmapped_read( a ); } + virtual void cpu_write( addr_t a, int data ){ unmapped_write( a, data ); } + + // Reads byte as CPU would when executing code. Only works for RAM/ROM, + // NOT I/O like sound chips. + int read_code( addr_t addr ) const; + +// Debugger services + + enum { mem_size = 0x10000 }; + + // CPU sits here when waiting for next call to play routine + enum { idle_addr = 0x5FF6 }; + + Nes_Cpu cpu; + + // Runs CPU to at least time t and returns false, or returns true + // if it encounters illegal instruction (halt). + virtual bool run_cpu_until( time_t t ); + + // CPU calls through to these to access memory (except instructions) + int read_mem( addr_t ); + void write_mem( addr_t, int ); + + // Address of play routine + addr_t play_addr() const { return get_addr( header_.play_addr ); } + + // Same as run_until, except emulation stops for any event (routine returned, + // play routine called, illegal instruction). + void run_once( time_t ); + + // Make a note of event + virtual void special_event( const char str [] ); + + +// Implementation +public: + Nsf_Impl(); + ~Nsf_Impl(); + +protected: + virtual blargg_err_t load_( Data_Reader& ); + virtual void unload(); + +private: + enum { low_ram_size = 0x800 }; + enum { fdsram_size = 0x6000 }; + enum { sram_size = 0x2000 }; + enum { unmapped_size= Nes_Cpu::page_size + 8 }; + enum { fds_banks = 2 }; + enum { bank_count = fds_banks + 8 }; + enum { banks_addr = idle_addr }; + enum { sram_addr = 0x6000 }; + + blargg_vector high_ram; + Rom_Data rom; + + // Play routine timing + time_t next_play; + time_t play_period; + int play_extra; + int play_delay; + bool enable_w4011; + Nes_Cpu::registers_t saved_state; // of interrupted init routine + + // Large objects after others + header_t header_; + Nes_Apu apu; + byte low_ram [low_ram_size]; + + // Larger RAM areas allocated separately + enum { fdsram_offset = sram_size + unmapped_size }; + byte* sram() { return high_ram.begin(); } + byte* unmapped_code() { return &high_ram [sram_size]; } + byte* fdsram() { return &high_ram [fdsram_offset]; } + int fds_enabled() const { return header_.chip_flags & header_t::fds_mask; } + + void map_memory(); + void write_bank( int index, int data ); + void jsr_then_stop( byte const addr [] ); + void push_byte( int ); + static addr_t get_addr( byte const [] ); + static int pcm_read( void*, int ); +}; + +#endif diff --git a/Frameworks/GME/gme/Okim6258_Emu.cpp b/Frameworks/GME/gme/Okim6258_Emu.cpp new file mode 100644 index 000000000..dce5c6552 --- /dev/null +++ b/Frameworks/GME/gme/Okim6258_Emu.cpp @@ -0,0 +1,66 @@ +// Game_Music_Emu $vers. http://www.slack.net/~ant/ + +#include "Okim6258_Emu.h" +#include "okim6258.h" + +Okim6258_Emu::Okim6258_Emu() { chip = 0; } + +Okim6258_Emu::~Okim6258_Emu() +{ + if ( chip ) device_stop_okim6258( chip ); +} + +int Okim6258_Emu::set_rate( int clock, int divider, int adpcm_type, int output_12bits ) +{ + if ( chip ) + { + device_stop_okim6258( chip ); + chip = 0; + } + + chip = device_start_okim6258( clock, divider, adpcm_type, output_12bits ); + if ( !chip ) + return 0; + + reset(); + return okim6258_get_vclk( chip ); +} + +void Okim6258_Emu::reset() +{ + device_reset_okim6258( chip ); +} + +void Okim6258_Emu::write( int addr, int data ) +{ + okim6258_write( chip, addr, data ); +} + +void Okim6258_Emu::run( int pair_count, sample_t* out ) +{ + stream_sample_t bufL[ 1024 ]; + stream_sample_t bufR[ 1024 ]; + stream_sample_t * buffers[2] = { bufL, bufR }; + + while (pair_count > 0) + { + int todo = pair_count; + if (todo > 1024) todo = 1024; + okim6258_update( chip, buffers, todo ); + + for (int i = 0; i < todo; i++) + { + int output_l = bufL [i]; + int output_r = bufR [i]; + output_l += out [0]; + output_r += out [1]; + if ( (short)output_l != output_l ) output_l = 0x7FFF ^ ( output_l >> 31 ); + if ( (short)output_r != output_r ) output_r = 0x7FFF ^ ( output_r >> 31 ); + out [0] = output_l; + out [1] = output_r; + out += 2; + } + + pair_count -= todo; + } +} diff --git a/Frameworks/GME/gme/Okim6258_Emu.h b/Frameworks/GME/gme/Okim6258_Emu.h new file mode 100644 index 000000000..9e91d5c79 --- /dev/null +++ b/Frameworks/GME/gme/Okim6258_Emu.h @@ -0,0 +1,29 @@ +// OKIM6258 sound chip emulator interface + +// Game_Music_Emu $vers +#ifndef OKIM6258_EMU_H +#define OKIM6258_EMU_H + +class Okim6258_Emu { + void* chip; +public: + Okim6258_Emu(); + ~Okim6258_Emu(); + + // Sets output sample rate and chip clock rates, in Hz. Returns non-zero + // if error. + int set_rate( int clock, int divider, int adpcm_type, int output_12bits ); + + // Resets to power-up state + void reset(); + + // Writes data to addr + void write( int addr, int data ); + + // Runs and writes pair_count*2 samples to output + typedef short sample_t; + enum { out_chan_count = 2 }; // stereo + void run( int pair_count, sample_t* out ); +}; + +#endif diff --git a/Frameworks/GME/gme/Okim6295_Emu.cpp b/Frameworks/GME/gme/Okim6295_Emu.cpp new file mode 100644 index 000000000..9d9c3e6c2 --- /dev/null +++ b/Frameworks/GME/gme/Okim6295_Emu.cpp @@ -0,0 +1,77 @@ +// Game_Music_Emu $vers. http://www.slack.net/~ant/ + +#include "Okim6295_Emu.h" +#include "okim6295.h" + +Okim6295_Emu::Okim6295_Emu() { chip = 0; } + +Okim6295_Emu::~Okim6295_Emu() +{ + if ( chip ) device_stop_okim6295( chip ); +} + +int Okim6295_Emu::set_rate( int clock_rate ) +{ + if ( chip ) + { + device_stop_okim6295( chip ); + chip = 0; + } + + chip = device_start_okim6295( clock_rate ); + if ( !chip ) + return 0; + + reset(); + return (clock_rate & 0x7FFFFFFF) / ((clock_rate & 0x80000000) ? 132 : 165); +} + +void Okim6295_Emu::reset() +{ + device_reset_okim6295( chip ); + okim6295_set_mute_mask( chip, 0 ); +} + +void Okim6295_Emu::write( int addr, int data ) +{ + okim6295_w( chip, addr, data ); +} + +void Okim6295_Emu::write_rom( int size, int start, int length, void * data ) +{ + okim6295_write_rom( chip, size, start, length, (const UINT8 *) data ); +} + +void Okim6295_Emu::mute_voices( int mask ) +{ + okim6295_set_mute_mask( chip, mask ); +} + +void Okim6295_Emu::run( int pair_count, sample_t* out ) +{ + stream_sample_t bufL[ 1024 ]; + stream_sample_t bufR[ 1024 ]; + stream_sample_t * buffers[2] = { bufL, bufR }; + + while (pair_count > 0) + { + int todo = pair_count; + if (todo > 1024) todo = 1024; + okim6295_update( chip, buffers, todo ); + + for (int i = 0; i < todo; i++) + { + int output_l = bufL [i]; + int output_r = bufR [i]; + output_l += out [0]; + output_r += out [1]; + if ( (short)output_l != output_l ) output_l = 0x7FFF ^ ( output_l >> 31 ); + if ( (short)output_r != output_r ) output_r = 0x7FFF ^ ( output_r >> 31 ); + out [0] = output_l; + out [1] = output_r; + out += 2; + } + + pair_count -= todo; + } +} diff --git a/Frameworks/GME/gme/Okim6295_Emu.h b/Frameworks/GME/gme/Okim6295_Emu.h new file mode 100644 index 000000000..3f307b085 --- /dev/null +++ b/Frameworks/GME/gme/Okim6295_Emu.h @@ -0,0 +1,36 @@ +// OKIM6295 sound chip emulator interface + +// Game_Music_Emu $vers +#ifndef OKIM6295_EMU_H +#define OKIM6295_EMU_H + +class Okim6295_Emu { + void* chip; +public: + Okim6295_Emu(); + ~Okim6295_Emu(); + + // Sets output sample rate and chip clock rates, in Hz. Returns non-zero + // if error. + int set_rate( int clock_rate ); + + // Resets to power-up state + void reset(); + + // Mutes voice n if bit n (1 << n) of mask is set + enum { channel_count = 4 }; + void mute_voices( int mask ); + + // Writes data to addr + void write( int addr, int data ); + + // Scales ROM size, then writes length bytes from data at start offset + void write_rom( int size, int start, int length, void * data ); + + // Runs and writes pair_count*2 samples to output + typedef short sample_t; + enum { out_chan_count = 2 }; // stereo + void run( int pair_count, sample_t* out ); +}; + +#endif diff --git a/Frameworks/GME/gme/Opl_Apu.cpp b/Frameworks/GME/gme/Opl_Apu.cpp new file mode 100644 index 000000000..0a3c45dd5 --- /dev/null +++ b/Frameworks/GME/gme/Opl_Apu.cpp @@ -0,0 +1,271 @@ +#include "Opl_Apu.h" + +#include "blargg_source.h" + +#include "ym2413.h" +#include "fmopl.h" + +Opl_Apu::Opl_Apu() { opl = 0; opl_memory = 0; } + +blargg_err_t Opl_Apu::init( long clock, long rate, blip_time_t period, type_t type ) +{ + type_ = type; + clock_ = clock; + rate_ = rate; + period_ = period; + set_output( 0, 0 ); + volume( 1.0 ); + switch (type) + { + case type_opll: + case type_msxmusic: + case type_smsfmunit: + opl = ym2413_init( clock, rate, 0 ); + break; + + case type_vrc7: + opl = ym2413_init( clock, rate, 1 ); + break; + + case type_opl: + opl = ym3526_init( clock, rate ); + break; + + case type_msxaudio: + //logfile = fopen("c:\\temp\\msxaudio.log", "wb"); + opl = y8950_init( clock, rate ); + opl_memory = malloc( 32768 ); + y8950_set_delta_t_memory( opl, opl_memory, 32768 ); + break; + + case type_opl2: + opl = ym3812_init( clock, rate ); + break; + } + reset(); + return 0; +} + +Opl_Apu::~Opl_Apu() +{ + if (opl) + { + switch (type_) + { + case type_opll: + case type_msxmusic: + case type_smsfmunit: + case type_vrc7: + ym2413_shutdown( opl ); + break; + + case type_opl: + ym3526_shutdown( opl ); + break; + + case type_msxaudio: + y8950_shutdown( opl ); + free( opl_memory ); + //fclose( logfile ); + break; + + case type_opl2: + ym3812_shutdown( opl ); + break; + } + } +} + +void Opl_Apu::reset() +{ + addr = 0; + next_time = 0; + last_amp = 0; + + switch (type_) + { + case type_opll: + case type_msxmusic: + case type_smsfmunit: + case type_vrc7: + ym2413_reset_chip( opl ); + break; + + case type_opl: + ym3526_reset_chip( opl ); + break; + + case type_msxaudio: + y8950_reset_chip( opl ); + break; + + case type_opl2: + ym3812_reset_chip( opl ); + break; + } +} + +void Opl_Apu::write_data( blip_time_t time, int data ) +{ + run_until( time ); + switch (type_) + { + case type_opll: + case type_msxmusic: + case type_smsfmunit: + case type_vrc7: + ym2413_write( opl, 0, addr ); + ym2413_write( opl, 1, data ); + break; + + case type_opl: + ym3526_write( opl, 0, addr ); + ym3526_write( opl, 1, data ); + break; + + case type_msxaudio: + /*if ( addr >= 7 && addr <= 7 + 11 ) + { + unsigned char temp [2] = { addr - 7, data }; + fwrite( &temp, 1, 2, logfile ); + }*/ + y8950_write( opl, 0, addr ); + y8950_write( opl, 1, data ); + break; + + case type_opl2: + ym3812_write( opl, 0, addr ); + ym3812_write( opl, 1, data ); + break; + } +} + +int Opl_Apu::read( blip_time_t time, int port ) +{ + run_until( time ); + switch (type_) + { + case type_opll: + case type_msxmusic: + case type_smsfmunit: + case type_vrc7: + return ym2413_read( opl, port ); + + case type_opl: + return ym3526_read( opl, port ); + + case type_msxaudio: + { + int ret = y8950_read( opl, port ); + /*unsigned char temp [2] = { port + 0x80, ret }; + fwrite( &temp, 1, 2, logfile );*/ + return ret; + } + + case type_opl2: + return ym3812_read( opl, port ); + } + + return 0; +} + +void Opl_Apu::end_frame( blip_time_t time ) +{ + run_until( time ); + next_time -= time; + + if ( output_ ) + output_->set_modified(); +} + +void Opl_Apu::run_until( blip_time_t end_time ) +{ + if ( end_time > next_time ) + { + blip_time_t time_delta = end_time - next_time; + blip_time_t time = next_time; + unsigned count = time_delta / period_ + 1; + switch (type_) + { + case type_opll: + case type_msxmusic: + case type_smsfmunit: + case type_vrc7: + { + SAMP bufMO[ 1024 ]; + SAMP bufRO[ 1024 ]; + SAMP * buffers[2] = { bufMO, bufRO }; + + while ( count > 0 ) + { + unsigned todo = count; + if ( todo > 1024 ) todo = 1024; + ym2413_update_one( opl, buffers, todo ); + + if ( output_ ) + { + int last_amp = this->last_amp; + for ( unsigned i = 0; i < todo; i++ ) + { + int amp = bufMO [i] + bufRO [i]; + int delta = amp - last_amp; + if ( delta ) + { + last_amp = amp; + synth.offset_inline( time, delta, output_ ); + } + time += period_; + } + this->last_amp = last_amp; + } + else time += period_ * todo; + + count -= todo; + } + } + break; + + case type_opl: + case type_msxaudio: + case type_opl2: + { + OPLSAMPLE buffer[ 1024 ]; + + while ( count > 0 ) + { + unsigned todo = count; + if ( todo > 1024 ) todo = 1024; + switch (type_) + { + case type_opl: ym3526_update_one( opl, buffer, todo ); break; + case type_msxaudio: y8950_update_one( opl, buffer, todo ); break; + case type_opl2: ym3812_update_one( opl, buffer, todo ); break; + default: break; + } + + if ( output_ ) + { + int last_amp = this->last_amp; + for ( unsigned i = 0; i < todo; i++ ) + { + int amp = buffer [i]; + int delta = amp - last_amp; + if ( delta ) + { + last_amp = amp; + synth.offset_inline( time, delta, output_ ); + } + time += period_; + } + this->last_amp = last_amp; + } + else time += period_ * todo; + + count -= todo; + } + } + break; + } + next_time = time; + } +} \ No newline at end of file diff --git a/Frameworks/GME/gme/Opl_Apu.h b/Frameworks/GME/gme/Opl_Apu.h new file mode 100644 index 000000000..86d73d748 --- /dev/null +++ b/Frameworks/GME/gme/Opl_Apu.h @@ -0,0 +1,63 @@ +#ifndef OPL_APU_H +#define OPL_APU_H + +#include "blargg_common.h" +#include "Blip_Buffer.h" + +#include + +class Opl_Apu { +public: + Opl_Apu(); + ~Opl_Apu(); + + enum type_t { type_opll = 0x10, type_msxmusic = 0x11, type_smsfmunit = 0x12, + type_vrc7 = 0x13, type_opl = 0x20, type_msxaudio = 0x21, type_opl2 = 0x22 }; + blargg_err_t init( long clock, long rate, blip_time_t period, type_t ); + + void reset(); + void volume( double v ) { synth.volume( 1.0 / (4096 * 6) * v ); } + void treble_eq( blip_eq_t const& eq ) { synth.treble_eq( eq ); } + enum { osc_count = 1 }; + void osc_output( int index, Blip_Buffer* ); + void set_output( int i, Blip_Buffer* buf, Blip_Buffer* = NULL, Blip_Buffer* = NULL ) { osc_output( 0, buf ); } + void end_frame( blip_time_t ); + + void write_addr( int data ) { addr = data; } + void write_data( blip_time_t, int data ); + + int read( blip_time_t, int port ); + + static bool supported() { return true; } + +private: + // noncopyable + Opl_Apu( const Opl_Apu& ); + Opl_Apu& operator = ( const Opl_Apu& ); + + Blip_Buffer* output_; + type_t type_; + void* opl; + void* opl_memory; + //FILE* logfile; + unsigned char regs[ 0x100 ]; + blip_time_t next_time; + int last_amp; + int addr; + + long clock_; + long rate_; + blip_time_t period_; + + Blip_Synth_Fast synth; + + void run_until( blip_time_t ); +}; + +inline void Opl_Apu::osc_output( int i, Blip_Buffer* buf ) +{ + assert( (unsigned) i < osc_count ); + output_ = buf; +} + +#endif diff --git a/Frameworks/GME/gme/Pwm_Emu.cpp b/Frameworks/GME/gme/Pwm_Emu.cpp new file mode 100644 index 000000000..7bb9722d1 --- /dev/null +++ b/Frameworks/GME/gme/Pwm_Emu.cpp @@ -0,0 +1,66 @@ +// Game_Music_Emu $vers. http://www.slack.net/~ant/ + +#include "Pwm_Emu.h" +#include "pwm.h" + +Pwm_Emu::Pwm_Emu() { chip = 0; } + +Pwm_Emu::~Pwm_Emu() +{ + if ( chip ) device_stop_pwm( chip ); +} + +int Pwm_Emu::set_rate( int clock ) +{ + if ( chip ) + { + device_stop_pwm( chip ); + chip = 0; + } + + chip = device_start_pwm( clock ); + if ( !chip ) + return 1; + + reset(); + return 0; +} + +void Pwm_Emu::reset() +{ + device_reset_pwm( chip ); +} + +void Pwm_Emu::write( int channel, int data ) +{ + pwm_chn_w( chip, channel, data ); +} + +void Pwm_Emu::run( int pair_count, sample_t* out ) +{ + stream_sample_t bufL[ 1024 ]; + stream_sample_t bufR[ 1024 ]; + stream_sample_t * buffers[2] = { bufL, bufR }; + + while (pair_count > 0) + { + int todo = pair_count; + if (todo > 1024) todo = 1024; + pwm_update( chip, buffers, todo ); + + for (int i = 0; i < todo; i++) + { + int output_l = bufL [i]; + int output_r = bufR [i]; + output_l += out [0]; + output_r += out [1]; + if ( (short)output_l != output_l ) output_l = 0x7FFF ^ ( output_l >> 31 ); + if ( (short)output_r != output_r ) output_r = 0x7FFF ^ ( output_r >> 31 ); + out [0] = output_l; + out [1] = output_r; + out += 2; + } + + pair_count -= todo; + } +} diff --git a/Frameworks/GME/gme/Pwm_Emu.h b/Frameworks/GME/gme/Pwm_Emu.h new file mode 100644 index 000000000..1ba1074cd --- /dev/null +++ b/Frameworks/GME/gme/Pwm_Emu.h @@ -0,0 +1,33 @@ +// PWM sound chip emulator interface + +// Game_Music_Emu $vers +#ifndef PWM_EMU_H +#define PWM_EMU_H + +class Pwm_Emu { + void* chip; +public: + Pwm_Emu(); + ~Pwm_Emu(); + + // Sets output sample rate and chip clock rates, in Hz. Returns non-zero + // if error. + int set_rate( int clock ); + + // Resets to power-up state + void reset(); + + // Mutes voice n if bit n (1 << n) of mask is set + enum { channel_count = 24 }; + void mute_voices( int mask ); + + // Writes data to channel + void write( int channel, int data ); + + // Runs and writes pair_count*2 samples to output + typedef short sample_t; + enum { out_chan_count = 2 }; // stereo + void run( int pair_count, sample_t* out ); +}; + +#endif diff --git a/Frameworks/GME/gme/Qsound_Apu.cpp b/Frameworks/GME/gme/Qsound_Apu.cpp new file mode 100644 index 000000000..8a530dc81 --- /dev/null +++ b/Frameworks/GME/gme/Qsound_Apu.cpp @@ -0,0 +1,83 @@ +// Game_Music_Emu $vers. http://www.slack.net/~ant/ + +#include "Qsound_Apu.h" +#include "qmix.h" + +Qsound_Apu::Qsound_Apu() { chip = 0; rom = 0; rom_size = 0; sample_rate = 0; } + +Qsound_Apu::~Qsound_Apu() +{ + if ( chip ) free( chip ); + if ( rom ) free( rom ); +} + +int Qsound_Apu::set_rate( int clock_rate ) +{ + if ( chip ) + { + free( chip ); + chip = 0; + } + + chip = malloc( _qmix_get_state_size() ); + if ( !chip ) + return 0; + + reset(); + + return clock_rate / 166; +} + +void Qsound_Apu::set_sample_rate( int sample_rate ) +{ + this->sample_rate = sample_rate; + if ( chip ) _qmix_set_sample_rate( chip, sample_rate ); +} + +void Qsound_Apu::reset() +{ + _qmix_clear_state( chip ); + _qmix_set_sample_rate( chip, sample_rate ); + if ( rom ) _qmix_set_sample_rom( chip, rom, rom_size ); +} + +void Qsound_Apu::write( int addr, int data ) +{ + _qmix_command( chip, addr, data ); +} + +void Qsound_Apu::write_rom( int size, int start, int length, void const* data ) +{ + if ( size > rom_size ) + { + rom_size = size; + rom = realloc( rom, size ); + } + if ( start > size ) start = size; + if ( start + length > size ) length = size - start; + memcpy( (uint8*)rom + start, data, length ); + if ( chip ) _qmix_set_sample_rom( chip, rom, rom_size ); +} + +void Qsound_Apu::run( int pair_count, sample_t* out ) +{ + sint16 buf[ 1024 * 2 ]; + + while (pair_count > 0) + { + int todo = pair_count; + if (todo > 1024) todo = 1024; + _qmix_render( chip, buf, todo ); + + for (int i = 0; i < todo * 2; i++) + { + int output = buf [i]; + output += out [0]; + if ( (short)output != output ) output = 0x7FFF ^ ( output >> 31 ); + out [0] = output; + out++; + } + + pair_count -= todo; + } +} diff --git a/Frameworks/GME/gme/Qsound_Apu.h b/Frameworks/GME/gme/Qsound_Apu.h new file mode 100644 index 000000000..8cbea22b1 --- /dev/null +++ b/Frameworks/GME/gme/Qsound_Apu.h @@ -0,0 +1,36 @@ +// Capcom QSound sound chip emulator interface + +// Game_Music_Emu $vers +#ifndef QSOUND_APU_H +#define QSOUND_APU_H + +class Qsound_Apu { + void* chip; + void* rom; + int rom_size; + int sample_rate; +public: + Qsound_Apu(); + ~Qsound_Apu(); + + // Sets output sample rate and chip clock rates, in Hz. Returns non-zero + // if error. + int set_rate( int clock_rate ); + void set_sample_rate( int sample_rate ); + + // Resets to power-up state + void reset(); + + // Writes data to addr + void write( int addr, int data ); + + // Scales ROM size, then writes length bytes from data at start offset + void write_rom( int size, int start, int length, void const* data ); + + // Runs and writes pair_count*2 samples to output + typedef short sample_t; + enum { out_chan_count = 2 }; // stereo + void run( int pair_count, sample_t* out ); +}; + +#endif diff --git a/Frameworks/GME/gme/Resampler.cpp b/Frameworks/GME/gme/Resampler.cpp new file mode 100644 index 000000000..16a0626b8 --- /dev/null +++ b/Frameworks/GME/gme/Resampler.cpp @@ -0,0 +1,79 @@ +// $package. http://www.slack.net/~ant/ + +#include "Resampler.h" + +/* Copyright (C) 2004-2008 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +#include "blargg_source.h" + +Resampler::Resampler() +{ + write_pos = 0; + rate_ = 0; +} + +Resampler::~Resampler() { } + +void Resampler::clear() +{ + write_pos = 0; + clear_(); +} + +inline int Resampler::resample_wrapper( sample_t out [], int* out_size, + sample_t const in [], int in_size ) +{ + assert( rate() ); + + sample_t* out_ = out; + int result = resample_( &out_, out + *out_size, in, in_size ) - in; + assert( out_ <= out + *out_size ); + assert( result <= in_size ); + + *out_size = out_ - out; + return result; +} + +int Resampler::resample( sample_t out [], int out_size, sample_t const in [], int* in_size ) +{ + *in_size = resample_wrapper( out, &out_size, in, *in_size ); + return out_size; +} + + +//// Buffering + +blargg_err_t Resampler::resize_buffer( int new_size ) +{ + RETURN_ERR( buf.resize( new_size ) ); + clear(); + return blargg_ok; +} + +int Resampler::skip_input( int count ) +{ + write_pos -= count; + if ( write_pos < 0 ) // occurs when downsampling + { + count += write_pos; + write_pos = 0; + } + memmove( buf.begin(), &buf [count], write_pos * sizeof buf [0] ); + return count; +} + +int Resampler::read( sample_t out [], int out_size ) +{ + if ( out_size ) + skip_input( resample_wrapper( out, &out_size, buf.begin(), write_pos ) ); + return out_size; +} diff --git a/Frameworks/GME/gme/Resampler.h b/Frameworks/GME/gme/Resampler.h new file mode 100644 index 000000000..ff285121d --- /dev/null +++ b/Frameworks/GME/gme/Resampler.h @@ -0,0 +1,110 @@ +// Common interface for resamplers + +// $package +#ifndef RESAMPLER_H +#define RESAMPLER_H + +#include "blargg_common.h" + +class Resampler { +public: + + virtual ~Resampler(); + + // Sets input/output resampling ratio + blargg_err_t set_rate( double ); + + // Current input/output ratio + double rate() const { return rate_; } + + // Samples are 16-bit signed + typedef short sample_t; + +// One of two different buffering schemes can be used, as decided by the caller: + +// External buffering (caller provides input buffer) + + // Resamples in to at most n out samples and returns number of samples actually + // written. Sets *in_size to number of input samples that aren't needed anymore + // and should be removed from input. + int resample( sample_t out [], int n, sample_t const in [], int* in_size ); + +// Internal buffering (resampler manages buffer) + + // Resizes input buffer to n samples, then clears it + blargg_err_t resize_buffer( int n ); + + // Clears input buffer + void clear(); + + // Writes at most n samples to input buffer and returns number actually written. + // Result will be less than n if there isn't enough free space in buffer. + int write( sample_t const in [], int n ); + + // Number of input samples in buffer + int written() const { return write_pos; } + + // Removes first n input samples from buffer, fewer if there aren't that many. + // Returns number of samples actually removed. + int skip_input( int n ); + + // Resamples input to at most n output samples. Returns number of samples + // actually written to out. Result will be less than n if there aren't + // enough input samples in buffer. + int read( sample_t out [], int n ); + +// Direct writing to input buffer, instead of using write( in, n ) above + + // Pointer to place to write input samples + sample_t* buffer() { return &buf [write_pos]; } + + // Number of samples that can be written to buffer() + int buffer_free() const { return buf.size() - write_pos; } + + // Notifies resampler that n input samples have been written to buffer(). + // N must not be greater than buffer_free(). + void write( int n ); + +// Derived interface +protected: + virtual blargg_err_t set_rate_( double rate ) BLARGG_PURE( ; ) + + virtual void clear_() { } + + // Resample as many available in samples as will fit within out_size and + // return pointer past last input sample read and set *out just past + // the last output sample. + virtual sample_t const* resample_( sample_t** out, sample_t const* out_end, + sample_t const in [], int in_size ) BLARGG_PURE( { return in; } ) + +// Implementation +public: + Resampler(); + +private: + blargg_vector buf; + int write_pos; + double rate_; + + int resample_wrapper( sample_t out [], int* out_size, + sample_t const in [], int in_size ); +}; + +inline void Resampler::write( int count ) +{ + write_pos += count; + assert( (unsigned) write_pos <= buf.size() ); +} + +inline blargg_err_t Resampler::set_rate_( double r ) +{ + rate_ = r; + return blargg_ok; +} + +inline blargg_err_t Resampler::set_rate( double r ) +{ + return set_rate_( r ); +} + +#endif diff --git a/Frameworks/GME/gme/Rf5C164_Emu.cpp b/Frameworks/GME/gme/Rf5C164_Emu.cpp new file mode 100644 index 000000000..605729c6d --- /dev/null +++ b/Frameworks/GME/gme/Rf5C164_Emu.cpp @@ -0,0 +1,82 @@ +// Game_Music_Emu $vers. http://www.slack.net/~ant/ + +#include "Rf5C164_Emu.h" +#include "scd_pcm.h" + +Rf5C164_Emu::Rf5C164_Emu() { chip = 0; } + +Rf5C164_Emu::~Rf5C164_Emu() +{ + if ( chip ) device_stop_rf5c164( chip ); +} + +int Rf5C164_Emu::set_rate( int clock ) +{ + if ( chip ) + { + device_stop_rf5c164( chip ); + chip = 0; + } + + chip = device_start_rf5c164( clock ); + if ( !chip ) + return 1; + + reset(); + return 0; +} + +void Rf5C164_Emu::reset() +{ + device_reset_rf5c164( chip ); + rf5c164_set_mute_mask( chip, 0 ); +} + +void Rf5C164_Emu::write( int addr, int data ) +{ + rf5c164_w( chip, addr, data ); +} + +void Rf5C164_Emu::write_mem( int addr, int data ) +{ + rf5c164_mem_w( chip, addr, data ); +} + +void Rf5C164_Emu::write_ram( int start, int length, void * data ) +{ + rf5c164_write_ram( chip, start, length, (const UINT8 *) data ); +} + +void Rf5C164_Emu::mute_voices( int mask ) +{ + rf5c164_set_mute_mask( chip, mask ); +} + +void Rf5C164_Emu::run( int pair_count, sample_t* out ) +{ + stream_sample_t bufL[ 1024 ]; + stream_sample_t bufR[ 1024 ]; + stream_sample_t * buffers[2] = { bufL, bufR }; + + while (pair_count > 0) + { + int todo = pair_count; + if (todo > 1024) todo = 1024; + rf5c164_update( chip, buffers, todo ); + + for (int i = 0; i < todo; i++) + { + int output_l = bufL [i]; + int output_r = bufR [i]; + output_l += out [0]; + output_r += out [1]; + if ( (short)output_l != output_l ) output_l = 0x7FFF ^ ( output_l >> 31 ); + if ( (short)output_r != output_r ) output_r = 0x7FFF ^ ( output_r >> 31 ); + out [0] = output_l; + out [1] = output_r; + out += 2; + } + + pair_count -= todo; + } +} diff --git a/Frameworks/GME/gme/Rf5C164_Emu.h b/Frameworks/GME/gme/Rf5C164_Emu.h new file mode 100644 index 000000000..25abf8dac --- /dev/null +++ b/Frameworks/GME/gme/Rf5C164_Emu.h @@ -0,0 +1,39 @@ +// RF5C164 sound chip emulator interface + +// Game_Music_Emu $vers +#ifndef RF5C164_EMU_H +#define RF5C164_EMU_H + +class Rf5C164_Emu { + void* chip; +public: + Rf5C164_Emu(); + ~Rf5C164_Emu(); + + // Sets output sample rate and chip clock rates, in Hz. Returns non-zero + // if error. + int set_rate( int clock ); + + // Resets to power-up state + void reset(); + + // Mutes voice n if bit n (1 << n) of mask is set + enum { channel_count = 8 }; + void mute_voices( int mask ); + + // Writes data to addr + void write( int addr, int data ); + + // Writes to memory + void write_mem( int addr, int data ); + + // Writes length bytes from data at start offset in RAM + void write_ram( int start, int length, void * data ); + + // Runs and writes pair_count*2 samples to output + typedef short sample_t; + enum { out_chan_count = 2 }; // stereo + void run( int pair_count, sample_t* out ); +}; + +#endif diff --git a/Frameworks/GME/gme/Rf5C68_Emu.cpp b/Frameworks/GME/gme/Rf5C68_Emu.cpp new file mode 100644 index 000000000..46d452438 --- /dev/null +++ b/Frameworks/GME/gme/Rf5C68_Emu.cpp @@ -0,0 +1,82 @@ +// Game_Music_Emu $vers. http://www.slack.net/~ant/ + +#include "Rf5C68_Emu.h" +#include "rf5c68.h" + +Rf5C68_Emu::Rf5C68_Emu() { chip = 0; } + +Rf5C68_Emu::~Rf5C68_Emu() +{ + if ( chip ) device_stop_rf5c68( chip ); +} + +int Rf5C68_Emu::set_rate() +{ + if ( chip ) + { + device_stop_rf5c68( chip ); + chip = 0; + } + + chip = device_start_rf5c68(); + if ( !chip ) + return 1; + + reset(); + return 0; +} + +void Rf5C68_Emu::reset() +{ + device_reset_rf5c68( chip ); + rf5c68_set_mute_mask( chip, 0 ); +} + +void Rf5C68_Emu::write( int addr, int data ) +{ + rf5c68_w( chip, addr, data ); +} + +void Rf5C68_Emu::write_mem( int addr, int data ) +{ + rf5c68_mem_w( chip, addr, data ); +} + +void Rf5C68_Emu::write_ram( int start, int length, void * data ) +{ + rf5c68_write_ram( chip, start, length, (const UINT8 *) data ); +} + +void Rf5C68_Emu::mute_voices( int mask ) +{ + rf5c68_set_mute_mask( chip, mask ); +} + +void Rf5C68_Emu::run( int pair_count, sample_t* out ) +{ + stream_sample_t bufL[ 1024 ]; + stream_sample_t bufR[ 1024 ]; + stream_sample_t * buffers[2] = { bufL, bufR }; + + while (pair_count > 0) + { + int todo = pair_count; + if (todo > 1024) todo = 1024; + rf5c68_update( chip, buffers, todo ); + + for (int i = 0; i < todo; i++) + { + int output_l = bufL [i]; + int output_r = bufR [i]; + output_l += out [0]; + output_r += out [1]; + if ( (short)output_l != output_l ) output_l = 0x7FFF ^ ( output_l >> 31 ); + if ( (short)output_r != output_r ) output_r = 0x7FFF ^ ( output_r >> 31 ); + out [0] = output_l; + out [1] = output_r; + out += 2; + } + + pair_count -= todo; + } +} diff --git a/Frameworks/GME/gme/Rf5C68_Emu.h b/Frameworks/GME/gme/Rf5C68_Emu.h new file mode 100644 index 000000000..e03332d9e --- /dev/null +++ b/Frameworks/GME/gme/Rf5C68_Emu.h @@ -0,0 +1,39 @@ +// RF5C68 sound chip emulator interface + +// Game_Music_Emu $vers +#ifndef RF5C68_EMU_H +#define RF5C68_EMU_H + +class Rf5C68_Emu { + void* chip; +public: + Rf5C68_Emu(); + ~Rf5C68_Emu(); + + // Sets output sample rate and chip clock rates, in Hz. Returns non-zero + // if error. + int set_rate(); + + // Resets to power-up state + void reset(); + + // Mutes voice n if bit n (1 << n) of mask is set + enum { channel_count = 8 }; + void mute_voices( int mask ); + + // Writes data to addr + void write( int addr, int data ); + + // Writes to memory + void write_mem( int addr, int data ); + + // Writes length bytes from data at start offset in RAM + void write_ram( int start, int length, void * data ); + + // Runs and writes pair_count*2 samples to output + typedef short sample_t; + enum { out_chan_count = 2 }; // stereo + void run( int pair_count, sample_t* out ); +}; + +#endif diff --git a/Frameworks/GME/gme/Rom_Data.cpp b/Frameworks/GME/gme/Rom_Data.cpp new file mode 100644 index 000000000..0fa5f2d2c --- /dev/null +++ b/Frameworks/GME/gme/Rom_Data.cpp @@ -0,0 +1,99 @@ +// Game_Music_Emu $vers. http://www.slack.net/~ant/ + +#include "Rom_Data.h" + +/* Copyright (C) 2003-2009 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +#include "blargg_source.h" + +void Rom_Data::clear() +{ + file_size_ = 0; + rom_addr = 0; + mask = 0; + rom.clear(); +} + +Rom_Data::Rom_Data( int page_size ) : + pad_size( page_size + pad_extra ) +{ + // page_size should be power of 2 + check( (page_size & (page_size - 1)) == 0 ); + + clear(); +} + +Rom_Data::~Rom_Data() +{ } + +// Reads file into array, placing file_offset bytes of padding before the beginning, and pad_size after the end +blargg_err_t Rom_Data::load_( Data_Reader& in, int header_size, int file_offset ) +{ + clear(); + file_size_ = in.remain(); + if ( file_size_ <= header_size ) // <= because there must be data after header + return blargg_err_file_type; + + RETURN_ERR( rom.resize( file_offset + file_size_ + pad_size ) ); + + return in.read( rom.begin() + file_offset, file_size_ ); +} + +blargg_err_t Rom_Data::load( Data_Reader& in, int header_size, + void* header_out, int fill ) +{ + int file_offset = pad_size - header_size; + blargg_err_t err = load_( in, header_size, file_offset ); + if ( err ) + { + clear(); + return err; + } + + file_size_ -= header_size; + memcpy( header_out, &rom [file_offset], header_size ); + + memset( rom.begin() , fill, pad_size ); + memset( rom.end() - pad_size, fill, pad_size ); + + return blargg_ok; +} + +void Rom_Data::set_addr( int addr ) +{ + int const page_size = pad_size - pad_extra; + + // Minimum size that contains all bytes and is a multiple of page_size + int const size = (addr + file_size_ + page_size - 1) / page_size * page_size; + + // Find lowest power of 2 that is >= size + int power2 = 1; + while ( power2 < size ) + power2 *= 2; + + mask = power2 - 1; + + // Address of first byte of ROM (possibly negative) + rom_addr = addr - page_size - pad_extra; + + if ( rom.resize( size - rom_addr + pad_extra ) ) { } // OK if shrink fails +} + +byte* Rom_Data::at_addr( int addr ) +{ + int offset = mask_addr( addr ) - rom_addr; + + if ( (unsigned) offset > (unsigned) (rom.size() - pad_size) ) + offset = 0; // unmapped + + return &rom [offset]; +} diff --git a/Frameworks/GME/gme/Rom_Data.h b/Frameworks/GME/gme/Rom_Data.h new file mode 100644 index 000000000..7b39dfaf9 --- /dev/null +++ b/Frameworks/GME/gme/Rom_Data.h @@ -0,0 +1,94 @@ +// Manages ROM data loaded from file in an efficient manner + +// Game_Music_Emu $vers +#ifndef ROM_DATA_H +#define ROM_DATA_H + +#include "blargg_common.h" +#include "Data_Reader.h" + +/* Loads a ROM file into memory and allows access to it in page-sized chunks. + +* ROM file consists of header followed by ROM data. Instead of storing the entire +ROM contents, the file only stores the occupied portion, with the bytes before and +after that cleared to some value. The size and format of the header is up to the +caller, as is the starting address of the ROM data following it. File loading is +performed with a single read, rather than two or more that might otherwise be +required. + +* Once ROM data is loaded and its address specified, a pointer to any "page" can +be obtained. ROM data is mirrored using smallest power of 2 that contains it. +Addresses not aligned to pages can also be used, but this might cause unexpected +results. + +Example with file data of size 0x0C put at address 0x0F, with page size of 8: + +---------------0123456789AB--------------------0123456789AB---------... +^ ^ ^ ^ ^ ^ ^ ^ ^ +0 0x08 0x10 0x18 0x20 0x28 0x30 0x38 0x40 + +at_addr(0x00) = pointer to 8 bytes of fill. +at_addr(0x08) = pointer to 7 bytes of fill, followed by first byte of file. +at_addr(0x10) = pointer to next 8 bytes of file. +at_addr(0x18) = pointer to last 3 bytes of file, followed by 5 bytes of fill. +at_addr(0x20) = pointer to 8 bytes of fill. +at_addr(0x28) = pointer to 7 bytes of fill, followed by first byte of file. +etc. */ + +class Rom_Data { + enum { pad_extra = 8 }; +public: + typedef unsigned char byte; + + // Page_size should be a power of 2 + Rom_Data( int page_size ); + + // Loads file into memory, then copies header to *header_out and fills + // unmapped bank and file data padding with fill. Returns blargg_err_file_type + // if in.remain() <= header_size. + blargg_err_t load( Data_Reader& in, int header_size, void* header_out, int fill ); + + // Below, "file data" refers to data AFTER the header + + // Size of file data + int file_size() const { return file_size_; } + + // Pointer to beginning of file data + byte * begin() { return rom.begin() + pad_size; } + byte const* begin() const { return rom.begin() + pad_size; } + + // Pointer to unmapped page cleared with fill value + byte* unmapped() { return rom.begin(); } + + // Sets address that file data will start at. Must be set before using following + // functions, and cannot be set more than once. + void set_addr( int addr ); + + // Address of first empty page (file size + addr rounded up to multiple of page_size) + int size() const { return rom.size() - pad_extra + rom_addr; } + + // Masks address to nearest power of two greater than size() + int mask_addr( int addr ) const { return addr & mask; } + + // Pointer to page beginning at addr, or unmapped() if outside data. + // Mirrored using mask_addr(). + byte* at_addr( int addr ); + + // Frees memory + void clear(); + +// Implementation +public: + ~Rom_Data(); + +protected: + blargg_vector rom; + int mask; + int rom_addr; + int const pad_size; + int file_size_; + + blargg_err_t load_( Data_Reader& in, int header_size, int file_offset ); +}; + +#endif diff --git a/Frameworks/GME/gme/Sap_Core.cpp b/Frameworks/GME/gme/Sap_Core.cpp new file mode 100644 index 000000000..daaaf4ddc --- /dev/null +++ b/Frameworks/GME/gme/Sap_Core.cpp @@ -0,0 +1,192 @@ +// Game_Music_Emu $vers. http://www.slack.net/~ant/ + +#include "Sap_Core.h" + +/* Copyright (C) 2006-2008 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +#include "blargg_source.h" + +int const idle_addr = 0xD2D2; + +Sap_Core::Sap_Core() +{ + set_tempo( 1 ); +} + +void Sap_Core::push( int b ) +{ + mem.ram [0x100 + cpu.r.sp--] = (byte) b; +} + +void Sap_Core::jsr_then_stop( addr_t addr ) +{ + cpu.r.pc = addr; + + // Some rips pop three bytes off stack before RTS. + push( (idle_addr - 1) >> 8 ); + push( idle_addr - 1 ); + + // 3 bytes so that RTI or RTS will jump to idle_addr. + // RTI will use the first two bytes as the address, 0xD2D2. + // RTS will use the last two bytes, 0xD2D1, which it internally increments. + push( (idle_addr - 1) >> 8 ); + push( (idle_addr - 1) >> 8 ); + push( idle_addr - 1 ); +} + +// Runs routine and allows it up to one second to return +void Sap_Core::run_routine( addr_t addr ) +{ + jsr_then_stop( addr ); + run_cpu( lines_per_frame * base_scanline_period * 60 ); + check( cpu.r.pc == idle_addr ); + check( cpu.r.sp >= 0xFF - 6 ); +} + +inline void Sap_Core::call_init( int track ) +{ + cpu.r.a = track; + + switch ( info.type ) + { + case 'B': + run_routine( info.init_addr ); + break; + + case 'C': + cpu.r.a = 0x70; + cpu.r.x = info.music_addr&0xFF; + cpu.r.y = info.music_addr >> 8; + run_routine( info.play_addr + 3 ); + cpu.r.a = 0; + cpu.r.x = track; + run_routine( info.play_addr + 3 ); + break; + + case 'D': + check( info.fastplay == lines_per_frame ); + jsr_then_stop( info.init_addr ); + break; + } +} + +void Sap_Core::setup_ram() +{ + memset( &mem, 0, sizeof mem ); + + ram() [idle_addr] = cpu.halt_opcode; + + addr_t const irq_addr = idle_addr - 1; + ram() [irq_addr] = cpu.halt_opcode; + ram() [0xFFFE] = (byte) irq_addr; + ram() [0xFFFF] = irq_addr >> 8; +} + +blargg_err_t Sap_Core::start_track( int track, info_t const& new_info ) +{ + info = new_info; + + check( ram() [idle_addr] == cpu.halt_opcode ); + + apu_ .reset( &apu_impl_ ); + apu2_.reset( &apu_impl_ ); + + cpu.reset( ram() ); + + frame_start = 0; + next_play = play_period() * 4; + saved_state.pc = idle_addr; + + time_mask = 0; // disables sound during init + call_init( track ); + time_mask = ~0; + + return blargg_ok; +} + +blargg_err_t Sap_Core::run_until( time_t end ) +{ + while ( cpu.time() < end ) + { + time_t next = min( next_play, end ); + if ( (run_cpu( next ) && cpu.r.pc != idle_addr) || cpu.error_count() ) + // TODO: better error + return BLARGG_ERR( BLARGG_ERR_GENERIC, "Emulation error (illegal instruction)" ); + + if ( cpu.r.pc == idle_addr ) + { + if ( saved_state.pc == idle_addr ) + { + // no code to run until next play call + cpu.set_time( next ); + } + else + { + // play had interrupted non-returning init, so restore registers + // init routine was running + check( cpu.r.sp == saved_state.sp - 3 ); + cpu.r = saved_state; + saved_state.pc = idle_addr; + } + } + + if ( cpu.time() >= next_play ) + { + next_play += play_period(); + + if ( cpu.r.pc == idle_addr || info.type == 'D' ) + { + // Save state if init routine is still running + if ( cpu.r.pc != idle_addr ) + { + check( info.type == 'D' ); + check( saved_state.pc == idle_addr ); + saved_state = cpu.r; + } + + addr_t addr = info.play_addr; + if ( info.type == 'C' ) + addr += 6; + jsr_then_stop( addr ); + } + else + { + dprintf( "init/play hadn't returned before next play call\n" ); + } + } + } + return blargg_ok; +} + +blargg_err_t Sap_Core::end_frame( time_t end ) +{ + RETURN_ERR( run_until( end ) ); + + cpu.adjust_time( -end ); + + time_t frame_time = lines_per_frame * scanline_period; + while ( frame_start < end ) + frame_start += frame_time; + frame_start -= end + frame_time; + + if ( (next_play -= end) < 0 ) + { + next_play = 0; + check( false ); + } + + apu_.end_frame( end ); + if ( info.stereo ) + apu2_.end_frame( end ); + + return blargg_ok; +} diff --git a/Frameworks/GME/gme/Sap_Core.h b/Frameworks/GME/gme/Sap_Core.h new file mode 100644 index 000000000..e5703575a --- /dev/null +++ b/Frameworks/GME/gme/Sap_Core.h @@ -0,0 +1,91 @@ +// Atari XL/XE SAP core CPU and RAM emulator + +// Game_Music_Emu $vers +#ifndef SAP_CORE_H +#define SAP_CORE_H + +#include "Sap_Apu.h" +#include "Nes_Cpu.h" + +class Sap_Core { +public: + + // Sound chips and common state + Sap_Apu& apu() { return apu_; } + Sap_Apu& apu2() { return apu2_; } + Sap_Apu_Impl& apu_impl() { return apu_impl_; } + + // Adjusts music tempo, where 1.0 is normal. Can be changed while playing. + void set_tempo( double ); + + // Clears RAM and sets up default vectors, etc. + void setup_ram(); + + // 64K RAM to load file data blocks into + BOOST::uint8_t* ram() { return mem.ram; } + + // Calls init routine and configures playback. RAM must have been + // set up already. + struct info_t { + int init_addr; + int play_addr; + int music_addr; + int type; + int fastplay; + bool stereo; + }; + blargg_err_t start_track( int track, info_t const& ); + + // Ends time frame at time t, then begins new at time 0 + typedef Nes_Cpu::time_t time_t; // Clock count + blargg_err_t end_frame( time_t t ); + + +// Implementation +public: + Sap_Core(); + +private: + enum { base_scanline_period = 114 }; + enum { lines_per_frame = 312 }; + typedef Nes_Cpu::addr_t addr_t; + + time_t scanline_period; + time_t next_play; + time_t time_mask; + time_t frame_start; + Nes_Cpu cpu; + Nes_Cpu::registers_t saved_state; + info_t info; + Sap_Apu apu_; + Sap_Apu apu2_; + + // large items + struct { + BOOST::uint8_t padding1 [ 0x100]; + BOOST::uint8_t ram [0x10000]; + BOOST::uint8_t padding2 [ 0x100]; + } mem; // TODO: put on freestore + Sap_Apu_Impl apu_impl_; + + void push( int b ); + void jsr_then_stop( addr_t ); + void run_routine( addr_t ); + void call_init( int track ); + bool run_cpu( time_t end ); + int play_addr(); + int read_d40b(); + int read_mem( addr_t ); + void write_D2xx( int d2xx, int data ); + + time_t time() const { return cpu.time() & time_mask; } + blargg_err_t run_until( time_t t ); + time_t play_period() const { return info.fastplay * scanline_period; } +}; + +inline void Sap_Core::set_tempo( double t ) +{ + scanline_period = (int) (base_scanline_period / t + 0.5); +} + +#endif diff --git a/Frameworks/GME/gme/SegaPcm_Emu.cpp b/Frameworks/GME/gme/SegaPcm_Emu.cpp new file mode 100644 index 000000000..8c18898dc --- /dev/null +++ b/Frameworks/GME/gme/SegaPcm_Emu.cpp @@ -0,0 +1,77 @@ +// Game_Music_Emu $vers. http://www.slack.net/~ant/ + +#include "SegaPcm_Emu.h" +#include "segapcm.h" + +SegaPcm_Emu::SegaPcm_Emu() { chip = 0; } + +SegaPcm_Emu::~SegaPcm_Emu() +{ + if ( chip ) device_stop_segapcm( chip ); +} + +int SegaPcm_Emu::set_rate( int intf_type ) +{ + if ( chip ) + { + device_stop_segapcm( chip ); + chip = 0; + } + + chip = device_start_segapcm( intf_type ); + if ( !chip ) + return 1; + + reset(); + return 0; +} + +void SegaPcm_Emu::reset() +{ + device_reset_segapcm( chip ); + segapcm_set_mute_mask( chip, 0 ); +} + +void SegaPcm_Emu::write( int addr, int data ) +{ + sega_pcm_w( chip, addr, data ); +} + +void SegaPcm_Emu::write_rom( int size, int start, int length, void * data ) +{ + sega_pcm_write_rom( chip, size, start, length, (const UINT8 *) data ); +} + +void SegaPcm_Emu::mute_voices( int mask ) +{ + segapcm_set_mute_mask( chip, mask ); +} + +void SegaPcm_Emu::run( int pair_count, sample_t* out ) +{ + stream_sample_t bufL[ 1024 ]; + stream_sample_t bufR[ 1024 ]; + stream_sample_t * buffers[2] = { bufL, bufR }; + + while (pair_count > 0) + { + int todo = pair_count; + if (todo > 1024) todo = 1024; + SEGAPCM_update( chip, buffers, todo ); + + for (int i = 0; i < todo; i++) + { + int output_l = bufL [i]; + int output_r = bufR [i]; + output_l += out [0]; + output_r += out [1]; + if ( (short)output_l != output_l ) output_l = 0x7FFF ^ ( output_l >> 31 ); + if ( (short)output_r != output_r ) output_r = 0x7FFF ^ ( output_r >> 31 ); + out [0] = output_l; + out [1] = output_r; + out += 2; + } + + pair_count -= todo; + } +} diff --git a/Frameworks/GME/gme/SegaPcm_Emu.h b/Frameworks/GME/gme/SegaPcm_Emu.h new file mode 100644 index 000000000..ee2722b42 --- /dev/null +++ b/Frameworks/GME/gme/SegaPcm_Emu.h @@ -0,0 +1,36 @@ +// Sega PCM sound chip emulator interface + +// Game_Music_Emu $vers +#ifndef SEGAPCM_EMU_H +#define SEGAPCM_EMU_H + +class SegaPcm_Emu { + void* chip; +public: + SegaPcm_Emu(); + ~SegaPcm_Emu(); + + // Sets output sample rate and chip clock rates, in Hz. Returns non-zero + // if error. + int set_rate( int intf_type ); + + // Resets to power-up state + void reset(); + + // Mutes voice n if bit n (1 << n) of mask is set + enum { channel_count = 16 }; + void mute_voices( int mask ); + + // Writes data to addr + void write( int addr, int data ); + + // Scales ROM size, then writes length bytes from data at start offset + void write_rom( int size, int start, int length, void * data ); + + // Runs and writes pair_count*2 samples to output + typedef short sample_t; + enum { out_chan_count = 2 }; // stereo + void run( int pair_count, sample_t* out ); +}; + +#endif diff --git a/Frameworks/GME/gme/Sgc_Core.cpp b/Frameworks/GME/gme/Sgc_Core.cpp new file mode 100644 index 000000000..bcb5f3a57 --- /dev/null +++ b/Frameworks/GME/gme/Sgc_Core.cpp @@ -0,0 +1,108 @@ +// Game_Music_Emu $vers. http://www.slack.net/~ant/ + +#include "Sgc_Core.h" + +/* Copyright (C) 2009 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +#include "blargg_source.h" + +void Sgc_Core::set_tempo( double t ) +{ + set_play_period( clock_rate() / (header().rate ? 50 : 60) / t ); +} + +blargg_err_t Sgc_Core::load_( Data_Reader& dr ) +{ + RETURN_ERR( Sgc_Impl::load_( dr ) ); + + if ( sega_mapping() && fm_apu_.supported() ) + RETURN_ERR( fm_apu_.init( clock_rate(), clock_rate() / 72 ) ); + + set_tempo( 1.0 ); + return blargg_ok; +} + +blargg_err_t Sgc_Core::start_track( int t ) +{ + if ( sega_mapping() ) + { + apu_.reset(); + fm_apu_.reset(); + fm_accessed = false; + } + else + { + apu_.reset( 0x0003, 15 ); + } + + return Sgc_Impl::start_track( t ); +} + +blargg_err_t Sgc_Core::end_frame( time_t t ) +{ + RETURN_ERR( Sgc_Impl::end_frame( t ) ); + apu_.end_frame( t ); + if ( sega_mapping() && fm_accessed ) + { + if ( fm_apu_.supported() ) + fm_apu_.end_frame( t ); + else + set_warning( "FM sound not supported" ); + } + + return blargg_ok; +} + +Sgc_Core::Sgc_Core() +{ } + +Sgc_Core::~Sgc_Core() +{ } + +void Sgc_Core::cpu_out( time_t time, addr_t addr, int data ) +{ + int port = addr & 0xFF; + + if ( sega_mapping() ) + { + switch ( port ) + { + case 0x06: + apu_.write_ggstereo( time, data ); + return; + + case 0x7E: + case 0x7F: + apu_.write_data( time, data ); dprintf( "$7E<-%02X\n", data ); + return; + + case 0xF0: + fm_accessed = true; + if ( fm_apu_.supported() ) + fm_apu_.write_addr( data );//, dprintf( "$F0<-%02X\n", data ); + return; + + case 0xF1: + fm_accessed = true; + if ( fm_apu_.supported() ) + fm_apu_.write_data( time, data );//, dprintf( "$F1<-%02X\n", data ); + return; + } + } + else if ( port >= 0xE0 ) + { + apu_.write_data( time, data ); + return; + } + + Sgc_Impl::cpu_out( time, addr, data ); +} diff --git a/Frameworks/GME/gme/Sgc_Core.h b/Frameworks/GME/gme/Sgc_Core.h new file mode 100644 index 000000000..23c11cbd3 --- /dev/null +++ b/Frameworks/GME/gme/Sgc_Core.h @@ -0,0 +1,44 @@ +// Sega/Game Gear/Coleco SGC music file emulator core + +// Game_Music_Emu $vers +#ifndef SGC_CORE_H +#define SGC_CORE_H + +#include "Sgc_Impl.h" +#include "Sms_Fm_Apu.h" +#include "Sms_Apu.h" + +class Sgc_Core : public Sgc_Impl { +public: + + // Adjusts music tempo, where 1.0 is normal. Can be changed while playing. + // Resets to 1.0 when loading file. + void set_tempo( double ); + + // Starts track, where 0 is the first. + blargg_err_t start_track( int ); + + // Ends time frame at time t + blargg_err_t end_frame( time_t t ); + + // SN76489 sound chip + Sms_Apu& apu() { return apu_; } + Sms_Fm_Apu& fm_apu() { return fm_apu_; } + +protected: + // Overrides + virtual void cpu_out( time_t, addr_t, int data ); + virtual blargg_err_t load_( Data_Reader& ); + +// Implementation +public: + Sgc_Core(); + ~Sgc_Core(); + +private: + bool fm_accessed; + Sms_Apu apu_; + Sms_Fm_Apu fm_apu_; +}; + +#endif diff --git a/Frameworks/GME/gme/Sgc_Cpu.cpp b/Frameworks/GME/gme/Sgc_Cpu.cpp new file mode 100644 index 000000000..b63e42b83 --- /dev/null +++ b/Frameworks/GME/gme/Sgc_Cpu.cpp @@ -0,0 +1,36 @@ +// Game_Music_Emu $vers. http://www.slack.net/~ant/ + +#include "Sgc_Impl.h" + +#include "blargg_endian.h" +//#include "z80_cpu_log.h" + +/* Copyright (C) 2009 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +#include "blargg_source.h" + +#define OUT_PORT( addr, data ) cpu_out( TIME(), addr, data ) +#define IN_PORT( addr ) cpu_in( addr ) +#define WRITE_MEM( addr, data ) cpu_write( addr, data ) +#define IDLE_ADDR idle_addr +#define CPU cpu +#define RST_BASE vectors_addr + +#define CPU_BEGIN \ +bool Sgc_Impl::run_cpu( time_t end_time )\ +{\ + cpu.set_end_time( end_time ); + + #include "Z80_Cpu_run.h" + + return warning; +} diff --git a/Frameworks/GME/gme/Sgc_Emu.cpp b/Frameworks/GME/gme/Sgc_Emu.cpp new file mode 100644 index 000000000..010ee71d6 --- /dev/null +++ b/Frameworks/GME/gme/Sgc_Emu.cpp @@ -0,0 +1,167 @@ +// Game_Music_Emu $vers. http://www.slack.net/~ant/ + +#include "Sgc_Emu.h" + +/* Copyright (C) 2009 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +#include "blargg_source.h" + +int const osc_count = Sms_Apu::osc_count + Sms_Fm_Apu::osc_count; + +Sgc_Emu::Sgc_Emu() +{ + set_type( gme_sgc_type ); + set_silence_lookahead( 6 ); + set_gain( 1.2 ); +} + +Sgc_Emu::~Sgc_Emu() { } + +void Sgc_Emu::unload() +{ + core_.unload(); + Music_Emu::unload(); +} + +// Track info + +static void copy_sgc_fields( Sgc_Emu::header_t const& h, track_info_t* out ) +{ + GME_COPY_FIELD( h, out, game ); + GME_COPY_FIELD( h, out, author ); + GME_COPY_FIELD( h, out, copyright ); +} + +static void hash_sgc_file( Sgc_Emu::header_t const& h, byte const* data, int data_size, Music_Emu::Hash_Function& out ) +{ + out.hash_( &h.vers, sizeof(h.vers) ); + out.hash_( &h.rate, sizeof(h.rate) ); + out.hash_( &h.reserved1[0], sizeof(h.reserved1) ); + out.hash_( &h.load_addr[0], sizeof(h.load_addr) ); + out.hash_( &h.init_addr[0], sizeof(h.init_addr) ); + out.hash_( &h.play_addr[0], sizeof(h.play_addr) ); + out.hash_( &h.stack_ptr[0], sizeof(h.stack_ptr) ); + out.hash_( &h.reserved2[0], sizeof(h.reserved2) ); + out.hash_( &h.rst_addrs[0], sizeof(h.rst_addrs) ); + out.hash_( &h.mapping[0], sizeof(h.mapping) ); + out.hash_( &h.first_song, sizeof(h.first_song) ); + out.hash_( &h.song_count, sizeof(h.song_count) ); + out.hash_( &h.first_effect, sizeof(h.first_effect) ); + out.hash_( &h.last_effect, sizeof(h.last_effect) ); + out.hash_( &h.system, sizeof(h.system) ); + out.hash_( &h.reserved3[0], sizeof(h.reserved3) ); + out.hash_( data, data_size ); +} + +blargg_err_t Sgc_Emu::track_info_( track_info_t* out, int ) const +{ + copy_sgc_fields( header(), out ); + return blargg_ok; +} + +struct Sgc_File : Gme_Info_ +{ + Sgc_Emu::header_t const* h; + + Sgc_File() { set_type( gme_sgc_type ); } + + blargg_err_t load_mem_( byte const begin [], int size ) + { + h = ( Sgc_Emu::header_t const* ) begin; + + set_track_count( h->song_count ); + if ( !h->valid_tag() ) + return blargg_err_file_type; + + return blargg_ok; + } + + blargg_err_t track_info_( track_info_t* out, int ) const + { + copy_sgc_fields( *h, out ); + return blargg_ok; + } + + blargg_err_t hash_( Hash_Function& out ) const + { + hash_sgc_file( *h, file_begin() + h->size, file_end() - file_begin() - h->size, out ); + return blargg_ok; + } +}; + +static Music_Emu* new_sgc_emu () { return BLARGG_NEW Sgc_Emu ; } +static Music_Emu* new_sgc_file() { return BLARGG_NEW Sgc_File; } + +gme_type_t_ const gme_sgc_type [1] = {{ "Z80 PSG", 0, &new_sgc_emu, &new_sgc_file, "SGC", 1 }}; + +// Setup + +blargg_err_t Sgc_Emu::load_( Data_Reader& in ) +{ + RETURN_ERR( core_.load( in ) ); + set_warning( core_.warning() ); + set_track_count( header().song_count ); + set_voice_count( core_.sega_mapping() ? osc_count : core_.apu().osc_count ); + + core_.apu ().volume( gain() ); + core_.fm_apu().volume( gain() ); + + static const char* const names [osc_count + 1] = { + "Square 1", "Square 2", "Square 3", "Noise", "FM" + }; + set_voice_names( names ); + + static int const types [osc_count + 1] = { + wave_type+1, wave_type+2, wave_type+3, mixed_type+1, mixed_type+2 + }; + set_voice_types( types ); + + return setup_buffer( core_.clock_rate() ); +} + +void Sgc_Emu::update_eq( blip_eq_t const& eq ) +{ + core_.apu ().treble_eq( eq ); + core_.fm_apu().treble_eq( eq ); +} + +void Sgc_Emu::set_voice( int i, Blip_Buffer* c, Blip_Buffer* l, Blip_Buffer* r ) +{ + if ( i < core_.apu().osc_count ) + core_.apu().set_output( i, c, l, r ); + else + core_.fm_apu().set_output( c, l, r ); +} + +void Sgc_Emu::set_tempo_( double t ) +{ + core_.set_tempo( t ); +} + +blargg_err_t Sgc_Emu::start_track_( int track ) +{ + RETURN_ERR( core_.start_track( track ) ); + return Classic_Emu::start_track_( track ); +} + +blargg_err_t Sgc_Emu::run_clocks( blip_time_t& duration, int ) +{ + RETURN_ERR( core_.end_frame( duration ) ); + set_warning( core_.warning() ); + return blargg_ok; +} + +blargg_err_t Sgc_Emu::hash_( Hash_Function& out ) const +{ + hash_sgc_file( header(), core_.rom_().begin(), core_.rom_().file_size(), out ); + return blargg_ok; +} \ No newline at end of file diff --git a/Frameworks/GME/gme/Sgc_Emu.h b/Frameworks/GME/gme/Sgc_Emu.h new file mode 100644 index 000000000..112f950d7 --- /dev/null +++ b/Frameworks/GME/gme/Sgc_Emu.h @@ -0,0 +1,45 @@ +// Sega/Game Gear/Coleco SGC music file emulator + +// Game_Music_Emu $vers +#ifndef SGC_EMU_H +#define SGC_EMU_H + +#include "Classic_Emu.h" +#include "Sgc_Core.h" + +class Sgc_Emu : public Classic_Emu { +public: + // SGC file header (see Sgc_Impl.h) + typedef Sgc_Core::header_t header_t; + + // Header for currently loaded file + header_t const& header() const { return core_.header(); } + + blargg_err_t hash_( Hash_Function& ) const; + + // Sets 0x2000-byte Coleco BIOS. Necessary to play Coleco tracks. + static void set_coleco_bios( void const* p ){ Sgc_Core::set_coleco_bios( p ); } + + static gme_type_t static_type() { return gme_sgc_type; } + +// Internal +public: + Sgc_Emu(); + ~Sgc_Emu(); + +protected: + // Classic_Emu overrides + virtual blargg_err_t track_info_( track_info_t*, int track ) const; + virtual blargg_err_t load_( Data_Reader& ); + virtual blargg_err_t start_track_( int ); + virtual blargg_err_t run_clocks( blip_time_t&, int ); + virtual void set_tempo_( double ); + virtual void set_voice( int, Blip_Buffer*, Blip_Buffer*, Blip_Buffer* ); + virtual void update_eq( blip_eq_t const& ); + virtual void unload(); + +private: + Sgc_Core core_; +}; + +#endif diff --git a/Frameworks/GME/gme/Sgc_Impl.cpp b/Frameworks/GME/gme/Sgc_Impl.cpp new file mode 100644 index 000000000..497966ab8 --- /dev/null +++ b/Frameworks/GME/gme/Sgc_Impl.cpp @@ -0,0 +1,225 @@ +// Game_Music_Emu $vers. http://www.slack.net/~ant/ + +#include "Sgc_Impl.h" + +/* Copyright (C) 2006-2009 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +#include "blargg_source.h" + +void const* Sgc_Impl::coleco_bios; + +Sgc_Impl::Sgc_Impl() : + rom( bank_size ) +{ + assert( offsetof (header_t,copyright [32]) == header_t::size ); +} + +Sgc_Impl::~Sgc_Impl() +{ } + +bool Sgc_Impl::header_t::valid_tag() const +{ + return 0 == memcmp( tag, "SGC\x1A", 4 ); +} + +blargg_err_t Sgc_Impl::load_( Data_Reader& in ) +{ + RETURN_ERR( rom.load( in, header_.size, &header_, 0 ) ); + + if ( !header_.valid_tag() ) + return blargg_err_file_type; + + if ( header_.vers != 1 ) + set_warning( "Unknown file version" ); + + if ( header_.system > 2 ) + set_warning( "Unknown system" ); + + addr_t load_addr = get_le16( header_.load_addr ); + if ( load_addr < 0x400 ) + set_warning( "Invalid load address" ); + + rom.set_addr( load_addr ); + play_period = clock_rate() / 60; + + if ( sega_mapping() ) + { + RETURN_ERR( ram.resize( 0x2000 + Sgc_Cpu::page_padding ) ); + RETURN_ERR( ram2.resize( bank_size + Sgc_Cpu::page_padding ) ); + } + else + { + RETURN_ERR( ram.resize( 0x400 + Sgc_Cpu::page_padding ) ); + } + + RETURN_ERR( vectors.resize( Sgc_Cpu::page_size + Sgc_Cpu::page_padding ) ); + + // TODO: doesn't need to be larger than page size, if we do mapping calls right + RETURN_ERR( unmapped_write.resize( bank_size ) ); + + return blargg_ok; +} + +void Sgc_Impl::unload() +{ + rom.clear(); + vectors.clear(); + ram.clear(); + ram2.clear(); + unmapped_write.clear(); + Gme_Loader::unload(); +} + +blargg_err_t Sgc_Impl::start_track( int track ) +{ + memset( ram .begin(), 0, ram .size() ); + memset( ram2.begin(), 0, ram2.size() ); + memset( vectors.begin(), 0xFF, vectors.size() ); + cpu.reset( unmapped_write.begin(), rom.unmapped() ); + + if ( sega_mapping() ) + { + vectors_addr = 0x10000 - Sgc_Cpu::page_size; + idle_addr = vectors_addr; + for ( int i = 1; i < 8; ++i ) + { + vectors [i*8 + 0] = 0xC3; // JP addr + vectors [i*8 + 1] = header_.rst_addrs [i*2 + 0]; + vectors [i*8 + 2] = header_.rst_addrs [i*2 + 1]; + } + + cpu.map_mem( 0xC000, 0x2000, ram.begin() ); + cpu.map_mem( vectors_addr, cpu.page_size, unmapped_write.begin(), vectors.begin() ); + + bank2 = NULL; + for ( int i = 0; i < 4; ++i ) + cpu_write( 0xFFFC + i, header_.mapping [i] ); + } + else + { + if ( !coleco_bios ) + return BLARGG_ERR( BLARGG_ERR_CALLER, "Coleco BIOS not set" ); + + vectors_addr = 0; + cpu.map_mem( 0, 0x2000, unmapped_write.begin(), coleco_bios ); + for ( int i = 0; i < 8; ++i ) + cpu.map_mem( 0x6000 + i*0x400, 0x400, ram.begin() ); + + idle_addr = 0x2000; + cpu.map_mem( 0x2000, cpu.page_size, unmapped_write.begin(), vectors.begin() ); + + for ( int i = 0; i < 0x8000 / bank_size; ++i ) + { + int addr = 0x8000 + i*bank_size; + cpu.map_mem( addr, bank_size, unmapped_write.begin(), rom.at_addr( addr ) ); + } + } + + cpu.r.sp = get_le16( header_.stack_ptr ); + cpu.r.b.a = track; + next_play = play_period; + + jsr( header_.init_addr ); + + return blargg_ok; +} + +// Emulation + +void Sgc_Impl::jsr( byte const (&addr) [2] ) +{ + *cpu.write( --cpu.r.sp ) = idle_addr >> 8; + *cpu.write( --cpu.r.sp ) = idle_addr & 0xFF; + cpu.r.pc = get_le16( addr ); +} + +void Sgc_Impl::set_bank( int bank, void const* data ) +{ + //dprintf( "map bank %d to %p\n", bank, (byte*) data - rom.at_addr( 0 ) ); + cpu.map_mem( bank * bank_size, bank_size, unmapped_write.begin(), data ); +} + +void Sgc_Impl::cpu_write( addr_t addr, int data ) +{ + if ( (addr ^ 0xFFFC) > 3 || !sega_mapping() ) + { + *cpu.write( addr ) = data; + return; + } + + switch ( addr ) + { + case 0xFFFC: + cpu.map_mem( 2 * bank_size, bank_size, ram2.begin() ); + if ( data & 0x08 ) + break; + + bank2 = ram2.begin(); + // FALL THROUGH + + case 0xFFFF: { + bool rom_mapped = (cpu.read( 2 * bank_size ) == bank2); + bank2 = rom.at_addr( data * bank_size ); + if ( rom_mapped ) + set_bank( 2, bank2 ); + break; + } + + case 0xFFFD: + set_bank( 0, rom.at_addr( data * bank_size ) ); + break; + + case 0xFFFE: + set_bank( 1, rom.at_addr( data * bank_size ) ); + break; + } +} + +int Sgc_Impl::cpu_in( addr_t addr ) +{ + dprintf( "in %02X\n", addr ); + return 0; +} + +void Sgc_Impl::cpu_out( time_t, addr_t addr, int ) +{ + dprintf( "out %02X\n", addr & 0xFF ); +} + +blargg_err_t Sgc_Impl::end_frame( time_t end ) +{ + while ( cpu.time() < end ) + { + time_t next = min( end, next_play ); + if ( run_cpu( next ) ) + { + set_warning( "Unsupported CPU instruction" ); + cpu.set_time( next ); + } + + if ( cpu.r.pc == idle_addr ) + cpu.set_time( next ); + + if ( cpu.time() >= next_play ) + { + next_play += play_period; + if ( cpu.r.pc == idle_addr ) + jsr( header_.play_addr ); + } + } + + next_play -= end; + check( next_play >= 0 ); + cpu.adjust_time( -end ); + + return blargg_ok; +} diff --git a/Frameworks/GME/gme/Sgc_Impl.h b/Frameworks/GME/gme/Sgc_Impl.h new file mode 100644 index 000000000..e9b873c53 --- /dev/null +++ b/Frameworks/GME/gme/Sgc_Impl.h @@ -0,0 +1,116 @@ +// Sega/Game Gear/Coleco SGC music file emulator implementation internals + +// Game_Music_Emu $vers +#ifndef SGC_IMPL_H +#define SGC_IMPL_H + +#include "Gme_Loader.h" +#include "Rom_Data.h" +#include "Z80_Cpu.h" + +class Sgc_Impl : public Gme_Loader { +public: + + // SGC file header + struct header_t + { + enum { size = 0xA0 }; + + char tag [4]; // "SGC\x1A" + byte vers; // 0x01 + byte rate; // 0=NTSC 1=PAL + byte reserved1 [2]; + byte load_addr [2]; + byte init_addr [2]; + byte play_addr [2]; + byte stack_ptr [2]; + byte reserved2 [2]; + byte rst_addrs [7*2]; + byte mapping [4]; // Used by Sega only + byte first_song; // Song to start playing first + byte song_count; + byte first_effect; + byte last_effect; + byte system; // 0=Master System 1=Game Gear 2=Colecovision + byte reserved3 [23]; + char game [32]; // strings can be 32 chars, NOT terminated + char author [32]; + char copyright [32]; + + // True if header has valid file signature + bool valid_tag() const; + + int effect_count() const { return last_effect ? last_effect - first_effect + 1 : 0; } + }; + + // Header for currently loaded file + header_t const& header() const { return header_; } + + Rom_Data const& rom_() const { return rom; } + + int clock_rate() const { return header_.rate ? 3546893 : 3579545; } + + // 0x2000 bytes + static void set_coleco_bios( void const* p ) { coleco_bios = p; } + + // Clocks between calls to play routine + typedef int time_t; + void set_play_period( time_t p ) { play_period = p; } + + // 0 = first track + blargg_err_t start_track( int ); + + // Runs for t clocks + blargg_err_t end_frame( time_t t ); + + // True if Master System or Game Gear + bool sega_mapping() const; + +protected: + typedef Z80_Cpu Sgc_Cpu; + Sgc_Cpu cpu; + + typedef int addr_t; + virtual void cpu_out( time_t, addr_t, int data ) BLARGG_PURE( ; ) + +// Implementation +public: + Sgc_Impl(); + ~Sgc_Impl(); + virtual void unload(); + +protected: + virtual blargg_err_t load_( Data_Reader& ); + +private: + enum { bank_size = 0x4000 }; + + Rom_Data rom; + time_t play_period; + time_t next_play; + void const* bank2; // ROM selected for bank 2, in case RAM is currently hiding it + addr_t vectors_addr; // RST vectors start here + addr_t idle_addr; // return address for init/play routines + static void const* coleco_bios; + + // large items + header_t header_; + blargg_vector vectors; + blargg_vector ram; + blargg_vector ram2; + blargg_vector unmapped_write; + + bool run_cpu( time_t end ); + void jsr( byte const (&addr) [2] ); + void cpu_write( addr_t, int data ); + int cpu_in( addr_t ); + + void set_bank( int bank, void const* data ); +}; + +inline bool Sgc_Impl::sega_mapping() const +{ + return header_.system <= 1; +} + +#endif diff --git a/Frameworks/GME/gme/Sms_Fm_Apu.cpp b/Frameworks/GME/gme/Sms_Fm_Apu.cpp new file mode 100644 index 000000000..9cd7e5518 --- /dev/null +++ b/Frameworks/GME/gme/Sms_Fm_Apu.cpp @@ -0,0 +1,80 @@ +#include "Sms_Fm_Apu.h" + +#include "blargg_source.h" + +Sms_Fm_Apu::Sms_Fm_Apu() +{ } + +Sms_Fm_Apu::~Sms_Fm_Apu() +{ } + +blargg_err_t Sms_Fm_Apu::init( double clock_rate, double sample_rate ) +{ + period_ = clock_rate / sample_rate + 0.5; + CHECK_ALLOC( !apu.set_rate( sample_rate, clock_rate ) ); + + set_output( 0 ); + volume( 1.0 ); + reset(); + return blargg_ok; +} + +void Sms_Fm_Apu::reset() +{ + addr = 0; + next_time = 0; + last_amp = 0; + + apu.reset(); +} + +void Sms_Fm_Apu::write_data( blip_time_t time, int data ) +{ + if ( time > next_time ) + run_until( time ); + + apu.write( addr, data ); +} + +void Sms_Fm_Apu::run_until( blip_time_t end_time ) +{ + assert( end_time > next_time ); + + Blip_Buffer* const output = this->output_; + if ( !output ) + { + next_time = end_time; + return; + } + + blip_time_t time = next_time; + do + { + Ym2413_Emu::sample_t samples [2] = {0}; + apu.run( 1, samples ); + int amp = (samples [0] + samples [1]) >> 1; + + int delta = amp - last_amp; + if ( delta ) + { + last_amp = amp; + synth.offset_inline( time, delta, output ); + } + time += period_; + } + while ( time < end_time ); + + next_time = time; +} + +void Sms_Fm_Apu::end_frame( blip_time_t time ) +{ + if ( time > next_time ) + run_until( time ); + + next_time -= time; + assert( next_time >= 0 ); + + if ( output_ ) + output_->set_modified(); +} diff --git a/Frameworks/GME/gme/Sms_Fm_Apu.h b/Frameworks/GME/gme/Sms_Fm_Apu.h new file mode 100644 index 000000000..5fc2ea1ee --- /dev/null +++ b/Frameworks/GME/gme/Sms_Fm_Apu.h @@ -0,0 +1,47 @@ +#ifndef SMS_FM_APU_H +#define SMS_FM_APU_H + +#include "Blip_Buffer.h" +#include "Ym2413_Emu.h" + +class Sms_Fm_Apu { +public: + static bool supported() { return Ym2413_Emu::supported(); } + blargg_err_t init( double clock_rate, double sample_rate ); + + void set_output( Blip_Buffer* b, Blip_Buffer* = NULL, Blip_Buffer* = NULL ) { output_ = b; } + void volume( double v ) { synth.volume( 0.4 / 4096 * v ); } + void treble_eq( blip_eq_t const& eq ) { synth.treble_eq( eq ); } + + void reset(); + + void write_addr( int data ) { addr = data; } + void write_data( blip_time_t, int data ); + + void end_frame( blip_time_t t ); + +// Implementation +public: + Sms_Fm_Apu(); + ~Sms_Fm_Apu(); + BLARGG_DISABLE_NOTHROW + enum { osc_count = 1 }; + void set_output( int i, Blip_Buffer* b, Blip_Buffer* = NULL, Blip_Buffer* = NULL ) { output_ = b; } + +private: + Blip_Buffer* output_; + blip_time_t next_time; + int last_amp; + int addr; + + int clock_; + int rate_; + blip_time_t period_; + + Blip_Synth_Norm synth; + Ym2413_Emu apu; + + void run_until( blip_time_t ); +}; + +#endif diff --git a/Frameworks/GME/gme/Spc_Filter.cpp b/Frameworks/GME/gme/Spc_Filter.cpp new file mode 100644 index 000000000..746af8ccd --- /dev/null +++ b/Frameworks/GME/gme/Spc_Filter.cpp @@ -0,0 +1,81 @@ +// snes_spc $vers. http://www.slack.net/~ant/ + +#include "Spc_Filter.h" + +/* Copyright (C) 2007 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +#include "blargg_source.h" + +void Spc_Filter::clear() { memset( ch, 0, sizeof ch ); } + +Spc_Filter::Spc_Filter() +{ + enabled = true; + gain = gain_unit; + bass = bass_norm; + clear(); +} + +void Spc_Filter::run( short io [], int count ) +{ + require( (count & 1) == 0 ); // must be even + + int const gain = this->gain; + if ( enabled ) + { + int const bass = this->bass; + chan_t* c = &ch [2]; + do + { + // cache in registers + int sum = (--c)->sum; + int pp1 = c->pp1; + int p1 = c->p1; + + for ( int i = 0; i < count; i += 2 ) + { + // Low-pass filter (two point FIR with coeffs 0.25, 0.75) + int f = io [i] + p1; + p1 = io [i] * 3; + + // High-pass filter ("leaky integrator") + int delta = f - pp1; + pp1 = f; + int s = sum >> (gain_bits + 2); + sum += (delta * gain) - (sum >> bass); + + // Clamp to 16 bits + if ( (short) s != s ) + s = (s >> 31) ^ 0x7FFF; + + io [i] = (short) s; + } + + c->p1 = p1; + c->pp1 = pp1; + c->sum = sum; + ++io; + } + while ( c != ch ); + } + else if ( gain != gain_unit ) + { + short* const end = io + count; + while ( io < end ) + { + int s = (*io * gain) >> gain_bits; + if ( (short) s != s ) + s = (s >> 31) ^ 0x7FFF; + *io++ = (short) s; + } + } +} diff --git a/Frameworks/GME/gme/Spc_Filter.h b/Frameworks/GME/gme/Spc_Filter.h new file mode 100644 index 000000000..06758c971 --- /dev/null +++ b/Frameworks/GME/gme/Spc_Filter.h @@ -0,0 +1,53 @@ +// Simple low-pass and high-pass filter to better match sound output of a SNES + +// snes_spc $vers +#ifndef SPC_FILTER_H +#define SPC_FILTER_H + +#include "blargg_common.h" + +struct Spc_Filter { +public: + + // Filters count samples of stereo sound in place. Count must be a multiple of 2. + typedef short sample_t; + void run( sample_t io [], int count ); + +// Optional features + + // Clears filter to silence + void clear(); + + // Sets gain (volume), where gain_unit is normal. Gains greater than gain_unit + // are fine, since output is clamped to 16-bit sample range. + enum { gain_unit = 0x100 }; + void set_gain( int gain ); + + // Enables/disables filtering (when disabled, gain is still applied) + void enable( bool b ); + + // Sets amount of bass (logarithmic scale) + enum { bass_none = 0 }; + enum { bass_norm = 8 }; // normal amount + enum { bass_max = 31 }; + void set_bass( int bass ); + +public: + Spc_Filter(); + BLARGG_DISABLE_NOTHROW +private: + enum { gain_bits = 8 }; + int gain; + int bass; + bool enabled; + struct chan_t { int p1, pp1, sum; }; + chan_t ch [2]; +}; + +inline void Spc_Filter::enable( bool b ) { enabled = b; } + +inline void Spc_Filter::set_gain( int g ) { gain = g; } + +inline void Spc_Filter::set_bass( int b ) { bass = b; } + +#endif diff --git a/Frameworks/GME/gme/Spc_Sfm.cpp b/Frameworks/GME/gme/Spc_Sfm.cpp new file mode 100644 index 000000000..91ce894ac --- /dev/null +++ b/Frameworks/GME/gme/Spc_Sfm.cpp @@ -0,0 +1,430 @@ +// Game_Music_Emu $vers. http://www.slack.net/~ant/ + +#include "Spc_Sfm.h" + +#include "blargg_endian.h" + +#include + +/* Copyright (C) 2004-2013 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +#include "blargg_source.h" + +// TODO: support Spc_Filter's bass + +Sfm_Emu::Sfm_Emu() +{ + set_type( gme_sfm_type ); + set_gain( 1.4 ); +} + +Sfm_Emu::~Sfm_Emu() { } + +// Track info + +static void hash_sfm_file( byte const* data, int data_size, Music_Emu::Hash_Function& out ) +{ + out.hash_( data, data_size ); +} + +blargg_err_t Sfm_Emu::track_info_( track_info_t* out, int ) const +{ + const char * title = metadata.enumValue("information:title"); + if (title) strncpy( out->song, title, 255 ); + else out->song[0] = 0; + out->song[255] = '\0'; + return blargg_ok; +} + +static blargg_err_t check_sfm_header( void const* header ) +{ + if ( memcmp( header, "SFM1", 4 ) ) + return blargg_err_file_type; + return blargg_ok; +} + +struct Sfm_File : Gme_Info_ +{ + blargg_vector data; + Bml_Parser metadata; + + Sfm_File() { set_type( gme_sfm_type ); } + + blargg_err_t load_( Data_Reader& in ) + { + int file_size = in.remain(); + if ( file_size < Sfm_Emu::sfm_min_file_size ) + return blargg_err_file_type; + RETURN_ERR( data.resize( file_size ) ); + RETURN_ERR( in.read( data.begin(), data.end() - data.begin() ) ); + RETURN_ERR( check_sfm_header( data.begin() ) ); + int metadata_size = get_le32( data.begin() + 4 ); + byte temp = data[ 8 + metadata_size ]; + data[ 8 + metadata_size ] = '\0'; + metadata.parseDocument( (const char *)data.begin() + 8 ); + data[ 8 + metadata_size ] = temp; + return blargg_ok; + } + + blargg_err_t track_info_( track_info_t* out, int ) const + { + const char * title = metadata.enumValue("information:title"); + if (title) strncpy( out->song, title, 255 ); + else out->song[0] = 0; + out->song[255] = '\0'; + return blargg_ok; + } + + blargg_err_t hash_( Hash_Function& out ) const + { + hash_sfm_file( data.begin(), data.end() - data.begin(), out ); + return blargg_ok; + } +}; + +static Music_Emu* new_sfm_emu () { return BLARGG_NEW Sfm_Emu ; } +static Music_Emu* new_sfm_file() { return BLARGG_NEW Sfm_File; } + +gme_type_t_ const gme_sfm_type [1] = {{ "Super Nintendo with log", 1, &new_sfm_emu, &new_sfm_file, "SFM", 0 }}; + +// Setup + +blargg_err_t Sfm_Emu::set_sample_rate_( int sample_rate ) +{ + RETURN_ERR( apu.init() ); + if ( sample_rate != native_sample_rate ) + { + RETURN_ERR( resampler.resize_buffer( native_sample_rate / 20 * 2 ) ); + RETURN_ERR( resampler.set_rate( (double) native_sample_rate / sample_rate ) ); // 0.9965 rolloff + } + return blargg_ok; +} + +void Sfm_Emu::mute_voices_( int m ) +{ + Music_Emu::mute_voices_( m ); + apu.mute_voices( m ); +} + +blargg_err_t Sfm_Emu::load_mem_( byte const in [], int size ) +{ + set_voice_count( Spc_Dsp::voice_count ); + if ( size < Sfm_Emu::sfm_min_file_size ) + return blargg_err_file_type; + + static const char* const names [Spc_Dsp::voice_count] = { + "DSP 1", "DSP 2", "DSP 3", "DSP 4", "DSP 5", "DSP 6", "DSP 7", "DSP 8" + }; + set_voice_names( names ); + + return check_sfm_header( in ); +} + +// Emulation + +void Sfm_Emu::set_tempo_( double t ) +{ + apu.set_tempo( (int) (t * Snes_Spc::tempo_unit) ); +} + +// (n ? n : 256) +#define IF_0_THEN_256( n ) ((uint8_t) ((n) - 1) + 1) + +#define META_ENUM_INT(n) (value = metadata.enumValue(n), value ? strtoul(value, &end, 10) : 0) + +static const byte ipl_rom[0x40] = +{ + 0xCD, 0xEF, 0xBD, 0xE8, 0x00, 0xC6, 0x1D, 0xD0, + 0xFC, 0x8F, 0xAA, 0xF4, 0x8F, 0xBB, 0xF5, 0x78, + 0xCC, 0xF4, 0xD0, 0xFB, 0x2F, 0x19, 0xEB, 0xF4, + 0xD0, 0xFC, 0x7E, 0xF4, 0xD0, 0x0B, 0xE4, 0xF5, + 0xCB, 0xF4, 0xD7, 0x00, 0xFC, 0xD0, 0xF3, 0xAB, + 0x01, 0x10, 0xEF, 0x7E, 0xF4, 0x10, 0xEB, 0xBA, + 0xF6, 0xDA, 0x00, 0xBA, 0xF4, 0xC4, 0xF4, 0xDD, + 0x5D, 0xD0, 0xDB, 0x1F, 0x00, 0x00, 0xC0, 0xFF +}; + +blargg_err_t Sfm_Emu::start_track_( int track ) +{ + RETURN_ERR( Music_Emu::start_track_( track ) ); + resampler.clear(); + filter.clear(); + const byte * ptr = file_begin(); + int metadata_size = get_le32(ptr + 4); + if ( file_size() < metadata_size + Sfm_Emu::sfm_min_file_size ) + return "SFM file too small"; + char * temp = new char[metadata_size + 1]; + temp[metadata_size] = '\0'; + memcpy(temp, ptr + 8, metadata_size); + metadata.parseDocument(temp); + delete [] temp; + + apu.init_rom( ipl_rom ); + + apu.reset(); + + memcpy( apu.m.ram.ram, ptr + 8 + metadata_size, 65536 ); + + memcpy( apu.dsp.m.regs, ptr + 8 + metadata_size + 65536, 128 ); + + apu.set_sfm_queue( ptr + 8 + metadata_size + 65536 + 128, ptr + file_size() ); + + byte regs[Snes_Spc::reg_count] = {0}; + + char * end; + const char * value; + + regs[Snes_Spc::r_test] = META_ENUM_INT("smp:test"); + regs[Snes_Spc::r_control] |= META_ENUM_INT("smp:iplrom") ? 0x80 : 0; + regs[Snes_Spc::r_dspaddr] = META_ENUM_INT("smp:dspaddr"); + + value = metadata.enumValue("smp:ram"); + if (value) + { + regs[Snes_Spc::r_f8] = strtoul(value, &end, 10); + if (*end) + { + value = end + 1; + regs[Snes_Spc::r_f9] = strtoul(value, &end, 10); + } + } + + char temp_path[256]; + for (int i = 0; i < 3; ++i) + { + sprintf(temp_path, "smp:timer[%u]:", i); + size_t length = strlen(temp_path); + strcpy(temp_path + length, "enable"); + value = metadata.enumValue(temp_path); + if (value) + { + regs[Snes_Spc::r_control] |= strtoul(value, &end, 10) ? 1 << i : 0; + } + strcpy(temp_path + length, "target"); + value = metadata.enumValue(temp_path); + if (value) + { + regs[Snes_Spc::r_t0target + i] = strtoul(value, &end, 10); + } + strcpy(temp_path + length, "stage"); + value = metadata.enumValue(temp_path); + if (value) + { + for (int j = 0; j < 3; ++j) + { + if (value) value = strchr(value, ','); + if (value) ++value; + } + if (value) + { + regs[Snes_Spc::r_t0out + i] = strtoul(value, &end, 10); + } + } + } + + apu.load_regs( regs ); + apu.m.rom_enabled = 0; + apu.regs_loaded(); + + for (int i = 0; i < 3; ++i) + { + sprintf(temp_path, "smp:timer[%u]:", i); + size_t length = strlen(temp_path); + strcpy(temp_path + length, "stage"); + value = metadata.enumValue(temp_path); + if (value) + { + const char * stage = value; + apu.m.timers[i].next_time = strtoul(stage, &end, 10) + 1; + for (int j = 0; j < 2; ++j) + { + if (stage) stage = strchr(stage, ','); + if (stage) ++stage; + } + if (stage) + { + apu.m.timers[i].divider = strtoul(value, &end, 10); + } + } + } + + apu.dsp.m.echo_hist_pos = &apu.dsp.m.echo_hist[META_ENUM_INT("dsp:echohistaddr")]; + + value = metadata.enumValue("dsp:echohistdata"); + if (value) + { + for (int i = 0; i < 8; ++i) + { + apu.dsp.m.echo_hist[i][0] = strtoul(value, &end, 10); + value = strchr(value, ','); + if (!value) break; + ++value; + apu.dsp.m.echo_hist[i][1] = strtoul(value, &end, 10); + value = strchr(value, ','); + if (!value) break; + ++value; + } + } + + apu.dsp.m.phase = META_ENUM_INT("dsp:sample"); + apu.dsp.m.kon = META_ENUM_INT("dsp:kon"); + apu.dsp.m.noise = META_ENUM_INT("dsp:noise"); + apu.dsp.m.counter = META_ENUM_INT("dsp:counter"); + apu.dsp.m.echo_offset = META_ENUM_INT("dsp:echooffset"); + apu.dsp.m.echo_length = META_ENUM_INT("dsp:echolength"); + apu.dsp.m.new_kon = META_ENUM_INT("dsp:koncache"); + apu.dsp.m.endx_buf = META_ENUM_INT("dsp:endx"); + apu.dsp.m.envx_buf = META_ENUM_INT("dsp:envx"); + apu.dsp.m.outx_buf = META_ENUM_INT("dsp:outx"); + apu.dsp.m.t_pmon = META_ENUM_INT("dsp:pmon"); + apu.dsp.m.t_non = META_ENUM_INT("dsp:non"); + apu.dsp.m.t_eon = META_ENUM_INT("dsp:eon"); + apu.dsp.m.t_dir = META_ENUM_INT("dsp:dir"); + apu.dsp.m.t_koff = META_ENUM_INT("dsp:koff"); + apu.dsp.m.t_brr_next_addr = META_ENUM_INT("dsp:brrnext"); + apu.dsp.m.t_adsr0 = META_ENUM_INT("dsp:adsr0"); + apu.dsp.m.t_brr_header = META_ENUM_INT("dsp:brrheader"); + apu.dsp.m.t_brr_byte = META_ENUM_INT("dsp:brrdata"); + apu.dsp.m.t_srcn = META_ENUM_INT("dsp:srcn"); + apu.dsp.m.t_esa = META_ENUM_INT("dsp:esa"); + apu.dsp.m.t_echo_enabled = !META_ENUM_INT("dsp:echodisable"); + apu.dsp.m.t_dir_addr = META_ENUM_INT("dsp:diraddr"); + apu.dsp.m.t_pitch = META_ENUM_INT("dsp:pitch"); + apu.dsp.m.t_output = META_ENUM_INT("dsp:output"); + apu.dsp.m.t_looped = META_ENUM_INT("dsp:looped"); + apu.dsp.m.t_echo_ptr = META_ENUM_INT("dsp:echoaddr"); + + +#define META_ENUM_LEVELS(n, o) \ + value = metadata.enumValue(n); \ + if (value) \ + { \ + (o)[0] = strtoul(value, &end, 10); \ + if (*end) \ + { \ + value = end + 1; \ + (o)[1] = strtoul(value, &end, 10); \ + } \ + } + + META_ENUM_LEVELS("dsp:mainout", apu.dsp.m.t_main_out); + META_ENUM_LEVELS("dsp:echoout", apu.dsp.m.t_echo_out); + META_ENUM_LEVELS("dsp:echoin", apu.dsp.m.t_echo_in); + +#undef META_ENUM_LEVELS + + for (int i = 0; i < 8; ++i) + { + sprintf(temp_path, "dsp:voice[%u]:", i); + size_t length = strlen(temp_path); + Spc_Dsp::voice_t & voice = apu.dsp.m.voices[i]; + strcpy(temp_path + length, "brrhistaddr"); + value = metadata.enumValue(temp_path); + if (value) + { + voice.buf_pos = strtoul(value, &end, 10); + } + strcpy(temp_path + length, "brrhistdata"); + value = metadata.enumValue(temp_path); + if (value) + { + for (int j = 0; j < Spc_Dsp::brr_buf_size; ++j) + { + voice.buf[j] = voice.buf[j + Spc_Dsp::brr_buf_size] = strtoul(value, &end, 10); + if (!*end) break; + value = end + 1; + } + } + strcpy(temp_path + length, "interpaddr"); + voice.interp_pos = META_ENUM_INT(temp_path); + strcpy(temp_path + length, "brraddr"); + voice.brr_addr = META_ENUM_INT(temp_path); + strcpy(temp_path + length, "brroffset"); + voice.brr_offset = META_ENUM_INT(temp_path); + strcpy(temp_path + length, "vbit"); + voice.vbit = META_ENUM_INT(temp_path); + strcpy(temp_path + length, "vidx"); + voice.regs = &apu.dsp.m.regs[META_ENUM_INT(temp_path)]; + strcpy(temp_path + length, "kondelay"); + voice.kon_delay = META_ENUM_INT(temp_path); + strcpy(temp_path + length, "envmode"); + voice.env_mode = (Spc_Dsp::env_mode_t) META_ENUM_INT(temp_path); + strcpy(temp_path + length, "env"); + voice.env = META_ENUM_INT(temp_path); + strcpy(temp_path + length, "envxout"); + voice.t_envx_out = META_ENUM_INT(temp_path); + strcpy(temp_path + length, "envcache"); + voice.hidden_env = META_ENUM_INT(temp_path); + } + + filter.set_gain( (int) (gain() * Spc_Filter::gain_unit) ); + apu.clear_echo( true ); + return blargg_ok; +} + +#undef META_ENUM_INT + +blargg_err_t Sfm_Emu::play_and_filter( int count, sample_t out [] ) +{ + RETURN_ERR( apu.play( count, out ) ); + filter.run( out, count ); + return blargg_ok; +} + +blargg_err_t Sfm_Emu::skip_( int count ) +{ + if ( sample_rate() != native_sample_rate ) + { + count = (int) (count * resampler.rate()) & ~1; + count -= resampler.skip_input( count ); + } + + // TODO: shouldn't skip be adjusted for the 64 samples read afterwards? + + if ( count > 0 ) + { + RETURN_ERR( apu.skip( count ) ); + filter.clear(); + } + + // eliminate pop due to resampler + const int resampler_latency = 64; + sample_t buf [resampler_latency]; + return play_( resampler_latency, buf ); +} + +blargg_err_t Sfm_Emu::play_( int count, sample_t out [] ) +{ + if ( sample_rate() == native_sample_rate ) + return play_and_filter( count, out ); + + int remain = count; + while ( remain > 0 ) + { + remain -= resampler.read( &out [count - remain], remain ); + if ( remain > 0 ) + { + int n = resampler.buffer_free(); + RETURN_ERR( play_and_filter( n, resampler.buffer() ) ); + resampler.write( n ); + } + } + check( remain == 0 ); + return blargg_ok; +} + +blargg_err_t Sfm_Emu::hash_( Hash_Function& out ) const +{ + hash_sfm_file( file_begin(), file_size(), out ); + return blargg_ok; +} + diff --git a/Frameworks/GME/gme/Spc_Sfm.h b/Frameworks/GME/gme/Spc_Sfm.h new file mode 100644 index 000000000..d81318b02 --- /dev/null +++ b/Frameworks/GME/gme/Spc_Sfm.h @@ -0,0 +1,67 @@ +// Super Nintendo SFM music file emulator + +// Game_Music_Emu $vers +#ifndef SPC_SFM_H +#define SPC_SFM_H + +#include "Music_Emu.h" +#include "Snes_Spc.h" +#include "Spc_Filter.h" + +#include "Bml_Parser.h" + +#if GME_SPC_FAST_RESAMPLER + #include "Upsampler.h" + typedef Upsampler Spc_Emu_Resampler; +#else + #include "Fir_Resampler.h" + typedef Fir_Resampler<24> Spc_Emu_Resampler; +#endif + +class Sfm_Emu : public Music_Emu { +public: + // Minimum allowed file size + enum { sfm_min_file_size = 8 + 65536 + 128 }; + + // The Super Nintendo hardware samples at 32kHz. Other sample rates are + // handled by resampling the 32kHz output; emulation accuracy is not affected. + enum { native_sample_rate = 32000 }; + + // Disables annoying pseudo-surround effect some music uses + void disable_surround( bool disable = true ) { apu.disable_surround( disable ); } + + // Enables gaussian, cubic or sinc interpolation + void interpolation_level( int level = 0 ) { apu.interpolation_level( level ); } + + const Snes_Spc * get_apu() const; + + blargg_err_t hash_( Hash_Function& ) const; + + static gme_type_t static_type() { return gme_sfm_type; } + +// Implementation +public: + Sfm_Emu(); + ~Sfm_Emu(); + +protected: + virtual blargg_err_t load_mem_( byte const [], int ); + virtual blargg_err_t track_info_( track_info_t*, int track ) const; + virtual blargg_err_t set_sample_rate_( int ); + virtual blargg_err_t start_track_( int ); + virtual blargg_err_t play_( int, sample_t [] ); + virtual blargg_err_t skip_( int ); + virtual void mute_voices_( int ); + virtual void set_tempo_( double ); + +private: + Spc_Emu_Resampler resampler; + Spc_Filter filter; + Snes_Spc apu; + + Bml_Parser metadata; + + blargg_err_t play_and_filter( int count, sample_t out [] ); +}; + +#endif // SPC_SFM_H diff --git a/Frameworks/GME/gme/Track_Filter.cpp b/Frameworks/GME/gme/Track_Filter.cpp new file mode 100644 index 000000000..693af10f2 --- /dev/null +++ b/Frameworks/GME/gme/Track_Filter.cpp @@ -0,0 +1,293 @@ +// Game_Music_Emu $vers. http://www.slack.net/~ant/ + +#include "Track_Filter.h" + +/* Copyright (C) 2003-2008 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +#include "blargg_source.h" + +int const fade_block_size = 512; +int const fade_shift = 8; // fade ends with gain at 1.0 / (1 << fade_shift) +int const silence_threshold = 8; + +blargg_err_t Track_Filter::init( callbacks_t* c ) +{ + callbacks = c; + return buf.resize( buf_size ); +} + +void Track_Filter::clear_time_vars() +{ + emu_time = buf_remain; + out_time = 0; + silence_time = 0; + silence_count = 0; +} + +void Track_Filter::stop() +{ + emu_track_ended_ = true; + track_ended_ = true; + fade_start = indefinite_count; + fade_step = 1; + buf_remain = 0; + emu_error = NULL; + clear_time_vars(); +} + +Track_Filter::Track_Filter() : setup_() +{ + callbacks = NULL; + setup_.max_silence = indefinite_count; + silence_ignored_ = false; + stop(); +} + +Track_Filter::~Track_Filter() { } + +blargg_err_t Track_Filter::start_track() +{ + emu_error = NULL; + stop(); + + emu_track_ended_ = false; + track_ended_ = false; + + if ( !silence_ignored_ ) + { + // play until non-silence or end of track + while ( emu_time < setup_.max_initial ) + { + fill_buf(); + if ( buf_remain | emu_track_ended_ ) + break; + } + } + + clear_time_vars(); + return emu_error; +} + +void Track_Filter::end_track_if_error( blargg_err_t err ) +{ + if ( err ) + { + emu_error = err; + emu_track_ended_ = true; + } +} + +blargg_err_t Track_Filter::skip( int count ) +{ + emu_error = NULL; + out_time += count; + + // remove from silence and buf first + { + int n = min( count, silence_count ); + silence_count -= n; + count -= n; + + n = min( count, buf_remain ); + buf_remain -= n; + count -= n; + } + + if ( count && !emu_track_ended_ ) + { + emu_time += count; + silence_time = emu_time; // would otherwise be invalid + end_track_if_error( callbacks->skip_( count ) ); + } + + if ( !(silence_count | buf_remain) ) // caught up to emulator, so update track ended + track_ended_ |= emu_track_ended_; + + return emu_error; +} + +blargg_err_t Track_Filter::skip_( int count ) +{ + while ( count && !emu_track_ended_ ) + { + int n = buf_size; + if ( n > count ) + n = count; + count -= n; + RETURN_ERR( callbacks->play_( n, buf.begin() ) ); + } + return blargg_ok; +} + +// Fading + +void Track_Filter::set_fade( int start, int length ) +{ + fade_start = start; + fade_step = length / (fade_block_size * fade_shift); + if ( fade_step < 1 ) + fade_step = 1; +} + +bool Track_Filter::is_fading() const +{ + return out_time >= fade_start && fade_start != indefinite_count; +} + +// unit / pow( 2.0, (double) x / step ) +static int int_log( int x, int step, int unit ) +{ + int shift = x / step; + int fraction = (x - shift * step) * unit / step; + return ((unit - fraction) + (fraction >> 1)) >> shift; +} + +void Track_Filter::handle_fade( sample_t out [], int out_count ) +{ + for ( int i = 0; i < out_count; i += fade_block_size ) + { + int const shift = 14; + int const unit = 1 << shift; + int gain = int_log( (out_time + i - fade_start) / fade_block_size, + fade_step, unit ); + if ( gain < (unit >> fade_shift) ) + track_ended_ = emu_track_ended_ = true; + + sample_t* io = &out [i]; + for ( int count = min( fade_block_size, out_count - i ); count; --count ) + { + *io = sample_t ((*io * gain) >> shift); + ++io; + } + } +} + +// Silence detection + +void Track_Filter::emu_play( sample_t out [], int count ) +{ + emu_time += count; + if ( !emu_track_ended_ ) + end_track_if_error( callbacks->play_( count, out ) ); + else + memset( out, 0, count * sizeof *out ); +} + +// number of consecutive silent samples at end +static int count_silence( Track_Filter::sample_t begin [], int size ) +{ + Track_Filter::sample_t first = *begin; + *begin = silence_threshold * 2; // sentinel + Track_Filter::sample_t* p = begin + size; + while ( (unsigned) (*--p + silence_threshold) <= (unsigned) silence_threshold * 2 ) { } + *begin = first; + return size - (p - begin); +} + +// fill internal buffer and check it for silence +void Track_Filter::fill_buf() +{ + assert( !buf_remain ); + if ( !emu_track_ended_ ) + { + emu_play( buf.begin(), buf_size ); + int silence = count_silence( buf.begin(), buf_size ); + if ( silence < buf_size ) + { + silence_time = emu_time - silence; + buf_remain = buf_size; + return; + } + } + silence_count += buf_size; +} + +blargg_err_t Track_Filter::play( int out_count, sample_t out [] ) +{ + emu_error = NULL; + if ( track_ended_ ) + { + memset( out, 0, out_count * sizeof *out ); + } + else + { + assert( emu_time >= out_time ); + + // prints nifty graph of how far ahead we are when searching for silence + //dprintf( "%*s \n", int ((emu_time - out_time) * 7 / 44100), "*" ); + + // use any remaining silence samples + int pos = 0; + if ( silence_count ) + { + if ( !silence_ignored_ ) + { + // during a run of silence, run emulator at >=2x speed so it gets ahead + int ahead_time = setup_.lookahead * (out_time + out_count - silence_time) + + silence_time; + while ( emu_time < ahead_time && !(buf_remain | emu_track_ended_) ) + fill_buf(); + + // end track if sufficient silence has been found + if ( emu_time - silence_time > setup_.max_silence ) + { + track_ended_ = emu_track_ended_ = true; + silence_count = out_count; + buf_remain = 0; + } + } + + // fill from remaining silence + pos = min( silence_count, out_count ); + memset( out, 0, pos * sizeof *out ); + silence_count -= pos; + } + + // use any remaining samples from buffer + if ( buf_remain ) + { + int n = min( buf_remain, (int) (out_count - pos) ); + memcpy( out + pos, buf.begin() + (buf_size - buf_remain), n * sizeof *out ); + buf_remain -= n; + pos += n; + } + + // generate remaining samples normally + int remain = out_count - pos; + if ( remain ) + { + emu_play( out + pos, remain ); + track_ended_ |= emu_track_ended_; + + if ( silence_ignored_ && !is_fading() ) + { + // if left unupdated, ahead_time could become too large + silence_time = emu_time; + } + else + { + // check end for a new run of silence + int silence = count_silence( out + pos, remain ); + if ( silence < remain ) + silence_time = emu_time - silence; + + if ( emu_time - silence_time >= buf_size ) + fill_buf(); // cause silence detection on next play() + } + } + + if ( is_fading() ) + handle_fade( out, out_count ); + } + out_time += out_count; + return emu_error; +} diff --git a/Frameworks/GME/gme/Track_Filter.h b/Frameworks/GME/gme/Track_Filter.h new file mode 100644 index 000000000..e0389f0bc --- /dev/null +++ b/Frameworks/GME/gme/Track_Filter.h @@ -0,0 +1,105 @@ +// Removes silence from beginning of track, fades end of track. Also looks ahead +// for excessive silence, and if found, ends track. + +// Game_Music_Emu $vers +#ifndef TRACK_FILTER_H +#define TRACK_FILTER_H + +#include "blargg_common.h" + +class Track_Filter { +public: + typedef int sample_count_t; + typedef short sample_t; + + enum { indefinite_count = INT_MAX/2 + 1 }; + + struct callbacks_t { + // Samples may be stereo or mono + virtual blargg_err_t play_( int count, sample_t* out ) BLARGG_PURE( { return blargg_ok; } ) + virtual blargg_err_t skip_( int count ) BLARGG_PURE( { return blargg_ok; } ) + virtual ~callbacks_t() { } // avoids silly "non-virtual dtor" warning + }; + + // Initializes filter. Must be done once before using object. + blargg_err_t init( callbacks_t* ); + + struct setup_t { + sample_count_t max_initial; // maximum silence to strip from beginning of track + sample_count_t max_silence; // maximum silence in middle of track without it ending + int lookahead; // internal speed when looking ahead for silence (2=200% etc.) + }; + + // Gets/sets setup + setup_t const& setup() const { return setup_; } + void setup( setup_t const& s ) { setup_ = s; } + + // Disables automatic end-of-track detection and skipping of silence at beginning + void ignore_silence( bool disable = true ) { silence_ignored_ = disable; } + + // Clears state and skips initial silence in track + blargg_err_t start_track(); + + // Sets time that fade starts, and how long until track ends. + void set_fade( sample_count_t start, sample_count_t length ); + + // Generates n samples into buf + blargg_err_t play( int n, sample_t buf [] ); + + // Skips n samples + blargg_err_t skip( int n ); + + // Number of samples played/skipped since start_track() + int sample_count() const { return out_time; } + + // True if track ended. Causes are end of source samples, end of fade, + // or excessive silence. + bool track_ended() const { return track_ended_; } + + // Clears state + void stop(); + +// For use by callbacks + + // Sets internal "track ended" flag and stops generation of further source samples + void set_track_ended() { emu_track_ended_ = true; } + + // For use by skip_() callback + blargg_err_t skip_( int count ); + +// Implementation +public: + Track_Filter(); + ~Track_Filter(); + +private: + callbacks_t* callbacks; + setup_t setup_; + const char* emu_error; + bool silence_ignored_; + + // Timing + int out_time; // number of samples played since start of track + int emu_time; // number of samples emulator has generated since start of track + int emu_track_ended_; // emulator has reached end of track + volatile int track_ended_; + void clear_time_vars(); + void end_track_if_error( blargg_err_t ); + + // Fading + int fade_start; + int fade_step; + bool is_fading() const; + void handle_fade( sample_t out [], int count ); + + // Silence detection + int silence_time; // absolute number of samples where most recent silence began + int silence_count; // number of samples of silence to play before using buf + int buf_remain; // number of samples left in silence buffer + enum { buf_size = 2048 }; + blargg_vector buf; + void fill_buf(); + void emu_play( sample_t out [], int count ); +}; + +#endif diff --git a/Frameworks/GME/gme/Upsampler.cpp b/Frameworks/GME/gme/Upsampler.cpp new file mode 100644 index 000000000..5331176d0 --- /dev/null +++ b/Frameworks/GME/gme/Upsampler.cpp @@ -0,0 +1,73 @@ +// $package. http://www.slack.net/~ant/ + +#include "Upsampler.h" + +/* Copyright (C) 2004-2008 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +#include "blargg_source.h" + +int const shift = 15; +int const unit = 1 << shift; + +void Upsampler::clear_() +{ + pos = 0; + Resampler::clear_(); +} + +Upsampler::Upsampler() +{ + clear(); +} + +blargg_err_t Upsampler::set_rate_( double new_factor ) +{ + step = (int) (new_factor * unit + 0.5); + return Resampler::set_rate_( 1.0 / unit * step ); +} + +Resampler::sample_t const* Upsampler::resample_( sample_t** out_, sample_t const* out_end, + sample_t const in [], int in_size ) +{ + in_size -= write_offset; + if ( in_size > 0 ) + { + sample_t* BLARGG_RESTRICT out = *out_; + sample_t const* const in_end = in + in_size; + + int const step = this->step; + int pos = this->pos; + + do + { + #define INTERP( i, out )\ + {\ + int t = in [0 + i] * (unit - pos) + in [stereo + i] * pos;\ + out = t >> shift;\ + } + + int out_0; + INTERP( 0, out_0 ) + INTERP( 1, out [0] = out_0; out [1] ) + out += stereo; + + pos += step; + in += ((unsigned) pos >> shift) * stereo; + pos &= unit - 1; + } + while ( in < in_end && out < out_end ); + + this->pos = pos; + *out_ = out; + } + return in; +} diff --git a/Frameworks/GME/gme/Upsampler.h b/Frameworks/GME/gme/Upsampler.h new file mode 100644 index 000000000..7159d67d1 --- /dev/null +++ b/Frameworks/GME/gme/Upsampler.h @@ -0,0 +1,25 @@ +// Increases sampling rate using linear interpolation + +// $package +#ifndef UPSAMPLER_H +#define UPSAMPLER_H + +#include "Resampler.h" + +class Upsampler : public Resampler { +public: + Upsampler(); + +protected: + virtual blargg_err_t set_rate_( double ); + virtual void clear_(); + virtual sample_t const* resample_( sample_t**, sample_t const*, sample_t const [], int ); + +protected: + enum { stereo = 2 }; + enum { write_offset = 2 * stereo }; + int pos; + int step; +}; + +#endif diff --git a/Frameworks/GME/gme/Vgm_Core.cpp b/Frameworks/GME/gme/Vgm_Core.cpp new file mode 100644 index 000000000..7bfded31e --- /dev/null +++ b/Frameworks/GME/gme/Vgm_Core.cpp @@ -0,0 +1,2242 @@ +// Game_Music_Emu $vers. http://www.slack.net/~ant/ + +#include "Vgm_Core.h" + +#include "dac_control.h" + +#include "blargg_endian.h" +#include + +// Needed for OKIM6295 system detection +#include "Vgm_Emu.h" + +/* Copyright (C) 2003-2008 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +#include "blargg_source.h" + +int const stereo = 2; +int const fm_time_bits = 12; +int const blip_time_bits = 12; + +enum { + cmd_gg_stereo = 0x4F, + cmd_gg_stereo_2 = 0x3F, + cmd_psg = 0x50, + cmd_psg_2 = 0x30, + cmd_ym2413 = 0x51, + cmd_ym2413_2 = 0xA1, + cmd_ym2612_port0 = 0x52, + cmd_ym2612_2_port0 = 0xA2, + cmd_ym2612_port1 = 0x53, + cmd_ym2612_2_port1 = 0xA3, + cmd_ym2151 = 0x54, + cmd_ym2151_2 = 0xA4, + cmd_ym2203 = 0x55, + cmd_ym2203_2 = 0xA5, + cmd_ym2608_port0 = 0x56, + cmd_ym2608_2_port0 = 0xA6, + cmd_ym2608_port1 = 0x57, + cmd_ym2608_2_port1 = 0xA7, + cmd_ym2610_port0 = 0x58, + cmd_ym2610_2_port0 = 0xA8, + cmd_ym2610_port1 = 0x59, + cmd_ym2610_2_port1 = 0xA9, + cmd_ym3812 = 0x5A, + cmd_ym3812_2 = 0xAA, + cmd_ymz280b = 0x5D, + cmd_ymf262_port0 = 0x5E, + cmd_ymf262_2_port0 = 0xAE, + cmd_ymf262_port1 = 0x5F, + cmd_ymf262_2_port1 = 0xAF, + cmd_delay = 0x61, + cmd_delay_735 = 0x62, + cmd_delay_882 = 0x63, + cmd_byte_delay = 0x64, + cmd_end = 0x66, + cmd_data_block = 0x67, + cmd_ram_block = 0x68, + cmd_short_delay = 0x70, + cmd_pcm_delay = 0x80, + cmd_dacctl_setup = 0x90, + cmd_dacctl_data = 0x91, + cmd_dacctl_freq = 0x92, + cmd_dacctl_play = 0x93, + cmd_dacctl_stop = 0x94, + cmd_dacctl_playblock= 0x95, + cmd_ay8910 = 0xA0, + cmd_rf5c68 = 0xB0, + cmd_rf5c164 = 0xB1, + cmd_pwm = 0xB2, + cmd_okim6258_write = 0xB7, + cmd_okim6295_write = 0xB8, + cmd_huc6280_write = 0xB9, + cmd_k053260_write = 0xBA, + cmd_segapcm_write = 0xC0, + cmd_rf5c68_mem = 0xC1, + cmd_rf5c164_mem = 0xC2, + cmd_qsound_write = 0xC4, + cmd_k051649_write = 0xD2, + cmd_k054539_write = 0xD3, + cmd_c140 = 0xD4, + cmd_pcm_seek = 0xE0, + + rf5c68_ram_block = 0x01, + rf5c164_ram_block = 0x02, + + pcm_block_type = 0x00, + pcm_aux_block_type = 0x40, + rom_block_type = 0x80, + ram_block_type = 0xC0, + + rom_segapcm = 0x80, + rom_ym2608_deltat = 0x81, + rom_ym2610_adpcm = 0x82, + rom_ym2610_deltat = 0x83, + rom_ymz280b = 0x86, + rom_okim6295 = 0x8B, + rom_k054539 = 0x8C, + rom_c140 = 0x8D, + rom_k053260 = 0x8E, + rom_qsound = 0x8F, + + ram_rf5c68 = 0xC0, + ram_rf5c164 = 0xC1, + ram_nesapu = 0xC2, + + ym2612_dac_port = 0x2A, + ym2612_dac_pan_port = 0xB6 +}; + +inline int command_len( int command ) +{ + static byte const lens [0x10] = { + // 0 1 2 3 4 5 6 7 8 9 A B C D E F + 1,1,1,2,2,3,1,1,1,1,3,3,4,4,5,5 + }; + int len = lens [command >> 4]; + check( len != 1 ); + return len; +} + +int Vgm_Core::run_ym2151( int chip, int time ) +{ + return ym2151[!!chip].run_until( time ); +} + +int Vgm_Core::run_ym2203( int chip, int time ) +{ + return ym2203[!!chip].run_until( time ); +} + +int Vgm_Core::run_ym2413( int chip, int time ) +{ + return ym2413[!!chip].run_until( time ); +} + +int Vgm_Core::run_ym2612( int chip, int time ) +{ + return ym2612[!!chip].run_until( time ); +} + +int Vgm_Core::run_ym2610( int chip, int time ) +{ + return ym2610[!!chip].run_until( time ); +} + +int Vgm_Core::run_ym2608( int chip, int time ) +{ + return ym2608[!!chip].run_until( time ); +} + +int Vgm_Core::run_ym3812( int chip, int time ) +{ + return ym3812[!!chip].run_until( time ); +} + +int Vgm_Core::run_ymf262( int chip, int time ) +{ + return ymf262[!!chip].run_until( time ); +} + +int Vgm_Core::run_ymz280b( int time ) +{ + return ymz280b.run_until( time ); +} + +int Vgm_Core::run_c140( int time ) +{ + return c140.run_until( time ); +} + +int Vgm_Core::run_segapcm( int time ) +{ + return segapcm.run_until( time ); +} + +int Vgm_Core::run_rf5c68( int time ) +{ + return rf5c68.run_until( time ); +} + +int Vgm_Core::run_rf5c164( int time ) +{ + return rf5c164.run_until( time ); +} + +int Vgm_Core::run_pwm( int time ) +{ + return pwm.run_until( time ); +} + +int Vgm_Core::run_okim6258( int time ) +{ + return okim6258.run_until( time ); +} + +int Vgm_Core::run_okim6295( int chip, int time ) +{ + return okim6295[!!chip].run_until( time ); +} + +int Vgm_Core::run_k051649( int time ) +{ + return k051649.run_until( time ); +} + +int Vgm_Core::run_k053260( int time ) +{ + return k053260.run_until( time ); +} + +int Vgm_Core::run_k054539( int time ) +{ + return k054539.run_until( time ); +} + +int Vgm_Core::run_qsound( int chip, int time ) +{ + return qsound[!!chip].run_until( time ); +} + +/* Recursive fun starts here! */ +int Vgm_Core::run_dac_control( int time ) +{ + if (dac_control_recursion) return 1; + + ++dac_control_recursion; + for ( unsigned i = 0; i < DacCtrlUsed; i++ ) + { + int time_start = DacCtrlTime[DacCtrlMap[i]]; + if ( time > time_start ) + { + DacCtrlTime[DacCtrlMap[i]] = time; + daccontrol_update( dac_control [i], time_start, time - time_start ); + } + } + --dac_control_recursion; + + return 1; +} + +Vgm_Core::Vgm_Core() +{ + blip_buf[0] = stereo_buf[0].center(); + blip_buf[1] = blip_buf[0]; + has_looped = false; + DacCtrlUsed = 0; + dac_control = NULL; + memset( PCMBank, 0, sizeof( PCMBank ) ); + memset( &PCMTbl, 0, sizeof( PCMTbl ) ); + memset( DacCtrl, 0, sizeof( DacCtrl ) ); + memset( DacCtrlTime, 0, sizeof( DacCtrlTime ) ); +} + +Vgm_Core::~Vgm_Core() +{ + for (unsigned i = 0; i < DacCtrlUsed; i++) device_stop_daccontrol( dac_control [i] ); + if ( dac_control ) free( dac_control ); + for (unsigned i = 0; i < PCM_BANK_COUNT; i++) + { + if ( PCMBank [i].Bank ) free( PCMBank [i].Bank ); + if ( PCMBank [i].Data ) free( PCMBank [i].Data ); + } + if ( PCMTbl.Entries ) free( PCMTbl.Entries ); +} + +typedef unsigned int FUINT8; +typedef unsigned int FUINT16; + +void Vgm_Core::ReadPCMTable(unsigned DataSize, const byte* Data) +{ + byte ValSize; + unsigned TblSize; + + PCMTbl.ComprType = Data[0x00]; + PCMTbl.CmpSubType = Data[0x01]; + PCMTbl.BitDec = Data[0x02]; + PCMTbl.BitCmp = Data[0x03]; + PCMTbl.EntryCount = get_le16( Data + 0x04 ); + + ValSize = (PCMTbl.BitDec + 7) / 8; + TblSize = PCMTbl.EntryCount * ValSize; + + PCMTbl.Entries = realloc(PCMTbl.Entries, TblSize); + memcpy(PCMTbl.Entries, Data + 0x06, TblSize); +} + +bool Vgm_Core::DecompressDataBlk(VGM_PCM_DATA* Bank, unsigned DataSize, const byte* Data) +{ + UINT8 ComprType; + UINT8 BitDec; + FUINT8 BitCmp; + UINT8 CmpSubType; + UINT16 AddVal; + const UINT8* InPos; + const UINT8* InDataEnd; + UINT8* OutPos; + const UINT8* OutDataEnd; + FUINT16 InVal; + FUINT16 OutVal = 0; + FUINT8 ValSize; + FUINT8 InShift; + FUINT8 OutShift; + UINT8* Ent1B; + UINT16* Ent2B; + + // ReadBits Variables + FUINT8 BitsToRead; + FUINT8 BitReadVal; + FUINT8 InValB; + FUINT8 BitMask; + FUINT8 OutBit; + + // Variables for DPCM + UINT16 OutMask; + + ComprType = Data[0x00]; + Bank->DataSize = get_le32( Data + 0x01 ); + + switch(ComprType) + { + case 0x00: // n-Bit compression + BitDec = Data[0x05]; + BitCmp = Data[0x06]; + CmpSubType = Data[0x07]; + AddVal = get_le16( Data + 0x08 ); + + if (CmpSubType == 0x02) + { + Ent1B = (UINT8*)PCMTbl.Entries; + Ent2B = (UINT16*)PCMTbl.Entries; + if (! PCMTbl.EntryCount) + { + Bank->DataSize = 0x00; + return false; + } + else if (BitDec != PCMTbl.BitDec || BitCmp != PCMTbl.BitCmp) + { + Bank->DataSize = 0x00; + return false; + } + } + + ValSize = (BitDec + 7) / 8; + InPos = Data + 0x0A; + InDataEnd = Data + DataSize; + InShift = 0; + OutShift = BitDec - BitCmp; + OutDataEnd = Bank->Data + Bank->DataSize; + + for (OutPos = Bank->Data; OutPos < OutDataEnd && InPos < InDataEnd; OutPos += ValSize) + { + //InVal = ReadBits(Data, InPos, &InShift, BitCmp); + // inlined - is 30% faster + OutBit = 0x00; + InVal = 0x0000; + BitsToRead = BitCmp; + while(BitsToRead) + { + BitReadVal = (BitsToRead >= 8) ? 8 : BitsToRead; + BitsToRead -= BitReadVal; + BitMask = (1 << BitReadVal) - 1; + + InShift += BitReadVal; + InValB = (*InPos << InShift >> 8) & BitMask; + if (InShift >= 8) + { + InShift -= 8; + InPos ++; + if (InShift) + InValB |= (*InPos << InShift >> 8) & BitMask; + } + + InVal |= InValB << OutBit; + OutBit += BitReadVal; + } + + switch(CmpSubType) + { + case 0x00: // Copy + OutVal = InVal + AddVal; + break; + case 0x01: // Shift Left + OutVal = (InVal << OutShift) + AddVal; + break; + case 0x02: // Table + switch(ValSize) + { + case 0x01: + OutVal = Ent1B[InVal]; + break; + case 0x02: + OutVal = Ent2B[InVal]; + break; + } + break; + } + + //memcpy(OutPos, &OutVal, ValSize); + if (ValSize == 0x01) + *((UINT8*)OutPos) = (UINT8)OutVal; + else //if (ValSize == 0x02) + *((UINT16*)OutPos) = (UINT16)OutVal; + } + break; + case 0x01: // Delta-PCM + BitDec = Data[0x05]; + BitCmp = Data[0x06]; + OutVal = get_le16( Data + 0x08 ); + + Ent1B = (UINT8*)PCMTbl.Entries; + Ent2B = (UINT16*)PCMTbl.Entries; + if (! PCMTbl.EntryCount) + { + Bank->DataSize = 0x00; + return false; + } + else if (BitDec != PCMTbl.BitDec || BitCmp != PCMTbl.BitCmp) + { + Bank->DataSize = 0x00; + return false; + } + + ValSize = (BitDec + 7) / 8; + OutMask = (1 << BitDec) - 1; + InPos = Data + 0x0A; + InDataEnd = Data + DataSize; + InShift = 0; + OutShift = BitDec - BitCmp; + OutDataEnd = Bank->Data + Bank->DataSize; + AddVal = 0x0000; + + for (OutPos = Bank->Data; OutPos < OutDataEnd && InPos < InDataEnd; OutPos += ValSize) + { + //InVal = ReadBits(Data, InPos, &InShift, BitCmp); + // inlined - is 30% faster + OutBit = 0x00; + InVal = 0x0000; + BitsToRead = BitCmp; + while(BitsToRead) + { + BitReadVal = (BitsToRead >= 8) ? 8 : BitsToRead; + BitsToRead -= BitReadVal; + BitMask = (1 << BitReadVal) - 1; + + InShift += BitReadVal; + InValB = (*InPos << InShift >> 8) & BitMask; + if (InShift >= 8) + { + InShift -= 8; + InPos ++; + if (InShift) + InValB |= (*InPos << InShift >> 8) & BitMask; + } + + InVal |= InValB << OutBit; + OutBit += BitReadVal; + } + + switch(ValSize) + { + case 0x01: + AddVal = Ent1B[InVal]; + OutVal += AddVal; + OutVal &= OutMask; + *((UINT8*)OutPos) = (UINT8)OutVal; + break; + case 0x02: + AddVal = Ent2B[InVal]; + OutVal += AddVal; + OutVal &= OutMask; + *((UINT16*)OutPos) = (UINT16)OutVal; + break; + } + } + break; + default: + return false; + } + + return true; +} + +void Vgm_Core::AddPCMData(byte Type, unsigned DataSize, const byte* Data) +{ + unsigned CurBnk; + VGM_PCM_BANK* TempPCM; + VGM_PCM_DATA* TempBnk; + unsigned BankSize; + bool RetVal; + + + if ((Type & 0x3F) >= PCM_BANK_COUNT || has_looped) + return; + + if (Type == 0x7F) + { + ReadPCMTable( DataSize, Data ); + return; + } + + TempPCM = &PCMBank[Type & 0x3F]; + CurBnk = TempPCM->BankCount; + TempPCM->BankCount ++; + TempPCM->BnkPos ++; + if (TempPCM->BnkPos < TempPCM->BankCount) + return; // Speed hack (for restarting playback) + TempPCM->Bank = (VGM_PCM_DATA*)realloc(TempPCM->Bank, + sizeof(VGM_PCM_DATA) * TempPCM->BankCount); + + if (! (Type & 0x40)) + BankSize = DataSize; + else + BankSize = get_le32( Data + 1 ); + TempPCM->Data = ( byte * ) realloc(TempPCM->Data, TempPCM->DataSize + BankSize); + TempBnk = &TempPCM->Bank[CurBnk]; + TempBnk->DataStart = TempPCM->DataSize; + if (! (Type & 0x40)) + { + TempBnk->DataSize = DataSize; + TempBnk->Data = TempPCM->Data + TempBnk->DataStart; + memcpy(TempBnk->Data, Data, DataSize); + } + else + { + TempBnk->Data = TempPCM->Data + TempBnk->DataStart; + RetVal = DecompressDataBlk(TempBnk, DataSize, Data); + if (! RetVal) + { + TempBnk->Data = NULL; + TempBnk->DataSize = 0x00; + return; + } + } + TempPCM->DataSize += BankSize; +} + +const byte* Vgm_Core::GetPointerFromPCMBank(byte Type, unsigned DataPos) +{ + if (Type >= PCM_BANK_COUNT) + return NULL; + + if (DataPos >= PCMBank[Type].DataSize) + return NULL; + + return &PCMBank[Type].Data[DataPos]; +} + +void Vgm_Core::dac_control_grow(byte chip_id) +{ + for ( unsigned i = 0; i < DacCtrlUsed; i++ ) + { + if ( DacCtrlUsg [i] == chip_id ) + { + device_reset_daccontrol( dac_control [i] ); + return; + } + } + unsigned chip_mapped = DacCtrlUsed; + DacCtrlUsg [DacCtrlUsed++] = chip_id; + DacCtrlMap [chip_id] = chip_mapped; + dac_control = (void**) realloc( dac_control, DacCtrlUsed * sizeof(void*) ); + dac_control [chip_mapped] = device_start_daccontrol( vgm_rate, this ); + device_reset_daccontrol( dac_control [chip_mapped] ); +} + +extern "C" void chip_reg_write(void * context, UINT32 Sample, UINT8 ChipType, UINT8 ChipID, UINT8 Port, UINT8 Offset, UINT8 Data) +{ + Vgm_Core * core = (Vgm_Core *) context; + core->chip_reg_write(Sample, ChipType, ChipID, Port, Offset, Data); +} + +void Vgm_Core::chip_reg_write(unsigned Sample, byte ChipType, byte ChipID, byte Port, byte Offset, byte Data) +{ + run_dac_control( Sample ); /* Let's get recursive! */ + ChipID = !!ChipID; + switch (ChipType) + { + case 0x02: + switch (Port) + { + case 0: + if ( Offset == ym2612_dac_port ) + { + write_pcm( Sample, ChipID, Data ); + } + else if ( run_ym2612( ChipID, to_fm_time( Sample ) ) ) + { + if ( Offset == 0x2B ) + { + dac_disabled[ChipID] = (Data >> 7 & 1) - 1; + dac_amp[ChipID] |= dac_disabled[ChipID]; + } + ym2612[ChipID].write0( Offset, Data ); + } + break; + + case 1: + if ( run_ym2612( ChipID, to_fm_time( Sample ) ) ) + { + if ( Offset == ym2612_dac_pan_port ) + { + Blip_Buffer * blip_buf = NULL; + switch ( Data >> 6 ) + { + case 0: blip_buf = NULL; break; + case 1: blip_buf = stereo_buf[0].right(); break; + case 2: blip_buf = stereo_buf[0].left(); break; + case 3: blip_buf = stereo_buf[0].center(); break; + } + /*if ( this->blip_buf != blip_buf ) + { + blip_time_t blip_time = to_psg_time( vgm_time ); + if ( this->blip_buf ) pcm.offset_inline( blip_time, -dac_amp, this->blip_buf ); + if ( blip_buf ) pcm.offset_inline( blip_time, dac_amp, blip_buf ); + }*/ + this->blip_buf[ChipID] = blip_buf; + } + ym2612[ChipID].write1( Offset, Data ); + } + break; + } + break; + + case 0x11: + if ( run_pwm( to_fm_time( Sample ) ) ) + pwm.write( Port, ( ( Offset ) << 8 ) + Data ); + break; + + case 0x00: + psg[ChipID].write_data( to_psg_time( Sample ), Data ); + break; + + case 0x01: + if ( run_ym2413( ChipID, to_fm_time( Sample ) ) ) + ym2413[ChipID].write( Offset, Data ); + break; + + case 0x03: + if ( run_ym2151( ChipID, to_fm_time( Sample ) ) ) + ym2151[ChipID].write( Offset, Data ); + break; + + case 0x06: + if ( run_ym2203( ChipID, to_fm_time( Sample ) ) ) + ym2203[ChipID].write( Offset, Data ); + break; + + case 0x07: + if ( run_ym2608( ChipID, to_fm_time( Sample ) ) ) + { + switch (Port) + { + case 0: ym2608[ChipID].write0( Offset, Data ); break; + case 1: ym2608[ChipID].write1( Offset, Data ); break; + } + } + break; + + case 0x08: + if ( run_ym2610( ChipID, to_fm_time( Sample ) ) ) + { + switch (Port) + { + case 0: ym2610[ChipID].write0( Offset, Data ); break; + case 1: ym2610[ChipID].write1( Offset, Data ); break; + } + } + break; + + case 0x09: + if ( run_ym3812( ChipID, to_fm_time( Sample ) ) ) + ym3812[ChipID].write( Offset, Data ); + break; + + case 0x0C: + if ( run_ymf262( ChipID, to_fm_time( Sample ) ) ) + { + switch (Port) + { + case 0: ymf262[ChipID].write0( Offset, Data ); break; + case 1: ymf262[ChipID].write1( Offset, Data ); break; + } + } + break; + + case 0x0F: + if ( run_ymz280b( to_fm_time( Sample ) ) ) + ymz280b.write( Offset, Data ); + break; + + case 0x12: + ay[ChipID].write_addr( Offset ); + ay[ChipID].write_data( to_ay_time( Sample ), Data ); + break; + + case 0x17: + if ( run_okim6258( to_fm_time( Sample ) ) ) + okim6258.write( Offset, Data ); + break; + + case 0x18: + if ( run_okim6295( ChipID, to_fm_time( Sample ) ) ) + okim6295[ChipID].write( Offset, Data ); + break; + + case 0x19: + if ( run_k051649( to_fm_time( Sample ) ) ) + k051649.write( Port, Offset, Data ); + break; + + case 0x1A: + if ( run_k054539( to_fm_time( Sample ) ) ) + k054539.write( ( Port << 8 ) | Offset, Data ); + break; + + case 0x1B: + huc6280[ChipID].write_data( to_huc6280_time( Sample ), 0x800 + Offset, Data ); + break; + + case 0x1D: + if ( run_k053260( to_fm_time( Sample ) ) ) + k053260.write( Offset, Data ); + break; + + case 0x1F: + if ( run_qsound( ChipID, Sample ) ) + qsound[ ChipID ].write( Data, ( Port << 8 ) + Offset ); + break; + } +} + +void Vgm_Core::set_tempo( double t ) +{ + if ( file_begin() ) + { + vgm_rate = (int) (44100 * t + 0.5); + blip_time_factor = (int) ((double) + (1 << blip_time_bits) / vgm_rate * stereo_buf[0].center()->clock_rate() + 0.5); + blip_ay_time_factor = (int) ((double) + (1 << blip_time_bits) / vgm_rate * stereo_buf[1].center()->clock_rate() + 0.5); + blip_huc6280_time_factor = (int) ((double) + (1 << blip_time_bits) / vgm_rate * stereo_buf[2].center()->clock_rate() + 0.5); + //dprintf( "blip_time_factor: %ld\n", blip_time_factor ); + //dprintf( "vgm_rate: %ld\n", vgm_rate ); + // TODO: remove? calculates vgm_rate more accurately (above differs at most by one Hz only) + //blip_time_factor = (int) floor( double (1 << blip_time_bits) * psg_rate_ / 44100 / t + 0.5 ); + //vgm_rate = (int) floor( double (1 << blip_time_bits) * psg_rate_ / blip_time_factor + 0.5 ); + + fm_time_factor = 2 + (int) (fm_rate * (1 << fm_time_bits) / vgm_rate + 0.5); + } +} + +bool Vgm_Core::header_t::valid_tag() const +{ + return !memcmp( tag, "Vgm ", 4 ); +} + +int Vgm_Core::header_t::size() const +{ + unsigned int version = get_le32( this->version ); + unsigned int data_offset; + if ( version >= 0x150 ) + { + data_offset = get_le32( this->data_offset ); + if ( data_offset ) data_offset += offsetof( header_t, data_offset ); + } + else data_offset = 0x40; + unsigned expected_size = ( version > 0x150 ) ? ( ( version > 0x160 ) ? unsigned(size_max) : unsigned(size_151) ) : unsigned(size_min); + if ( expected_size > data_offset ) expected_size = data_offset ? (data_offset > unsigned(size_max) ? unsigned(size_max) : data_offset) : unsigned(size_min); + return expected_size; +} + +void Vgm_Core::header_t::cleanup() +{ + unsigned int version = get_le32( this->version ); + + if ( size() < size_max ) memset( ((byte*)this) + size(), 0, size_max - size() ); + + if ( version < 0x161 ) + { + memset( this->gbdmg_rate, 0, size_max - offsetof(header_t, gbdmg_rate) ); + } + + if ( version < 0x160 ) + { + volume_modifier = 0; + reserved = 0; + loop_base = 0; + } + + if ( version < 0x151 ) memset( this->rf5c68_rate, 0, size_max - size_min ); + + if ( version < 0x150 ) + { + set_le32( data_offset, size_min - offsetof(header_t, data_offset) ); + sn76489_flags = 0; + set_le32( segapcm_rate, 0 ); + set_le32( segapcm_reg, 0 ); + } + + if ( version < 0x110 ) + { + set_le16( noise_feedback, 0 ); + noise_width = 0; + unsigned int rate = get_le32( ym2413_rate ); + set_le32( ym2612_rate, rate ); + set_le32( ym2151_rate, rate ); + } + + if ( version < 0x101 ) + { + set_le32( frame_rate, 0 ); + } +} + +blargg_err_t Vgm_Core::load_mem_( byte const data [], int size ) +{ + assert( offsetof (header_t, rf5c68_rate) == header_t::size_min ); + assert( offsetof (header_t, extra_offset[4]) == header_t::size_max ); + + if ( size <= header_t::size_min ) + return blargg_err_file_type; + + memcpy( &_header, data, header_t::size_min ); + + header_t const& h = header(); + + if ( !h.valid_tag() ) + return blargg_err_file_type; + + int version = get_le32( h.version ); + + check( version < 0x100 ); + + if ( version > 0x150 ) + { + if ( size < header().size() ) + return "Invalid header"; + + memcpy( &_header.rf5c68_rate, data + offsetof (header_t, rf5c68_rate), header().size() - header_t::size_min ); + } + + _header.cleanup(); + + // Get loop + loop_begin = file_end(); + if ( get_le32( h.loop_offset ) ) + loop_begin = &data [get_le32( h.loop_offset ) + offsetof (header_t,loop_offset)]; + + // PSG rate + int psg_rate = get_le32( h.psg_rate ) & 0x3FFFFFFF; + if ( !psg_rate ) + psg_rate = 3579545; + stereo_buf[0].clock_rate( psg_rate ); + + int ay_rate = get_le32( h.ay8910_rate ) & 0xBFFFFFFF; + if ( !ay_rate ) + ay_rate = 2000000; + stereo_buf[1].clock_rate( ay_rate * 2 ); + ay[0].set_type( (Ay_Apu::Ay_Apu_Type) header().ay8910_type ); + ay[1].set_type( (Ay_Apu::Ay_Apu_Type) header().ay8910_type ); + + int huc6280_rate = get_le32( h.huc6280_rate ) & 0xBFFFFFFF; + if ( !huc6280_rate ) + huc6280_rate = 3579545; + stereo_buf[2].clock_rate( huc6280_rate * 2 ); + + // Disable FM + fm_rate = 0; + ymz280b.enable( false ); + ymf262[0].enable( false ); + ymf262[1].enable( false ); + ym3812[0].enable( false ); + ym3812[1].enable( false ); + ym2612[0].enable( false ); + ym2612[1].enable( false ); + ym2610[0].enable( false ); + ym2610[1].enable( false ); + ym2608[0].enable( false ); + ym2608[1].enable( false ); + ym2413[0].enable( false ); + ym2413[1].enable( false ); + ym2203[0].enable( false ); + ym2203[1].enable( false ); + ym2151[0].enable( false ); + ym2151[1].enable( false ); + c140.enable( false ); + segapcm.enable( false ); + rf5c68.enable( false ); + rf5c164.enable( false ); + pwm.enable( false ); + okim6258.enable( false ); + okim6295[0].enable( false ); + okim6295[1].enable( false ); + k051649.enable( false ); + k053260.enable( false ); + k054539.enable( false ); + qsound[0].enable( false ); + qsound[1].enable( false ); + + set_tempo( 1 ); + + return blargg_ok; +} + +// Update pre-1.10 header FM rates by scanning commands +void Vgm_Core::update_fm_rates( int* ym2151_rate, int* ym2413_rate, int* ym2612_rate ) const +{ + byte const* p = file_begin() + header().size(); + int data_offset = get_le32( header().data_offset ); + check( data_offset ); + if ( data_offset ) + p += data_offset + offsetof( header_t, data_offset ) - header().size(); + while ( p < file_end() ) + { + switch ( *p ) + { + case cmd_end: + return; + + case cmd_psg: + case cmd_byte_delay: + p += 2; + break; + + case cmd_delay: + p += 3; + break; + + case cmd_data_block: + p += 7 + get_le32( p + 3 ); + break; + + case cmd_ram_block: + p += 12; + break; + + case cmd_ym2413: + *ym2151_rate = 0; + *ym2612_rate = 0; + return; + + case cmd_ym2612_port0: + case cmd_ym2612_port1: + *ym2612_rate = *ym2413_rate; + *ym2413_rate = 0; + *ym2151_rate = 0; + return; + + case cmd_ym2151: + *ym2151_rate = *ym2413_rate; + *ym2413_rate = 0; + *ym2612_rate = 0; + return; + + default: + p += command_len( *p ); + } + } +} + +blargg_err_t Vgm_Core::init_chips( double* rate, bool reinit ) +{ + int ymz280b_rate = get_le32( header().ymz280b_rate ) & 0xBFFFFFFF; + int ymf262_rate = get_le32( header().ymf262_rate ) & 0xBFFFFFFF; + int ym3812_rate = get_le32( header().ym3812_rate ) & 0xBFFFFFFF; + int ym2612_rate = get_le32( header().ym2612_rate ) & 0xBFFFFFFF; + int ym2610_rate = get_le32( header().ym2610_rate ) & 0x3FFFFFFF; + int ym2608_rate = get_le32( header().ym2608_rate ) & 0x3FFFFFFF; + int ym2413_rate = get_le32( header().ym2413_rate ) & 0xBFFFFFFF; + int ym2203_rate = get_le32( header().ym2203_rate ) & 0xBFFFFFFF; + int ym2151_rate = get_le32( header().ym2151_rate ) & 0xBFFFFFFF; + int c140_rate = get_le32( header().c140_rate ) & 0xBFFFFFFF; + int segapcm_rate = get_le32( header().segapcm_rate ) & 0xBFFFFFFF; + int rf5c68_rate = get_le32( header().rf5c68_rate ) & 0xBFFFFFFF; + int rf5c164_rate = get_le32( header().rf5c164_rate ) & 0xBFFFFFFF; + int pwm_rate = get_le32( header().pwm_rate ) & 0xBFFFFFFF; + int okim6258_rate = get_le32( header().okim6258_rate ) & 0xBFFFFFFF; + int okim6295_rate = get_le32( header().okim6295_rate ) & 0xBFFFFFFF; + int k051649_rate = get_le32( header().k051649_rate ) & 0xBFFFFFFF; + int k053260_rate = get_le32( header().k053260_rate ) & 0xBFFFFFFF; + int k054539_rate = get_le32( header().k054539_rate ) & 0xBFFFFFFF; + int qsound_rate = get_le32( header().qsound_rate ) & 0xBFFFFFFF; + if ( ym2413_rate && get_le32( header().version ) < 0x110 ) + update_fm_rates( &ym2151_rate, &ym2413_rate, &ym2612_rate ); + + *rate = vgm_rate; + + if ( ymf262_rate ) + { + bool dual_chip = !!(header().ymf262_rate[3] & 0x40); + double gain = dual_chip ? 0.5 : 1.0; + double fm_rate = ymf262_rate / 288.0; + int result; + if ( !reinit ) + { + result = ymf262[0].set_rate( fm_rate, ymf262_rate ); + CHECK_ALLOC( !result ); + } + RETURN_ERR( ymf262[0].setup( fm_rate / vgm_rate, 0.85, gain ) ); + ymf262[0].enable(); + if ( dual_chip ) + { + if ( !reinit ) + { + result = ymf262[1].set_rate( fm_rate, ymf262_rate ); + CHECK_ALLOC( !result ); + } + RETURN_ERR( ymf262[1].setup( fm_rate / vgm_rate, 0.85, gain ) ); + ymf262[1].enable(); + } + } + if ( ym3812_rate ) + { + bool dual_chip = !!(header().ym3812_rate[3] & 0x40); + double gain = dual_chip ? 0.5 : 1.0; + double fm_rate = ym3812_rate / 72.0; + int result; + if ( !reinit ) + { + result = ym3812[0].set_rate( fm_rate, ym3812_rate ); + CHECK_ALLOC( !result ); + } + RETURN_ERR( ym3812[0].setup( fm_rate / vgm_rate, 0.85, gain ) ); + ym3812[0].enable(); + if ( dual_chip ) + { + if ( !reinit ) + { + result = ym3812[1].set_rate( fm_rate, ym3812_rate ); + CHECK_ALLOC( !result ); + } + RETURN_ERR( ym3812[1].setup( fm_rate / vgm_rate, 0.85, gain ) ); + ym3812[1].enable(); + } + } + if ( ym2612_rate ) + { + bool dual_chip = !!(header().ym2612_rate[3] & 0x40); + double gain = dual_chip ? 0.5 : 1.0; + double fm_rate = ym2612_rate / 144.0; + if ( !reinit ) + { + RETURN_ERR( ym2612[0].set_rate( fm_rate, ym2612_rate ) ); + } + RETURN_ERR( ym2612[0].setup( fm_rate / vgm_rate, 0.85, gain ) ); + ym2612[0].enable(); + if ( dual_chip ) + { + if ( !reinit ) + { + RETURN_ERR( ym2612[1].set_rate( fm_rate, ym2612_rate ) ); + } + RETURN_ERR( ym2612[1].setup( fm_rate / vgm_rate, 0.85, gain ) ); + ym2612[1].enable(); + } + } + if ( ym2610_rate ) + { + bool dual_chip = !!(header().ym2610_rate[3] & 0x40); + bool is_2610b = !!(header().ym2610_rate[3] & 0x80); + double gain = dual_chip ? 0.5 : 1.0; + double fm_rate = ym2610_rate / 72.0; + int result; + if ( !reinit ) + { + result = ym2610[0].set_rate( fm_rate, ym2610_rate, is_2610b ); + CHECK_ALLOC( !result ); + } + RETURN_ERR( ym2610[0].setup( fm_rate / vgm_rate, 0.85, gain ) ); + ym2610[0].enable(); + if ( dual_chip ) + { + if ( !reinit ) + { + result = ym2610[1].set_rate( fm_rate, ym2610_rate, is_2610b ); + CHECK_ALLOC( !result ); + } + RETURN_ERR( ym2610[1].setup( fm_rate / vgm_rate, 0.85, gain ) ); + ym2610[1].enable(); + } + } + if ( ym2608_rate ) + { + bool dual_chip = !!(header().ym2610_rate[3] & 0x40); + double gain = dual_chip ? 1.0 : 2.0; + double fm_rate = ym2608_rate / 72.0; + int result; + if ( !reinit ) + { + result = ym2608[0].set_rate( fm_rate, ym2608_rate ); + CHECK_ALLOC( !result ); + } + RETURN_ERR( ym2608[0].setup( fm_rate / vgm_rate, 0.85, gain ) ); + ym2608[0].enable(); + if ( dual_chip ) + { + if ( !reinit ) + { + result = ym2608[1].set_rate( fm_rate, ym2608_rate ); + CHECK_ALLOC( !result ); + } + RETURN_ERR( ym2608[1].setup( fm_rate / vgm_rate, 0.85, gain ) ); + ym2608[1].enable(); + } + } + if ( ym2413_rate ) + { + bool dual_chip = !!(header().ym2413_rate[3] & 0x40); + double gain = dual_chip ? 0.5 : 1.0; + double fm_rate = ym2413_rate / 72.0; + int result; + if ( !reinit ) + { + result = ym2413[0].set_rate( fm_rate, ym2413_rate ); + if ( result == 2 ) + return "YM2413 FM sound not supported"; + CHECK_ALLOC( !result ); + } + RETURN_ERR( ym2413[0].setup( fm_rate / vgm_rate, 0.85, gain ) ); + ym2413[0].enable(); + if ( dual_chip ) + { + if ( !reinit ) + { + result = ym2413[1].set_rate( fm_rate, ym2413_rate ); + CHECK_ALLOC( !result ); + } + RETURN_ERR( ym2413[1].setup( fm_rate / vgm_rate, 0.85, gain ) ); + ym2413[1].enable(); + } + } + if ( ym2151_rate ) + { + bool dual_chip = !!(header().ym2151_rate[3] & 0x40); + double gain = dual_chip ? 0.5 : 1.0; + double fm_rate = ym2151_rate / 64.0; + int result; + if ( !reinit ) + { + result = ym2151[0].set_rate( fm_rate, ym2151_rate ); + CHECK_ALLOC( !result ); + } + RETURN_ERR( ym2151[0].setup( fm_rate / vgm_rate, 0.85, gain ) ); + ym2151[0].enable(); + if ( dual_chip ) + { + if ( !reinit ) + { + result = ym2151[1].set_rate( fm_rate, ym2151_rate ); + CHECK_ALLOC( !result ); + } + RETURN_ERR( ym2151[1].setup( fm_rate / vgm_rate, 0.85, gain ) ); + ym2151[1].enable(); + } + } + if ( ym2203_rate ) + { + bool dual_chip = !!(header().ym2203_rate[3] & 0x40); + double gain = dual_chip ? 0.5 : 1.0; + double fm_rate = ym2203_rate / 72.0; + int result; + if ( !reinit ) + { + result = ym2203[0].set_rate( fm_rate, ym2203_rate ); + CHECK_ALLOC ( !result ); + } + RETURN_ERR( ym2203[0].setup( fm_rate / vgm_rate, 0.85, gain ) ); + ym2203[0].enable(); + if ( dual_chip ) + { + if ( !reinit ) + { + result = ym2203[1].set_rate( fm_rate, ym2203_rate ); + CHECK_ALLOC ( !result ); + } + RETURN_ERR( ym2203[1].setup( fm_rate / vgm_rate, 0.85, gain ) ); + ym2203[1].enable(); + } + } + + if ( segapcm_rate ) + { + double pcm_rate = segapcm_rate / 128.0; + if ( !reinit ) + { + int result = segapcm.set_rate( get_le32( header().segapcm_reg ) ); + CHECK_ALLOC( !result ); + } + RETURN_ERR( segapcm.setup( pcm_rate / vgm_rate, 0.85, 1.5 ) ); + segapcm.enable(); + } + if ( rf5c68_rate ) + { + double pcm_rate = rf5c68_rate / 384.0; + if ( !reinit ) + { + int result = rf5c68.set_rate(); + CHECK_ALLOC( !result ); + } + RETURN_ERR( rf5c68.setup( pcm_rate / vgm_rate, 0.85, 0.6875 ) ); + rf5c68.enable(); + } + if ( rf5c164_rate ) + { + double pcm_rate = rf5c164_rate / 384.0; + if ( !reinit ) + { + int result = rf5c164.set_rate( rf5c164_rate ); + CHECK_ALLOC( !result ); + } + RETURN_ERR( rf5c164.setup( pcm_rate / vgm_rate, 0.85, 0.5 ) ); + rf5c164.enable(); + } + if ( pwm_rate ) + { + double pcm_rate = 22020.0; + if ( !reinit ) + { + int result = pwm.set_rate( pwm_rate ); + CHECK_ALLOC( !result ); + } + RETURN_ERR( pwm.setup( pcm_rate / vgm_rate, 0.85, 0.875 ) ); + pwm.enable(); + } + if ( okim6258_rate ) + { + if ( !reinit ) + { + okim6258_hz = okim6258.set_rate( okim6258_rate, header().okim6258_flags & 0x03, ( header().okim6258_flags & 0x04 ) >> 2, ( header().okim6258_flags & 0x08 ) >> 3 ); + CHECK_ALLOC( okim6258_hz ); + } + RETURN_ERR( okim6258.setup( (double)okim6258_hz / vgm_rate, 0.85, 1.0 ) ); + okim6258.enable(); + } + if ( okim6295_rate ) + { + // moo + Mem_File_Reader rdr( file_begin(), file_size() ); + Music_Emu * vgm = gme_vgm_type->new_info(); + track_info_t info; + vgm->load( rdr ); + vgm->track_info( &info, 0 ); + delete vgm; + + bool is_cp_system = strncmp( info.system, "CP", 2 ) == 0; + bool dual_chip = !!( header().okim6295_rate[3] & 0x40 ); + double gain = is_cp_system ? 0.4296875 : 1.0; + if ( dual_chip ) gain *= 0.5; + if ( !reinit ) + { + okim6295_hz = okim6295[0].set_rate( okim6295_rate ); + CHECK_ALLOC( okim6295_hz ); + } + RETURN_ERR( okim6295[0].setup( (double)okim6295_hz / vgm_rate, 0.85, gain ) ); + okim6295[0].enable(); + if ( dual_chip ) + { + if ( !reinit ) + { + int result = okim6295[1].set_rate( okim6295_rate ); + CHECK_ALLOC( result ); + } + RETURN_ERR( okim6295[1].setup( (double)okim6295_hz / vgm_rate, 0.85, gain ) ); + okim6295[1].enable(); + } + } + if ( c140_rate ) + { + double pcm_rate = c140_rate; + if ( !reinit ) + { + int result = c140.set_rate( header().c140_type, c140_rate, c140_rate ); + CHECK_ALLOC( !result ); + } + RETURN_ERR( c140.setup( pcm_rate / vgm_rate, 0.85, 1.0 ) ); + c140.enable(); + } + if ( k051649_rate ) + { + double pcm_rate = k051649_rate / 16.0; + if ( !reinit ) + { + int result = k051649.set_rate( k051649_rate ); + CHECK_ALLOC( !result ); + } + RETURN_ERR( k051649.setup( pcm_rate / vgm_rate, 0.85, 1.0 ) ); + k051649.enable(); + } + if ( k053260_rate ) + { + double pcm_rate = k053260_rate / 32.0; + if ( !reinit ) + { + int result = k053260.set_rate( k053260_rate ); + CHECK_ALLOC( !result ); + } + RETURN_ERR( k053260.setup( pcm_rate / vgm_rate, 0.85, 1.0 ) ); + k053260.enable(); + } + if ( k054539_rate ) + { + double pcm_rate = k054539_rate; + if ( !reinit ) + { + int result = k054539.set_rate( k054539_rate, header().k054539_flags ); + CHECK_ALLOC( !result ); + } + RETURN_ERR( k054539.setup( pcm_rate / vgm_rate, 0.85, 1.0 ) ); + k054539.enable(); + } + if ( ymz280b_rate ) + { + if ( !reinit ) + { + ymz280b_hz = ymz280b.set_rate( ymz280b_rate ); + CHECK_ALLOC( ymz280b_hz ); + } + RETURN_ERR( ymz280b.setup( (double)ymz280b_hz / vgm_rate, 0.85, 0.59375 ) ); + ymz280b.enable(); + } + if ( qsound_rate ) + { + double pcm_rate = (double)qsound_rate / 166.0; + if ( !reinit ) + { + int result = qsound[0].set_rate( qsound_rate ); + CHECK_ALLOC( result ); + } + qsound[0].set_sample_rate( vgm_rate ); + RETURN_ERR( qsound[0].setup( 1.0, 0.85, 1.0 ) ); + qsound[0].enable(); + } + + fm_rate = *rate; + + return blargg_ok; +} + +void Vgm_Core::start_track() +{ + psg[0].reset( get_le16( header().noise_feedback ), header().noise_width ); + psg[1].reset( get_le16( header().noise_feedback ), header().noise_width ); + ay[0].reset(); + ay[1].reset(); + huc6280[0].reset(); + huc6280[1].reset(); + + blip_buf[0] = stereo_buf[0].center(); + blip_buf[1] = blip_buf[0]; + + dac_disabled[0] = -1; + dac_disabled[1] = -1; + pos = file_begin() + header().size(); + dac_amp[0] = -1; + dac_amp[1] = -1; + vgm_time = 0; + int data_offset = get_le32( header().data_offset ); + check( data_offset ); + if ( data_offset ) + pos += data_offset + offsetof (header_t,data_offset) - header().size(); + pcm_pos = pos; + + if ( uses_fm() ) + { + if ( rf5c68.enabled() ) + rf5c68.reset(); + + if ( rf5c164.enabled() ) + rf5c164.reset(); + + if ( segapcm.enabled() ) + segapcm.reset(); + + if ( pwm.enabled() ) + pwm.reset(); + + if ( okim6258.enabled() ) + okim6258.reset(); + + if ( okim6295[0].enabled() ) + okim6295[0].reset(); + + if ( okim6295[1].enabled() ) + okim6295[1].reset(); + + if ( k051649.enabled() ) + k051649.reset(); + + if ( k053260.enabled() ) + k053260.reset(); + + if ( k054539.enabled() ) + k054539.reset(); + + if ( c140.enabled() ) + c140.reset(); + + if ( ym2151[0].enabled() ) + ym2151[0].reset(); + + if ( ym2151[1].enabled() ) + ym2151[1].reset(); + + if ( ym2203[0].enabled() ) + ym2203[0].reset(); + + if ( ym2203[1].enabled() ) + ym2203[1].reset(); + + if ( ym2413[0].enabled() ) + ym2413[0].reset(); + + if ( ym2413[1].enabled() ) + ym2413[1].reset(); + + if ( ym2612[0].enabled() ) + ym2612[0].reset(); + + if ( ym2612[1].enabled() ) + ym2612[1].reset(); + + if ( ym2610[0].enabled() ) + ym2610[0].reset(); + + if ( ym2610[1].enabled() ) + ym2610[1].reset(); + + if ( ym2608[0].enabled() ) + ym2608[0].reset(); + + if ( ym2608[1].enabled() ) + ym2608[0].reset(); + + if ( ym3812[0].enabled() ) + ym3812[0].reset(); + + if ( ym3812[1].enabled() ) + ym3812[1].reset(); + + if ( ymf262[0].enabled() ) + ymf262[0].reset(); + + if ( ymf262[1].enabled() ) + ymf262[1].reset(); + + if ( ymz280b.enabled() ) + ymz280b.reset(); + + if ( qsound[0].enabled() ) + qsound[0].reset(); + + if ( qsound[1].enabled() ) + qsound[1].reset(); + + stereo_buf[0].clear(); + stereo_buf[1].clear(); + stereo_buf[2].clear(); + } + + for ( unsigned i = 0; i < DacCtrlUsed; i++ ) + { + device_reset_daccontrol( dac_control [i] ); + DacCtrlTime[DacCtrlMap[i]] = 0; + } + + for ( unsigned i = 0; i < PCM_BANK_COUNT; i++) + { + // reset PCM Bank, but not the data + // (this way I don't need to decompress the data again when restarting) + PCMBank [i].DataPos = 0; + PCMBank [i].BnkPos = 0; + } + PCMTbl.EntryCount = 0; + + fm_time_offset = 0; + ay_time_offset = 0; + huc6280_time_offset = 0; + + dac_control_recursion = 0; +} + +inline Vgm_Core::fm_time_t Vgm_Core::to_fm_time( vgm_time_t t ) const +{ + return (t * fm_time_factor + fm_time_offset) >> fm_time_bits; +} + +inline blip_time_t Vgm_Core::to_psg_time( vgm_time_t t ) const +{ + return (t * blip_time_factor) >> blip_time_bits; +} + +inline blip_time_t Vgm_Core::to_ay_time( vgm_time_t t ) const +{ + return (t * blip_ay_time_factor) >> blip_time_bits; +} + +inline blip_time_t Vgm_Core::to_huc6280_time( vgm_time_t t ) const +{ + return (t * blip_huc6280_time_factor) >> blip_time_bits; +} + +void Vgm_Core::write_pcm( vgm_time_t vgm_time, int chip, int amp ) +{ + chip = !!chip; + if ( blip_buf[chip] ) + { + check( amp >= 0 ); + blip_time_t blip_time = to_psg_time( vgm_time ); + int old = dac_amp[chip]; + int delta = amp - old; + dac_amp[chip] = amp; + blip_buf[chip]->set_modified(); + if ( old >= 0 ) // first write is ignored, to avoid click + pcm.offset_inline( blip_time, delta, blip_buf[chip] ); + else + dac_amp[chip] |= dac_disabled[chip]; + } +} + +blip_time_t Vgm_Core::run( vgm_time_t end_time ) +{ + vgm_time_t vgm_time = this->vgm_time; + vgm_time_t vgm_loop_time = ~0; + int ChipID; + byte const* pos = this->pos; + if ( pos > file_end() ) + set_warning( "Stream lacked end event" ); + + while ( vgm_time < end_time && pos < file_end() ) + { + // TODO: be sure there are enough bytes left in stream for particular command + // so we don't read past end + switch ( *pos++ ) + { + case cmd_end: + if ( vgm_loop_time == ~0 ) vgm_loop_time = vgm_time; + else if ( vgm_loop_time == vgm_time ) loop_begin = file_end(); // XXX some files may loop forever on a region without any delay commands + pos = loop_begin; // if not looped, loop_begin == file_end() + if ( pos != file_end() ) has_looped = true; + break; + + case cmd_delay_735: + vgm_time += 735; + break; + + case cmd_delay_882: + vgm_time += 882; + break; + + case cmd_gg_stereo: + psg[0].write_ggstereo( to_psg_time( vgm_time ), *pos++ ); + break; + + case cmd_gg_stereo_2: + psg[1].write_ggstereo( to_psg_time( vgm_time ), *pos++ ); + break; + + case cmd_psg: + psg[0].write_data( to_psg_time( vgm_time ), *pos++ ); + break; + + case cmd_psg_2: + psg[1].write_data( to_psg_time( vgm_time ), *pos++ ); + break; + + case cmd_ay8910: + ChipID = !!(pos [0] & 0x80); + chip_reg_write( vgm_time, 0x12, ChipID, 0x00, pos [0] & 0x7F, pos [1] ); + pos += 2; + break; + + case cmd_delay: + vgm_time += pos [1] * 0x100 + pos [0]; + pos += 2; + break; + + case cmd_byte_delay: + vgm_time += *pos++; + break; + + case cmd_segapcm_write: + if ( get_le32( header().segapcm_rate ) > 0 ) + if ( run_segapcm( to_fm_time( vgm_time ) ) ) + segapcm.write( get_le16( pos ), pos [2] ); + pos += 3; + break; + + case cmd_rf5c68: + if ( run_rf5c68( to_fm_time( vgm_time ) ) ) + rf5c68.write( pos [0], pos [1] ); + pos += 2; + break; + + case cmd_rf5c68_mem: + if ( run_rf5c68( to_fm_time( vgm_time ) ) ) + rf5c68.write_mem( get_le16( pos ), pos [2] ); + pos += 3; + break; + + case cmd_rf5c164: + if ( run_rf5c164( to_fm_time( vgm_time ) ) ) + rf5c164.write( pos [0], pos [1] ); + pos += 2; + break; + + case cmd_rf5c164_mem: + if ( run_rf5c164( to_fm_time( vgm_time ) ) ) + rf5c164.write_mem( get_le16( pos ), pos [2] ); + pos += 3; + break; + + case cmd_pwm: + chip_reg_write( vgm_time, 0x11, 0x00, pos [0] >> 4, pos [0] & 0x0F, pos [1] ); + pos += 2; + break; + + case cmd_c140: + if ( get_le32( header().c140_rate ) > 0 ) + if ( run_c140( to_fm_time( vgm_time ) ) ) + c140.write( get_be16( pos ), pos [2] ); + pos += 3; + break; + + case cmd_ym2151: + chip_reg_write( vgm_time, 0x03, 0x00, 0x00, pos [0], pos [1] ); + pos += 2; + break; + + case cmd_ym2151_2: + chip_reg_write( vgm_time, 0x03, 0x01, 0x00, pos [0], pos [1] ); + pos += 2; + break; + + case cmd_ym2203: + chip_reg_write( vgm_time, 0x06, 0x00, 0x00, pos [0], pos [1] ); + pos += 2; + break; + + case cmd_ym2203_2: + chip_reg_write( vgm_time, 0x06, 0x01, 0x00, pos [0], pos [1] ); + pos += 2; + break; + + case cmd_ym2413: + chip_reg_write( vgm_time, 0x01, 0x00, 0x00, pos [0], pos [1] ); + pos += 2; + break; + + case cmd_ym2413_2: + chip_reg_write( vgm_time, 0x01, 0x01, 0x00, pos [0], pos [1] ); + pos += 2; + break; + + case cmd_ym3812: + chip_reg_write( vgm_time, 0x09, 0x00, 0x00, pos [0], pos [1] ); + pos += 2; + break; + + case cmd_ym3812_2: + chip_reg_write( vgm_time, 0x09, 0x01, 0x00, pos [0], pos [1] ); + pos += 2; + break; + + case cmd_ymf262_port0: + chip_reg_write( vgm_time, 0x0C, 0x00, 0x00, pos [0], pos [1] ); + pos += 2; + break; + + case cmd_ymf262_2_port0: + chip_reg_write( vgm_time, 0x0C, 0x01, 0x00, pos [0], pos [1] ); + pos += 2; + break; + + case cmd_ymf262_port1: + chip_reg_write( vgm_time, 0x0C, 0x00, 0x01, pos [0], pos [1] ); + pos += 2; + break; + + case cmd_ymf262_2_port1: + chip_reg_write( vgm_time, 0x0C, 0x01, 0x01, pos [0], pos [1] ); + pos += 2; + break; + + case cmd_ymz280b: + chip_reg_write( vgm_time, 0x0F, 0x00, 0x00, pos [0], pos [1] ); + pos += 2; + break; + + case cmd_ym2612_port0: + chip_reg_write( vgm_time, 0x02, 0x00, 0x00, pos [0], pos [1] ); + pos += 2; + break; + + case cmd_ym2612_2_port0: + chip_reg_write( vgm_time, 0x02, 0x01, 0x00, pos [0], pos [1] ); + pos += 2; + break; + + case cmd_ym2612_port1: + chip_reg_write( vgm_time, 0x02, 0x00, 0x01, pos [0], pos [1] ); + pos += 2; + break; + + case cmd_ym2612_2_port1: + chip_reg_write( vgm_time, 0x02, 0x01, 0x01, pos [0], pos [1] ); + pos += 2; + break; + + case cmd_ym2610_port0: + chip_reg_write( vgm_time, 0x08, 0x00, 0x00, pos [0], pos [1] ); + pos += 2; + break; + + case cmd_ym2610_2_port0: + chip_reg_write( vgm_time, 0x08, 0x01, 0x00, pos [0], pos [1] ); + pos += 2; + break; + + case cmd_ym2610_port1: + chip_reg_write( vgm_time, 0x08, 0x00, 0x01, pos [0], pos [1] ); + pos += 2; + break; + + case cmd_ym2610_2_port1: + chip_reg_write( vgm_time, 0x08, 0x01, 0x01, pos [0], pos [1] ); + pos += 2; + break; + + case cmd_ym2608_port0: + chip_reg_write( vgm_time, 0x07, 0x00, 0x00, pos [0], pos [1] ); + pos += 2; + break; + + case cmd_ym2608_2_port0: + chip_reg_write( vgm_time, 0x07, 0x01, 0x00, pos [0], pos [1] ); + pos += 2; + break; + + case cmd_ym2608_port1: + chip_reg_write( vgm_time, 0x07, 0x00, 0x01, pos [0], pos [1] ); + pos += 2; + break; + + case cmd_ym2608_2_port1: + chip_reg_write( vgm_time, 0x07, 0x01, 0x01, pos [0], pos [1] ); + pos += 2; + break; + + case cmd_okim6258_write: + chip_reg_write( vgm_time, 0x17, 0x00, 0x00, pos [0] & 0x7F, pos [1] ); + pos += 2; + break; + + case cmd_okim6295_write: + ChipID = (pos [0] & 0x80) ? 1 : 0; + chip_reg_write( vgm_time, 0x18, ChipID, 0x00, pos [0] & 0x7F, pos [1] ); + pos += 2; + break; + + case cmd_huc6280_write: + ChipID = (pos [0] & 0x80) ? 1 : 0; + chip_reg_write( vgm_time, 0x1B, ChipID, 0x00, pos [0] & 0x7F, pos [1] ); + pos += 2; + break; + + case cmd_k051649_write: + chip_reg_write( vgm_time, 0x19, 0x00, pos [0] & 0x7F, pos [1], pos [2] ); + pos += 3; + break; + + case cmd_k053260_write: + chip_reg_write( vgm_time, 0x1D, 0x00, 0x00, pos [0] & 0x7F, pos [1] ); + pos += 2; + break; + + case cmd_k054539_write: + chip_reg_write( vgm_time, 0x1A, 0x00, pos [0] & 0x7F, pos [1], pos [2] ); + pos += 3; + break; + + case cmd_qsound_write: + chip_reg_write( vgm_time, 0x1F, 0x00, pos [0], pos [1], pos [2] ); + pos += 3; + break; + + case cmd_dacctl_setup: + if ( run_dac_control( vgm_time ) ) + { + unsigned chip = pos [0]; + if ( chip < 0xFF ) + { + if ( ! DacCtrl [chip].Enable ) + { + dac_control_grow( chip ); + DacCtrl [chip].Enable = true; + } + daccontrol_setup_chip( dac_control [DacCtrlMap [chip]], pos [1] & 0x7F, ( pos [1] & 0x80 ) >> 7, get_be16( pos + 2 ) ); + } + } + pos += 4; + break; + + case cmd_dacctl_data: + if ( run_dac_control( vgm_time ) ) + { + unsigned chip = pos [0]; + if ( chip < 0xFF && DacCtrl [chip].Enable ) + { + DacCtrl [chip].Bank = pos [1]; + if ( DacCtrl [chip].Bank >= 0x40 ) + DacCtrl [chip].Bank = 0x00; + + VGM_PCM_BANK * TempPCM = &PCMBank [DacCtrl [chip].Bank]; + daccontrol_set_data( dac_control [DacCtrlMap [chip]], TempPCM->Data, TempPCM->DataSize, pos [2], pos [3] ); + } + } + pos += 4; + break; + case cmd_dacctl_freq: + if ( run_dac_control( vgm_time ) ) + { + unsigned chip = pos [0]; + if ( chip < 0xFF && DacCtrl [chip].Enable ) + { + daccontrol_set_frequency( dac_control [DacCtrlMap [chip]], get_le32( pos + 1 ) ); + } + } + pos += 5; + break; + case cmd_dacctl_play: + if ( run_dac_control( vgm_time ) ) + { + unsigned chip = pos [0]; + if ( chip < 0xFF && DacCtrl [chip].Enable && PCMBank [DacCtrl [chip].Bank].BankCount ) + { + daccontrol_start( dac_control [DacCtrlMap [chip]], get_le32( pos + 1 ), pos [5], get_le32( pos + 6 ) ); + } + } + pos += 10; + break; + case cmd_dacctl_stop: + if ( run_dac_control( vgm_time ) ) + { + unsigned chip = pos [0]; + if ( chip < 0xFF && DacCtrl [chip].Enable ) + { + daccontrol_stop( dac_control [DacCtrlMap [chip]] ); + } + else if ( chip == 0xFF ) + { + for ( unsigned i = 0; i < DacCtrlUsed; i++ ) + { + daccontrol_stop( dac_control [i] ); + } + } + } + pos++; + break; + case cmd_dacctl_playblock: + if ( run_dac_control( vgm_time ) ) + { + unsigned chip = pos [0]; + if ( chip < 0xFF && DacCtrl [chip].Enable && PCMBank [DacCtrl [chip].Bank].BankCount ) + { + VGM_PCM_BANK * TempPCM = &PCMBank [DacCtrl [chip].Bank]; + unsigned block_number = get_le16( pos + 1 ); + if ( block_number >= TempPCM->BankCount ) + block_number = 0; + VGM_PCM_DATA * TempBnk = &TempPCM->Bank [block_number]; + unsigned flags = DCTRL_LMODE_BYTES | ((pos [4] & 1) << 7); + daccontrol_start( dac_control [DacCtrlMap [chip]], TempBnk->DataStart, flags, TempBnk->DataSize ); + } + } + pos += 4; + break; + + case cmd_data_block: { + check( *pos == cmd_end ); + int type = pos [1]; + int size = get_le32( pos + 2 ); + int chipid = 0; + if ( size & 0x80000000 ) + { + size &= 0x7FFFFFFF; + chipid = 1; + } + pos += 6; + switch ( type & 0xC0 ) + { + case pcm_block_type: + case pcm_aux_block_type: + AddPCMData( type, size, pos ); + break; + + case rom_block_type: + if ( size >= 8 ) + { + int rom_size = get_le32( pos ); + int data_start = get_le32( pos + 4 ); + int data_size = size - 8; + void * rom_data = ( void * ) ( pos + 8 ); + + switch ( type ) + { + case rom_segapcm: + if ( segapcm.enabled() ) + segapcm.write_rom( rom_size, data_start, data_size, rom_data ); + break; + + case rom_ym2608_deltat: + if ( ym2608[chipid].enabled() ) + { + ym2608[chipid].write_rom( 0x02, rom_size, data_start, data_size, rom_data ); + } + break; + + case rom_ym2610_adpcm: + case rom_ym2610_deltat: + if ( ym2610[chipid].enabled() ) + { + int rom_id = 0x01 + ( type - rom_ym2610_adpcm ); + ym2610[chipid].write_rom( rom_id, rom_size, data_start, data_size, rom_data ); + } + break; + + case rom_ymz280b: + if ( ymz280b.enabled() ) + ymz280b.write_rom( rom_size, data_start, data_size, rom_data ); + break; + + case rom_okim6295: + if ( okim6295[chipid].enabled() ) + okim6295[chipid].write_rom( rom_size, data_start, data_size, rom_data ); + break; + + case rom_k054539: + if ( k054539.enabled() ) + k054539.write_rom( rom_size, data_start, data_size, rom_data ); + break; + + case rom_c140: + if ( c140.enabled() ) + c140.write_rom( rom_size, data_start, data_size, rom_data ); + break; + + case rom_k053260: + if ( k053260.enabled() ) + k053260.write_rom( rom_size, data_start, data_size, rom_data ); + break; + + case rom_qsound: + if ( qsound[chipid].enabled() ) + qsound[chipid].write_rom( rom_size, data_start, data_size, rom_data ); + break; + } + } + break; + + case ram_block_type: + if ( size >= 2 ) + { + int data_start = get_le16( pos ); + int data_size = size - 2; + void * ram_data = ( void * ) ( pos + 2 ); + + switch ( type ) + { + case ram_rf5c68: + if ( rf5c68.enabled() ) + rf5c68.write_ram( data_start, data_size, ram_data ); + break; + + case ram_rf5c164: + if ( rf5c164.enabled() ) + rf5c164.write_ram( data_start, data_size, ram_data ); + break; + } + } + break; + } + pos += size; + break; + } + + case cmd_ram_block: { + check( *pos == cmd_end ); + int type = pos[ 1 ]; + int data_start = get_le24( pos + 2 ); + int data_addr = get_le24( pos + 5 ); + int data_size = get_le24( pos + 8 ); + if ( !data_size ) data_size += 0x01000000; + void * data_ptr = (void *) GetPointerFromPCMBank( type, data_start ); + switch ( type ) + { + case rf5c68_ram_block: + if ( rf5c68.enabled() ) + rf5c68.write_ram( data_addr, data_size, data_ptr ); + break; + + case rf5c164_ram_block: + if ( rf5c164.enabled() ) + rf5c164.write_ram( data_addr, data_size, data_ptr ); + break; + } + pos += 11; + break; + } + + case cmd_pcm_seek: + pcm_pos = GetPointerFromPCMBank( 0, get_le32( pos ) ); + pos += 4; + break; + + default: + int cmd = pos [-1]; + switch ( cmd & 0xF0 ) + { + case cmd_pcm_delay: + chip_reg_write( vgm_time, 0x02, 0x00, 0x00, ym2612_dac_port, *pcm_pos++ ); + vgm_time += cmd & 0x0F; + break; + + case cmd_short_delay: + vgm_time += (cmd & 0x0F) + 1; + break; + + case 0x50: + pos += 2; + break; + + default: + pos += command_len( cmd ) - 1; + set_warning( "Unknown stream event" ); + } + } + } + vgm_time -= end_time; + this->pos = pos; + this->vgm_time = vgm_time; + + return to_psg_time( end_time ); +} + +blip_time_t Vgm_Core::run_psg( int msec ) +{ + blip_time_t t = run( msec * vgm_rate / 1000 ); + psg[0].end_frame( t ); + psg[1].end_frame( t ); + return t; +} + +int Vgm_Core::play_frame( blip_time_t blip_time, int sample_count, blip_sample_t out [] ) +{ + // to do: timing is working mostly by luck + int min_pairs = (unsigned) sample_count / 2; + int vgm_time = (min_pairs << fm_time_bits) / fm_time_factor - 1; + assert( to_fm_time( vgm_time ) <= min_pairs ); + int pairs; + while ( (pairs = to_fm_time( vgm_time )) < min_pairs ) + vgm_time++; + //dprintf( "pairs: %d, min_pairs: %d\n", pairs, min_pairs ); + + memset( out, 0, pairs * stereo * sizeof *out ); + + if ( ymf262[0].enabled() ) + { + ymf262[0].begin_frame( out ); + if ( ymf262[1].enabled() ) + { + ymf262[1].begin_frame( out ); + } + } + if ( ym3812[0].enabled() ) + { + ym3812[0].begin_frame( out ); + if ( ym3812[1].enabled() ) + { + ym3812[1].begin_frame( out ); + } + } + if ( ym2612[0].enabled() ) + { + ym2612[0].begin_frame( out ); + if ( ym2612[1].enabled() ) + { + ym2612[1].begin_frame( out ); + } + } + if ( ym2610[0].enabled() ) + { + ym2610[0].begin_frame( out ); + if ( ym2610[1].enabled() ) + { + ym2610[1].begin_frame( out ); + } + } + if ( ym2608[0].enabled() ) + { + ym2608[0].begin_frame( out ); + if ( ym2608[1].enabled() ) + { + ym2608[1].begin_frame( out ); + } + } + if ( ym2413[0].enabled() ) + { + ym2413[0].begin_frame( out ); + if ( ym2413[1].enabled() ) + { + ym2413[1].begin_frame( out ); + } + } + if ( ym2203[0].enabled() ) + { + ym2203[0].begin_frame( out ); + if ( ym2203[1].enabled() ) + { + ym2203[1].begin_frame( out ); + } + } + if ( ym2151[0].enabled() ) + { + ym2151[0].begin_frame( out ); + if ( ym2151[1].enabled() ) + { + ym2151[1].begin_frame( out ); + } + } + + if ( c140.enabled() ) + { + c140.begin_frame( out ); + } + if ( segapcm.enabled() ) + { + segapcm.begin_frame( out ); + } + if ( rf5c68.enabled() ) + { + rf5c68.begin_frame( out ); + } + if ( rf5c164.enabled() ) + { + rf5c164.begin_frame( out ); + } + if ( pwm.enabled() ) + { + pwm.begin_frame( out ); + } + if ( okim6258.enabled() ) + { + okim6258.begin_frame( out ); + } + if ( okim6295[0].enabled() ) + { + okim6295[0].begin_frame( out ); + if ( okim6295[1].enabled() ) + { + okim6295[1].begin_frame( out ); + } + } + if ( k051649.enabled() ) + { + k051649.begin_frame( out ); + } + if ( k053260.enabled() ) + { + k053260.begin_frame( out ); + } + if ( k054539.enabled() ) + { + k054539.begin_frame( out ); + } + if ( ymz280b.enabled() ) + { + ymz280b.begin_frame( out ); + } + if ( qsound[0].enabled() ) + { + qsound[0].begin_frame( out ); + if ( qsound[1].enabled() ) + { + qsound[1].begin_frame( out ); + } + } + + run( vgm_time ); + + run_dac_control( vgm_time ); + + run_ymf262( 0, pairs ); run_ymf262( 1, pairs ); + run_ym3812( 0, pairs ); run_ym3812( 1, pairs ); + run_ym2612( 0, pairs ); run_ym2612( 1, pairs ); + run_ym2610( 0, pairs ); run_ym2610( 1, pairs ); + run_ym2608( 0, pairs ); run_ym2608( 1, pairs ); + run_ym2413( 0, pairs ); run_ym2413( 1, pairs ); + run_ym2203( 0, pairs ); run_ym2203( 1, pairs ); + run_ym2151( 0, pairs ); run_ym2151( 1, pairs ); + run_c140( pairs ); + run_segapcm( pairs ); + run_rf5c68( pairs ); + run_rf5c164( pairs ); + run_pwm( pairs ); + run_okim6258( pairs ); + run_okim6295( 0, pairs ); run_okim6295( 1, pairs ); + run_k051649( pairs ); + run_k053260( pairs ); + run_k054539( pairs ); + run_ymz280b( pairs ); + run_qsound( 0, pairs ); run_qsound( 1, pairs ); + + fm_time_offset = (vgm_time * fm_time_factor + fm_time_offset) - (pairs << fm_time_bits); + + psg[0].end_frame( blip_time ); + psg[1].end_frame( blip_time ); + + ay_time_offset = (vgm_time * blip_ay_time_factor + ay_time_offset) - (pairs << blip_time_bits); + + blip_time_t ay_end_time = to_ay_time( vgm_time ); + ay[0].end_frame( ay_end_time ); + ay[1].end_frame( ay_end_time ); + + huc6280_time_offset = (vgm_time * blip_huc6280_time_factor + huc6280_time_offset) - (pairs << blip_time_bits); + + blip_time_t huc6280_end_time = to_huc6280_time( vgm_time ); + huc6280[0].end_frame( huc6280_end_time ); + huc6280[1].end_frame( huc6280_end_time ); + + memset( DacCtrlTime, 0, sizeof(DacCtrlTime) ); + + return pairs * stereo; +} diff --git a/Frameworks/GME/gme/Vgm_Core.h b/Frameworks/GME/gme/Vgm_Core.h new file mode 100644 index 000000000..c5c53ea7c --- /dev/null +++ b/Frameworks/GME/gme/Vgm_Core.h @@ -0,0 +1,346 @@ +// Sega VGM music file emulator core + +// Game_Music_Emu $vers +#ifndef VGM_CORE_H +#define VGM_CORE_H + +#include "Gme_Loader.h" +#include "Ymz280b_Emu.h" +#include "Ymf262_Emu.h" +#include "Ym2612_Emu.h" +#include "Ym2610b_Emu.h" +#include "Ym2608_Emu.h" +#include "Ym3812_Emu.h" +#include "Ym2413_Emu.h" +#include "Ym2151_Emu.h" +#include "C140_Emu.h" +#include "SegaPcm_Emu.h" +#include "Rf5C68_Emu.h" +#include "Rf5C164_Emu.h" +#include "Pwm_Emu.h" +#include "Okim6258_Emu.h" +#include "Okim6295_Emu.h" +#include "K051649_Emu.h" +#include "K053260_Emu.h" +#include "K054539_Emu.h" +#include "Qsound_Apu.h" +#include "Ym2203_Emu.h" +#include "Ay_Apu.h" +#include "Hes_Apu.h" +#include "Sms_Apu.h" +#include "Multi_Buffer.h" +#include "Chip_Resampler.h" + + template + class Chip_Emu : public Emu { + int last_time; + short* out; + enum { disabled_time = -1 }; + public: + Chip_Emu() { last_time = disabled_time; out = NULL; } + void enable( bool b = true ) { last_time = b ? 0 : disabled_time; } + bool enabled() const { return last_time != disabled_time; } + void begin_frame( short* buf ) { out = buf; last_time = 0; } + + int run_until( int time ) + { + int count = time - last_time; + if ( count > 0 ) + { + if ( last_time < 0 ) + return false; + last_time = time; + short* p = out; + out += count * Emu::out_chan_count; + Emu::run( count, p ); + } + return true; + } + }; + +class Vgm_Core : public Gme_Loader { +public: + + // VGM file header + struct header_t + { + enum { size_min = 0x40 }; + enum { size_151 = 0x80 }; + enum { size_max = 0xC0 }; + + char tag [4]; // 0x00 + byte data_size [4]; // 0x04 + byte version [4]; // 0x08 + byte psg_rate [4]; // 0x0C + byte ym2413_rate [4]; // 0x10 + byte gd3_offset [4]; // 0x14 + byte track_duration [4]; // 0x18 + byte loop_offset [4]; // 0x1C + byte loop_duration [4]; // 0x20 + byte frame_rate [4]; // 0x24 v1.01 V + byte noise_feedback [2]; // 0x28 v1.10 V + byte noise_width; // 0x2A + byte sn76489_flags; // 0x2B v1.51 < + byte ym2612_rate [4]; // 0x2C v1.10 V + byte ym2151_rate [4]; // 0x30 + byte data_offset [4]; // 0x34 v1.50 V + byte segapcm_rate [4]; // 0x38 v1.51 V + byte segapcm_reg [4]; // 0x3C + byte rf5c68_rate [4]; // 0x40 + byte ym2203_rate [4]; // 0x44 + byte ym2608_rate [4]; // 0x48 + byte ym2610_rate [4]; // 0x4C + byte ym3812_rate [4]; // 0x50 + byte ym3526_rate [4]; // 0x54 + byte y8950_rate [4]; // 0x58 + byte ymf262_rate [4]; // 0x5C + byte ymf278b_rate [4]; // 0x60 + byte ymf271_rate [4]; // 0x64 + byte ymz280b_rate [4]; // 0x68 + byte rf5c164_rate [4]; // 0x6C + byte pwm_rate [4]; // 0x70 + byte ay8910_rate [4]; // 0x74 + byte ay8910_type; // 0x78 + byte ay8910_flags; // 0x79 + byte ym2203_ay8910_flags;// 0x7A + byte ym2608_ay8910_flags;// 0x7B + byte volume_modifier; // 0x7C v1.60 V + byte reserved; // 0x7D + byte loop_base; // 0x7E + byte loop_modifier; // 0x7F v1.51 < + byte gbdmg_rate [4]; // 0x80 v1.61 V + byte nesapu_rate [4]; // 0x84 + byte multipcm_rate [4]; // 0x88 + byte upd7759_rate [4]; // 0x8C + byte okim6258_rate [4]; // 0x90 + byte okim6258_flags; // 0x94 + byte k054539_flags; // 0x95 + byte c140_type; // 0x96 + byte reserved_flags; // 0x97 + byte okim6295_rate [4]; // 0x98 + byte k051649_rate [4]; // 0x9C + byte k054539_rate [4]; // 0xA0 + byte huc6280_rate [4]; // 0xA4 + byte c140_rate [4]; // 0xA8 + byte k053260_rate [4]; // 0xAC + byte pokey_rate [4]; // 0xB0 + byte qsound_rate [4]; // 0xB4 + byte reserved2 [4]; // 0xB8 + byte extra_offset [4]; // 0xBC + + // True if header has valid file signature + bool valid_tag() const; + int size() const; + void cleanup(); + }; + + // Header for currently loaded file + header_t const& header() const { return _header; } + + // Raw file data, for parsing GD3 tags + byte const* file_begin() const { return Gme_Loader::file_begin(); } + byte const* file_end () const { return Gme_Loader::file_end(); } + + // If file uses FM, initializes FM sound emulator using *sample_rate. If + // *sample_rate is zero, sets *sample_rate to the proper accurate rate and + // uses that. The output of the FM sound emulator is resampled to the + // final sampling rate. + blargg_err_t init_chips( double* fm_rate, bool reinit = false ); + + // True if any FM chips are used by file. Always false until init_fm() + // is called. + bool uses_fm() const { return ym2612[0].enabled() || ym2413[0].enabled() || ym2151[0].enabled() || c140.enabled() || + segapcm.enabled() || rf5c68.enabled() || rf5c164.enabled() || pwm.enabled() || okim6258.enabled() || okim6295[0].enabled() || + k051649.enabled() || k053260.enabled() || k054539.enabled() || ym2203[0].enabled() || ym3812[0].enabled() || ymf262[0].enabled() || + ymz280b.enabled() || ym2610[0].enabled() || ym2608[0].enabled() || qsound[0].enabled() || + (header().ay8910_rate[0] | header().ay8910_rate[1] | header().ay8910_rate[2] | header().ay8910_rate[3]) || + (header().huc6280_rate[0] | header().huc6280_rate[1] | header().huc6280_rate[2] | header().huc6280_rate[3]); } + + // Adjusts music tempo, where 1.0 is normal. Can be changed while playing. + // Loading a file resets tempo to 1.0. + void set_tempo( double ); + + void set_sample_rate( int r ) { sample_rate = r; } + + // Starts track + void start_track(); + + // Runs PSG-only VGM for msec and returns number of clocks it ran for + blip_time_t run_psg( int msec ); + + // Plays FM for at most count samples into *out, and returns number of + // samples actually generated (always even). Also runs PSG for blip_time. + int play_frame( blip_time_t blip_time, int count, blip_sample_t out [] ); + + // True if all of file data has been played + bool track_ended() const { return pos >= file_end(); } + + // 0 for PSG and YM2612 DAC, 1 for AY, 2 for HuC6280 + Stereo_Buffer stereo_buf[3]; + + // PCM sound is always generated here + Blip_Buffer * blip_buf[2]; + + // PSG sound chips, for assigning to Blip_Buffer, and setting volume and EQ + Sms_Apu psg[2]; + Ay_Apu ay[2]; + Hes_Apu huc6280[2]; + + // PCM synth, for setting volume and EQ + Blip_Synth_Fast pcm; + + // FM sound chips + Chip_Resampler_Emu ymf262[2]; + Chip_Resampler_Emu ym3812[2]; + Chip_Resampler_Emu ym2612[2]; + Chip_Resampler_Emu ym2610[2]; + Chip_Resampler_Emu ym2608[2]; + Chip_Resampler_Emu ym2413[2]; + Chip_Resampler_Emu ym2151[2]; + Chip_Resampler_Emu ym2203[2]; + + // PCM sound chips + Chip_Resampler_Emu c140; + Chip_Resampler_Emu segapcm; + Chip_Resampler_Emu rf5c68; + Chip_Resampler_Emu rf5c164; + Chip_Resampler_Emu pwm; + Chip_Resampler_Emu okim6258; int okim6258_hz; + Chip_Resampler_Emu okim6295[2]; int okim6295_hz; + Chip_Resampler_Emu k051649; + Chip_Resampler_Emu k053260; + Chip_Resampler_Emu k054539; + Chip_Resampler_Emu ymz280b; int ymz280b_hz; + Chip_Resampler_Emu qsound[2]; + + // DAC control + typedef struct daccontrol_data + { + bool Enable; + byte Bank; + } DACCTRL_DATA; + + byte DacCtrlUsed; + byte DacCtrlUsg[0xFF]; + DACCTRL_DATA DacCtrl[0xFF]; + byte DacCtrlMap[0xFF]; + int DacCtrlTime[0xFF]; + void ** dac_control; + + void dac_control_grow(byte chip_id); + + int dac_control_recursion; + + int run_dac_control( int time ); + +public: + void chip_reg_write(unsigned Sample, byte ChipType, byte ChipID, byte Port, byte Offset, byte Data); + +// Implementation +public: + Vgm_Core(); + ~Vgm_Core(); + +protected: + virtual blargg_err_t load_mem_( byte const [], int ); + +private: + // blip_time_t // PSG clocks + typedef int vgm_time_t; // 44100 per second, REGARDLESS of sample rate + typedef int fm_time_t; // FM sample count + + int sample_rate; + int vgm_rate; // rate of log, 44100 normally, adjusted by tempo + double fm_rate; // FM samples per second + + header_t _header; + + // VGM to FM time + int fm_time_factor; + int fm_time_offset; + fm_time_t to_fm_time( vgm_time_t ) const; + + // VGM to PSG time + int blip_time_factor; + blip_time_t to_psg_time( vgm_time_t ) const; + + int blip_ay_time_factor; + int ay_time_offset; + blip_time_t to_ay_time( vgm_time_t ) const; + + int blip_huc6280_time_factor; + int huc6280_time_offset; + blip_time_t to_huc6280_time( vgm_time_t ) const; + + // Current time and position in log + vgm_time_t vgm_time; + byte const* pos; + byte const* loop_begin; + bool has_looped; + + // PCM + enum { PCM_BANK_COUNT = 0x40 }; + typedef struct _vgm_pcm_bank_data + { + unsigned DataSize; + byte* Data; + unsigned DataStart; + } VGM_PCM_DATA; + typedef struct _vgm_pcm_bank + { + unsigned BankCount; + VGM_PCM_DATA* Bank; + unsigned DataSize; + byte* Data; + unsigned DataPos; + unsigned BnkPos; + } VGM_PCM_BANK; + + typedef struct pcmbank_table + { + byte ComprType; + byte CmpSubType; + byte BitDec; + byte BitCmp; + unsigned EntryCount; + void* Entries; + } PCMBANK_TBL; + + VGM_PCM_BANK PCMBank[PCM_BANK_COUNT]; + PCMBANK_TBL PCMTbl; + + void ReadPCMTable(unsigned DataSize, const byte* Data); + void AddPCMData(byte Type, unsigned DataSize, const byte* Data); + bool DecompressDataBlk(VGM_PCM_DATA* Bank, unsigned DataSize, const byte* Data); + const byte* GetPointerFromPCMBank(byte Type, unsigned DataPos); + + byte const* pcm_pos; // current position in PCM data + int dac_amp[2]; + int dac_disabled[2]; // -1 if disabled + void write_pcm( vgm_time_t, int chip, int amp ); + + blip_time_t run( vgm_time_t ); + int run_ym2151( int chip, int time ); + int run_ym2203( int chip, int time ); + int run_ym2413( int chip, int time ); + int run_ym2612( int chip, int time ); + int run_ym3812( int chip, int time ); + int run_ymf262( int chip, int time ); + int run_ym2610( int chip, int time ); + int run_ym2608( int chip, int time ); + int run_ymz280b( int time ); + int run_c140( int time ); + int run_segapcm( int time ); + int run_rf5c68( int time ); + int run_rf5c164( int time ); + int run_pwm( int time ); + int run_okim6258( int time ); + int run_okim6295( int chip, int time ); + int run_k051649( int time ); + int run_k053260( int time ); + int run_k054539( int time ); + int run_qsound( int chip, int time ); + void update_fm_rates( int* ym2151_rate, int* ym2413_rate, int* ym2612_rate ) const; +}; + +#endif diff --git a/Frameworks/GME/gme/Ym2151_Emu.cpp b/Frameworks/GME/gme/Ym2151_Emu.cpp new file mode 100644 index 000000000..b7703e7f4 --- /dev/null +++ b/Frameworks/GME/gme/Ym2151_Emu.cpp @@ -0,0 +1,75 @@ +// Game_Music_Emu $vers. http://www.slack.net/~ant/ + +#include "Ym2151_Emu.h" +#include "ym2151.h" + +Ym2151_Emu::Ym2151_Emu() { PSG = 0; } + +Ym2151_Emu::~Ym2151_Emu() +{ + if ( PSG ) ym2151_shutdown( PSG ); +} + +int Ym2151_Emu::set_rate( double sample_rate, double clock_rate ) +{ + if ( PSG ) + { + ym2151_shutdown( PSG ); + PSG = 0; + } + + PSG = ym2151_init( clock_rate, sample_rate ); + if ( !PSG ) + return 1; + + reset(); + return 0; +} + +void Ym2151_Emu::reset() +{ + ym2151_reset_chip( PSG ); + ym2151_set_mask( PSG, 0 ); +} + +static stream_sample_t* DUMMYBUF[0x02] = {(stream_sample_t*)NULL, (stream_sample_t*)NULL}; + +void Ym2151_Emu::write( int addr, int data ) +{ + ym2151_update_one( PSG, DUMMYBUF, 0 ); + ym2151_write_reg( PSG, addr, data ); +} + +void Ym2151_Emu::mute_voices( int mask ) +{ + ym2151_set_mask( PSG, mask ); +} + +void Ym2151_Emu::run( int pair_count, sample_t* out ) +{ + SAMP bufL[ 1024 ]; + SAMP bufR[ 1024 ]; + SAMP * buffers[2] = { bufL, bufR }; + + while (pair_count > 0) + { + int todo = pair_count; + if (todo > 1024) todo = 1024; + ym2151_update_one( PSG, buffers, todo ); + + for (int i = 0; i < todo; i++) + { + int output_l = bufL [i]; + int output_r = bufR [i]; + output_l += out [0]; + output_r += out [1]; + if ( (short)output_l != output_l ) output_l = 0x7FFF ^ ( output_l >> 31 ); + if ( (short)output_r != output_r ) output_r = 0x7FFF ^ ( output_r >> 31 ); + out [0] = output_l; + out [1] = output_r; + out += 2; + } + + pair_count -= todo; + } +} diff --git a/Frameworks/GME/gme/Ym2151_Emu.h b/Frameworks/GME/gme/Ym2151_Emu.h new file mode 100644 index 000000000..a55c6f894 --- /dev/null +++ b/Frameworks/GME/gme/Ym2151_Emu.h @@ -0,0 +1,33 @@ +// YM2151 FM sound chip emulator interface + +// Game_Music_Emu $vers +#ifndef YM2151_EMU_H +#define YM2151_EMU_H + +class Ym2151_Emu { + void* PSG; +public: + Ym2151_Emu(); + ~Ym2151_Emu(); + + // Sets output sample rate and chip clock rates, in Hz. Returns non-zero + // if error. + int set_rate( double sample_rate, double clock_rate ); + + // Resets to power-up state + void reset(); + + // Mutes voice n if bit n (1 << n) of mask is set + enum { channel_count = 8 }; + void mute_voices( int mask ); + + // Writes data to addr + void write( int addr, int data ); + + // Runs and writes pair_count*2 samples to output + typedef short sample_t; + enum { out_chan_count = 2 }; // stereo + void run( int pair_count, sample_t* out ); +}; + +#endif diff --git a/Frameworks/GME/gme/Ym2203_Emu.cpp b/Frameworks/GME/gme/Ym2203_Emu.cpp new file mode 100644 index 000000000..a8aad8291 --- /dev/null +++ b/Frameworks/GME/gme/Ym2203_Emu.cpp @@ -0,0 +1,155 @@ +// Game_Music_Emu $vers. http://www.slack.net/~ant/ + +#include "Ym2203_Emu.h" +#include "fm.h" +#include + +static void psg_set_clock(void *param, int clock) +{ + Ym2203_Emu *info = (Ym2203_Emu *)param; + info->psg_set_clock( clock ); +} + +static void psg_write(void *param, int address, int data) +{ + Ym2203_Emu *info = (Ym2203_Emu *)param; + info->psg_write( address, data ); +} + +static int psg_read(void *param) +{ + Ym2203_Emu *info = (Ym2203_Emu *)param; + return info->psg_read(); +} + +static void psg_reset(void *param) +{ + Ym2203_Emu *info = (Ym2203_Emu *)param; + info->psg_reset(); +} + +static const ssg_callbacks psgintf = +{ + psg_set_clock, + psg_write, + psg_read, + psg_reset +}; + +Ym2203_Emu::Ym2203_Emu() { opn = 0; psg.set_type( Ay_Apu::Ym2203 ); } + +Ym2203_Emu::~Ym2203_Emu() +{ + if ( opn ) ym2203_shutdown( opn ); +} + +int Ym2203_Emu::set_rate( int sample_rate, int clock_rate ) +{ + if ( opn ) + { + ym2203_shutdown( opn ); + opn = 0; + } + + opn = ym2203_init( this, clock_rate, sample_rate, &psgintf ); + if ( !opn ) + return 1; + + this->sample_rate = sample_rate; + psg_clock = clock_rate * 2; + + buffer.set_sample_rate( sample_rate ); + buffer.clock_rate( psg_clock ); + + psg.volume( 1.0 ); + + reset(); + return 0; +} + +void Ym2203_Emu::reset() +{ + psg.reset(); + ym2203_reset_chip( opn ); + mute_voices( 0 ); +} + +static stream_sample_t* DUMMYBUF[0x02] = {(stream_sample_t*)NULL, (stream_sample_t*)NULL}; + +void Ym2203_Emu::write( int addr, int data ) +{ + ym2203_update_one( opn, DUMMYBUF, 0 ); + ym2203_write( opn, 0, addr ); + ym2203_write( opn, 1, data ); +} + +void Ym2203_Emu::mute_voices( int mask ) +{ + ym2203_set_mutemask( opn, mask ); + for ( unsigned i = 0, j = 1 << 3; i < 3; i++, j <<= 1) + { + Blip_Buffer * buf = ( mask & j ) ? NULL : &buffer; + psg.set_output( i, buf ); + } +} + +void Ym2203_Emu::run( int pair_count, sample_t* out ) +{ + blip_sample_t buf[ 1024 ]; + FMSAMPLE bufL[ 1024 ]; + FMSAMPLE bufR[ 1024 ]; + FMSAMPLE * buffers[2] = { bufL, bufR }; + + blip_time_t psg_end_time = pair_count * psg_clock / sample_rate; + psg.end_frame( psg_end_time ); + buffer.end_frame( psg_end_time ); + + while (pair_count > 0) + { + int todo = pair_count; + if (todo > 1024) todo = 1024; + ym2203_update_one( opn, buffers, todo ); + + int sample_count = buffer.read_samples( buf, todo ); + memset( buf + sample_count, 0, ( todo - sample_count ) * sizeof( blip_sample_t ) ); + + for (int i = 0; i < todo; i++) + { + int output_l, output_r; + int output = buf [i]; + output_l = output + bufL [i]; + output_r = output + bufR [i]; + output_l += out [0]; + output_r += out [1]; + if ( (short)output_l != output_l ) output_l = 0x7FFF ^ ( output_l >> 31 ); + if ( (short)output_r != output_r ) output_r = 0x7FFF ^ ( output_r >> 31 ); + out [0] = output_l; + out [1] = output_r; + out += 2; + } + + pair_count -= todo; + } +} + +void Ym2203_Emu::psg_set_clock( int clock ) +{ + psg_clock = clock * 2; + buffer.clock_rate( psg_clock ); +} + +void Ym2203_Emu::psg_write( int addr, int data ) +{ + if ( !(addr & 1) ) psg.write_addr( data ); + else psg.write_data( 0, data ); +} + +int Ym2203_Emu::psg_read() +{ + return psg.read(); +} + +void Ym2203_Emu::psg_reset() +{ + psg.reset(); +} diff --git a/Frameworks/GME/gme/Ym2203_Emu.h b/Frameworks/GME/gme/Ym2203_Emu.h new file mode 100644 index 000000000..248a70e58 --- /dev/null +++ b/Frameworks/GME/gme/Ym2203_Emu.h @@ -0,0 +1,45 @@ +// YM2203 FM sound chip emulator interface + +// Game_Music_Emu $vers +#ifndef YM2203_EMU_H +#define YM2203_EMU_H + +#include "Ay_Apu.h" + +class Ym2203_Emu { + void* opn; + Ay_Apu psg; + Blip_Buffer buffer; + unsigned sample_rate; + unsigned psg_clock; +public: + Ym2203_Emu(); + ~Ym2203_Emu(); + + // Sets output chip clock rate, in Hz. Returns non-zero + // if error. + int set_rate( int sample_rate, int clock_rate ); + + // Resets to power-up state + void reset(); + + // Mutes voice n if bit n (1 << n) of mask is set + enum { channel_count = 6 }; + void mute_voices( int mask ); + + // Writes data to addr + void write( int addr, int data ); + + // Runs and writes pair_count*2 samples to output + typedef short sample_t; + enum { out_chan_count = 2 }; // stereo + void run( int pair_count, sample_t* out ); + + // SSG interface + inline void psg_set_clock( int clock ); + inline void psg_write( int addr, int data ); + inline int psg_read(); + inline void psg_reset(); +}; + +#endif diff --git a/Frameworks/GME/gme/Ym2608_Emu.cpp b/Frameworks/GME/gme/Ym2608_Emu.cpp new file mode 100644 index 000000000..fd0680dab --- /dev/null +++ b/Frameworks/GME/gme/Ym2608_Emu.cpp @@ -0,0 +1,167 @@ +// Game_Music_Emu $vers. http://www.slack.net/~ant/ + +#include "Ym2608_Emu.h" +#include "fm.h" +#include + +static void psg_set_clock(void *param, int clock) +{ + Ym2608_Emu *info = (Ym2608_Emu *)param; + info->psg_set_clock( clock ); +} + +static void psg_write(void *param, int address, int data) +{ + Ym2608_Emu *info = (Ym2608_Emu *)param; + info->psg_write( address, data ); +} + +static int psg_read(void *param) +{ + Ym2608_Emu *info = (Ym2608_Emu *)param; + return info->psg_read(); +} + +static void psg_reset(void *param) +{ + Ym2608_Emu *info = (Ym2608_Emu *)param; + info->psg_reset(); +} + +static const ssg_callbacks psgintf = +{ + psg_set_clock, + psg_write, + psg_read, + psg_reset +}; + +Ym2608_Emu::Ym2608_Emu() { opn = 0; psg.set_type( Ay_Apu::Ym2608 ); } + +Ym2608_Emu::~Ym2608_Emu() +{ + if ( opn ) ym2608_shutdown( opn ); +} + +int Ym2608_Emu::set_rate( int sample_rate, int clock_rate ) +{ + if ( opn ) + { + ym2608_shutdown( opn ); + opn = 0; + } + + opn = ym2608_init( this, clock_rate, sample_rate, &psgintf ); + if ( !opn ) + return 1; + + this->sample_rate = sample_rate; + psg_clock = clock_rate * 2; + + buffer.set_sample_rate( sample_rate ); + buffer.clock_rate( psg_clock ); + + psg.volume( 1.0 ); + + reset(); + return 0; +} + +void Ym2608_Emu::reset() +{ + psg.reset(); + ym2608_reset_chip( opn ); + mute_voices( 0 ); +} + +static stream_sample_t* DUMMYBUF[0x02] = {(stream_sample_t*)NULL, (stream_sample_t*)NULL}; + +void Ym2608_Emu::write0( int addr, int data ) +{ + ym2608_update_one( opn, DUMMYBUF, 0 ); + ym2608_write( opn, 0, addr ); + ym2608_write( opn, 1, data ); +} + +void Ym2608_Emu::write1( int addr, int data ) +{ + ym2608_update_one( opn, DUMMYBUF, 0 ); + ym2608_write( opn, 2, addr ); + ym2608_write( opn, 3, data ); +} + +void Ym2608_Emu::write_rom( int rom_id, int size, int start, int length, void * data ) +{ + ym2608_write_pcmrom( opn, rom_id, size, start, length, (const UINT8 *) data ); +} + +void Ym2608_Emu::mute_voices( int mask ) +{ + ym2608_set_mutemask( opn, mask ); + for ( unsigned i = 0, j = 1 << 6; i < 3; i++, j <<= 1) + { + Blip_Buffer * buf = ( mask & j ) ? NULL : &buffer; + psg.set_output( i, buf ); + } +} + +void Ym2608_Emu::run( int pair_count, sample_t* out ) +{ + blip_sample_t buf[ 1024 ]; + FMSAMPLE bufL[ 1024 ]; + FMSAMPLE bufR[ 1024 ]; + FMSAMPLE * buffers[2] = { bufL, bufR }; + + blip_time_t psg_end_time = pair_count * psg_clock / sample_rate; + psg.end_frame( psg_end_time ); + buffer.end_frame( psg_end_time ); + + while (pair_count > 0) + { + int todo = pair_count; + if (todo > 1024) todo = 1024; + ym2608_update_one( opn, buffers, todo ); + + int sample_count = buffer.read_samples( buf, todo ); + memset( buf + sample_count, 0, ( todo - sample_count ) * sizeof( blip_sample_t ) ); + + for (int i = 0; i < todo; i++) + { + int output_l, output_r; + int output = buf [i]; + output_l = output + bufL [i]; + output_r = output + bufR [i]; + output_l += out [0]; + output_r += out [1]; + if ( (short)output_l != output_l ) output_l = 0x7FFF ^ ( output_l >> 31 ); + if ( (short)output_r != output_r ) output_r = 0x7FFF ^ ( output_r >> 31 ); + out [0] = output_l; + out [1] = output_r; + out += 2; + } + + pair_count -= todo; + } +} + +void Ym2608_Emu::psg_set_clock( int clock ) +{ + psg_clock = clock * 2; + buffer.clock_rate( psg_clock ); +} + +void Ym2608_Emu::psg_write( int addr, int data ) +{ + if ( !(addr & 1) ) psg.write_addr( data ); + else psg.write_data( 0, data ); +} + +int Ym2608_Emu::psg_read() +{ + return psg.read(); +} + +void Ym2608_Emu::psg_reset() +{ + psg.reset(); +} diff --git a/Frameworks/GME/gme/Ym2608_Emu.h b/Frameworks/GME/gme/Ym2608_Emu.h new file mode 100644 index 000000000..49a44ba4a --- /dev/null +++ b/Frameworks/GME/gme/Ym2608_Emu.h @@ -0,0 +1,49 @@ +// YM2608 FM sound chip emulator interface + +// Game_Music_Emu $vers +#ifndef YM2608_EMU_H +#define YM2608_EMU_H + +#include "Ay_Apu.h" + +class Ym2608_Emu { + void* opn; + Ay_Apu psg; + Blip_Buffer buffer; + unsigned sample_rate; + unsigned psg_clock; +public: + Ym2608_Emu(); + ~Ym2608_Emu(); + + // Sets output chip clock rate, in Hz. Returns non-zero + // if error. + int set_rate( int sample_rate, int clock_rate ); + + // Resets to power-up state + void reset(); + + // Mutes voice n if bit n (1 << n) of mask is set + enum { channel_count = 9 }; + void mute_voices( int mask ); + + // Writes data to addr + void write0( int addr, int data ); + void write1( int addr, int data ); + + // Sets ROM type, scales ROM size, then writes length bytes from data at start offset + void write_rom( int rom_id, int size, int start, int length, void * data ); + + // Runs and writes pair_count*2 samples to output + typedef short sample_t; + enum { out_chan_count = 2 }; // stereo + void run( int pair_count, sample_t* out ); + + // SSG interface + inline void psg_set_clock( int clock ); + inline void psg_write( int addr, int data ); + inline int psg_read(); + inline void psg_reset(); +}; + +#endif diff --git a/Frameworks/GME/gme/Ym2610b_Emu.cpp b/Frameworks/GME/gme/Ym2610b_Emu.cpp new file mode 100644 index 000000000..ff4b8c7ee --- /dev/null +++ b/Frameworks/GME/gme/Ym2610b_Emu.cpp @@ -0,0 +1,173 @@ +// Game_Music_Emu $vers. http://www.slack.net/~ant/ + +#include "Ym2610b_Emu.h" +#include "fm.h" +#include + +static void psg_set_clock(void *param, int clock) +{ + Ym2610b_Emu *info = (Ym2610b_Emu *)param; + info->psg_set_clock( clock ); +} + +static void psg_write(void *param, int address, int data) +{ + Ym2610b_Emu *info = (Ym2610b_Emu *)param; + info->psg_write( address, data ); +} + +static int psg_read(void *param) +{ + Ym2610b_Emu *info = (Ym2610b_Emu *)param; + return info->psg_read(); +} + +static void psg_reset(void *param) +{ + Ym2610b_Emu *info = (Ym2610b_Emu *)param; + info->psg_reset(); +} + +static const ssg_callbacks psgintf = +{ + psg_set_clock, + psg_write, + psg_read, + psg_reset +}; + +Ym2610b_Emu::Ym2610b_Emu() { opn = 0; } + +Ym2610b_Emu::~Ym2610b_Emu() +{ + if ( opn ) ym2610_shutdown( opn ); +} + +int Ym2610b_Emu::set_rate( int sample_rate, int clock_rate, bool is_2610b ) +{ + if ( opn ) + { + ym2610_shutdown( opn ); + opn = 0; + } + + psg.set_type( is_2610b ? Ay_Apu::Ym2610b : Ay_Apu::Ym2610 ); + + opn = ym2610_init( this, clock_rate, sample_rate, &psgintf ); + if ( !opn ) + return 1; + + this->sample_rate = sample_rate; + psg_clock = clock_rate * 2; + this->is_2610b = is_2610b; + + buffer.set_sample_rate( sample_rate ); + buffer.clock_rate( psg_clock ); + + psg.volume( 1.0 ); + + reset(); + return 0; +} + +void Ym2610b_Emu::reset() +{ + psg.reset(); + ym2610_reset_chip( opn ); + mute_voices( 0 ); +} + +static stream_sample_t* DUMMYBUF[0x02] = {(stream_sample_t*)NULL, (stream_sample_t*)NULL}; + +void Ym2610b_Emu::write0( int addr, int data ) +{ + if ( is_2610b ) ym2610b_update_one( opn, DUMMYBUF, 0 ); + else ym2610_update_one( opn, DUMMYBUF, 0 ); + ym2610_write( opn, 0, addr ); + ym2610_write( opn, 1, data ); +} + +void Ym2610b_Emu::write1( int addr, int data ) +{ + if ( is_2610b ) ym2610b_update_one( opn, DUMMYBUF, 0 ); + else ym2610_update_one( opn, DUMMYBUF, 0 ); + ym2610_write( opn, 2, addr ); + ym2610_write( opn, 3, data ); +} + +void Ym2610b_Emu::write_rom( int rom_id, int size, int start, int length, void * data ) +{ + ym2610_write_pcmrom( opn, rom_id, size, start, length, (const UINT8 *) data ); +} + +void Ym2610b_Emu::mute_voices( int mask ) +{ + ym2610_set_mutemask( opn, mask ); + for ( unsigned i = 0, j = 1 << 6; i < 3; i++, j <<= 1) + { + Blip_Buffer * buf = ( mask & j ) ? NULL : &buffer; + psg.set_output( i, buf ); + } +} + +void Ym2610b_Emu::run( int pair_count, sample_t* out ) +{ + blip_sample_t buf[ 1024 ]; + FMSAMPLE bufL[ 1024 ]; + FMSAMPLE bufR[ 1024 ]; + FMSAMPLE * buffers[2] = { bufL, bufR }; + + blip_time_t psg_end_time = pair_count * psg_clock / sample_rate; + psg.end_frame( psg_end_time ); + buffer.end_frame( psg_end_time ); + + while (pair_count > 0) + { + int todo = pair_count; + if (todo > 1024) todo = 1024; + if ( is_2610b ) ym2610b_update_one( opn, buffers, todo ); + else ym2610_update_one( opn, buffers, todo ); + + int sample_count = buffer.read_samples( buf, todo ); + memset( buf + sample_count, 0, ( todo - sample_count ) * sizeof( blip_sample_t ) ); + + for (int i = 0; i < todo; i++) + { + int output_l, output_r; + int output = buf [i]; + output_l = output + bufL [i]; + output_r = output + bufR [i]; + output_l += out [0]; + output_r += out [1]; + if ( (short)output_l != output_l ) output_l = 0x7FFF ^ ( output_l >> 31 ); + if ( (short)output_r != output_r ) output_r = 0x7FFF ^ ( output_r >> 31 ); + out [0] = output_l; + out [1] = output_r; + out += 2; + } + + pair_count -= todo; + } +} + +void Ym2610b_Emu::psg_set_clock( int clock ) +{ + psg_clock = clock * 2; + buffer.clock_rate( psg_clock ); +} + +void Ym2610b_Emu::psg_write( int addr, int data ) +{ + if ( !(addr & 1) ) psg.write_addr( data ); + else psg.write_data( 0, data ); +} + +int Ym2610b_Emu::psg_read() +{ + return psg.read(); +} + +void Ym2610b_Emu::psg_reset() +{ + psg.reset(); +} diff --git a/Frameworks/GME/gme/Ym2610b_Emu.h b/Frameworks/GME/gme/Ym2610b_Emu.h new file mode 100644 index 000000000..634ffb1ff --- /dev/null +++ b/Frameworks/GME/gme/Ym2610b_Emu.h @@ -0,0 +1,50 @@ +// YM2610B FM sound chip emulator interface + +// Game_Music_Emu $vers +#ifndef YM2610B_EMU_H +#define YM2610B_EMU_H + +#include "Ay_Apu.h" + +class Ym2610b_Emu { + void* opn; + Ay_Apu psg; + Blip_Buffer buffer; + unsigned sample_rate; + unsigned psg_clock; + bool is_2610b; +public: + Ym2610b_Emu(); + ~Ym2610b_Emu(); + + // Sets output chip clock rate, in Hz. Returns non-zero + // if error. + int set_rate( int sample_rate, int clock_rate, bool is_2610b ); + + // Resets to power-up state + void reset(); + + // Mutes voice n if bit n (1 << n) of mask is set + enum { channel_count = 9 }; + void mute_voices( int mask ); + + // Writes data to addr + void write0( int addr, int data ); + void write1( int addr, int data ); + + // Sets ROM type, scales ROM size, then writes length bytes from data at start offset + void write_rom( int rom_id, int size, int start, int length, void * data ); + + // Runs and writes pair_count*2 samples to output + typedef short sample_t; + enum { out_chan_count = 2 }; // stereo + void run( int pair_count, sample_t* out ); + + // SSG interface + inline void psg_set_clock( int clock ); + inline void psg_write( int addr, int data ); + inline int psg_read(); + inline void psg_reset(); +}; + +#endif diff --git a/Frameworks/GME/gme/Ym2612_Emu_Gens.cpp b/Frameworks/GME/gme/Ym2612_Emu_Gens.cpp new file mode 100644 index 000000000..514e7a3d3 --- /dev/null +++ b/Frameworks/GME/gme/Ym2612_Emu_Gens.cpp @@ -0,0 +1,1299 @@ +// Game_Music_Emu $vers. http://www.slack.net/~ant/ + +// Based on Gens 2.10 ym2612.c + +#include "Ym2612_Emu.h" + +#include +#include +#include +#include +#include +#include + +/* Copyright (C) 2002 Stéphane Dallongeville (gens AT consolemul.com) */ +/* Copyright (C) 2004-2007 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +// This is mostly the original source in its C style and all. +// +// Somewhat optimized and simplified. Uses a template to generate the many +// variants of Update_Chan. Rewrote header file. In need of full rewrite by +// someone more familiar with FM sound and the YM2612. Has some inaccuracies +// compared to the Sega Genesis sound, particularly being mixed at such a +// high sample accuracy (the Genesis sounds like it has onn(ly 8 bit samples). +// - Shay + +#ifdef BLARGG_ENABLE_OPTIMIZER + #include BLARGG_ENABLE_OPTIMIZER +#endif + +const int output_bits = 14; + +struct slot_t +{ + const int *DT; // parametre detune + int MUL; // parametre "multiple de frequence" + int TL; // Total Level = volume lorsque l'enveloppe est au plus haut + int TLL; // Total Level ajusted + int SLL; // Sustin Level (ajusted) = volume où l'enveloppe termine sa premiere phase de regression + int KSR_S; // Key Scale Rate Shift = facteur de prise en compte du KSL dans la variations de l'enveloppe + int KSR; // Key Scale Rate = cette valeur est calculee par rapport à la frequence actuelle, elle va influer + // sur les differents parametres de l'enveloppe comme l'attaque, le decay ... comme dans la realite ! + int SEG; // Type enveloppe SSG + const int *AR; // Attack Rate (table pointeur) = Taux d'attaque (AR [KSR]) + const int *DR; // Decay Rate (table pointeur) = Taux pour la regression (DR [KSR]) + const int *SR; // Sustin Rate (table pointeur) = Taux pour le maintien (SR [KSR]) + const int *RR; // Release Rate (table pointeur) = Taux pour le rel'chement (RR [KSR]) + int Fcnt; // Frequency Count = compteur-frequence pour determiner l'amplitude actuelle (SIN [Finc >> 16]) + int Finc; // frequency step = pas d'incrementation du compteur-frequence + // plus le pas est grand, plus la frequence est aïgu (ou haute) + int Ecurp; // Envelope current phase = cette variable permet de savoir dans quelle phase + // de l'enveloppe on se trouve, par exemple phase d'attaque ou phase de maintenue ... + // en fonction de la valeur de cette variable, on va appeler une fonction permettant + // de mettre à jour l'enveloppe courante. + int Ecnt; // Envelope counter = le compteur-enveloppe permet de savoir où l'on se trouve dans l'enveloppe + int Einc; // Envelope step courant + int Ecmp; // Envelope counter limite pour la prochaine phase + int EincA; // Envelope step for Attack = pas d'incrementation du compteur durant la phase d'attaque + // cette valeur est egal à AR [KSR] + int EincD; // Envelope step for Decay = pas d'incrementation du compteur durant la phase de regression + // cette valeur est egal à DR [KSR] + int EincS; // Envelope step for Sustain = pas d'incrementation du compteur durant la phase de maintenue + // cette valeur est egal à SR [KSR] + int EincR; // Envelope step for Release = pas d'incrementation du compteur durant la phase de rel'chement + // cette valeur est egal à RR [KSR] + int *OUTp; // pointeur of SLOT output = pointeur permettant de connecter la sortie de ce slot à l'entree + // d'un autre ou carrement à la sortie de la voie + int INd; // input data of the slot = donnees en entree du slot + int ChgEnM; // Change envelop mask. + int AMS; // AMS depth level of this SLOT = degre de modulation de l'amplitude par le LFO + int AMSon; // AMS enable flag = drapeau d'activation de l'AMS +}; + +struct channel_t +{ + int S0_OUT [4]; // anciennes sorties slot 0 (pour le feed back) + int LEFT; // LEFT enable flag + int RIGHT; // RIGHT enable flag + int ALGO; // Algorythm = determine les connections entre les operateurs + int FB; // shift count of self feed back = degre de "Feed-Back" du SLOT 1 (il est son unique entree) + int FMS; // Frequency Modulation Sensitivity of channel = degre de modulation de la frequence sur la voie par le LFO + int AMS; // Amplitude Modulation Sensitivity of channel = degre de modulation de l'amplitude sur la voie par le LFO + int FNUM [4]; // hauteur frequence de la voie (+ 3 pour le mode special) + int FOCT [4]; // octave de la voie (+ 3 pour le mode special) + int KC [4]; // Key Code = valeur fonction de la frequence (voir KSR pour les slots, KSR = KC >> KSR_S) + slot_t SLOT [4]; // four slot.operators = les 4 slots de la voie + int FFlag; // Frequency step recalculation flag +}; + +struct state_t +{ + int TimerBase; // TimerBase calculation + int Status; // YM2612 Status (timer overflow) + int TimerA; // timerA limit = valeur jusqu'à laquelle le timer A doit compter + int TimerAL; + int TimerAcnt; // timerA counter = valeur courante du Timer A + int TimerB; // timerB limit = valeur jusqu'à laquelle le timer B doit compter + int TimerBL; + int TimerBcnt; // timerB counter = valeur courante du Timer B + int Mode; // Mode actuel des voie 3 et 6 (normal / special) + int DAC; // DAC enabled flag + channel_t CHANNEL [Ym2612_Emu::channel_count]; // Les 6 voies du YM2612 + int REG [2] [0x100]; // Sauvegardes des valeurs de tout les registres, c'est facultatif + // cela nous rend le debuggage plus facile +}; + +#undef PI +#define PI 3.14159265358979323846 + +#define ATTACK 0 +#define DECAY 1 +#define SUBSTAIN 2 +#define RELEASE 3 + +// SIN_LBITS <= 16 +// LFO_HBITS <= 16 +// (SIN_LBITS + SIN_HBITS) <= 26 +// (ENV_LBITS + ENV_HBITS) <= 28 +// (LFO_LBITS + LFO_HBITS) <= 28 + +#define SIN_HBITS 12 // Sinus phase counter int part +#define SIN_LBITS (26 - SIN_HBITS) // Sinus phase counter float part (best setting) + +#if (SIN_LBITS > 16) +#define SIN_LBITS 16 // Can't be greater than 16 bits +#endif + +#define ENV_HBITS 12 // Env phase counter int part +#define ENV_LBITS (28 - ENV_HBITS) // Env phase counter float part (best setting) + +#define LFO_HBITS 10 // LFO phase counter int part +#define LFO_LBITS (28 - LFO_HBITS) // LFO phase counter float part (best setting) + +#define SIN_LENGHT (1 << SIN_HBITS) +#define ENV_LENGHT (1 << ENV_HBITS) +#define LFO_LENGHT (1 << LFO_HBITS) + +#define TL_LENGHT (ENV_LENGHT * 3) // Env + TL scaling + LFO + +#define SIN_MASK (SIN_LENGHT - 1) +#define ENV_MASK (ENV_LENGHT - 1) +#define LFO_MASK (LFO_LENGHT - 1) + +#define ENV_STEP (96.0 / ENV_LENGHT) // ENV_MAX = 96 dB + +#define ENV_ATTACK ((ENV_LENGHT * 0) << ENV_LBITS) +#define ENV_DECAY ((ENV_LENGHT * 1) << ENV_LBITS) +#define ENV_END ((ENV_LENGHT * 2) << ENV_LBITS) + +#define MAX_OUT_BITS (SIN_HBITS + SIN_LBITS + 2) // Modulation = -4 <--> +4 +#define MAX_OUT ((1 << MAX_OUT_BITS) - 1) + +#define PG_CUT_OFF ((int) (78.0 / ENV_STEP)) +//#define ENV_CUT_OFF ((int) (68.0 / ENV_STEP)) + +#define AR_RATE 399128 +#define DR_RATE 5514396 + +//#define AR_RATE 426136 +//#define DR_RATE (AR_RATE * 12) + +#define LFO_FMS_LBITS 9 // FIXED (LFO_FMS_BASE gives somethink as 1) +#define LFO_FMS_BASE ((int) (0.05946309436 * 0.0338 * (double) (1 << LFO_FMS_LBITS))) + +#define S0 0 // Stupid typo of the YM2612 +#define S1 2 +#define S2 1 +#define S3 3 + +struct tables_t +{ + short SIN_TAB [SIN_LENGHT]; // SINUS TABLE (offset into TL TABLE) + int LFOcnt; // LFO counter = compteur-frequence pour le LFO + int LFOinc; // LFO step counter = pas d'incrementation du compteur-frequence du LFO + // plus le pas est grand, plus la frequence est grande + unsigned int AR_TAB [128]; // Attack rate table + unsigned int DR_TAB [96]; // Decay rate table + unsigned int DT_TAB [8] [32]; // Detune table + unsigned int SL_TAB [16]; // Substain level table + unsigned int NULL_RATE [32]; // Table for NULL rate + int LFO_INC_TAB [8]; // LFO step table + + short ENV_TAB [2 * ENV_LENGHT + 8]; // ENV CURVE TABLE (attack & decay) + + short LFO_ENV_TAB [LFO_LENGHT]; // LFO AMS TABLE (adjusted for 11.8 dB) + short LFO_FREQ_TAB [LFO_LENGHT]; // LFO FMS TABLE + int TL_TAB [TL_LENGHT * 2]; // TOTAL LEVEL TABLE (positif and minus) + unsigned int DECAY_TO_ATTACK [ENV_LENGHT]; // Conversion from decay to attack phase + unsigned int FINC_TAB [2048]; // Frequency step table +}; + +static const unsigned char DT_DEF_TAB [4 * 32] = +{ +// FD = 0 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + +// FD = 1 + 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, + 2, 3, 3, 3, 4, 4, 4, 5, 5, 6, 6, 7, 8, 8, 8, 8, + +// FD = 2 + 1, 1, 1, 1, 2, 2, 2, 2, 2, 3, 3, 3, 4, 4, 4, 5, + 5, 6, 6, 7, 8, 8, 9, 10, 11, 12, 13, 14, 16, 16, 16, 16, + +// FD = 3 + 2, 2, 2, 2, 2, 3, 3, 3, 4, 4, 4, 5, 5, 6, 6, 7, + 8 , 8, 9, 10, 11, 12, 13, 14, 16, 17, 19, 20, 22, 22, 22, 22 +}; + +static const unsigned char FKEY_TAB [16] = +{ + 0, 0, 0, 0, + 0, 0, 0, 1, + 2, 3, 3, 3, + 3, 3, 3, 3 +}; + +static const unsigned char LFO_AMS_TAB [4] = +{ + 31, 4, 1, 0 +}; + +static const unsigned char LFO_FMS_TAB [8] = +{ + LFO_FMS_BASE * 0, LFO_FMS_BASE * 1, + LFO_FMS_BASE * 2, LFO_FMS_BASE * 3, + LFO_FMS_BASE * 4, LFO_FMS_BASE * 6, + LFO_FMS_BASE * 12, LFO_FMS_BASE * 24 +}; + +inline void YM2612_Special_Update() { } + +struct Ym2612_Impl +{ + enum { channel_count = Ym2612_Emu::channel_count }; + + state_t YM2612; + int mute_mask; + tables_t g; + + void KEY_ON( channel_t&, int ); + void KEY_OFF( channel_t&, int ); + int SLOT_SET( int, int ); + int CHANNEL_SET( int, int ); + int YM_SET( int, int ); + + void set_rate( double sample_rate, double clock_factor ); + void reset(); + void write0( int addr, int data ); + void write1( int addr, int data ); + void run_timer( int ); + void run( int pair_count, Ym2612_Emu::sample_t [] ); +}; + +void Ym2612_Impl::KEY_ON( channel_t& ch, int nsl) +{ + slot_t *SL = &(ch.SLOT [nsl]); // on recupere le bon pointeur de slot + + if (SL->Ecurp == RELEASE) // la touche est-elle rel'chee ? + { + SL->Fcnt = 0; + + // Fix Ecco 2 splash sound + + SL->Ecnt = (g.DECAY_TO_ATTACK [g.ENV_TAB [SL->Ecnt >> ENV_LBITS]] + ENV_ATTACK) & SL->ChgEnM; + SL->ChgEnM = ~0; + +// SL->Ecnt = g.DECAY_TO_ATTACK [g.ENV_TAB [SL->Ecnt >> ENV_LBITS]] + ENV_ATTACK; +// SL->Ecnt = 0; + + SL->Einc = SL->EincA; + SL->Ecmp = ENV_DECAY; + SL->Ecurp = ATTACK; + } +} + + +void Ym2612_Impl::KEY_OFF(channel_t& ch, int nsl) +{ + slot_t *SL = &(ch.SLOT [nsl]); // on recupere le bon pointeur de slot + + if (SL->Ecurp != RELEASE) // la touche est-elle appuyee ? + { + if (SL->Ecnt < ENV_DECAY) // attack phase ? + { + SL->Ecnt = (g.ENV_TAB [SL->Ecnt >> ENV_LBITS] << ENV_LBITS) + ENV_DECAY; + } + + SL->Einc = SL->EincR; + SL->Ecmp = ENV_END; + SL->Ecurp = RELEASE; + } +} + + +int Ym2612_Impl::SLOT_SET( int Adr, int data ) +{ + int nch = Adr & 3; + if ( nch == 3 ) + return 1; + + channel_t& ch = YM2612.CHANNEL [nch + (Adr & 0x100 ? 3 : 0)]; + slot_t& sl = ch.SLOT [(Adr >> 2) & 3]; + + switch ( Adr & 0xF0 ) + { + case 0x30: + if ( (sl.MUL = (data & 0x0F)) != 0 ) sl.MUL <<= 1; + else sl.MUL = 1; + + sl.DT = (int*) g.DT_TAB [(data >> 4) & 7]; + + ch.SLOT [0].Finc = -1; + + break; + + case 0x40: + sl.TL = data & 0x7F; + + // SOR2 do a lot of TL adjustement and this fix R.Shinobi jump sound... + YM2612_Special_Update(); + +#if ((ENV_HBITS - 7) < 0) + sl.TLL = sl.TL >> (7 - ENV_HBITS); +#else + sl.TLL = sl.TL << (ENV_HBITS - 7); +#endif + + break; + + case 0x50: + sl.KSR_S = 3 - (data >> 6); + + ch.SLOT [0].Finc = -1; + + if (data &= 0x1F) sl.AR = (int*) &g.AR_TAB [data << 1]; + else sl.AR = (int*) &g.NULL_RATE [0]; + + sl.EincA = sl.AR [sl.KSR]; + if (sl.Ecurp == ATTACK) sl.Einc = sl.EincA; + break; + + case 0x60: + if ( (sl.AMSon = (data & 0x80)) != 0 ) sl.AMS = ch.AMS; + else sl.AMS = 31; + + if (data &= 0x1F) sl.DR = (int*) &g.DR_TAB [data << 1]; + else sl.DR = (int*) &g.NULL_RATE [0]; + + sl.EincD = sl.DR [sl.KSR]; + if (sl.Ecurp == DECAY) sl.Einc = sl.EincD; + break; + + case 0x70: + if (data &= 0x1F) sl.SR = (int*) &g.DR_TAB [data << 1]; + else sl.SR = (int*) &g.NULL_RATE [0]; + + sl.EincS = sl.SR [sl.KSR]; + if ((sl.Ecurp == SUBSTAIN) && (sl.Ecnt < ENV_END)) sl.Einc = sl.EincS; + break; + + case 0x80: + sl.SLL = g.SL_TAB [data >> 4]; + + sl.RR = (int*) &g.DR_TAB [((data & 0xF) << 2) + 2]; + + sl.EincR = sl.RR [sl.KSR]; + if ((sl.Ecurp == RELEASE) && (sl.Ecnt < ENV_END)) sl.Einc = sl.EincR; + break; + + case 0x90: + // SSG-EG envelope shapes : + /* + E At Al H + + 1 0 0 0 \\\\ + 1 0 0 1 \___ + 1 0 1 0 \/\/ + 1 0 1 1 \ + 1 1 0 0 //// + 1 1 0 1 / + 1 1 1 0 /\/\ + 1 1 1 1 /___ + + E = SSG-EG enable + At = Start negate + Al = Altern + H = Hold */ + + if(data & 0x08) sl.SEG = data & 0x0F; + else sl.SEG = 0; + break; + } + + return 0; +} + + +int Ym2612_Impl::CHANNEL_SET( int Adr, int data ) +{ + int num = Adr & 3; + if ( num == 3 ) + return 1; + + channel_t& ch = YM2612.CHANNEL [num + (Adr & 0x100 ? 3 : 0)]; + + switch ( Adr & 0xFC ) + { + case 0xA0: + YM2612_Special_Update(); + + ch.FNUM [0] = (ch.FNUM [0] & 0x700) + data; + ch.KC [0] = (ch.FOCT [0] << 2) | FKEY_TAB [ch.FNUM [0] >> 7]; + + ch.SLOT [0].Finc = -1; + break; + + case 0xA4: + YM2612_Special_Update(); + + ch.FNUM [0] = (ch.FNUM [0] & 0x0FF) + ((data & 0x07) << 8); + ch.FOCT [0] = (data & 0x38) >> 3; + ch.KC [0] = (ch.FOCT [0] << 2) | FKEY_TAB [ch.FNUM [0] >> 7]; + + ch.SLOT [0].Finc = -1; + break; + + case 0xA8: + if ( Adr < 0x100 ) + { + num++; + + YM2612_Special_Update(); + + YM2612.CHANNEL [2].FNUM [num] = (YM2612.CHANNEL [2].FNUM [num] & 0x700) + data; + YM2612.CHANNEL [2].KC [num] = (YM2612.CHANNEL [2].FOCT [num] << 2) | + FKEY_TAB [YM2612.CHANNEL [2].FNUM [num] >> 7]; + + YM2612.CHANNEL [2].SLOT [0].Finc = -1; + } + break; + + case 0xAC: + if ( Adr < 0x100 ) + { + num++; + + YM2612_Special_Update(); + + YM2612.CHANNEL [2].FNUM [num] = (YM2612.CHANNEL [2].FNUM [num] & 0x0FF) + ((data & 0x07) << 8); + YM2612.CHANNEL [2].FOCT [num] = (data & 0x38) >> 3; + YM2612.CHANNEL [2].KC [num] = (YM2612.CHANNEL [2].FOCT [num] << 2) | + FKEY_TAB [YM2612.CHANNEL [2].FNUM [num] >> 7]; + + YM2612.CHANNEL [2].SLOT [0].Finc = -1; + } + break; + + case 0xB0: + if ( ch.ALGO != (data & 7) ) + { + // Fix VectorMan 2 heli sound (level 1) + YM2612_Special_Update(); + + ch.ALGO = data & 7; + + ch.SLOT [0].ChgEnM = 0; + ch.SLOT [1].ChgEnM = 0; + ch.SLOT [2].ChgEnM = 0; + ch.SLOT [3].ChgEnM = 0; + } + + ch.FB = 9 - ((data >> 3) & 7); // Real thing ? + +// if (ch.FB = ((data >> 3) & 7)) ch.FB = 9 - ch.FB; // Thunder force 4 (music stage 8), Gynoug, Aladdin bug sound... +// else ch.FB = 31; + break; + + case 0xB4: { + YM2612_Special_Update(); + + ch.LEFT = 0 - ((data >> 7) & 1); + ch.RIGHT = 0 - ((data >> 6) & 1); + + ch.AMS = LFO_AMS_TAB [(data >> 4) & 3]; + ch.FMS = LFO_FMS_TAB [data & 7]; + + for ( int i = 0; i < 4; i++ ) + { + slot_t& sl = ch.SLOT [i]; + sl.AMS = (sl.AMSon ? ch.AMS : 31); + } + break; + } + } + + return 0; +} + + +int Ym2612_Impl::YM_SET(int Adr, int data) +{ + switch ( Adr ) + { + case 0x22: + if (data & 8) // LFO enable + { + // Cool Spot music 1, LFO modified severals time which + // distord the sound, have to check that on a real genesis... + + g.LFOinc = g.LFO_INC_TAB [data & 7]; + } + else + { + g.LFOinc = g.LFOcnt = 0; + } + break; + + case 0x24: + YM2612.TimerA = (YM2612.TimerA & 0x003) | (((int) data) << 2); + + if (YM2612.TimerAL != (1024 - YM2612.TimerA) << 12) + { + YM2612.TimerAcnt = YM2612.TimerAL = (1024 - YM2612.TimerA) << 12; + } + break; + + case 0x25: + YM2612.TimerA = (YM2612.TimerA & 0x3FC) | (data & 3); + + if (YM2612.TimerAL != (1024 - YM2612.TimerA) << 12) + { + YM2612.TimerAcnt = YM2612.TimerAL = (1024 - YM2612.TimerA) << 12; + } + break; + + case 0x26: + YM2612.TimerB = data; + + if (YM2612.TimerBL != (256 - YM2612.TimerB) << (4 + 12)) + { + YM2612.TimerBcnt = YM2612.TimerBL = (256 - YM2612.TimerB) << (4 + 12); + } + break; + + case 0x27: + // Parametre divers + // b7 = CSM MODE + // b6 = 3 slot mode + // b5 = reset b + // b4 = reset a + // b3 = timer enable b + // b2 = timer enable a + // b1 = load b + // b0 = load a + + if ((data ^ YM2612.Mode) & 0x40) + { + // We changed the channel 2 mode, so recalculate phase step + // This fix the punch sound in Street of Rage 2 + + YM2612_Special_Update(); + + YM2612.CHANNEL [2].SLOT [0].Finc = -1; // recalculate phase step + } + +// if ((data & 2) && (YM2612.Status & 2)) YM2612.TimerBcnt = YM2612.TimerBL; +// if ((data & 1) && (YM2612.Status & 1)) YM2612.TimerAcnt = YM2612.TimerAL; + +// YM2612.Status &= (~data >> 4); // Reset du Status au cas ou c'est demande + YM2612.Status &= (~data >> 4) & (data >> 2); // Reset Status + + YM2612.Mode = data; + break; + + case 0x28: { + int nch = data & 3; + if ( nch == 3 ) + return 1; + if ( data & 4 ) + nch += 3; + channel_t& ch = YM2612.CHANNEL [nch]; + + YM2612_Special_Update(); + + if (data & 0x10) KEY_ON(ch, S0); // On appuie sur la touche pour le slot 1 + else KEY_OFF(ch, S0); // On rel'che la touche pour le slot 1 + if (data & 0x20) KEY_ON(ch, S1); // On appuie sur la touche pour le slot 3 + else KEY_OFF(ch, S1); // On rel'che la touche pour le slot 3 + if (data & 0x40) KEY_ON(ch, S2); // On appuie sur la touche pour le slot 2 + else KEY_OFF(ch, S2); // On rel'che la touche pour le slot 2 + if (data & 0x80) KEY_ON(ch, S3); // On appuie sur la touche pour le slot 4 + else KEY_OFF(ch, S3); // On rel'che la touche pour le slot 4 + break; + } + + case 0x2B: + if (YM2612.DAC ^ (data & 0x80)) YM2612_Special_Update(); + + YM2612.DAC = data & 0x80; // activation/desactivation du DAC + break; + } + + return 0; +} + +void Ym2612_Impl::set_rate( double sample_rate, double clock_rate ) +{ + assert( sample_rate ); + assert( !clock_rate || clock_rate > sample_rate ); + + int i; + + // 144 = 12 * (prescale * 2) = 12 * 6 * 2 + // prescale set to 6 by default + + double Frequence = (clock_rate ? clock_rate / sample_rate / 144.0 : 1.0); + if ( fabs( Frequence - 1.0 ) < 0.0000001 ) + Frequence = 1.0; + YM2612.TimerBase = int (Frequence * 4096.0); + + // Tableau TL : + // [0 - 4095] = +output [4095 - ...] = +output overflow (fill with 0) + // [12288 - 16383] = -output [16384 - ...] = -output overflow (fill with 0) + + for ( i = 0; i < TL_LENGHT; i++ ) + { + if (i >= PG_CUT_OFF) // YM2612 cut off sound after 78 dB (14 bits output ?) + { + g.TL_TAB [TL_LENGHT + i] = g.TL_TAB [i] = 0; + } + else + { + // Decibel -> Voltage + g.TL_TAB [i] = int (MAX_OUT / pow( 10.0, ENV_STEP / 20.0f * i )); + g.TL_TAB [TL_LENGHT + i] = -g.TL_TAB [i]; + } + } + + // Tableau SIN : + // g.SIN_TAB [x] [y] = sin(x) * y; + // x = phase and y = volume + + g.SIN_TAB [0] = g.SIN_TAB [SIN_LENGHT / 2] = PG_CUT_OFF; + + for ( i = 1; i <= SIN_LENGHT / 4; i++ ) + { + // Sinus in dB + double x = 20 * log10( 1 / sin( 2.0 * PI * i / SIN_LENGHT ) ); // convert to dB + + int j = (int) (x / ENV_STEP); // Get TL range + + if (j > PG_CUT_OFF) j = (int) PG_CUT_OFF; + + g.SIN_TAB [i] = g.SIN_TAB [(SIN_LENGHT / 2) - i] = j; + g.SIN_TAB [(SIN_LENGHT / 2) + i] = g.SIN_TAB [SIN_LENGHT - i] = TL_LENGHT + j; + } + + // Tableau LFO (LFO wav) : + + for ( i = 0; i < LFO_LENGHT; i++ ) + { + double x = 1 + sin( 2.0 * PI * i * (1.0 / LFO_LENGHT) ); // Sinus + x *= 11.8 / ENV_STEP / 2; // ajusted to MAX enveloppe modulation + + g.LFO_ENV_TAB [i] = (int) x; + + x = sin( 2.0 * PI * i * (1.0 / LFO_LENGHT) ); // Sinus + x *= (1 << (LFO_HBITS - 1)) - 1; + + g.LFO_FREQ_TAB [i] = (int) x; + } + + // Tableau Enveloppe : + // g.ENV_TAB [0] -> g.ENV_TAB [ENV_LENGHT - 1] = attack curve + // g.ENV_TAB [ENV_LENGHT] -> g.ENV_TAB [2 * ENV_LENGHT - 1] = decay curve + + for ( i = 0; i < ENV_LENGHT; i++ ) + { + // Attack curve (x^8 - music level 2 Vectorman 2) + double x = pow( ((ENV_LENGHT - 1) - i) / (double) ENV_LENGHT, 8.0 ); + x *= ENV_LENGHT; + + g.ENV_TAB [i] = (int) x; + + // Decay curve (just linear) + g.ENV_TAB [ENV_LENGHT + i] = i; + } + for ( i = 0; i < 8; i++ ) + g.ENV_TAB [i + ENV_LENGHT * 2] = 0; + + g.ENV_TAB [ENV_END >> ENV_LBITS] = ENV_LENGHT - 1; // for the stopped state + + // Tableau pour la conversion Attack -> Decay and Decay -> Attack + + int j = ENV_LENGHT - 1; + for ( i = 0; i < ENV_LENGHT; i++ ) + { + while ( j && g.ENV_TAB [j] < i ) + j--; + + g.DECAY_TO_ATTACK [i] = j << ENV_LBITS; + } + + // Tableau pour le Substain Level + + for ( i = 0; i < 15; i++ ) + { + double x = i * 3 / ENV_STEP; // 3 and not 6 (Mickey Mania first music for test) + + g.SL_TAB [i] = ((int) x << ENV_LBITS) + ENV_DECAY; + } + + g.SL_TAB [15] = ((ENV_LENGHT - 1) << ENV_LBITS) + ENV_DECAY; // special case : volume off + + // Tableau Frequency Step + { + // 0.5 because MUL = value * 2 + #if SIN_LBITS + SIN_HBITS - (21 - 7) < 0 + double const factor = 0.5 / (1 << ((21 - 7) - SIN_LBITS - SIN_HBITS)) * Frequence; + #else + double const factor = 0.5 * (1 << (SIN_LBITS + SIN_HBITS - (21 - 7))) * Frequence; + #endif + for ( i = 0; i < 2048; i++ ) + g.FINC_TAB [i] = unsigned (i * factor); + } + + // Tableaux Attack & Decay Rate + + for ( i = 0; i < 4; i++ ) + { + g.AR_TAB [i] = 0; + g.DR_TAB [i] = 0; + } + + for ( i = 0; i < 60; i++ ) + { + double x = + (1.0 + ((i & 3) * 0.25)) * // bits 0-1 : x1.00, x1.25, x1.50, x1.75 + (ENV_LENGHT << ENV_LBITS) * // on ajuste pour le tableau g.ENV_TAB + Frequence * + (1 << (i >> 2)); // bits 2-5 : shift bits (x2^0 - x2^15) + + g.AR_TAB [i + 4] = (unsigned int) (x / AR_RATE); + g.DR_TAB [i + 4] = (unsigned int) (x / DR_RATE); + } + + for ( i = 64; i < 96; i++ ) + { + g.AR_TAB [i] = g.AR_TAB [63]; + g.DR_TAB [i] = g.DR_TAB [63]; + + g.NULL_RATE [i - 64] = 0; + } + + for ( i = 96; i < 128; i++ ) + g.AR_TAB [i] = 0; + + // Tableau Detune + { + #if SIN_LBITS + SIN_HBITS - 21 < 0 + double const factor = 1.0 / (1 << (21 - SIN_LBITS - SIN_HBITS)) * Frequence; + #else + double const factor = (1 << (SIN_LBITS + SIN_HBITS - 21)) * Frequence; + #endif + for ( i = 0; i < 4; i++ ) + { + for ( int j = 0; j < 32; j++ ) + { + double y = DT_DEF_TAB [(i << 5) + j] * factor; + + g.DT_TAB [i + 0] [j] = (int) y; + g.DT_TAB [i + 4] [j] = (int) -y; + } + } + } + + // Tableau LFO + g.LFO_INC_TAB [0] = unsigned (3.98 * (1 << (LFO_HBITS + LFO_LBITS)) / sample_rate); + g.LFO_INC_TAB [1] = unsigned (5.56 * (1 << (LFO_HBITS + LFO_LBITS)) / sample_rate); + g.LFO_INC_TAB [2] = unsigned (6.02 * (1 << (LFO_HBITS + LFO_LBITS)) / sample_rate); + g.LFO_INC_TAB [3] = unsigned (6.37 * (1 << (LFO_HBITS + LFO_LBITS)) / sample_rate); + g.LFO_INC_TAB [4] = unsigned (6.88 * (1 << (LFO_HBITS + LFO_LBITS)) / sample_rate); + g.LFO_INC_TAB [5] = unsigned (9.63 * (1 << (LFO_HBITS + LFO_LBITS)) / sample_rate); + g.LFO_INC_TAB [6] = unsigned (48.1 * (1 << (LFO_HBITS + LFO_LBITS)) / sample_rate); + g.LFO_INC_TAB [7] = unsigned (72.2 * (1 << (LFO_HBITS + LFO_LBITS)) / sample_rate); + + reset(); +} + +const char* Ym2612_Emu::set_rate( double sample_rate, double clock_rate ) +{ + if ( !impl ) + { + impl = (Ym2612_Impl*) malloc( sizeof *impl ); + if ( !impl ) + return "Out of memory"; + impl->mute_mask = 0; + } + memset( &impl->YM2612, 0, sizeof impl->YM2612 ); + + impl->set_rate( sample_rate, clock_rate ); + + return 0; +} + +Ym2612_Emu::~Ym2612_Emu() +{ + free( impl ); +} + +inline void Ym2612_Impl::write0( int opn_addr, int data ) +{ + assert( (unsigned) data <= 0xFF ); + + if ( opn_addr < 0x30 ) + { + YM2612.REG [0] [opn_addr] = data; + YM_SET( opn_addr, data ); + } + else if ( YM2612.REG [0] [opn_addr] != data ) + { + YM2612.REG [0] [opn_addr] = data; + + if ( opn_addr < 0xA0 ) + SLOT_SET( opn_addr, data ); + else + CHANNEL_SET( opn_addr, data ); + } +} + +inline void Ym2612_Impl::write1( int opn_addr, int data ) +{ + assert( (unsigned) data <= 0xFF ); + + if ( opn_addr >= 0x30 && YM2612.REG [1] [opn_addr] != data ) + { + YM2612.REG [1] [opn_addr] = data; + + if ( opn_addr < 0xA0 ) + SLOT_SET( opn_addr + 0x100, data ); + else + CHANNEL_SET( opn_addr + 0x100, data ); + } +} + +void Ym2612_Emu::reset() +{ + impl->reset(); +} + +void Ym2612_Impl::reset() +{ + g.LFOcnt = 0; + YM2612.TimerA = 0; + YM2612.TimerAL = 0; + YM2612.TimerAcnt = 0; + YM2612.TimerB = 0; + YM2612.TimerBL = 0; + YM2612.TimerBcnt = 0; + YM2612.DAC = 0; + + YM2612.Status = 0; + + int i; + for ( i = 0; i < channel_count; i++ ) + { + channel_t& ch = YM2612.CHANNEL [i]; + + ch.LEFT = ~0; + ch.RIGHT = ~0; + ch.ALGO = 0; + ch.FB = 31; + ch.FMS = 0; + ch.AMS = 0; + + for ( int j = 0 ;j < 4 ; j++ ) + { + ch.S0_OUT [j] = 0; + ch.FNUM [j] = 0; + ch.FOCT [j] = 0; + ch.KC [j] = 0; + + ch.SLOT [j].Fcnt = 0; + ch.SLOT [j].Finc = 0; + ch.SLOT [j].Ecnt = ENV_END; // Put it at the end of Decay phase... + ch.SLOT [j].Einc = 0; + ch.SLOT [j].Ecmp = 0; + ch.SLOT [j].Ecurp = RELEASE; + + ch.SLOT [j].ChgEnM = 0; + } + } + + for ( i = 0; i < 0x100; i++ ) + { + YM2612.REG [0] [i] = -1; + YM2612.REG [1] [i] = -1; + } + + for ( i = 0xB6; i >= 0xB4; i-- ) + { + write0( i, 0xC0 ); + write1( i, 0xC0 ); + } + + for ( i = 0xB2; i >= 0x22; i-- ) + { + write0( i, 0 ); + write1( i, 0 ); + } + + write0( 0x2A, 0x80 ); +} + +void Ym2612_Emu::write0( int addr, int data ) +{ + impl->write0( addr, data ); +} + +void Ym2612_Emu::write1( int addr, int data ) +{ + impl->write1( addr, data ); +} + +void Ym2612_Emu::mute_voices( int mask ) { impl->mute_mask = mask; } + +static void update_envelope_( slot_t* sl ) +{ + switch ( sl->Ecurp ) + { + case 0: + // Env_Attack_Next + + // Verified with Gynoug even in HQ (explode SFX) + sl->Ecnt = ENV_DECAY; + + sl->Einc = sl->EincD; + sl->Ecmp = sl->SLL; + sl->Ecurp = DECAY; + break; + + case 1: + // Env_Decay_Next + + // Verified with Gynoug even in HQ (explode SFX) + sl->Ecnt = sl->SLL; + + sl->Einc = sl->EincS; + sl->Ecmp = ENV_END; + sl->Ecurp = SUBSTAIN; + break; + + case 2: + // Env_Substain_Next(slot_t *SL) + if(sl->SEG & 8) // SSG envelope type + { + if(sl->SEG & 1) + { + sl->Ecnt = ENV_END; + sl->Einc = 0; + sl->Ecmp = ENV_END + 1; + } + else + { + // re KEY ON + + // SL->Fcnt = 0; + // SL->ChgEnM = 0xFFFFFFFF; + + sl->Ecnt = 0; + sl->Einc = sl->EincA; + sl->Ecmp = ENV_DECAY; + sl->Ecurp = ATTACK; + } + + sl->SEG ^= (sl->SEG & 2) << 1; + + break; + } + // fall through + + case 3: + // Env_Release_Next + sl->Ecnt = ENV_END; + sl->Einc = 0; + sl->Ecmp = ENV_END + 1; + break; + + // default: no op + } +} + +inline void update_envelope( slot_t& sl ) +{ + int ecmp = sl.Ecmp; + if ( (sl.Ecnt += sl.Einc) >= ecmp ) + update_envelope_( &sl ); +} + +template +struct ym2612_update_chan { + static void func( tables_t&, channel_t&, Ym2612_Emu::sample_t [], int ); +}; + +typedef void (*ym2612_update_chan_t)( tables_t&, channel_t&, Ym2612_Emu::sample_t*, int ); + +template +void ym2612_update_chan::func( tables_t& g, channel_t& ch, + Ym2612_Emu::sample_t* buf, int length ) +{ + int not_end = ch.SLOT [S3].Ecnt - ENV_END; + + // algo is a compile-time constant, so all conditions based on it are resolved + // during compilation + + // special cases + if ( algo == 7 ) + not_end |= ch.SLOT [S0].Ecnt - ENV_END; + + if ( algo >= 5 ) + not_end |= ch.SLOT [S2].Ecnt - ENV_END; + + if ( algo >= 4 ) + not_end |= ch.SLOT [S1].Ecnt - ENV_END; + + int CH_S0_OUT_1 = ch.S0_OUT [1]; + + int in0 = ch.SLOT [S0].Fcnt; + int in1 = ch.SLOT [S1].Fcnt; + int in2 = ch.SLOT [S2].Fcnt; + int in3 = ch.SLOT [S3].Fcnt; + + int YM2612_LFOinc = g.LFOinc; + int YM2612_LFOcnt = g.LFOcnt + YM2612_LFOinc; + + if ( !not_end ) + return; + + do + { + // envelope + int const env_LFO = g.LFO_ENV_TAB [YM2612_LFOcnt >> LFO_LBITS & LFO_MASK]; + + short const* const ENV_TAB = g.ENV_TAB; + + #define CALC_EN( x ) \ + int temp##x = ENV_TAB [ch.SLOT [S##x].Ecnt >> ENV_LBITS]; \ + int en##x; \ + if (ch.SLOT [S##x].SEG & 4) { \ + if (temp##x > ENV_MASK) en##x = 0; \ + else en##x = (temp##x ^ ENV_MASK) + ch.SLOT [S##x].TLL + (env_LFO >> ch.SLOT [S##x].AMS); \ + } else en##x = temp##x + ch.SLOT [S##x].TLL + (env_LFO >> ch.SLOT [S##x].AMS); + + CALC_EN( 0 ) + CALC_EN( 1 ) + CALC_EN( 2 ) + CALC_EN( 3 ) + + int const* const TL_TAB = g.TL_TAB; + + #define SINT( i, o ) (TL_TAB [g.SIN_TAB [(i)] + (o)]) + + // feedback + int CH_S0_OUT_0 = ch.S0_OUT [0]; + { + int temp = in0 + ((CH_S0_OUT_0 + CH_S0_OUT_1) >> ch.FB); + CH_S0_OUT_1 = CH_S0_OUT_0; + CH_S0_OUT_0 = SINT( (temp >> SIN_LBITS) & SIN_MASK, en0 ); + } + + int CH_OUTd; + if ( algo == 0 ) + { + int temp = in1 + CH_S0_OUT_1; + temp = in2 + SINT( (temp >> SIN_LBITS) & SIN_MASK, en1 ); + temp = in3 + SINT( (temp >> SIN_LBITS) & SIN_MASK, en2 ); + CH_OUTd = SINT( (temp >> SIN_LBITS) & SIN_MASK, en3 ); + } + else if ( algo == 1 ) + { + int temp = in2 + CH_S0_OUT_1 + SINT( (in1 >> SIN_LBITS) & SIN_MASK, en1 ); + temp = in3 + SINT( (temp >> SIN_LBITS) & SIN_MASK, en2 ); + CH_OUTd = SINT( (temp >> SIN_LBITS) & SIN_MASK, en3 ); + } + else if ( algo == 2 ) + { + int temp = in2 + SINT( (in1 >> SIN_LBITS) & SIN_MASK, en1 ); + temp = in3 + CH_S0_OUT_1 + SINT( (temp >> SIN_LBITS) & SIN_MASK, en2 ); + CH_OUTd = SINT( (temp >> SIN_LBITS) & SIN_MASK, en3 ); + } + else if ( algo == 3 ) + { + int temp = in1 + CH_S0_OUT_1; + temp = in3 + SINT( (temp >> SIN_LBITS) & SIN_MASK, en1 ) + + SINT( (in2 >> SIN_LBITS) & SIN_MASK, en2 ); + CH_OUTd = SINT( (temp >> SIN_LBITS) & SIN_MASK, en3 ); + } + else if ( algo == 4 ) + { + int temp = in3 + SINT( (in2 >> SIN_LBITS) & SIN_MASK, en2 ); + CH_OUTd = SINT( (temp >> SIN_LBITS) & SIN_MASK, en3 ) + + SINT( ((in1 + CH_S0_OUT_1) >> SIN_LBITS) & SIN_MASK, en1 ); + //DO_LIMIT + } + else if ( algo == 5 ) + { + int temp = CH_S0_OUT_1; + CH_OUTd = SINT( ((in3 + temp) >> SIN_LBITS) & SIN_MASK, en3 ) + + SINT( ((in1 + temp) >> SIN_LBITS) & SIN_MASK, en1 ) + + SINT( ((in2 + temp) >> SIN_LBITS) & SIN_MASK, en2 ); + //DO_LIMIT + } + else if ( algo == 6 ) + { + CH_OUTd = SINT( (in3 >> SIN_LBITS) & SIN_MASK, en3 ) + + SINT( ((in1 + CH_S0_OUT_1) >> SIN_LBITS) & SIN_MASK, en1 ) + + SINT( (in2 >> SIN_LBITS) & SIN_MASK, en2 ); + //DO_LIMIT + } + else if ( algo == 7 ) + { + CH_OUTd = SINT( (in3 >> SIN_LBITS) & SIN_MASK, en3 ) + + SINT( (in1 >> SIN_LBITS) & SIN_MASK, en1 ) + + SINT( (in2 >> SIN_LBITS) & SIN_MASK, en2 ) + CH_S0_OUT_1; + //DO_LIMIT + } + + CH_OUTd *= 3; + CH_OUTd >>= MAX_OUT_BITS - output_bits + 2; + + // update phase + unsigned freq_LFO = ((g.LFO_FREQ_TAB [YM2612_LFOcnt >> LFO_LBITS & LFO_MASK] * + ch.FMS) >> (LFO_HBITS - 1 + 1)) + (1 << (LFO_FMS_LBITS - 1)); + YM2612_LFOcnt += YM2612_LFOinc; + in0 += (ch.SLOT [S0].Finc * freq_LFO) >> (LFO_FMS_LBITS - 1); + in1 += (ch.SLOT [S1].Finc * freq_LFO) >> (LFO_FMS_LBITS - 1); + in2 += (ch.SLOT [S2].Finc * freq_LFO) >> (LFO_FMS_LBITS - 1); + in3 += (ch.SLOT [S3].Finc * freq_LFO) >> (LFO_FMS_LBITS - 1); + + int t0 = buf [0] + (CH_OUTd & ch.LEFT); + int t1 = buf [1] + (CH_OUTd & ch.RIGHT); + + update_envelope( ch.SLOT [0] ); + update_envelope( ch.SLOT [1] ); + update_envelope( ch.SLOT [2] ); + update_envelope( ch.SLOT [3] ); + + ch.S0_OUT [0] = CH_S0_OUT_0; + buf [0] = t0; + buf [1] = t1; + buf += 2; + } + while ( --length ); + + ch.S0_OUT [1] = CH_S0_OUT_1; + + ch.SLOT [S0].Fcnt = in0; + ch.SLOT [S1].Fcnt = in1; + ch.SLOT [S2].Fcnt = in2; + ch.SLOT [S3].Fcnt = in3; +} + +static const ym2612_update_chan_t UPDATE_CHAN [8] = { + &ym2612_update_chan<0>::func, + &ym2612_update_chan<1>::func, + &ym2612_update_chan<2>::func, + &ym2612_update_chan<3>::func, + &ym2612_update_chan<4>::func, + &ym2612_update_chan<5>::func, + &ym2612_update_chan<6>::func, + &ym2612_update_chan<7>::func +}; + +void Ym2612_Impl::run_timer( int length ) +{ + int const step = 6; + int remain = length; + do + { + int n = step; + if ( n > remain ) + n = remain; + remain -= n; + + int i = n * YM2612.TimerBase; + if (YM2612.Mode & 1) // Timer A ON ? + { + // if ((YM2612.TimerAcnt -= 14073) <= 0) // 13879=NTSC (old: 14475=NTSC 14586=PAL) + if ((YM2612.TimerAcnt -= i) <= 0) + { + // timer a overflow + + YM2612.Status |= (YM2612.Mode & 0x04) >> 2; + YM2612.TimerAcnt += YM2612.TimerAL; + + if (YM2612.Mode & 0x80) + { + KEY_ON( YM2612.CHANNEL [2], 0 ); + KEY_ON( YM2612.CHANNEL [2], 1 ); + KEY_ON( YM2612.CHANNEL [2], 2 ); + KEY_ON( YM2612.CHANNEL [2], 3 ); + } + } + } + + if (YM2612.Mode & 2) // Timer B ON ? + { + // if ((YM2612.TimerBcnt -= 14073) <= 0) // 13879=NTSC (old: 14475=NTSC 14586=PAL) + if ((YM2612.TimerBcnt -= i) <= 0) + { + // timer b overflow + YM2612.Status |= (YM2612.Mode & 0x08) >> 2; + YM2612.TimerBcnt += YM2612.TimerBL; + } + } + } + while ( remain > 0 ); +} + +void Ym2612_Impl::run( int pair_count, Ym2612_Emu::sample_t out [] ) +{ + if ( pair_count <= 0 ) + return; + + if ( YM2612.Mode & 3 ) + run_timer( pair_count ); + + // Mise à jour des pas des compteurs-frequences s'ils ont ete modifies + + for ( int chi = 0; chi < channel_count; chi++ ) + { + channel_t& ch = YM2612.CHANNEL [chi]; + if ( ch.SLOT [0].Finc != -1 ) + continue; + + int i2 = 0; + if ( chi == 2 && (YM2612.Mode & 0x40) ) + i2 = 2; + + for ( int i = 0; i < 4; i++ ) + { + // static int seq [4] = { 2, 1, 3, 0 }; + // if ( i2 ) i2 = seq [i]; + + slot_t& sl = ch.SLOT [i]; + int finc = g.FINC_TAB [ch.FNUM [i2]] >> (7 - ch.FOCT [i2]); + int ksr = ch.KC [i2] >> sl.KSR_S; // keycode attenuation + sl.Finc = (finc + sl.DT [ch.KC [i2]]) * sl.MUL; + if (sl.KSR != ksr) // si le KSR a change alors + { // les differents taux pour l'enveloppe sont mis à jour + sl.KSR = ksr; + + sl.EincA = sl.AR [ksr]; + sl.EincD = sl.DR [ksr]; + sl.EincS = sl.SR [ksr]; + sl.EincR = sl.RR [ksr]; + + if (sl.Ecurp == ATTACK) + { + sl.Einc = sl.EincA; + } + else if (sl.Ecurp == DECAY) + { + sl.Einc = sl.EincD; + } + else if (sl.Ecnt < ENV_END) + { + if (sl.Ecurp == SUBSTAIN) + sl.Einc = sl.EincS; + else if (sl.Ecurp == RELEASE) + sl.Einc = sl.EincR; + } + } + + if ( i2 ) + i2 = (i2 ^ 2) ^ (i2 >> 1); + } + } + + for ( int i = 0; i < channel_count; i++ ) + { + if ( !(mute_mask & (1 << i)) && (i != 5 || !YM2612.DAC) ) + UPDATE_CHAN [YM2612.CHANNEL [i].ALGO]( g, YM2612.CHANNEL [i], out, pair_count ); + } + + g.LFOcnt += g.LFOinc * pair_count; +} + +void Ym2612_Emu::run( int pair_count, sample_t out [] ) { impl->run( pair_count, out ); } diff --git a/Frameworks/GME/gme/Ym2612_Emu_MAME.cpp b/Frameworks/GME/gme/Ym2612_Emu_MAME.cpp new file mode 100644 index 000000000..8765d2906 --- /dev/null +++ b/Frameworks/GME/gme/Ym2612_Emu_MAME.cpp @@ -0,0 +1,87 @@ +// Game_Music_Emu $vers. http://www.slack.net/~ant/ + +#include "Ym2612_Emu.h" +#include "fm.h" + +#include "blargg_errors.h" + +// Ym2612_Emu + +Ym2612_Emu::~Ym2612_Emu() +{ + if ( impl ) + ym2612_shutdown( impl ); +} + +const char* Ym2612_Emu::set_rate( double sample_rate, double clock_rate ) +{ + if ( impl ) + { + ym2612_shutdown( impl ); + impl = 0; + } + + if ( !clock_rate ) + clock_rate = sample_rate * 144.; + + impl = ym2612_init( (long) (clock_rate + 0.5), (long) (sample_rate + 0.5) ); + if ( !impl ) + return blargg_err_memory; + + return 0; +} + +void Ym2612_Emu::reset() +{ + ym2612_reset_chip( impl ); +} + +static stream_sample_t* DUMMYBUF[0x02] = {(stream_sample_t*)NULL, (stream_sample_t*)NULL}; + +void Ym2612_Emu::write0( int addr, int data ) +{ + ym2612_update_one( impl, DUMMYBUF, 0 ); + ym2612_write( impl, 0, addr ); + ym2612_write( impl, 1, data ); +} + +void Ym2612_Emu::write1( int addr, int data ) +{ + ym2612_update_one( impl, DUMMYBUF, 0 ); + ym2612_write( impl, 2, addr ); + ym2612_write( impl, 3, data ); +} + +void Ym2612_Emu::mute_voices( int mask ) +{ + ym2612_set_mutemask( impl, mask ); +} + +void Ym2612_Emu::run( int pair_count, sample_t* out ) +{ + stream_sample_t bufL[ 1024 ]; + stream_sample_t bufR[ 1024 ]; + stream_sample_t * buffers[2] = { bufL, bufR }; + + while (pair_count > 0) + { + int todo = pair_count; + if (todo > 1024) todo = 1024; + ym2612_update_one( impl, buffers, todo ); + + for (int i = 0; i < todo; i++) + { + int output_l = bufL [i]; + int output_r = bufR [i]; + output_l += out [0]; + output_r += out [1]; + if ( (short)output_l != output_l ) output_l = 0x7FFF ^ ( output_l >> 31 ); + if ( (short)output_r != output_r ) output_r = 0x7FFF ^ ( output_r >> 31 ); + out [0] = output_l; + out [1] = output_r; + out += 2; + } + + pair_count -= todo; + } +} diff --git a/Frameworks/GME/gme/Ym3812_Emu.cpp b/Frameworks/GME/gme/Ym3812_Emu.cpp new file mode 100644 index 000000000..ddec0357b --- /dev/null +++ b/Frameworks/GME/gme/Ym3812_Emu.cpp @@ -0,0 +1,66 @@ +// Game_Music_Emu $vers. http://www.slack.net/~ant/ + +#include "Ym3812_Emu.h" + +#include +#include "dbopl.h" + +Ym3812_Emu::Ym3812_Emu() { opl = 0; } + +Ym3812_Emu::~Ym3812_Emu() +{ + delete opl; +} + +int Ym3812_Emu::set_rate( int sample_rate, int clock_rate ) +{ + delete opl; + opl = 0; + + opl = new DBOPL::Chip; + if ( !opl ) + return 1; + + this->sample_rate = sample_rate; + this->clock_rate = clock_rate * 4; + + reset(); + return 0; +} + +void Ym3812_Emu::reset() +{ + opl->Setup( clock_rate, sample_rate ); +} + +void Ym3812_Emu::write( int addr, int data ) +{ + opl->WriteReg( addr, data ); +} + +void Ym3812_Emu::run( int pair_count, sample_t* out ) +{ + Bit32s buf[ 1024 ]; + + while (pair_count > 0) + { + int todo = pair_count; + if (todo > 1024) todo = 1024; + opl->GenerateBlock2( todo, buf ); + + for (int i = 0; i < todo; i++) + { + int output_l, output_r; + int output = buf [i]; + output_l = output + out [0]; + output_r = output + out [1]; + if ( (short)output_l != output_l ) output_l = 0x7FFF ^ ( output_l >> 31 ); + if ( (short)output_r != output_r ) output_r = 0x7FFF ^ ( output_r >> 31 ); + out [0] = output_l; + out [1] = output_r; + out += 2; + } + + pair_count -= todo; + } +} diff --git a/Frameworks/GME/gme/Ym3812_Emu.h b/Frameworks/GME/gme/Ym3812_Emu.h new file mode 100644 index 000000000..a3db4d85a --- /dev/null +++ b/Frameworks/GME/gme/Ym3812_Emu.h @@ -0,0 +1,35 @@ +// YM3812 FM sound chip emulator interface + +// Game_Music_Emu $vers +#ifndef YM3812_EMU_H +#define YM3812_EMU_H + +namespace DBOPL { + struct Chip; +} + +class Ym3812_Emu { + DBOPL::Chip * opl; + unsigned sample_rate; + unsigned clock_rate; +public: + Ym3812_Emu(); + ~Ym3812_Emu(); + + // Sets output sample rate and chip clock rates, in Hz. Returns non-zero + // if error. + int set_rate( int sample_rate, int clock_rate ); + + // Resets to power-up state + void reset(); + + // Writes data to addr + void write( int addr, int data ); + + // Runs and writes pair_count*2 samples to output + typedef short sample_t; + enum { out_chan_count = 2 }; // stereo + void run( int pair_count, sample_t* out ); +}; + +#endif diff --git a/Frameworks/GME/gme/Ymf262_Emu.cpp b/Frameworks/GME/gme/Ymf262_Emu.cpp new file mode 100644 index 000000000..f96ab6a36 --- /dev/null +++ b/Frameworks/GME/gme/Ymf262_Emu.cpp @@ -0,0 +1,93 @@ +// Game_Music_Emu $vers. http://www.slack.net/~ant/ + +#include "Ymf262_Emu.h" + +#include +#include "dbopl.h" + +Ymf262_Emu::Ymf262_Emu() { opl = 0; } + +Ymf262_Emu::~Ymf262_Emu() +{ + delete opl; +} + +int Ymf262_Emu::set_rate( int sample_rate, int clock_rate ) +{ + delete opl; + opl = 0; + + opl = new DBOPL::Chip; + if ( !opl ) + return 1; + + this->sample_rate = sample_rate; + this->clock_rate = clock_rate; + + reset(); + return 0; +} + +void Ymf262_Emu::reset() +{ + opl->Setup( clock_rate, sample_rate ); +} + +void Ymf262_Emu::write0( int addr, int data ) +{ + opl->WriteReg( addr, data ); +} + +void Ymf262_Emu::write1( int addr, int data ) +{ + opl->WriteReg( 0x100 + addr, data ); +} + +void Ymf262_Emu::run( int pair_count, sample_t* out ) +{ + Bit32s buf[ 2048 ]; + + while (pair_count > 0) + { + int todo = pair_count; + if (todo > 1024) todo = 1024; + + if ( opl->opl3Active ) + { + opl->GenerateBlock3( todo, buf ); + + for (int i = 0; i < todo; i++) + { + int output_l, output_r; + output_l = buf [i * 2]; + output_r = buf [i * 2 + 1]; + output_l += out [0]; + output_r += out [1]; + if ( (short)output_l != output_l ) output_l = 0x7FFF ^ ( output_l >> 31 ); + if ( (short)output_r != output_r ) output_r = 0x7FFF ^ ( output_r >> 31 ); + out [0] = output_l; + out [1] = output_r; + out += 2; + } + } + else + { + opl->GenerateBlock2( todo, buf ); + + for (int i = 0; i < todo; i++) + { + int output_l, output_r; + int output = buf [i]; + output_l = output + out [0]; + output_r = output + out [1]; + if ( (short)output_l != output_l ) output_l = 0x7FFF ^ ( output_l >> 31 ); + if ( (short)output_r != output_r ) output_r = 0x7FFF ^ ( output_r >> 31 ); + out [0] = output_l; + out [1] = output_r; + out += 2; + } + } + + pair_count -= todo; + } +} diff --git a/Frameworks/GME/gme/Ymf262_Emu.h b/Frameworks/GME/gme/Ymf262_Emu.h new file mode 100644 index 000000000..c44cb3d3c --- /dev/null +++ b/Frameworks/GME/gme/Ymf262_Emu.h @@ -0,0 +1,36 @@ +// YMF262 FM sound chip emulator interface + +// Game_Music_Emu $vers +#ifndef YMF262_EMU_H +#define YMF262_EMU_H + +namespace DBOPL { + struct Chip; +} + +class Ymf262_Emu { + DBOPL::Chip * opl; + unsigned sample_rate; + unsigned clock_rate; +public: + Ymf262_Emu(); + ~Ymf262_Emu(); + + // Sets output sample rate and chip clock rates, in Hz. Returns non-zero + // if error. + int set_rate( int sample_rate, int clock_rate ); + + // Resets to power-up state + void reset(); + + // Writes data to addr + void write0( int addr, int data ); + void write1( int addr, int data ); + + // Runs and writes pair_count*2 samples to output + typedef short sample_t; + enum { out_chan_count = 2 }; // stereo + void run( int pair_count, sample_t* out ); +}; + +#endif diff --git a/Frameworks/GME/gme/Ymz280b_Emu.cpp b/Frameworks/GME/gme/Ymz280b_Emu.cpp new file mode 100644 index 000000000..3bce2505e --- /dev/null +++ b/Frameworks/GME/gme/Ymz280b_Emu.cpp @@ -0,0 +1,78 @@ +// Game_Music_Emu $vers. http://www.slack.net/~ant/ + +#include "Ymz280b_Emu.h" +#include "ymz280b.h" + +Ymz280b_Emu::Ymz280b_Emu() { chip = 0; } + +Ymz280b_Emu::~Ymz280b_Emu() +{ + if ( chip ) device_stop_ymz280b( chip ); +} + +int Ymz280b_Emu::set_rate( int clock_rate ) +{ + if ( chip ) + { + device_stop_ymz280b( chip ); + chip = 0; + } + + chip = device_start_ymz280b( clock_rate ); + if ( !chip ) + return 0; + + reset(); + return clock_rate * 2 / 384; +} + +void Ymz280b_Emu::reset() +{ + device_reset_ymz280b( chip ); + ymz280b_set_mute_mask( chip, 0 ); +} + +void Ymz280b_Emu::write( int addr, int data ) +{ + ymz280b_w( chip, 0, addr ); + ymz280b_w( chip, 1, data ); +} + +void Ymz280b_Emu::write_rom( int size, int start, int length, void * data ) +{ + ymz280b_write_rom( chip, size, start, length, (const UINT8 *) data ); +} + +void Ymz280b_Emu::mute_voices( int mask ) +{ + ymz280b_set_mute_mask( chip, mask ); +} + +void Ymz280b_Emu::run( int pair_count, sample_t* out ) +{ + stream_sample_t bufL[ 1024 ]; + stream_sample_t bufR[ 1024 ]; + stream_sample_t * buffers[2] = { bufL, bufR }; + + while (pair_count > 0) + { + int todo = pair_count; + if (todo > 1024) todo = 1024; + ymz280b_update( chip, buffers, todo ); + + for (int i = 0; i < todo; i++) + { + int output_l = bufL [i]; + int output_r = bufR [i]; + output_l += out [0]; + output_r += out [1]; + if ( (short)output_l != output_l ) output_l = 0x7FFF ^ ( output_l >> 31 ); + if ( (short)output_r != output_r ) output_r = 0x7FFF ^ ( output_r >> 31 ); + out [0] = output_l; + out [1] = output_r; + out += 2; + } + + pair_count -= todo; + } +} diff --git a/Frameworks/GME/gme/Ymz280b_Emu.h b/Frameworks/GME/gme/Ymz280b_Emu.h new file mode 100644 index 000000000..8c899d3ad --- /dev/null +++ b/Frameworks/GME/gme/Ymz280b_Emu.h @@ -0,0 +1,36 @@ +// YMZ280B sound chip emulator interface + +// Game_Music_Emu $vers +#ifndef YMZ280B_EMU_H +#define YMZ280B_EMU_H + +class Ymz280b_Emu { + void* chip; +public: + Ymz280b_Emu(); + ~Ymz280b_Emu(); + + // Sets output sample rate and chip clock rates, in Hz. Returns non-zero + // if error. + int set_rate( int clock_rate ); + + // Resets to power-up state + void reset(); + + // Mutes voice n if bit n (1 << n) of mask is set + enum { channel_count = 8 }; + void mute_voices( int mask ); + + // Writes data to addr + void write( int addr, int data ); + + // Scales ROM size, then writes length bytes from data at start offset + void write_rom( int size, int start, int length, void * data ); + + // Runs and writes pair_count*2 samples to output + typedef short sample_t; + enum { out_chan_count = 2 }; // stereo + void run( int pair_count, sample_t* out ); +}; + +#endif diff --git a/Frameworks/GME/gme/Z80_Cpu.cpp b/Frameworks/GME/gme/Z80_Cpu.cpp new file mode 100644 index 000000000..80fb28dea --- /dev/null +++ b/Frameworks/GME/gme/Z80_Cpu.cpp @@ -0,0 +1,82 @@ +// $package. http://www.slack.net/~ant/ + +#include "Z80_Cpu.h" + +/* Copyright (C) 2006-2008 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +#include "blargg_source.h" + +// flags, named with hex value for clarity +int const S80 = 0x80; +int const Z40 = 0x40; +int const F20 = 0x20; +int const H10 = 0x10; +int const F08 = 0x08; +int const V04 = 0x04; +int const P04 = 0x04; +int const N02 = 0x02; +int const C01 = 0x01; + +Z80_Cpu::Z80_Cpu() +{ + cpu_state = &cpu_state_; + + for ( int i = 0x100; --i >= 0; ) + { + int even = 1; + for ( int p = i; p; p >>= 1 ) + even ^= p; + int n = (i & (S80 | F20 | F08)) | ((even & 1) * P04); + szpc [i] = n; + szpc [i + 0x100] = n | C01; + } + szpc [0x000] |= Z40; + szpc [0x100] |= Z40; +} + +inline void Z80_Cpu::set_page( int i, void* write, void const* read ) +{ + int offset = Z80_CPU_OFFSET( i * page_size ); + byte * write2 = STATIC_CAST(byte *,write) - offset; + byte const* read2 = STATIC_CAST(byte const*,read ) - offset; + cpu_state_.write [i] = write2; + cpu_state_.read [i] = read2; + cpu_state->write [i] = write2; + cpu_state->read [i] = read2; +} + +void Z80_Cpu::reset( void* unmapped_write, void const* unmapped_read ) +{ + check( cpu_state == &cpu_state_ ); + cpu_state = &cpu_state_; + cpu_state_.time = 0; + cpu_state_.base = 0; + end_time_ = 0; + + for ( int i = 0; i < page_count + 1; i++ ) + set_page( i, unmapped_write, unmapped_read ); + + memset( &r, 0, sizeof r ); +} + +void Z80_Cpu::map_mem( addr_t start, int size, void* write, void const* read ) +{ + // address range must begin and end on page boundaries + require( start % page_size == 0 ); + require( size % page_size == 0 ); + require( start + size <= 0x10000 ); + + for ( int offset = 0; offset < size; offset += page_size ) + set_page( (start + offset) >> page_bits, + STATIC_CAST(char *,write) + offset, + STATIC_CAST(char const*,read ) + offset ); +} diff --git a/Frameworks/GME/gme/Z80_Cpu.h b/Frameworks/GME/gme/Z80_Cpu.h new file mode 100644 index 000000000..b5f6f4d51 --- /dev/null +++ b/Frameworks/GME/gme/Z80_Cpu.h @@ -0,0 +1,122 @@ +// Z80 CPU emulator + +// $package +#ifndef Z80_CPU_H +#define Z80_CPU_H + +#include "blargg_endian.h" + +class Z80_Cpu { +public: + typedef int time_t; + typedef int addr_t; + typedef BOOST::uint8_t byte; + + // Clears registers and maps all pages to unmapped + void reset( void* unmapped_write, void const* unmapped_read ); + + // TODO: split mapping out of CPU + + // Maps memory. Start and size must be multiple of page_size. + enum { page_bits = 10 }; + enum { page_size = 1 << page_bits }; + void map_mem( addr_t addr, int size, void* write, void const* read ); + void map_mem( addr_t addr, int size, void* read_write ); + + // Maps address to pointer to that byte + byte * write( addr_t addr ); + byte const* read( addr_t addr ); + + // Time of beginning of next instruction + time_t time() const { return cpu_state->time + cpu_state->base; } + + // Alter current time + void set_time( time_t t ) { cpu_state->time = t - cpu_state->base; } + void adjust_time( int delta ) { cpu_state->time += delta; } + + #if BLARGG_BIG_ENDIAN + struct regs_t { byte b,c, d,e, h,l, flags,a; }; + #else + struct regs_t { byte c,b, e,d, l,h, a,flags; }; + #endif + BLARGG_STATIC_ASSERT( sizeof (regs_t) == 8 ); + + struct pairs_t { BOOST::uint16_t bc, de, hl, fa; }; + + // Registers are not updated until run() returns + struct registers_t { + BOOST::uint16_t pc; + BOOST::uint16_t sp; + BOOST::uint16_t ix; + BOOST::uint16_t iy; + union { + regs_t b; // b.b, b.c, b.d, b.e, b.h, b.l, b.flags, b.a + pairs_t w; // w.bc, w.de, w.hl. w.fa + }; + union { + regs_t b; + pairs_t w; + } alt; + byte iff1; + byte iff2; + byte r; + byte i; + byte im; + }; + //registers_t r; (below for efficiency) + + // can read this far past end of memory + enum { cpu_padding = 0x100 }; + + // Can read this many bytes past end of a page + enum { page_padding = 4 }; + + void set_end_time( time_t t ); +public: + Z80_Cpu(); + + enum { page_count = 0x10000 / page_size }; + byte szpc [0x200]; + time_t end_time_; + struct cpu_state_t { + byte const* read [page_count + 1]; + byte * write [page_count + 1]; + time_t base; + time_t time; + }; + cpu_state_t* cpu_state; // points to cpu_state_ or a local copy within run() + cpu_state_t cpu_state_; + void set_page( int i, void* write, void const* read ); +public: + registers_t r; +}; + +#if BLARGG_NONPORTABLE + #define Z80_CPU_OFFSET( addr ) (addr) +#else + #define Z80_CPU_OFFSET( addr ) ((addr) & (Z80_Cpu::page_size - 1)) +#endif + +inline Z80_Cpu::byte* Z80_Cpu::write( addr_t addr ) +{ + return cpu_state->write [(unsigned) addr >> page_bits] + Z80_CPU_OFFSET( addr ); +} + +inline Z80_Cpu::byte const* Z80_Cpu::read( addr_t addr ) +{ + return cpu_state->read [(unsigned) addr >> page_bits] + Z80_CPU_OFFSET( addr ); +} + +inline void Z80_Cpu::map_mem( addr_t addr, int size, void* p ) +{ + map_mem( addr, size, p, p ); +} + +inline void Z80_Cpu::set_end_time( time_t t ) +{ + time_t delta = cpu_state->base - t; + cpu_state->base = t; + cpu_state->time += delta; +} + +#endif diff --git a/Frameworks/GME/gme/Z80_Cpu_run.h b/Frameworks/GME/gme/Z80_Cpu_run.h new file mode 100644 index 000000000..7bb490b77 --- /dev/null +++ b/Frameworks/GME/gme/Z80_Cpu_run.h @@ -0,0 +1,1701 @@ +// $package. http://www.slack.net/~ant/ + +// Last validated with zexall 2009.12.05. +// Doesn't implement the R register or immediate interrupt after EI. +// Address wrap-around isn't completely correct, but is prevented from crashing emulator. +// 16-bit memory accesses are made directly to mapped memory, instead of using macro. + +#if 0 +/* Define these macros in the source file before #including this file. +- Parameters might be expressions, so they are best evaluated only once, +though they NEVER have side-effects, so multiple evaluation is OK. +- Output parameters might be a multiple-assignment expression like "a=x", +so they must NOT be parenthesized. +- Except where noted, time() and related functions will NOT work +correctly inside a macro. TIME() is always correct, and between FLUSH_TIME() and +CACHE_TIME() the normal time changing functions can be used. +- Macros "returning" void may use a {} statement block. */ + + // 0 <= addr <= 0xFFFF + 0x100 + // Optional; default uses whatever was set with map_mem() + int READ_CODE( addr_t ); + + // 0 <= addr <= 0xFFFF + 0x100 + // Optional; default uses whatever was set with map_mem() + int READ_MEM( addr_t ); + void WRITE_MEM( addr_t, int data ); + + // 0 <= port <= 0xFFFF (apparently upper 8 bits are output by hardware) + void OUT_PORT( int port, int data ); + int IN_PORT int port ); + + // Reference to Z80_Cpu object used for emulation + #define CPU cpu + +// The following can be used within macros: + + // Current time + time_t TIME(); + + // Allows use of time functions + void FLUSH_TIME(); + + // Must be used before end of macro if FLUSH_TIME() was used earlier + void CACHE_TIME(); + +// Configuration (optional; commented behavior if defined) + + // Optimizes as if map_mem( 0, 0x10000, FLAT_MEM, FLAT_MEM ) is always in effect + #define FLAT_MEM my_mem_array + + // If RST 7 ($FF) is encountered and PC = IDLE_ADDR, stops execution + #define IDLE_ADDR 0x1234 + + // Expanded just before beginning of code, to help debugger + #define CPU_BEGIN void my_run_cpu() { + +#endif + +/* Copyright (C) 2006-2008 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +#ifdef CPU_BEGIN + CPU_BEGIN +#endif + +#define R CPU.r + +// flags, named with hex value for clarity +int const S80 = 0x80; +int const Z40 = 0x40; +int const F20 = 0x20; +int const H10 = 0x10; +int const F08 = 0x08; +int const V04 = 0x04; +int const P04 = 0x04; +int const N02 = 0x02; +int const C01 = 0x01; + +#define SZ28P( n ) CPU.szpc [n] +#define SZ28PC( n ) CPU.szpc [n] +#define SZ28C( n ) (CPU.szpc [n] & ~P04) +#define SZ28( n ) SZ28C( n ) + +#define SET_R( n ) (void) (R.r = n) +#define GET_R() (R.r) + +// Time +#define TIME() (s_time + s.base) +#define FLUSH_TIME() {s.time = s_time;} +#define CACHE_TIME() {s_time = s.time;} + +// Memory +#define RW_MEM( addr, rw ) RW_PAGE( addr, rw ) [RW_OFFSET( addr )] +#ifndef READ_CODE + #define READ_CODE( addr ) RW_MEM( addr, read ) +#endif + +#ifdef FLAT_MEM + #define RW_PAGE( addr, rw ) FLAT_MEM + #define RW_OFFSET( addr ) (addr) + #define INSTR( off, addr ) READ_CODE( addr ) +#else + #define RW_PAGE( addr, rw ) s.rw [(unsigned) (addr) >> Z80_Cpu::page_bits] + #define RW_OFFSET( addr ) Z80_CPU_OFFSET( addr ) + #define INSTR( off, addr ) instr [off] +#endif + +#ifndef READ_MEM + #define READ_MEM( addr ) RW_MEM( addr, read ) +#endif + +#ifndef WRITE_MEM + #define WRITE_MEM( addr, data ) (RW_MEM( addr, write ) = data) +#endif + +#define READ_WORD( addr ) GET_LE16( &RW_MEM( addr, read ) ) +#define WRITE_WORD( addr, data ) SET_LE16( &RW_MEM( addr, write ), data ) + +// Truncation +#define BYTE( n ) ((BOOST::uint8_t ) (n)) /* (unsigned) n & 0xFF */ +#define SBYTE( n ) ((BOOST::int8_t ) (n)) /* (BYTE( n ) ^ 0x80) - 0x80 */ +#define WORD( n ) ((BOOST::uint16_t) (n)) /* (unsigned) n & 0xFFFF */ + +// Misc +#define CASE5( a, b, c, d, e ) case 0x##a:case 0x##b:case 0x##c:case 0x##d:case 0x##e +#define CASE6( a, b, c, d, e, f ) CASE5( a, b, c, d, e ): case 0x##f +#define CASE7( a, b, c, d, e, f, g ) CASE6( a, b, c, d, e, f ): case 0x##g +#define CASE8( a, b, c, d, e, f, g, h ) CASE7( a, b, c, d, e, f, g ): case 0x##h + +#if BLARGG_BIG_ENDIAN + #define R8( n, offset ) ((r.r8_ - offset) [n]) +#elif BLARGG_LITTLE_ENDIAN + #define R8( n, offset ) ((r.r8_ - offset) [(n) ^ 1]) +#else + #error "Byte order of CPU must be known" +#endif + +#define R16( n, shift, offset ) (r.r16_ [((unsigned) (n) >> shift) - (offset >> shift)]) + +#define EX( x, y ) \ + {\ + int temp = x;\ + x = y;\ + y = temp;\ + } + +#define EXX( name ) \ + EX( R.alt.name, r.name ) + +bool warning = false; +{ + Z80_Cpu::cpu_state_t s; + #ifdef FLAT_MEM + s.base = CPU.cpu_state_.base; + #else + s = CPU.cpu_state_; + #endif + CPU.cpu_state = &s; + + + union r_t { + Z80_Cpu::regs_t b; + Z80_Cpu::pairs_t w; + byte r8_ [8]; // indexed + BOOST::uint16_t r16_ [4]; + } r; + r.b = R.b; + + Z80_Cpu::time_t s_time = CPU.cpu_state_.time; + int pc = R.pc; + int sp = R.sp; + int ix = R.ix; // TODO: keep in memory for direct access? + int iy = R.iy; + int flags = R.b.flags; + + //goto loop; // confuses optimizer + s_time += 7; + pc -= 2; + +call_not_taken: + s_time -= 7; +jp_not_taken: + pc += 2; +loop: + + check( (unsigned) pc < 0x10000 + 1 ); // +1 so emulator can catch wrap-around + check( (unsigned) sp < 0x10000 ); + check( (unsigned) flags < 0x100 ); + check( (unsigned) ix < 0x10000 ); + check( (unsigned) iy < 0x10000 ); + + byte const* instr = RW_PAGE( pc, read ); + + int opcode; + + if ( RW_OFFSET( ~0 ) == ~0 ) + { + opcode = instr [RW_OFFSET( pc )]; + pc++; + instr += RW_OFFSET( pc ); + } + else + { + instr += RW_OFFSET( pc ); + opcode = *instr++; + pc++; + } + + static byte const clock_table [256 * 2] = { + // 0 1 2 3 4 5 6 7 8 9 A B C D E F + 4,10, 7, 6, 4, 4, 7, 4, 4,11, 7, 6, 4, 4, 7, 4, // 0 + 8,10, 7, 6, 4, 4, 7, 4,12,11, 7, 6, 4, 4, 7, 4, // 1 + 7,10,16, 6, 4, 4, 7, 4, 7,11,16, 6, 4, 4, 7, 4, // 2 + 7,10,13, 6,11,11,10, 4, 7,11,13, 6, 4, 4, 7, 4, // 3 + 4, 4, 4, 4, 4, 4, 7, 4, 4, 4, 4, 4, 4, 4, 7, 4, // 4 + 4, 4, 4, 4, 4, 4, 7, 4, 4, 4, 4, 4, 4, 4, 7, 4, // 5 + 4, 4, 4, 4, 4, 4, 7, 4, 4, 4, 4, 4, 4, 4, 7, 4, // 6 + 7, 7, 7, 7, 7, 7, 4, 7, 4, 4, 4, 4, 4, 4, 7, 4, // 7 + 4, 4, 4, 4, 4, 4, 7, 4, 4, 4, 4, 4, 4, 4, 7, 4, // 8 + 4, 4, 4, 4, 4, 4, 7, 4, 4, 4, 4, 4, 4, 4, 7, 4, // 9 + 4, 4, 4, 4, 4, 4, 7, 4, 4, 4, 4, 4, 4, 4, 7, 4, // A + 4, 4, 4, 4, 4, 4, 7, 4, 4, 4, 4, 4, 4, 4, 7, 4, // B + 11,10,10,10,17,11, 7,11,11,10,10, 8,17,17, 7,11, // C + 11,10,10,11,17,11, 7,11,11, 4,10,11,17, 8, 7,11, // D + 11,10,10,19,17,11, 7,11,11, 4,10, 4,17, 8, 7,11, // E + 11,10,10, 4,17,11, 7,11,11, 6,10, 4,17, 8, 7,11, // F + + // high four bits are $ED time - 8, low four bits are $DD/$FD time - 8 + //0 1 2 3 4 5 6 7 8 9 A B C D E F + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x07,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x07,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x06,0x0C,0x02,0x00,0x00,0x03,0x00,0x00,0x07,0x0C,0x02,0x00,0x00,0x03,0x00, + 0x00,0x00,0x00,0x00,0x0F,0x0F,0x0B,0x00,0x00,0x07,0x00,0x00,0x00,0x00,0x00,0x00, + 0x40,0x40,0x70,0xC0,0x00,0x60,0x0B,0x10,0x40,0x40,0x70,0xC0,0x00,0x60,0x0B,0x10, + 0x40,0x40,0x70,0xC0,0x00,0x60,0x0B,0x10,0x40,0x40,0x70,0xC0,0x00,0x60,0x0B,0x10, + 0x40,0x40,0x70,0xC0,0x00,0x60,0x0B,0xA0,0x40,0x40,0x70,0xC0,0x00,0x60,0x0B,0xA0, + 0x4B,0x4B,0x7B,0xCB,0x0B,0x6B,0x00,0x0B,0x40,0x40,0x70,0xC0,0x00,0x60,0x0B,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x0B,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0B,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x0B,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0B,0x00, + 0x80,0x80,0x80,0x80,0x00,0x00,0x0B,0x00,0x80,0x80,0x80,0x80,0x00,0x00,0x0B,0x00, + 0xD0,0xD0,0xD0,0xD0,0x00,0x00,0x0B,0x00,0xD0,0xD0,0xD0,0xD0,0x00,0x00,0x0B,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0F,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x06,0x00,0x0F,0x00,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x00,0x00,0x00, + }; + + if ( s_time >= 0 ) + goto out_of_time; + s_time += clock_table [opcode]; + + #ifdef Z80_CPU_LOG_H + //log_opcode( opcode, READ_CODE( pc ) ); + z80_cpu_log( "log.txt", pc - 1, opcode, READ_CODE( pc ), + READ_CODE( pc + 1 ), READ_CODE( pc + 2 ) ); + z80_log_regs( r.b.a, r.w.bc, r.w.de, r.w.hl, sp, ix, iy ); + #endif + +#define GET_ADDR() GET_LE16( &INSTR( 0, pc ) ) + + int data; + data = INSTR( 0, pc ); + + switch ( opcode ) + { +// Common + + case 0x00: // NOP + CASE7( 40, 49, 52, 5B, 64, 6D, 7F ): // LD B,B etc. + goto loop; + + case 0x08:{// EX AF,AF' + EXX( b.a ); + EX( R.alt.b.flags, flags ); + goto loop; + } + + case 0xD3: // OUT (imm),A + pc++; + OUT_PORT( (data + r.b.a * 0x100), r.b.a ); + goto loop; + + case 0x2E: // LD L,imm + pc++; + r.b.l = data; + goto loop; + + case 0x3E: // LD A,imm + pc++; + r.b.a = data; + goto loop; + + case 0x3A:{// LD A,(addr) + int addr = GET_ADDR(); + pc += 2; + r.b.a = READ_MEM( addr ); + goto loop; + } + +// Conditional + +#define ZERO (flags & Z40) +#define CARRY (flags & C01) +#define EVEN (flags & P04) +#define MINUS (flags & S80) + +// JR +// TODO: more efficient way to handle negative branch that wraps PC around +#define JR_( cond, clocks ) {\ + pc++;\ + if ( !(cond) )\ + goto loop;\ + int offset = SBYTE( data );\ + pc = WORD( pc + offset );\ + s_time += clocks;\ + goto loop;\ +} + +#define JR( cond ) JR_( cond, 5 ) + + case 0x20: JR( !ZERO ) // JR NZ,disp + case 0x28: JR( ZERO ) // JR Z,disp + case 0x30: JR( !CARRY ) // JR NC,disp + case 0x38: JR( CARRY ) // JR C,disp + case 0x18: JR_( true,0) // JR disp + + case 0x10:{// DJNZ disp + int temp = r.b.b - 1; + r.b.b = temp; + JR( temp ) + } + +// JP +#define JP( cond ) \ + if ( !(cond) )\ + goto jp_not_taken;\ + pc = GET_ADDR();\ + goto loop; + + case 0xC2: JP( !ZERO ) // JP NZ,addr + case 0xCA: JP( ZERO ) // JP Z,addr + case 0xD2: JP( !CARRY ) // JP NC,addr + case 0xDA: JP( CARRY ) // JP C,addr + case 0xE2: JP( !EVEN ) // JP PO,addr + case 0xEA: JP( EVEN ) // JP PE,addr + case 0xF2: JP( !MINUS ) // JP P,addr + case 0xFA: JP( MINUS ) // JP M,addr + + case 0xC3: // JP addr + pc = GET_ADDR(); + goto loop; + + case 0xE9: // JP HL + pc = r.w.hl; + goto loop; + +// RET +#define RET( cond ) \ + if ( cond )\ + goto ret_taken;\ + s_time -= 6;\ + goto loop; + + case 0xC0: RET( !ZERO ) // RET NZ + case 0xC8: RET( ZERO ) // RET Z + case 0xD0: RET( !CARRY ) // RET NC + case 0xD8: RET( CARRY ) // RET C + case 0xE0: RET( !EVEN ) // RET PO + case 0xE8: RET( EVEN ) // RET PE + case 0xF0: RET( !MINUS ) // RET P + case 0xF8: RET( MINUS ) // RET M + + case 0xC9: // RET + ret_taken: + pc = READ_WORD( sp ); + sp = WORD( sp + 2 ); + goto loop; + +// CALL +#define CALL( cond ) \ + if ( cond )\ + goto call_taken;\ + goto call_not_taken; + + case 0xC4: CALL( !ZERO ) // CALL NZ,addr + case 0xCC: CALL( ZERO ) // CALL Z,addr + case 0xD4: CALL( !CARRY ) // CALL NC,addr + case 0xDC: CALL( CARRY ) // CALL C,addr + case 0xE4: CALL( !EVEN ) // CALL PO,addr + case 0xEC: CALL( EVEN ) // CALL PE,addr + case 0xF4: CALL( !MINUS ) // CALL P,addr + case 0xFC: CALL( MINUS ) // CALL M,addr + + case 0xCD:{// CALL addr + call_taken: + int addr = pc + 2; + pc = GET_ADDR(); + sp = WORD( sp - 2 ); + WRITE_WORD( sp, addr ); + goto loop; + } + + case 0xFF: // RST + #ifdef IDLE_ADDR + if ( pc == IDLE_ADDR + 1 ) + goto hit_idle_addr; + #else + if ( pc > 0x10000 ) + { + pc = WORD( pc - 1 ); + s_time -= 11; + goto loop; + } + #endif + CASE7( C7, CF, D7, DF, E7, EF, F7 ): + data = pc; + pc = opcode & 0x38; + #ifdef RST_BASE + pc += RST_BASE; + #endif + goto push_data; + +// PUSH/POP + case 0xF5: // PUSH AF + data = r.b.a * 0x100u + flags; + goto push_data; + + case 0xC5: // PUSH BC + case 0xD5: // PUSH DE + case 0xE5: // PUSH HL + data = R16( opcode, 4, 0xC5 ); + push_data: + sp = WORD( sp - 2 ); + WRITE_WORD( sp, data ); + goto loop; + + case 0xF1: // POP AF + flags = READ_MEM( sp ); + r.b.a = READ_MEM( (sp + 1) ); + sp = WORD( sp + 2 ); + goto loop; + + case 0xC1: // POP BC + case 0xD1: // POP DE + case 0xE1: // POP HL + R16( opcode, 4, 0xC1 ) = READ_WORD( sp ); + sp = WORD( sp + 2 ); + goto loop; + +// ADC/ADD/SBC/SUB + case 0x96: // SUB (HL) + case 0x86: // ADD (HL) + flags &= ~C01; + case 0x9E: // SBC (HL) + case 0x8E: // ADC (HL) + data = READ_MEM( r.w.hl ); + goto adc_data; + + case 0xD6: // SUB A,imm + case 0xC6: // ADD imm + flags &= ~C01; + case 0xDE: // SBC A,imm + case 0xCE: // ADC imm + pc++; + goto adc_data; + + CASE7( 90, 91, 92, 93, 94, 95, 97 ): // SUB r + CASE7( 80, 81, 82, 83, 84, 85, 87 ): // ADD r + flags &= ~C01; + CASE7( 98, 99, 9A, 9B, 9C, 9D, 9F ): // SBC r + CASE7( 88, 89, 8A, 8B, 8C, 8D, 8F ): // ADC r + data = R8( opcode & 7, 0 ); + adc_data: { + int result = data + (flags & C01); + data ^= r.b.a; + flags = opcode >> 3 & N02; // bit 4 is set in subtract opcodes + if ( flags ) + result = -result; + result += r.b.a; + data ^= result; + flags +=(data & H10) + + ((data + 0x80) >> 6 & V04) + + SZ28C( result & 0x1FF ); + r.b.a = result; + goto loop; + } + +// CP + case 0xBE: // CP (HL) + data = READ_MEM( r.w.hl ); + goto cp_data; + + case 0xFE: // CP imm + pc++; + goto cp_data; + + CASE7( B8, B9, BA, BB, BC, BD, BF ): // CP r + data = R8( opcode, 0xB8 ); + cp_data: { + int result = r.b.a - data; + flags = N02 + (data & (F20 | F08)) + (result >> 8 & C01); + data ^= r.b.a; + flags +=(((result ^ r.b.a) & data) >> 5 & V04) + + (((data & H10) ^ result) & (S80 | H10)); + if ( BYTE( result ) ) + goto loop; + flags += Z40; + goto loop; + } + +// ADD HL,r.w + + case 0x39: // ADD HL,SP + data = sp; + goto add_hl_data; + + case 0x09: // ADD HL,BC + case 0x19: // ADD HL,DE + case 0x29: // ADD HL,HL + data = R16( opcode, 4, 0x09 ); + add_hl_data: { + int sum = r.w.hl + data; + data ^= r.w.hl; + r.w.hl = sum; + flags = (flags & (S80 | Z40 | V04)) + + (sum >> 16) + + (sum >> 8 & (F20 | F08)) + + ((data ^ sum) >> 8 & H10); + goto loop; + } + + case 0x27:{// DAA + int a = r.b.a; + if ( a > 0x99 ) + flags |= C01; + + int adjust = 0x60 * (flags & C01); + + if ( flags & H10 || (a & 0x0F) > 9 ) + adjust += 0x06; + + if ( flags & N02 ) + adjust = -adjust; + a += adjust; + + flags = (flags & (C01 | N02)) + + ((r.b.a ^ a) & H10) + + SZ28P( BYTE( a ) ); + r.b.a = a; + goto loop; + } + +// INC/DEC + case 0x34: // INC (HL) + data = READ_MEM( r.w.hl ) + 1; + WRITE_MEM( r.w.hl, data ); + goto inc_set_flags; + + CASE7( 04, 0C, 14, 1C, 24, 2C, 3C ): // INC r + data = ++R8( opcode >> 3, 0 ); + inc_set_flags: + flags = (flags & C01) + + (((data & 0x0F) - 1) & H10) + + SZ28( BYTE( data ) ); + if ( data != 0x80 ) + goto loop; + flags += V04; + goto loop; + + case 0x35: // DEC (HL) + data = READ_MEM( r.w.hl ) - 1; + WRITE_MEM( r.w.hl, data ); + goto dec_set_flags; + + CASE7( 05, 0D, 15, 1D, 25, 2D, 3D ): // DEC r + data = --R8( opcode >> 3, 0 ); + dec_set_flags: + flags = (flags & C01) + N02 + + (((data & 0x0F) + 1) & H10) + + SZ28( BYTE( data ) ); + if ( data != 0x7F ) + goto loop; + flags += V04; + goto loop; + + case 0x03: // INC BC + case 0x13: // INC DE + case 0x23: // INC HL + R16( opcode, 4, 0x03 )++; + goto loop; + + case 0x33: // INC SP + sp = WORD( sp + 1 ); + goto loop; + + case 0x0B: // DEC BC + case 0x1B: // DEC DE + case 0x2B: // DEC HL + R16( opcode, 4, 0x0B )--; + goto loop; + + case 0x3B: // DEC SP + sp = WORD( sp - 1 ); + goto loop; + +// AND + case 0xA6: // AND (HL) + data = READ_MEM( r.w.hl ); + goto and_data; + + case 0xE6: // AND imm + pc++; + goto and_data; + + CASE7( A0, A1, A2, A3, A4, A5, A7 ): // AND r + data = R8( opcode, 0xA0 ); + and_data: + r.b.a &= data; + flags = SZ28P( r.b.a ) + H10; + goto loop; + +// OR + case 0xB6: // OR (HL) + data = READ_MEM( r.w.hl ); + goto or_data; + + case 0xF6: // OR imm + pc++; + goto or_data; + + CASE7( B0, B1, B2, B3, B4, B5, B7 ): // OR r + data = R8( opcode, 0xB0 ); + or_data: + r.b.a |= data; + flags = SZ28P( r.b.a ); + goto loop; + +// XOR + case 0xAE: // XOR (HL) + data = READ_MEM( r.w.hl ); + goto xor_data; + + case 0xEE: // XOR imm + pc++; + goto xor_data; + + CASE7( A8, A9, AA, AB, AC, AD, AF ): // XOR r + data = R8( opcode, 0xA8 ); + xor_data: + r.b.a ^= data; + flags = SZ28P( r.b.a ); + goto loop; + +// LD + CASE7( 70, 71, 72, 73, 74, 75, 77 ): // LD (HL),r + WRITE_MEM( r.w.hl, R8( opcode, 0x70 ) ); + goto loop; + + CASE6( 41, 42, 43, 44, 45, 47 ): // LD B,r + CASE6( 48, 4A, 4B, 4C, 4D, 4F ): // LD C,r + CASE6( 50, 51, 53, 54, 55, 57 ): // LD D,r + CASE6( 58, 59, 5A, 5C, 5D, 5F ): // LD E,r + CASE6( 60, 61, 62, 63, 65, 67 ): // LD H,r + CASE6( 68, 69, 6A, 6B, 6C, 6F ): // LD L,r + CASE6( 78, 79, 7A, 7B, 7C, 7D ): // LD A,r + R8( opcode >> 3 & 7, 0 ) = R8( opcode & 7, 0 ); + goto loop; + + CASE5( 06, 0E, 16, 1E, 26 ): // LD r,imm + R8( opcode >> 3, 0 ) = data; + pc++; + goto loop; + + case 0x36: // LD (HL),imm + pc++; + WRITE_MEM( r.w.hl, data ); + goto loop; + + CASE7( 46, 4E, 56, 5E, 66, 6E, 7E ): // LD r,(HL) + R8( opcode >> 3, 8 ) = READ_MEM( r.w.hl ); + goto loop; + + case 0x01: // LD r.w,imm + case 0x11: + case 0x21: + R16( opcode, 4, 0x01 ) = GET_ADDR(); + pc += 2; + goto loop; + + case 0x31: // LD sp,imm + sp = GET_ADDR(); + pc += 2; + goto loop; + + case 0x2A:{// LD HL,(addr) + int addr = GET_ADDR(); + pc += 2; + r.w.hl = READ_WORD( addr ); + goto loop; + } + + case 0x32:{// LD (addr),A + int addr = GET_ADDR(); + pc += 2; + WRITE_MEM( addr, r.b.a ); + goto loop; + } + + case 0x22:{// LD (addr),HL + int addr = GET_ADDR(); + pc += 2; + WRITE_WORD( addr, r.w.hl ); + goto loop; + } + + case 0x02: // LD (BC),A + case 0x12: // LD (DE),A + WRITE_MEM( R16( opcode, 4, 0x02 ), r.b.a ); + goto loop; + + case 0x0A: // LD A,(BC) + case 0x1A: // LD A,(DE) + r.b.a = READ_MEM( R16( opcode, 4, 0x0A ) ); + goto loop; + + case 0xF9: // LD SP,HL + sp = r.w.hl; + goto loop; + +// Rotate + + case 0x07:{// RLCA + int temp = r.b.a; + temp = (temp << 1) + (temp >> 7); + flags = (flags & (S80 | Z40 | P04)) + + (temp & (F20 | F08 | C01)); + r.b.a = temp; + goto loop; + } + + case 0x0F:{// RRCA + int temp = r.b.a; + flags = (flags & (S80 | Z40 | P04)) + + (temp & C01); + temp = (temp << 7) + (temp >> 1); + flags += temp & (F20 | F08); + r.b.a = temp; + goto loop; + } + + case 0x17:{// RLA + int temp = (r.b.a << 1) + (flags & C01); + flags = (flags & (S80 | Z40 | P04)) + + (temp & (F20 | F08)) + + (temp >> 8); + r.b.a = temp; + goto loop; + } + + case 0x1F:{// RRA + int temp = (flags << 7) + (r.b.a >> 1); + flags = (flags & (S80 | Z40 | P04)) + + (temp & (F20 | F08)) + + (r.b.a & C01); + r.b.a = temp; + goto loop; + } + +// Misc + case 0x2F:{// CPL + int temp = ~r.b.a; + flags = (flags & (S80 | Z40 | P04 | C01)) + + (temp & (F20 | F08)) + + (H10 | N02); + r.b.a = temp; + goto loop; + } + + case 0x3F:{// CCF + flags = ((flags & (S80 | Z40 | P04 | C01)) ^ C01) + + (flags << 4 & H10) + + (r.b.a & (F20 | F08)); + goto loop; + } + + case 0x37: // SCF + flags = (flags & (S80 | Z40 | P04)) | C01 + + (r.b.a & (F20 | F08)); + goto loop; + + case 0xDB: // IN A,(imm) + pc++; + r.b.a = IN_PORT( (data + r.b.a * 0x100) ); + goto loop; + + case 0xE3:{// EX (SP),HL + int temp = READ_WORD( sp ); + WRITE_WORD( sp, r.w.hl ); + r.w.hl = temp; + goto loop; + } + + case 0xEB: // EX DE,HL + EX( r.w.hl, r.w.de ); + goto loop; + + case 0xD9: // EXX DE,HL + EXX( w.bc ); + EXX( w.de ); + EXX( w.hl ); + goto loop; + + case 0xF3: // DI + R.iff1 = 0; + R.iff2 = 0; + goto loop; + + case 0xFB: // EI + R.iff1 = 1; + R.iff2 = 1; + // TODO: delayed effect + goto loop; + + case 0x76: // HALT + goto halt; + +//////////////////////////////////////// CB prefix + { + case 0xCB: + pc++; + switch ( data ) + { + + // Rotate left + + #define RLC( read, write ) {\ + int result = read;\ + result = BYTE( result << 1 ) + (result >> 7);\ + flags = SZ28P( result ) + (result & C01);\ + write;\ + goto loop;\ + } + + case 0x06: // RLC (HL) + s_time += 7; + data = r.w.hl; + rlc_data_addr: + RLC( READ_MEM( data ), WRITE_MEM( data, result ) ) + + CASE7( 00, 01, 02, 03, 04, 05, 07 ):{// RLC r + byte& reg = R8( data, 0 ); + RLC( reg, reg = result ) + } + + #define RL( read, write ) {\ + int result = (read << 1) + (flags & C01);\ + flags = SZ28PC( result );\ + write;\ + goto loop;\ + } + + case 0x16: // RL (HL) + s_time += 7; + data = r.w.hl; + rl_data_addr: + RL( READ_MEM( data ), WRITE_MEM( data, result ) ) + + CASE7( 10, 11, 12, 13, 14, 15, 17 ):{// RL r + byte& reg = R8( data, 0x10 ); + RL( reg, reg = result ) + } + + #define SLA( read, low_bit, write ) {\ + int result = (read << 1) + low_bit;\ + flags = SZ28PC( result );\ + write;\ + goto loop;\ + } + + case 0x26: // SLA (HL) + s_time += 7; + data = r.w.hl; + sla_data_addr: + SLA( READ_MEM( data ), 0, WRITE_MEM( data, result ) ) + + CASE7( 20, 21, 22, 23, 24, 25, 27 ):{// SLA r + byte& reg = R8( data, 0x20 ); + SLA( reg, 0, reg = result ) + } + + case 0x36: // SLL (HL) + s_time += 7; + data = r.w.hl; + sll_data_addr: + SLA( READ_MEM( data ), 1, WRITE_MEM( data, result ) ) + + CASE7( 30, 31, 32, 33, 34, 35, 37 ):{// SLL r + byte& reg = R8( data, 0x30 ); + SLA( reg, 1, reg = result ) + } + + // Rotate right + + #define RRC( read, write ) {\ + int result = read;\ + flags = result & C01;\ + result = BYTE( result << 7 ) + (result >> 1);\ + flags += SZ28P( result );\ + write;\ + goto loop;\ + } + + case 0x0E: // RRC (HL) + s_time += 7; + data = r.w.hl; + rrc_data_addr: + RRC( READ_MEM( data ), WRITE_MEM( data, result ) ) + + CASE7( 08, 09, 0A, 0B, 0C, 0D, 0F ):{// RRC r + byte& reg = R8( data, 0x08 ); + RRC( reg, reg = result ) + } + + #define RR( read, write ) {\ + int result = read;\ + int temp = result & C01;\ + result = BYTE( flags << 7 ) + (result >> 1);\ + flags = SZ28P( result ) + temp;\ + write;\ + goto loop;\ + } + + case 0x1E: // RR (HL) + s_time += 7; + data = r.w.hl; + rr_data_addr: + RR( READ_MEM( data ), WRITE_MEM( data, result ) ) + + CASE7( 18, 19, 1A, 1B, 1C, 1D, 1F ):{// RR r + byte& reg = R8( data, 0x18 ); + RR( reg, reg = result ) + } + + #define SRA( read, write ) {\ + int result = read;\ + flags = result & C01;\ + result = (result & 0x80) + (result >> 1);\ + flags += SZ28P( result );\ + write;\ + goto loop;\ + } + + case 0x2E: // SRA (HL) + data = r.w.hl; + s_time += 7; + sra_data_addr: + SRA( READ_MEM( data ), WRITE_MEM( data, result ) ) + + CASE7( 28, 29, 2A, 2B, 2C, 2D, 2F ):{// SRA r + byte& reg = R8( data, 0x28 ); + SRA( reg, reg = result ) + } + + #define SRL( read, write ) {\ + int result = read;\ + flags = result & C01;\ + result >>= 1;\ + flags += SZ28P( result );\ + write;\ + goto loop;\ + } + + case 0x3E: // SRL (HL) + s_time += 7; + data = r.w.hl; + srl_data_addr: + SRL( READ_MEM( data ), WRITE_MEM( data, result ) ) + + CASE7( 38, 39, 3A, 3B, 3C, 3D, 3F ):{// SRL r + byte& reg = R8( data, 0x38 ); + SRL( reg, reg = result ) + } + + // BIT + { + int temp; + CASE8( 46, 4E, 56, 5E, 66, 6E, 76, 7E ): // BIT b,(HL) + s_time += 4; + temp = READ_MEM( r.w.hl ); + flags &= C01; + goto bit_temp; + CASE7( 40, 41, 42, 43, 44, 45, 47 ): // BIT 0,r + CASE7( 48, 49, 4A, 4B, 4C, 4D, 4F ): // BIT 1,r + CASE7( 50, 51, 52, 53, 54, 55, 57 ): // BIT 2,r + CASE7( 58, 59, 5A, 5B, 5C, 5D, 5F ): // BIT 3,r + CASE7( 60, 61, 62, 63, 64, 65, 67 ): // BIT 4,r + CASE7( 68, 69, 6A, 6B, 6C, 6D, 6F ): // BIT 5,r + CASE7( 70, 71, 72, 73, 74, 75, 77 ): // BIT 6,r + CASE7( 78, 79, 7A, 7B, 7C, 7D, 7F ): // BIT 7,r + temp = R8( data & 7, 0 ); + flags = (flags & C01) + (temp & (F20 | F08)); + bit_temp: + temp = temp & (1 << (data >> 3 & 7)); + flags += (temp & S80) + H10; + flags += (unsigned) --temp >> 8 & (Z40 | P04); + goto loop; + } + + // SET/RES + CASE8( 86, 8E, 96, 9E, A6, AE, B6, BE ): // RES b,(HL) + CASE8( C6, CE, D6, DE, E6, EE, F6, FE ):{// SET b,(HL) + s_time += 7; + int temp = READ_MEM( r.w.hl ); + int bit = 1 << (data >> 3 & 7); + temp |= bit; // SET + if ( !(data & 0x40) ) + temp ^= bit; // RES + WRITE_MEM( r.w.hl, temp ); + goto loop; + } + + CASE7( C0, C1, C2, C3, C4, C5, C7 ): // SET 0,r + CASE7( C8, C9, CA, CB, CC, CD, CF ): // SET 1,r + CASE7( D0, D1, D2, D3, D4, D5, D7 ): // SET 2,r + CASE7( D8, D9, DA, DB, DC, DD, DF ): // SET 3,r + CASE7( E0, E1, E2, E3, E4, E5, E7 ): // SET 4,r + CASE7( E8, E9, EA, EB, EC, ED, EF ): // SET 5,r + CASE7( F0, F1, F2, F3, F4, F5, F7 ): // SET 6,r + CASE7( F8, F9, FA, FB, FC, FD, FF ): // SET 7,r + R8( data & 7, 0 ) |= 1 << (data >> 3 & 7); + goto loop; + + CASE7( 80, 81, 82, 83, 84, 85, 87 ): // RES 0,r + CASE7( 88, 89, 8A, 8B, 8C, 8D, 8F ): // RES 1,r + CASE7( 90, 91, 92, 93, 94, 95, 97 ): // RES 2,r + CASE7( 98, 99, 9A, 9B, 9C, 9D, 9F ): // RES 3,r + CASE7( A0, A1, A2, A3, A4, A5, A7 ): // RES 4,r + CASE7( A8, A9, AA, AB, AC, AD, AF ): // RES 5,r + CASE7( B0, B1, B2, B3, B4, B5, B7 ): // RES 6,r + CASE7( B8, B9, BA, BB, BC, BD, BF ): // RES 7,r + R8( data & 7, 0 ) &= ~(1 << (data >> 3 & 7)); + goto loop; + } + assert( false ); + } + +#undef GET_ADDR +#define GET_ADDR() GET_LE16( &INSTR( 1, pc ) ) + +//////////////////////////////////////// ED prefix + { + case 0xED: + pc++; + s_time += (clock_table + 256) [data] >> 4; + switch ( data ) + { + { + int temp; + case 0x72: // SBC HL,SP + case 0x7A: // ADC HL,SP + temp = sp; + if ( 0 ) + case 0x42: // SBC HL,BC + case 0x52: // SBC HL,DE + case 0x62: // SBC HL,HL + case 0x4A: // ADC HL,BC + case 0x5A: // ADC HL,DE + case 0x6A: // ADC HL,HL + temp = R16( data >> 3 & 6, 1, 0 ); + int sum = temp + (flags & C01); + flags = ~data >> 2 & N02; + if ( flags ) + sum = -sum; + sum += r.w.hl; + temp ^= r.w.hl; + temp ^= sum; + flags +=(sum >> 16 & C01) + + (temp >> 8 & H10) + + (sum >> 8 & (S80 | F20 | F08)) + + ((temp + 0x8000) >> 14 & V04); + r.w.hl = sum; + if ( WORD( sum ) ) + goto loop; + flags += Z40; + goto loop; + } + + CASE8( 40, 48, 50, 58, 60, 68, 70, 78 ):{// IN r,(C) + int temp = IN_PORT( r.w.bc ); + R8( data >> 3, 8 ) = temp; + flags = (flags & C01) + SZ28P( temp ); + goto loop; + } + + case 0x71: // OUT (C),0 + r.b.flags = 0; + CASE7( 41, 49, 51, 59, 61, 69, 79 ): // OUT (C),r + OUT_PORT( r.w.bc, R8( data >> 3, 8 ) ); + goto loop; + + { + int temp; + case 0x73: // LD (ADDR),SP + temp = sp; + if ( 0 ) + case 0x43: // LD (ADDR),BC + case 0x53: // LD (ADDR),DE + temp = R16( data, 4, 0x43 ); + int addr = GET_ADDR(); + pc += 2; + WRITE_WORD( addr, temp ); + goto loop; + } + + case 0x4B: // LD BC,(ADDR) + case 0x5B:{// LD DE,(ADDR) + int addr = GET_ADDR(); + pc += 2; + R16( data, 4, 0x4B ) = READ_WORD( addr ); + goto loop; + } + + case 0x7B:{// LD SP,(ADDR) + int addr = GET_ADDR(); + pc += 2; + sp = READ_WORD( addr ); + goto loop; + } + + case 0x67:{// RRD + int temp = READ_MEM( r.w.hl ); + WRITE_MEM( r.w.hl, ((r.b.a << 4) + (temp >> 4)) ); + temp = (r.b.a & 0xF0) + (temp & 0x0F); + flags = (flags & C01) + SZ28P( temp ); + r.b.a = temp; + goto loop; + } + + case 0x6F:{// RLD + int temp = READ_MEM( r.w.hl ); + WRITE_MEM( r.w.hl, ((temp << 4) + (r.b.a & 0x0F)) ); + temp = (r.b.a & 0xF0) + (temp >> 4); + flags = (flags & C01) + SZ28P( temp ); + r.b.a = temp; + goto loop; + } + + CASE8( 44, 4C, 54, 5C, 64, 6C, 74, 7C ): // NEG + opcode = 0x10; // flag to do SBC instead of ADC + flags &= ~C01; + data = r.b.a; + r.b.a = 0; + goto adc_data; + + { + int inc; + case 0xA9: // CPD + case 0xB9: // CPDR + inc = -1; + if ( 0 ) + case 0xA1: // CPI + case 0xB1: // CPIR + inc = +1; + int addr = r.w.hl; + r.w.hl = addr + inc; + int temp = READ_MEM( addr ); + + int result = r.b.a - temp; + flags = (flags & C01) + N02 + + ((((temp ^ r.b.a) & H10) ^ result) & (S80 | H10)); + + if ( !BYTE( result ) ) + flags += Z40; + result -= (flags & H10) >> 4; + flags += result & F08; + flags += result << 4 & F20; + if ( !--r.w.bc ) + goto loop; + + flags += V04; + if ( flags & Z40 || data < 0xB0 ) + goto loop; + + pc -= 2; + s_time += 5; + goto loop; + } + + { + int inc; + case 0xA8: // LDD + case 0xB8: // LDDR + inc = -1; + if ( 0 ) + case 0xA0: // LDI + case 0xB0: // LDIR + inc = +1; + int addr = r.w.hl; + r.w.hl = addr + inc; + int temp = READ_MEM( addr ); + + addr = r.w.de; + r.w.de = addr + inc; + WRITE_MEM( addr, temp ); + + temp += r.b.a; + flags = (flags & (S80 | Z40 | C01)) + + (temp & F08) + (temp << 4 & F20); + if ( !--r.w.bc ) + goto loop; + + flags += V04; + if ( data < 0xB0 ) + goto loop; + + pc -= 2; + s_time += 5; + goto loop; + } + + { + int inc; + case 0xAB: // OUTD + case 0xBB: // OTDR + inc = -1; + if ( 0 ) + case 0xA3: // OUTI + case 0xB3: // OTIR + inc = +1; + int addr = r.w.hl; + r.w.hl = addr + inc; + int temp = READ_MEM( addr ); + + int b = --r.b.b; + flags = (temp >> 6 & N02) + SZ28( b ); + if ( b && data >= 0xB0 ) + { + pc -= 2; + s_time += 5; + } + + OUT_PORT( r.w.bc, temp ); + goto loop; + } + + { + int inc; + case 0xAA: // IND + case 0xBA: // INDR + inc = -1; + if ( 0 ) + case 0xA2: // INI + case 0xB2: // INIR + inc = +1; + + int addr = r.w.hl; + r.w.hl = addr + inc; + + int temp = IN_PORT( r.w.bc ); + + int b = --r.b.b; + flags = (temp >> 6 & N02) + SZ28( b ); + if ( b && data >= 0xB0 ) + { + pc -= 2; + s_time += 5; + } + + WRITE_MEM( addr, temp ); + goto loop; + } + + case 0x47: // LD I,A + R.i = r.b.a; + goto loop; + + case 0x4F: // LD R,A + SET_R( r.b.a ); + dprintf( "LD R,A not supported\n" ); + warning = true; + goto loop; + + case 0x57: // LD A,I + r.b.a = R.i; + goto ld_ai_common; + + case 0x5F: // LD A,R + r.b.a = GET_R(); + dprintf( "LD A,R not supported\n" ); + warning = true; + ld_ai_common: + flags = (flags & C01) + SZ28( r.b.a ) + (R.iff2 << 2 & V04); + goto loop; + + CASE8( 45, 4D, 55, 5D, 65, 6D, 75, 7D ): // RETI/RETN + R.iff1 = R.iff2; + goto ret_taken; + + case 0x46: case 0x4E: case 0x66: case 0x6E: // IM 0 + R.im = 0; + goto loop; + + case 0x56: case 0x76: // IM 1 + R.im = 1; + goto loop; + + case 0x5E: case 0x7E: // IM 2 + R.im = 2; + goto loop; + + default: + dprintf( "Opcode $ED $%02X not supported\n", data ); + warning = true; + goto loop; + } + assert( false ); + } + +//////////////////////////////////////// DD/FD prefix + { + int ixy; + case 0xDD: + ixy = ix; + goto ix_prefix; + case 0xFD: + ixy = iy; + ix_prefix: + pc++; + int data2 = READ_CODE( pc ); + s_time += (clock_table + 256) [data] & 0x0F; + switch ( data ) + { + // TODO: more efficient way of avoid negative address + // TODO: avoid using this as argument to READ_MEM() since it is evaluated twice + #define IXY_DISP( ixy, disp ) WORD( (ixy ) + (disp)) + + #define SET_IXY( in ) if ( opcode == 0xDD ) ix = in; else iy = in; + + // ADD/ADC/SUB/SBC + + case 0x96: // SUB (IXY+disp) + case 0x86: // ADD (IXY+disp) + flags &= ~C01; + case 0x9E: // SBC (IXY+disp) + case 0x8E: // ADC (IXY+disp) + pc++; + opcode = data; + data = READ_MEM( IXY_DISP( ixy, SBYTE( data2 ) ) ); + goto adc_data; + + case 0x94: // SUB HXY + case 0x84: // ADD HXY + flags &= ~C01; + case 0x9C: // SBC HXY + case 0x8C: // ADC HXY + opcode = data; + data = ixy >> 8; + goto adc_data; + + case 0x95: // SUB LXY + case 0x85: // ADD LXY + flags &= ~C01; + case 0x9D: // SBC LXY + case 0x8D: // ADC LXY + opcode = data; + data = BYTE( ixy ); + goto adc_data; + + { + int temp; + case 0x39: // ADD IXY,SP + temp = sp; + goto add_ixy_data; + + case 0x29: // ADD IXY,HL + temp = ixy; + goto add_ixy_data; + + case 0x09: // ADD IXY,BC + case 0x19: // ADD IXY,DE + temp = R16( data, 4, 0x09 ); + add_ixy_data: { + int sum = ixy + temp; + temp ^= ixy; + ixy = WORD( sum ); + flags = (flags & (S80 | Z40 | V04)) + + (sum >> 16) + + (sum >> 8 & (F20 | F08)) + + ((temp ^ sum) >> 8 & H10); + goto set_ixy; + } + } + + // AND + case 0xA6: // AND (IXY+disp) + pc++; + data = READ_MEM( IXY_DISP( ixy, SBYTE( data2 ) ) ); + goto and_data; + + case 0xA4: // AND HXY + data = ixy >> 8; + goto and_data; + + case 0xA5: // AND LXY + data = BYTE( ixy ); + goto and_data; + + // OR + case 0xB6: // OR (IXY+disp) + pc++; + data = READ_MEM( IXY_DISP( ixy, SBYTE( data2 ) ) ); + goto or_data; + + case 0xB4: // OR HXY + data = ixy >> 8; + goto or_data; + + case 0xB5: // OR LXY + data = BYTE( ixy ); + goto or_data; + + // XOR + case 0xAE: // XOR (IXY+disp) + pc++; + data = READ_MEM( IXY_DISP( ixy, SBYTE( data2 ) ) ); + goto xor_data; + + case 0xAC: // XOR HXY + data = ixy >> 8; + goto xor_data; + + case 0xAD: // XOR LXY + data = BYTE( ixy ); + goto xor_data; + + // CP + case 0xBE: // CP (IXY+disp) + pc++; + data = READ_MEM( IXY_DISP( ixy, SBYTE( data2 ) ) ); + goto cp_data; + + case 0xBC: // CP HXY + data = ixy >> 8; + goto cp_data; + + case 0xBD: // CP LXY + data = BYTE( ixy ); + goto cp_data; + + // LD + CASE7( 70, 71, 72, 73, 74, 75, 77 ): // LD (IXY+disp),r + data = R8( data, 0x70 ); + if ( 0 ) + case 0x36: // LD (IXY+disp),imm + pc++, data = READ_CODE( pc ); + pc++; + WRITE_MEM( IXY_DISP( ixy, SBYTE( data2 ) ), data ); + goto loop; + + CASE5( 44, 4C, 54, 5C, 7C ): // LD r,HXY + R8( data >> 3, 8 ) = ixy >> 8; + goto loop; + + case 0x64: // LD HXY,HXY + case 0x6D: // LD LXY,LXY + goto loop; + + CASE5( 45, 4D, 55, 5D, 7D ): // LD r,LXY + R8( data >> 3, 8 ) = ixy; + goto loop; + + CASE7( 46, 4E, 56, 5E, 66, 6E, 7E ): // LD r,(IXY+disp) + pc++; + R8( data >> 3, 8 ) = READ_MEM( IXY_DISP( ixy, SBYTE( data2 ) ) ); + goto loop; + + case 0x26: // LD HXY,imm + pc++; + goto ld_hxy_data; + + case 0x65: // LD HXY,LXY + data2 = BYTE( ixy ); + goto ld_hxy_data; + + CASE5( 60, 61, 62, 63, 67 ): // LD HXY,r + data2 = R8( data, 0x60 ); + ld_hxy_data: + ixy = BYTE( ixy ) + (data2 << 8); + goto set_ixy; + + case 0x2E: // LD LXY,imm + pc++; + goto ld_lxy_data; + + case 0x6C: // LD LXY,HXY + data2 = ixy >> 8; + goto ld_lxy_data; + + CASE5( 68, 69, 6A, 6B, 6F ): // LD LXY,r + data2 = R8( data, 0x68 ); + ld_lxy_data: + ixy = (ixy & 0xFF00) + data2; + set_ixy: + if ( opcode == 0xDD ) + { + ix = ixy; + goto loop; + } + iy = ixy; + goto loop; + + case 0xF9: // LD SP,IXY + sp = ixy; + goto loop; + + case 0x22:{// LD (ADDR),IXY + int addr = GET_ADDR(); + pc += 2; + WRITE_WORD( addr, ixy ); + goto loop; + } + + case 0x21: // LD IXY,imm + ixy = GET_ADDR(); + pc += 2; + goto set_ixy; + + case 0x2A:{// LD IXY,(addr) + int addr = GET_ADDR(); + ixy = READ_WORD( addr ); + pc += 2; + goto set_ixy; + } + + // DD/FD CB prefix + case 0xCB: { + data = IXY_DISP( ixy, SBYTE( data2 ) ); + pc++; + data2 = READ_CODE( pc ); + pc++; + switch ( data2 ) + { + case 0x06: goto rlc_data_addr; // RLC (IXY) + case 0x16: goto rl_data_addr; // RL (IXY) + case 0x26: goto sla_data_addr; // SLA (IXY) + case 0x36: goto sll_data_addr; // SLL (IXY) + case 0x0E: goto rrc_data_addr; // RRC (IXY) + case 0x1E: goto rr_data_addr; // RR (IXY) + case 0x2E: goto sra_data_addr; // SRA (IXY) + case 0x3E: goto srl_data_addr; // SRL (IXY) + + CASE8( 46, 4E, 56, 5E, 66, 6E, 76, 7E ):{// BIT b,(IXY+disp) + int temp = READ_MEM( data ); + temp = temp & (1 << (data2 >> 3 & 7)); + flags = (flags & C01) + H10 + (temp & S80); + flags += (unsigned) --temp >> 8 & (Z40 | P04); + goto loop; + } + + CASE8( 86, 8E, 96, 9E, A6, AE, B6, BE ): // RES b,(IXY+disp) + CASE8( C6, CE, D6, DE, E6, EE, F6, FE ):{// SET b,(IXY+disp) + int temp = READ_MEM( data ); + int bit = 1 << (data2 >> 3 & 7); + temp |= bit; // SET + if ( !(data2 & 0x40) ) + temp ^= bit; // RES + WRITE_MEM( data, temp ); + goto loop; + } + + default: + dprintf( "Opcode $%02X $CB $%02X not supported\n", opcode, data2 ); + warning = true; + goto loop; + } + assert( false ); + } + + // INC/DEC + case 0x23: // INC IXY + ixy = WORD( ixy + 1 ); + goto set_ixy; + + case 0x2B: // DEC IXY + ixy = WORD( ixy - 1 ); + goto set_ixy; + + case 0x34: // INC (IXY+disp) + ixy = IXY_DISP( ixy, SBYTE( data2 ) ); + pc++; + data = READ_MEM( ixy ) + 1; + WRITE_MEM( ixy, data ); + goto inc_set_flags; + + case 0x35: // DEC (IXY+disp) + ixy = IXY_DISP( ixy, SBYTE( data2 ) ); + pc++; + data = READ_MEM( ixy ) - 1; + WRITE_MEM( ixy, data ); + goto dec_set_flags; + + case 0x24: // INC HXY + ixy = WORD( ixy + 0x100 ); + data = ixy >> 8; + goto inc_xy_common; + + case 0x2C: // INC LXY + data = BYTE( ixy + 1 ); + ixy = (ixy & 0xFF00) + data; + inc_xy_common: + if ( opcode == 0xDD ) + { + ix = ixy; + goto inc_set_flags; + } + iy = ixy; + goto inc_set_flags; + + case 0x25: // DEC HXY + ixy = WORD( ixy - 0x100 ); + data = ixy >> 8; + goto dec_xy_common; + + case 0x2D: // DEC LXY + data = BYTE( ixy - 1 ); + ixy = (ixy & 0xFF00) + data; + dec_xy_common: + if ( opcode == 0xDD ) + { + ix = ixy; + goto dec_set_flags; + } + iy = ixy; + goto dec_set_flags; + + // PUSH/POP + case 0xE5: // PUSH IXY + data = ixy; + goto push_data; + + case 0xE1:{// POP IXY + ixy = READ_WORD( sp ); + sp = WORD( sp + 2 ); + goto set_ixy; + } + + // Misc + + case 0xE9: // JP (IXY) + pc = ixy; + goto loop; + + case 0xE3:{// EX (SP),IXY + int temp = READ_WORD( sp ); + WRITE_WORD( sp, ixy ); + ixy = temp; + goto set_ixy; + } + + default: + dprintf( "Unnecessary DD/FD prefix encountered\n" ); + warning = true; + pc--; + goto loop; + } + assert( false ); + } + + } + dprintf( "Unhandled main opcode: $%02X\n", opcode ); + assert( false ); + +#ifdef IDLE_ADDR +hit_idle_addr: + s_time -= 11; + goto out_of_time; +#endif +halt: + s_time &= 3; // increment by multiple of 4 +out_of_time: + pc--; + + r.b.flags = flags; + R.ix = ix; + R.iy = iy; + R.sp = sp; + R.pc = pc; + R.b = r.b; + + CPU.cpu_state_.base = s.base; + CPU.cpu_state_.time = s_time; + CPU.cpu_state = &CPU.cpu_state_; +} diff --git a/Frameworks/GME/gme/adlib.h b/Frameworks/GME/gme/adlib.h new file mode 100644 index 000000000..7d5ef6846 --- /dev/null +++ b/Frameworks/GME/gme/adlib.h @@ -0,0 +1,154 @@ +/* + * Copyright (C) 2002-2009 The DOSBox Team + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* $Id: adlib.h,v 1.4 2009/04/26 10:33:53 harekiet Exp $ */ + +#ifndef DOSBOX_ADLIB_H +#define DOSBOX_ADLIB_H + +/*#include "dosbox.h" +#include "mixer.h" +#include "inout.h" +#include "mixer.h" +#include "setup.h" +#include "pic.h" +#include "hardware.h"*/ + + +namespace Adlib { + +struct Timer { + double start; + double delay; + bool enabled, overflow, masked; + Bit8u counter; + Timer() { + masked = false; + overflow = false; + enabled = false; + counter = 0; + delay = 0; + } + //Call update before making any further changes + void Update( double time ) { + if ( !enabled || !delay ) + return; + double deltaStart = time - start; + //Only set the overflow flag when not masked + if ( deltaStart >= 0 && !masked ) { + overflow = 1; + } + } + //On a reset make sure the start is in sync with the next cycle + void Reset(const double& time ) { + overflow = false; + if ( !delay || !enabled ) + return; + double delta = (time - start); + double rem = fmod( delta, delay ); + double next = delay - rem; + start = time + next; + } + void Stop( ) { + enabled = false; + } + void Start( const double& time, Bits scale ) { + //Don't enable again + if ( enabled ) { + return; + } + enabled = true; + delay = 0.001 * (256 - counter ) * scale; + start = time + delay; + } + +}; + +struct Chip { + //Last selected register + Timer timer[2]; + //Check for it being a write to the timer + bool Write( Bit32u addr, Bit8u val ); + //Read the current timer state, will use current double + Bit8u Read( ); +}; + +//The type of handler this is +typedef enum { + MODE_OPL2, + MODE_DUALOPL2, + MODE_OPL3 +} Mode; + +/*class Handler { +public: + //Write an address to a chip, returns the address the chip sets + virtual Bit32u WriteAddr( Bit32u port, Bit8u val ) = 0; + //Write to a specific register in the chip + virtual void WriteReg( Bit32u addr, Bit8u val ) = 0; + //Generate a certain amount of samples + virtual void Generate( MixerChannel* chan, Bitu samples ) = 0; + //Initialize at a specific sample rate and mode + virtual void Init( Bitu rate ) = 0; + virtual ~Handler() { + } +};*/ + +//The cache for 2 chips or an opl3 +typedef Bit8u RegisterCache[512]; + +//Internal class used for dro capturing +class Capture; + +/*class Module: public Module_base { + IO_ReadHandleObject ReadHandler[3]; + IO_WriteHandleObject WriteHandler[3]; + MixerObject mixerObject; + + //Mode we're running in + Mode mode; + //Last selected address in the chip for the different modes + union { + Bit32u normal; + Bit8u dual[2]; + } reg; + void CacheWrite( Bit32u reg, Bit8u val ); + void DualWrite( Bit8u index, Bit8u reg, Bit8u val ); +public: + static OPL_Mode oplmode; + MixerChannel* mixerChan; + Bit32u lastUsed; //Ticks when adlib was last used to turn of mixing after a few second + + Handler* handler; //Handler that will generate the sound + RegisterCache cache; + Capture* capture; + Chip chip[2]; + + //Handle port writes + void PortWrite( Bitu port, Bitu val, Bitu iolen ); + Bitu PortRead( Bitu port, Bitu iolen ); + void Init( Mode m ); + + Module( Section* configuration); + ~Module(); +};*/ + + +} //Adlib namespace + +#endif diff --git a/Frameworks/GME/gme/blargg_common.cpp b/Frameworks/GME/gme/blargg_common.cpp new file mode 100644 index 000000000..ed548c4ca --- /dev/null +++ b/Frameworks/GME/gme/blargg_common.cpp @@ -0,0 +1,58 @@ +// $package. http://www.slack.net/~ant/ + +#include "blargg_common.h" + +/* Copyright (C) 2008-2009 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +#include "blargg_source.h" + +BLARGG_NAMESPACE_BEGIN + +// defined here to avoid need for blargg_errors.cpp in simple programs +blargg_err_def_t blargg_err_memory = BLARGG_ERR_MEMORY; + +void blargg_vector_::init() +{ + begin_ = NULL; + size_ = 0; +} + +void blargg_vector_::clear() +{ + void* p = begin_; + begin_ = NULL; + size_ = 0; + free( p ); +} + +blargg_err_t blargg_vector_::resize_( size_t n, size_t elem_size ) +{ + if ( n != size_ ) + { + if ( n == 0 ) + { + // Simpler to handle explicitly. Realloc will handle a size of 0, + // but then we have to avoid raising an error for a NULL return. + clear(); + } + else + { + void* p = realloc( begin_, n * elem_size ); + CHECK_ALLOC( p ); + begin_ = p; + size_ = n; + } + } + return blargg_ok; +} + +BLARGG_NAMESPACE_END diff --git a/Frameworks/GME/gme/blargg_errors.cpp b/Frameworks/GME/gme/blargg_errors.cpp new file mode 100644 index 000000000..552415659 --- /dev/null +++ b/Frameworks/GME/gme/blargg_errors.cpp @@ -0,0 +1,117 @@ +// $package. http://www.slack.net/~ant/ + +#include "blargg_errors.h" + +/* Copyright (C) 2009 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +#include "blargg_source.h" + +BLARGG_NAMESPACE_BEGIN + +blargg_err_def_t blargg_err_generic = BLARGG_ERR_GENERIC; +// blargg_err_memory is defined in blargg_common.cpp +blargg_err_def_t blargg_err_caller = BLARGG_ERR_CALLER; +blargg_err_def_t blargg_err_internal = BLARGG_ERR_INTERNAL; +blargg_err_def_t blargg_err_limitation = BLARGG_ERR_LIMITATION; + +blargg_err_def_t blargg_err_file_missing = BLARGG_ERR_FILE_MISSING; +blargg_err_def_t blargg_err_file_read = BLARGG_ERR_FILE_READ; +blargg_err_def_t blargg_err_file_write = BLARGG_ERR_FILE_WRITE; +blargg_err_def_t blargg_err_file_io = BLARGG_ERR_FILE_IO; +blargg_err_def_t blargg_err_file_full = BLARGG_ERR_FILE_FULL; +blargg_err_def_t blargg_err_file_eof = BLARGG_ERR_FILE_EOF; + +blargg_err_def_t blargg_err_file_type = BLARGG_ERR_FILE_TYPE; +blargg_err_def_t blargg_err_file_feature = BLARGG_ERR_FILE_FEATURE; +blargg_err_def_t blargg_err_file_corrupt = BLARGG_ERR_FILE_CORRUPT; + +const char* blargg_err_str( blargg_err_t err ) +{ + if ( !err ) + return ""; + + if ( *err == BLARGG_ERR_TYPE("")[0] ) + return err + 1; + + return err; +} + +bool blargg_is_err_type( blargg_err_t err, const char type [] ) +{ + if ( err ) + { + // True if first strlen(type) characters of err match type + char const* p = err; + while ( *type && *type == *p ) + { + type++; + p++; + } + + if ( !*type ) + return true; + } + + return false; +} + +const char* blargg_err_details( blargg_err_t err ) +{ + const char* p = err; + if ( !p ) + { + p = ""; + } + else if ( *p == BLARGG_ERR_TYPE("")[0] ) + { + while ( *p && *p != ';' ) + p++; + + // Skip ; and space after it + if ( *p ) + { + p++; + + check( *p == ' ' ); + if ( *p ) + p++; + } + } + return p; +} + +int blargg_err_to_code( blargg_err_t err, blargg_err_to_code_t const codes [] ) +{ + if ( !err ) + return 0; + + while ( codes->str && !blargg_is_err_type( err, codes->str ) ) + codes++; + + return codes->code; +} + +blargg_err_t blargg_code_to_err( int code, blargg_err_to_code_t const codes [] ) +{ + if ( !code ) + return blargg_ok; + + while ( codes->str && codes->code != code ) + codes++; + + if ( !codes->str ) + return blargg_err_generic; + + return codes->str; +} + +BLARGG_NAMESPACE_END diff --git a/Frameworks/GME/gme/blargg_errors.h b/Frameworks/GME/gme/blargg_errors.h new file mode 100644 index 000000000..10dd52884 --- /dev/null +++ b/Frameworks/GME/gme/blargg_errors.h @@ -0,0 +1,84 @@ +// Error strings and conversion functions + +// $package +#ifndef BLARGG_ERRORS_H +#define BLARGG_ERRORS_H + +#ifndef BLARGG_COMMON_H + #include "blargg_common.h" +#endif + +BLARGG_NAMESPACE_BEGIN + +typedef const char blargg_err_def_t []; + +// Basic errors +extern blargg_err_def_t blargg_err_generic; +extern blargg_err_def_t blargg_err_memory; +extern blargg_err_def_t blargg_err_caller; +extern blargg_err_def_t blargg_err_internal; +extern blargg_err_def_t blargg_err_limitation; + +// File low-level +extern blargg_err_def_t blargg_err_file_missing; // not found +extern blargg_err_def_t blargg_err_file_read; +extern blargg_err_def_t blargg_err_file_write; +extern blargg_err_def_t blargg_err_file_io; +extern blargg_err_def_t blargg_err_file_full; +extern blargg_err_def_t blargg_err_file_eof; + +// File high-level +extern blargg_err_def_t blargg_err_file_type; // wrong file type +extern blargg_err_def_t blargg_err_file_feature; +extern blargg_err_def_t blargg_err_file_corrupt; + +// C string describing error, or "" if err == NULL +const char* blargg_err_str( blargg_err_t err ); + +// True iff error is of given type, or false if err == NULL +bool blargg_is_err_type( blargg_err_t, const char type [] ); + +// Details of error without describing main cause, or "" if err == NULL +const char* blargg_err_details( blargg_err_t err ); + +// Converts error string to integer code using mapping table. Calls blargg_is_err_type() +// for each str and returns code on first match. Returns 0 if err == NULL. +struct blargg_err_to_code_t { + const char* str; + int code; +}; +int blargg_err_to_code( blargg_err_t err, blargg_err_to_code_t const [] ); + +// Converts error code back to string. If code == 0, returns NULL. If not in table, +// returns blargg_err_generic. +blargg_err_t blargg_code_to_err( int code, blargg_err_to_code_t const [] ); + +// Generates error string literal with details of cause +#define BLARGG_ERR( type, str ) (type "; " str) + +// Extra space to make it clear when blargg_err_str() isn't called to get +// printable version of error. At some point, I might prefix error strings +// with a code, to speed conversion to a code. +#define BLARGG_ERR_TYPE( str ) " " str + +// Error types to pass to BLARGG_ERR macro +#define BLARGG_ERR_GENERIC BLARGG_ERR_TYPE( "operation failed" ) +#define BLARGG_ERR_MEMORY BLARGG_ERR_TYPE( "out of memory" ) +#define BLARGG_ERR_CALLER BLARGG_ERR_TYPE( "internal usage bug" ) +#define BLARGG_ERR_INTERNAL BLARGG_ERR_TYPE( "internal bug" ) +#define BLARGG_ERR_LIMITATION BLARGG_ERR_TYPE( "exceeded limitation" ) + +#define BLARGG_ERR_FILE_MISSING BLARGG_ERR_TYPE( "file not found" ) +#define BLARGG_ERR_FILE_READ BLARGG_ERR_TYPE( "couldn't open file" ) +#define BLARGG_ERR_FILE_WRITE BLARGG_ERR_TYPE( "couldn't modify file" ) +#define BLARGG_ERR_FILE_IO BLARGG_ERR_TYPE( "read/write error" ) +#define BLARGG_ERR_FILE_FULL BLARGG_ERR_TYPE( "disk full" ) +#define BLARGG_ERR_FILE_EOF BLARGG_ERR_TYPE( "truncated file" ) + +#define BLARGG_ERR_FILE_TYPE BLARGG_ERR_TYPE( "wrong file type" ) +#define BLARGG_ERR_FILE_FEATURE BLARGG_ERR_TYPE( "unsupported file feature" ) +#define BLARGG_ERR_FILE_CORRUPT BLARGG_ERR_TYPE( "corrupt file" ) + +BLARGG_NAMESPACE_END + +#endif diff --git a/Frameworks/GME/gme/c140.c b/Frameworks/GME/gme/c140.c new file mode 100644 index 000000000..9ebb520d9 --- /dev/null +++ b/Frameworks/GME/gme/c140.c @@ -0,0 +1,620 @@ +/* +C140.c + +Simulator based on AMUSE sources. +The C140 sound chip is used by Namco System 2 and System 21 +The 219 ASIC (which incorporates a modified C140) is used by Namco NA-1 and NA-2 +This chip controls 24 channels (C140) or 16 (219) of PCM. +16 bytes are associated with each channel. +Channels can be 8 bit signed PCM, or 12 bit signed PCM. + +Timer behavior is not yet handled. + +Unmapped registers: + 0x1f8:timer interval? (Nx0.1 ms) + 0x1fa:irq ack? timer restart? + 0x1fe:timer switch?(0:off 1:on) + +-------------- + + ASIC "219" notes + + On the 219 ASIC used on NA-1 and NA-2, the high registers have the following + meaning instead: + 0x1f7: bank for voices 0-3 + 0x1f1: bank for voices 4-7 + 0x1f3: bank for voices 8-11 + 0x1f5: bank for voices 12-15 + + Some games (bkrtmaq, xday2) write to 0x1fd for voices 12-15 instead. Probably the bank registers + mirror at 1f8, in which case 1ff is also 0-3, 1f9 is also 4-7, 1fb is also 8-11, and 1fd is also 12-15. + + Each bank is 0x20000 (128k), and the voice addresses on the 219 are all multiplied by 2. + Additionally, the 219's base pitch is the same as the C352's (42667). But these changes + are IMO not sufficient to make this a separate file - all the other registers are + fully compatible. + + Finally, the 219 only has 16 voices. +*/ +/* + 2000.06.26 CAB fixed compressed pcm playback + 2002.07.20 R.Belmont added support for multiple banking types + 2006.01.08 R.Belmont added support for NA-1/2 "219" derivative +*/ + + +//#include "emu.h" +#include +#include +#include "c140.h" + +#define NULL ((void *)0) + +#define MAX_VOICE 24 + +struct voice_registers +{ + UINT8 volume_right; + UINT8 volume_left; + UINT8 frequency_msb; + UINT8 frequency_lsb; + UINT8 bank; + UINT8 mode; + UINT8 start_msb; + UINT8 start_lsb; + UINT8 end_msb; + UINT8 end_lsb; + UINT8 loop_msb; + UINT8 loop_lsb; + UINT8 reserved[4]; +}; + +typedef struct +{ + long ptoffset; + long pos; + long key; + //--work + long lastdt; + long prevdt; + long dltdt; + //--reg + long rvol; + long lvol; + long frequency; + long bank; + long mode; + + long sample_start; + long sample_end; + long sample_loop; + UINT8 Muted; +} VOICE; + +typedef struct _c140_state c140_state; +struct _c140_state +{ + int sample_rate; + //sound_stream *stream; + int banking_type; + /* internal buffers */ + INT16 *mixer_buffer_left; + INT16 *mixer_buffer_right; + + int baserate; + UINT32 pRomSize; + void *pRom; + UINT8 REG[0x200]; + + INT16 pcmtbl[8]; //2000.06.26 CAB + + VOICE voi[MAX_VOICE]; +}; + +/*INLINE c140_state *get_safe_token(device_t *device) +{ + assert(device != NULL); + assert(device->type() == C140); + return (c140_state *)downcast(device)->token(); +}*/ + + +static void init_voice( VOICE *v ) +{ + v->key=0; + v->ptoffset=0; + v->rvol=0; + v->lvol=0; + v->frequency=0; + v->bank=0; + v->mode=0; + v->sample_start=0; + v->sample_end=0; + v->sample_loop=0; +} +//READ8_DEVICE_HANDLER( c140_r ) +UINT8 c140_r(void *chip, offs_t offset) +{ + //c140_state *info = get_safe_token(device); + c140_state *info = (c140_state *) chip; + offset&=0x1ff; + return info->REG[offset]; +} + +/* + find_sample: compute the actual address of a sample given it's + address and banking registers, as well as the board type. + + I suspect in "real life" this works like the Sega MultiPCM where the banking + is done by a small PAL or GAL external to the sound chip, which can be switched + per-game or at least per-PCB revision as addressing range needs grow. + */ +static long find_sample(c140_state *info, long adrs, long bank, int voice) +{ + long newadr = 0; + + static const INT16 asic219banks[4] = { 0x1f7, 0x1f1, 0x1f3, 0x1f5 }; + + adrs=(bank<<16)+adrs; + + switch (info->banking_type) + { + case C140_TYPE_SYSTEM2: + // System 2 banking + newadr = ((adrs&0x200000)>>2)|(adrs&0x7ffff); + break; + + case C140_TYPE_SYSTEM21_A: + // System 21 type A (simple) banking. + // similar to System 2's. + newadr = ((adrs&0x300000)>>1)+(adrs&0x7ffff); + break; + + case C140_TYPE_SYSTEM21_B: + // System 21 type B (chip select) banking + + // get base address of sample inside the bank + newadr = ((adrs&0x100000)>>2) + (adrs&0x3ffff); + + // now add the starting bank offsets based on the 2 + // chip select bits. + // 0x40000 picks individual 512k ROMs + if (adrs & 0x40000) + { + newadr += 0x80000; + } + + // and 0x200000 which group of chips... + if (adrs & 0x200000) + { + newadr += 0x100000; + } + break; + + case C140_TYPE_ASIC219: + // ASIC219's banking is fairly simple + newadr = ((info->REG[asic219banks[voice/4]]&0x3) * 0x20000) + adrs; + break; + } + + return (newadr); +} +//WRITE8_DEVICE_HANDLER( c140_w ) +void c140_w(void *chip, offs_t offset, UINT8 data) +{ + //c140_state *info = get_safe_token(device); + c140_state *info = (c140_state *) chip; + //info->stream->update(); + + offset&=0x1ff; + + // mirror the bank registers on the 219, fixes bkrtmaq (and probably xday2 based on notes in the HLE) + if ((offset >= 0x1f8) && (info->banking_type == C140_TYPE_ASIC219)) + { + offset -= 8; + } + + info->REG[offset]=data; + if( offset<0x180 ) + { + VOICE *v = &info->voi[offset>>4]; + + if( (offset&0xf)==0x5 ) + { + if( data&0x80 ) + { + const struct voice_registers *vreg = (struct voice_registers *) &info->REG[offset&0x1f0]; + v->key=1; + v->ptoffset=0; + v->pos=0; + v->lastdt=0; + v->prevdt=0; + v->dltdt=0; + v->bank = vreg->bank; + v->mode = data; + + // on the 219 asic, addresses are in words + if (info->banking_type == C140_TYPE_ASIC219) + { + v->sample_loop = (vreg->loop_msb*256 + vreg->loop_lsb)*2; + v->sample_start = (vreg->start_msb*256 + vreg->start_lsb)*2; + v->sample_end = (vreg->end_msb*256 + vreg->end_lsb)*2; + + #if 0 + logerror("219: play v %d mode %02x start %x loop %x end %x\n", + offset>>4, v->mode, + find_sample(info, v->sample_start, v->bank, offset>>4), + find_sample(info, v->sample_loop, v->bank, offset>>4), + find_sample(info, v->sample_end, v->bank, offset>>4)); + #endif + } + else + { + v->sample_loop = vreg->loop_msb*256 + vreg->loop_lsb; + v->sample_start = vreg->start_msb*256 + vreg->start_lsb; + v->sample_end = vreg->end_msb*256 + vreg->end_lsb; + } + } + else + { + v->key=0; + } + } + } +} + +//void c140_set_base(device_t *device, void *base) +void c140_set_base(void *chip, void *base) +{ + //c140_state *info = get_safe_token(device); + c140_state *info = (c140_state *) chip; + info->pRom = base; +} + +/*INLINE int limit(INT32 in) +{ + if(in>0x7fff) return 0x7fff; + else if(in<-0x8000) return -0x8000; + return in; +}*/ + +//static STREAM_UPDATE( update_stereo ) +void c140_update(void *chip, stream_sample_t **outputs, int samples) +{ + //c140_state *info = (c140_state *)param; + c140_state *info = (c140_state *) chip; + int i,j; + + INT32 rvol,lvol; + INT32 dt; + INT32 sdt; + INT32 st,ed,sz; + + INT8 *pSampleData; + INT32 frequency,delta,offset,pos; + INT32 cnt, voicecnt; + INT32 lastdt,prevdt,dltdt; + float pbase=(float)info->baserate*2.0f / (float)info->sample_rate; + + INT16 *lmix, *rmix; + + if(samples>info->sample_rate) samples=info->sample_rate; + + /* zap the contents of the mixer buffer */ + memset(info->mixer_buffer_left, 0, samples * sizeof(INT16)); + memset(info->mixer_buffer_right, 0, samples * sizeof(INT16)); + if (info->pRom == NULL) + return; + + /* get the number of voices to update */ + voicecnt = (info->banking_type == C140_TYPE_ASIC219) ? 16 : 24; + + //--- audio update + for( i=0;ivoi[i]; + const struct voice_registers *vreg = (struct voice_registers *)&info->REG[i*16]; + + if( v->key && ! v->Muted) + { + frequency= vreg->frequency_msb*256 + vreg->frequency_lsb; + + /* Abort voice if no frequency value set */ + if(frequency==0) continue; + + /* Delta = frequency * ((8MHz/374)*2 / sample rate) */ + delta=(long)((float)frequency * pbase); + + /* Calculate left/right channel volumes */ + lvol=(vreg->volume_left*32)/MAX_VOICE; //32ch -> 24ch + rvol=(vreg->volume_right*32)/MAX_VOICE; + + /* Set mixer outputs base pointers */ + lmix = info->mixer_buffer_left; + rmix = info->mixer_buffer_right; + + /* Retrieve sample start/end and calculate size */ + st=v->sample_start; + ed=v->sample_end; + sz=ed-st; + + /* Retrieve base pointer to the sample data */ + //pSampleData=(signed char*)((FPTR)info->pRom + find_sample(info, st, v->bank, i)); + pSampleData = (INT8*)info->pRom + find_sample(info, st, v->bank, i); + + /* Fetch back previous data pointers */ + offset=v->ptoffset; + pos=v->pos; + lastdt=v->lastdt; + prevdt=v->prevdt; + dltdt=v->dltdt; + + /* Switch on data type - compressed PCM is only for C140 */ + if ((v->mode&8) && (info->banking_type != C140_TYPE_ASIC219)) + { + //compressed PCM (maybe correct...) + /* Loop for enough to fill sample buffer as requested */ + for(j=0;j>16)&0x7fff; + offset &= 0xffff; + pos+=cnt; + //for(;cnt>0;cnt--) + { + /* Check for the end of the sample */ + if(pos >= sz) + { + /* Check if its a looping sample, either stop or loop */ + if(v->mode&0x10) + { + pos = (v->sample_loop - st); + } + else + { + v->key=0; + break; + } + } + + /* Read the chosen sample byte */ + dt=pSampleData[pos]; + + /* decompress to 13bit range */ //2000.06.26 CAB + sdt=dt>>3; //signed + if(sdt<0) sdt = (sdt<<(dt&7)) - info->pcmtbl[dt&7]; + else sdt = (sdt<<(dt&7)) + info->pcmtbl[dt&7]; + + prevdt=lastdt; + lastdt=sdt; + dltdt=(lastdt - prevdt); + } + + /* Caclulate the sample value */ + dt=((dltdt*offset)>>16)+prevdt; + + /* Write the data to the sample buffers */ + *lmix++ +=(dt*lvol)>>(5+5); + *rmix++ +=(dt*rvol)>>(5+5); + } + } + else + { + /* linear 8bit signed PCM */ + for(j=0;j>16)&0x7fff; + offset &= 0xffff; + pos += cnt; + /* Check for the end of the sample */ + if(pos >= sz) + { + /* Check if its a looping sample, either stop or loop */ + if( v->mode&0x10 ) + { + pos = (v->sample_loop - st); + } + else + { + v->key=0; + break; + } + } + + if( cnt ) + { + prevdt=lastdt; + + if (info->banking_type == C140_TYPE_ASIC219) + { + //lastdt = pSampleData[BYTE_XOR_BE(pos)]; + lastdt = pSampleData[pos ^ 0x01]; + + // Sign + magnitude format + if ((v->mode & 0x01) && (lastdt & 0x80)) + lastdt = -(lastdt & 0x7f); + + // Sign flip + if (v->mode & 0x40) + lastdt = -lastdt; + } + else + { + lastdt=pSampleData[pos]; + } + + dltdt = (lastdt - prevdt); + } + + /* Caclulate the sample value */ + dt=((dltdt*offset)>>16)+prevdt; + + /* Write the data to the sample buffers */ + *lmix++ +=(dt*lvol)>>5; + *rmix++ +=(dt*rvol)>>5; + } + } + + /* Save positional data for next callback */ + v->ptoffset=offset; + v->pos=pos; + v->lastdt=lastdt; + v->prevdt=prevdt; + v->dltdt=dltdt; + } + } + + /* render to MAME's stream buffer */ + lmix = info->mixer_buffer_left; + rmix = info->mixer_buffer_right; + { + stream_sample_t *dest1 = outputs[0]; + stream_sample_t *dest2 = outputs[1]; + for (i = 0; i < samples; i++) + { + //*dest1++ = limit(8*(*lmix++)); + //*dest2++ = limit(8*(*rmix++)); + *dest1++ = 8 * (*lmix ++); + *dest2++ = 8 * (*rmix ++); + } + } +} + +//static DEVICE_START( c140 ) +void * device_start_c140(int sample_rate, int clock, int banking_type) +{ + //const c140_interface *intf = (const c140_interface *)device->static_config(); + //c140_state *info = get_safe_token(device); + c140_state *info; + int i; + + info = (c140_state *) malloc(sizeof(c140_state)); + if (!info) return info; + + //info->sample_rate=info->baserate=device->clock(); + info->sample_rate = sample_rate; + info->baserate = clock; + + //info->banking_type = intf->banking_type; + info->banking_type = banking_type; + + //info->stream = device->machine().sound().stream_alloc(*device,0,2,info->sample_rate,info,update_stereo); + + //info->pRom=*device->region(); + info->pRomSize = 0x00; + info->pRom = NULL; + + /* make decompress pcm table */ //2000.06.26 CAB + { + INT32 segbase=0; + for(i=0;i<8;i++) + { + info->pcmtbl[i]=segbase; //segment base value + segbase += 16<REG,0,sizeof(info->REG)); + { + int i; + for(i=0;ivoi[i] ); + }*/ + + /* allocate a pair of buffers to mix into - 1 second's worth should be more than enough */ + //info->mixer_buffer_left = auto_alloc_array(device->machine(), INT16, 2 * info->sample_rate); + info->mixer_buffer_left = (INT16*)malloc(sizeof(INT16) * 2 * info->sample_rate); + info->mixer_buffer_right = info->mixer_buffer_left + info->sample_rate; + + for (i = 0; i < MAX_VOICE; i ++) + info->voi[i].Muted = 0x00; + + return info; +} + +void device_stop_c140(void *chip) +{ + c140_state *info = (c140_state *) chip; + + free(info->pRom); info->pRom = NULL; + free(info->mixer_buffer_left); + free(info); +} + +void device_reset_c140(void *chip) +{ + c140_state *info = (c140_state *) chip; + int i; + + memset(info->REG, 0, sizeof(info->REG)); + + for(i = 0; i < MAX_VOICE; i ++) + init_voice( &info->voi[i] ); + + return; +} + +void c140_write_rom(void *chip, offs_t ROMSize, offs_t DataStart, offs_t DataLength, + const UINT8* ROMData) +{ + c140_state *info = (c140_state *) chip; + + if (info->pRomSize != ROMSize) + { + info->pRom = (UINT8*)realloc(info->pRom, ROMSize); + info->pRomSize = ROMSize; + memset(info->pRom, 0xFF, ROMSize); + } + if (DataStart > ROMSize) + return; + if (DataStart + DataLength > ROMSize) + DataLength = ROMSize - DataStart; + + memcpy((INT8*)info->pRom + DataStart, ROMData, DataLength); + + return; +} + + +void c140_set_mute_mask(void *chip, UINT32 MuteMask) +{ + c140_state *info = (c140_state *) chip; + UINT8 CurChn; + + for (CurChn = 0; CurChn < MAX_VOICE; CurChn ++) + info->voi[CurChn].Muted = (MuteMask >> CurChn) & 0x01; + + return; +} + + + + +/************************************************************************** + * Generic get_info + **************************************************************************/ + +/*DEVICE_GET_INFO( c140 ) +{ + switch (state) + { + // --- the following bits of info are returned as 64-bit signed integers --- // + case DEVINFO_INT_TOKEN_BYTES: info->i = sizeof(c140_state); break; + + // --- the following bits of info are returned as pointers to data or functions --- // + case DEVINFO_FCT_START: info->start = DEVICE_START_NAME( c140 ); break; + case DEVINFO_FCT_STOP: // nothing // break; + case DEVINFO_FCT_RESET: // nothing // break; + + // --- the following bits of info are returned as NULL-terminated strings --- // + case DEVINFO_STR_NAME: strcpy(info->s, "C140"); break; + case DEVINFO_STR_FAMILY: strcpy(info->s, "Namco PCM"); break; + case DEVINFO_STR_VERSION: strcpy(info->s, "1.0"); break; + case DEVINFO_STR_SOURCE_FILE: strcpy(info->s, __FILE__); break; + case DEVINFO_STR_CREDITS: strcpy(info->s, "Copyright Nicola Salmoria and the MAME Team"); break; + } +} + + +DEFINE_LEGACY_SOUND_DEVICE(C140, c140);*/ diff --git a/Frameworks/GME/gme/c140.h b/Frameworks/GME/gme/c140.h new file mode 100644 index 000000000..9345a38ed --- /dev/null +++ b/Frameworks/GME/gme/c140.h @@ -0,0 +1,58 @@ +/* C140.h */ + +#pragma once + +#ifndef __OSDCOMM_H__ +#define __OSDCOMM_H__ +typedef unsigned char UINT8; /* unsigned 8bit */ +typedef unsigned short UINT16; /* unsigned 16bit */ +typedef unsigned int UINT32; /* unsigned 32bit */ +typedef signed char INT8; /* signed 8bit */ +typedef signed short INT16; /* signed 16bit */ +typedef signed int INT32; /* signed 32bit */ + +typedef INT32 stream_sample_t; +typedef UINT32 offs_t; +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +void c140_update(void * chip, stream_sample_t **outputs, int samples); +void * device_start_c140(int sample_rate, int clock, int banking_type); +void device_stop_c140(void * chip); +void device_reset_c140(void * chip); + +//READ8_DEVICE_HANDLER( c140_r ); +//WRITE8_DEVICE_HANDLER( c140_w ); +UINT8 c140_r(void * chip, offs_t offset); +void c140_w(void * chip, offs_t offset, UINT8 data); + +//void c140_set_base(device_t *device, void *base); +void c140_set_base(void *chip, void *base); + +enum +{ + C140_TYPE_SYSTEM2, + C140_TYPE_SYSTEM21_A, + C140_TYPE_SYSTEM21_B, + C140_TYPE_ASIC219 +}; + +/*typedef struct _c140_interface c140_interface; +struct _c140_interface { + int banking_type; +};*/ + + +void c140_write_rom(void * chip, offs_t ROMSize, offs_t DataStart, offs_t DataLength, + const UINT8* ROMData); + +void c140_set_mute_mask(void * chip, UINT32 MuteMask); + +#ifdef __cplusplus +} +#endif + +//DECLARE_LEGACY_SOUND_DEVICE(C140, c140); diff --git a/Frameworks/GME/gme/dac_control.c b/Frameworks/GME/gme/dac_control.c new file mode 100644 index 000000000..867e4d858 --- /dev/null +++ b/Frameworks/GME/gme/dac_control.c @@ -0,0 +1,367 @@ + /************************ + * DAC Stream Control * + ***********************/ +// (Custom Driver to handle PCM Streams of YM2612 DAC and PWM.) +// +// Written on 3 February 2011 by Valley Bell +// Last Update: 25 April 2011 +// +// Only for usage in non-commercial, VGM file related software. + +/* How it basically works: + +1. send command X with data Y at frequency F to chip C +2. do that until you receive a STOP command, or until you sent N commands + +*/ + +#include "dac_control.h" + +#include + +#define INLINE static __inline + +void chip_reg_write(void * context, UINT32 Sample, UINT8 ChipType, UINT8 ChipID, UINT8 Port, UINT8 Offset, UINT8 Data); + +typedef struct _dac_control +{ + UINT32 SampleRate; + + // Commands sent to dest-chip + UINT8 DstChipType; + UINT8 DstChipID; + UINT16 DstCommand; + UINT8 CmdSize; + + UINT32 Frequency; // Frequency (Hz) at which the commands are sent + UINT32 DataLen; // to protect from reading beyond End Of Data + const UINT8* Data; + UINT32 DataStart; // Position where to start + UINT8 StepSize; // usually 1, set to 2 for L/R interleaved data + UINT8 StepBase; // usually 0, set to 0/1 for L/R interleaved data + UINT32 CmdsToSend; + + // Running Bits: 0 (01) - is playing + // 2 (04) - loop sample (simple loop from start to end) + // 4 (10) - already sent this command + // 7 (80) - disabled + UINT8 Running; + UINT32 Step; + UINT32 Pos; + UINT32 RemainCmds; + UINT8 DataStep; // always StepSize * CmdSize + + void * context; // context data sent to chip_reg_write +} dac_control; + +#ifndef NULL +#define NULL (void*)0 +#endif + +static void daccontrol_SendCommand(dac_control *chip, UINT32 Sample) +{ + UINT8 Port; + UINT8 Command; + UINT8 Data; + const UINT8* ChipData; + + if (chip->Running & 0x10) // command already sent + return; + if (chip->DataStart + chip->Pos >= chip->DataLen) + return; + + ChipData = chip->Data + (chip->DataStart + chip->Pos); + switch(chip->DstChipType) + { + // Support for the important chips + case 0x02: // YM2612 + Port = (chip->DstCommand & 0xFF00) >> 8; + Command = (chip->DstCommand & 0x00FF) >> 0; + Data = ChipData[0x00]; + chip_reg_write(chip->context, Sample, chip->DstChipType, chip->DstChipID, Port, Command, Data); + break; + case 0x11: // PWM + Port = (chip->DstCommand & 0x000F) >> 0; + Command = ChipData[0x01] & 0x0F; + Data = ChipData[0x00]; + chip_reg_write(chip->context, Sample, chip->DstChipType, chip->DstChipID, Port, Command, Data); + break; + // (Generic) Support for other chips (just for completeness) + case 0x00: // SN76496 + Command = (chip->DstCommand & 0x00F0) >> 0; + Data = ChipData[0x00] & 0x0F; + if (Command & 0x10) + { + // Volume Change (4-Bit value) + chip_reg_write(chip->context, Sample, chip->DstChipType, chip->DstChipID, 0x00, 0x00, Command | Data); + } + else + { + // Frequency Write (10-Bit value) + Port = ((ChipData[0x01] & 0x03) << 4) | ((ChipData[0x00] & 0xF0) >> 4); + chip_reg_write(chip->context, Sample, chip->DstChipType, chip->DstChipID, 0x00, 0x00, Command | Data); + chip_reg_write(chip->context, Sample, chip->DstChipType, chip->DstChipID, 0x00, 0x00, Port); + } + break; + case 0x01: // YM2413 + case 0x03: // YM2151 + case 0x09: // YM3812 + case 0x0A: // YM3526 + case 0x0B: // Y8950 + case 0x0F: // YMZ280B + case 0x12: // AY8910 + Command = (chip->DstCommand & 0x00FF) >> 0; + Data = ChipData[0x00]; + chip_reg_write(chip->context, Sample, chip->DstChipType, chip->DstChipID, 0x00, Command, Data); + break; + case 0x06: // YM2203 + case 0x07: // YM2608 + case 0x08: // YM2610/B + case 0x0C: // YMF262 + case 0x0D: // YMF278B + case 0x0E: // YMF271 + Port = (chip->DstCommand & 0xFF00) >> 8; + Command = (chip->DstCommand & 0x00FF) >> 0; + Data = ChipData[0x00]; + chip_reg_write(chip->context, Sample, chip->DstChipType, chip->DstChipID, Port, Command, Data); + break; + } + chip->Running |= 0x10; + + return; +} + +INLINE UINT32 muldiv64round(UINT32 Multiplicand, UINT32 Multiplier, UINT32 Divisor) +{ + // Yes, I'm correctly rounding the values. + return (UINT32)(((UINT64)Multiplicand * Multiplier + Multiplier / 2) / Divisor); +} + +void daccontrol_update(void *_chip, UINT32 base_clock, UINT32 samples) +{ + dac_control *chip = (dac_control *) _chip; + UINT32 NewPos; + UINT32 Sample; + + if (chip->Running & 0x80) // disabled + return; + if (! (chip->Running & 0x01)) // stopped + return; + + /*if (samples > 0x20) + { + // very effective Speed Hack for fast seeking + NewPos = chip->Step + (samples - 0x10); + NewPos = muldiv64round(NewPos * chip->DataStep, chip->Frequency, chip->SampleRate); + while(chip->RemainCmds && chip->Pos < NewPos) + { + chip->Pos += chip->DataStep; + chip->RemainCmds --; + } + }*/ + + Sample = 0; + chip->Step += samples; + // Formula: Step * Freq / SampleRate + NewPos = muldiv64round(chip->Step * chip->DataStep, chip->Frequency, chip->SampleRate); + + while(chip->RemainCmds && chip->Pos < NewPos) + { + daccontrol_SendCommand(chip, base_clock + muldiv64round(Sample, chip->SampleRate, chip->Frequency)); + Sample++; + chip->Pos += chip->DataStep; + chip->Running &= ~0x10; + chip->RemainCmds --; + } + + if (! chip->RemainCmds && (chip->Running & 0x04)) + { + // loop back to start + chip->RemainCmds = chip->CmdsToSend; + chip->Step = 0x00; + chip->Pos = 0x00; + } + + if (! chip->RemainCmds) + chip->Running &= ~0x01; // stop + + return; +} + +void * device_start_daccontrol(UINT32 samplerate, void * context) +{ + dac_control *chip; + + chip = (dac_control *) calloc(1, sizeof(dac_control)); + + chip->SampleRate = samplerate; + chip->context = context; + + chip->DstChipType = 0xFF; + chip->DstChipID = 0x00; + chip->DstCommand = 0x0000; + + chip->Running = 0xFF; // disable all actions (except setup_chip) + + return chip; +} + +void device_stop_daccontrol(void *_chip) +{ + dac_control *chip = (dac_control *) _chip; + + free( chip ); +} + +void device_reset_daccontrol(void *_chip) +{ + dac_control *chip = (dac_control *) _chip; + + chip->DstChipType = 0x00; + chip->DstChipID = 0x00; + chip->DstCommand = 0x00; + chip->CmdSize = 0x00; + + chip->Frequency = 0; + chip->DataLen = 0x00; + chip->Data = NULL; + chip->DataStart = 0x00; + chip->StepSize = 0x00; + chip->StepBase = 0x00; + + chip->Running = 0x00; + chip->Step = 0x00; + chip->Pos = 0x00; + chip->RemainCmds = 0x00; + chip->DataStep = 0x00; + + return; +} + +void daccontrol_setup_chip(void *_chip, UINT8 ChType, UINT8 ChNum, UINT16 Command) +{ + dac_control *chip = (dac_control *) _chip; + + chip->DstChipType = ChType; // TypeID (e.g. 0x02 for YM2612) + chip->DstChipID = ChNum; // chip number (to send commands to 1st or 2nd chip) + chip->DstCommand = Command; // Port and Command (would be 0x02A for YM2612) + + switch(chip->DstChipType) + { + case 0x00: // SN76496 + if (chip->DstCommand & 0x0010) + chip->CmdSize = 0x01; // Volume Write + else + chip->CmdSize = 0x02; // Frequency Write + break; + case 0x02: // YM2612 + chip->CmdSize = 0x01; + break; + case 0x11: // PWM + chip->CmdSize = 0x02; + break; + default: + chip->CmdSize = 0x01; + break; + } + chip->DataStep = chip->CmdSize * chip->StepSize; + + return; +} + +void daccontrol_set_data(void *_chip, const UINT8* Data, UINT32 DataLen, UINT8 StepSize, UINT8 StepBase) +{ + dac_control *chip = (dac_control *) _chip; + + if (chip->Running & 0x80) + return; + + if (DataLen && Data != NULL) + { + chip->DataLen = DataLen; + chip->Data = Data; + } + else + { + chip->DataLen = 0x00; + chip->Data = NULL; + } + chip->StepSize = StepSize ? StepSize : 1; + chip->StepBase = StepBase; + chip->DataStep = chip->CmdSize * chip->StepSize; + + return; +} + +void daccontrol_set_frequency(void *_chip, UINT32 Frequency) +{ + dac_control *chip = (dac_control *) _chip; + + if (chip->Running & 0x80) + return; + + chip->Frequency = Frequency; + + return; +} + +void daccontrol_start(void *_chip, UINT32 DataPos, UINT8 LenMode, UINT32 Length) +{ + dac_control *chip = (dac_control *) _chip; + UINT16 CmdStepBase; + + if (chip->Running & 0x80) + return; + + CmdStepBase = chip->CmdSize * chip->StepBase; + if (DataPos != 0xFFFFFFFF) // skip setting DataStart, if Pos == -1 + { + chip->DataStart = DataPos + CmdStepBase; + if (chip->DataStart > chip->DataLen) // catch bad value and force silence + chip->DataStart = chip->DataLen; + } + + switch(LenMode & 0x0F) + { + case DCTRL_LMODE_IGNORE: // Length is already set - ignore + break; + case DCTRL_LMODE_CMDS: // Length = number of commands + chip->CmdsToSend = Length; + break; + case DCTRL_LMODE_MSEC: // Length = time in msec + chip->CmdsToSend = 1000 * Length / chip->Frequency; + break; + case DCTRL_LMODE_TOEND: // play unti stop-command is received (or data-end is reached) + chip->CmdsToSend = (chip->DataLen - (chip->DataStart - CmdStepBase)) / chip->DataStep; + break; + case DCTRL_LMODE_BYTES: // raw byte count + chip->CmdsToSend = Length / chip->DataStep; + break; + default: + chip->CmdsToSend = 0x00; + break; + } + chip->RemainCmds = chip->CmdsToSend; + chip->Step = 0x00; + chip->Pos = 0x00; + + chip->Running &= ~0x04; + chip->Running |= (LenMode & 0x80) ? 0x04 : 0x00; // set loop mode + + chip->Running |= 0x01; // start + chip->Running &= ~0x10; // command isn't yet sent + + return; +} + +void daccontrol_stop(void *_chip) +{ + dac_control *chip = (dac_control *) _chip; + + if (chip->Running & 0x80) + return; + + chip->Running &= ~0x01; // stop + + return; +} diff --git a/Frameworks/GME/gme/dac_control.h b/Frameworks/GME/gme/dac_control.h new file mode 100644 index 000000000..4cf75656a --- /dev/null +++ b/Frameworks/GME/gme/dac_control.h @@ -0,0 +1,44 @@ +#ifndef _DAC_CONTROL_H_ +#define _DAC_CONTROL_H_ + +#ifndef __OSDCOMM_H__ +#define __OSDCOMM_H__ +typedef unsigned char UINT8; /* unsigned 8bit */ +typedef unsigned short UINT16; /* unsigned 16bit */ +typedef unsigned int UINT32; /* unsigned 32bit */ +typedef unsigned long long UINT64; /* unsigned 64bit */ +typedef signed char INT8; /* signed 8bit */ +typedef signed short INT16; /* signed 16bit */ +typedef signed int INT32; /* signed 32bit */ +typedef signed long long INT64; /* signed 64bit */ + +typedef INT32 stream_sample_t; +typedef UINT32 offs_t; +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +void * device_start_daccontrol(UINT32 samplerate, void *context); +void device_stop_daccontrol(void * chip); + +void daccontrol_update(void * chip, UINT32 base_clock, UINT32 samples); +void device_reset_daccontrol(void * chip); +void daccontrol_setup_chip(void * chip, UINT8 ChType, UINT8 ChNum, UINT16 Command); +void daccontrol_set_data(void *chip, const UINT8* Data, UINT32 DataLen, UINT8 StepSize, UINT8 StepBase); +void daccontrol_set_frequency(void *chip, UINT32 Frequency); +void daccontrol_start(void *chip, UINT32 DataPos, UINT8 LenMode, UINT32 Length); +void daccontrol_stop(void *chip); + +#define DCTRL_LMODE_IGNORE 0x00 +#define DCTRL_LMODE_CMDS 0x01 +#define DCTRL_LMODE_MSEC 0x02 +#define DCTRL_LMODE_TOEND 0x03 +#define DCTRL_LMODE_BYTES 0x0F + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/Frameworks/GME/gme/dbopl.cpp b/Frameworks/GME/gme/dbopl.cpp new file mode 100644 index 000000000..3dbe669b2 --- /dev/null +++ b/Frameworks/GME/gme/dbopl.cpp @@ -0,0 +1,1526 @@ +/* + * Copyright (C) 2002-2009 The DOSBox Team + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* + DOSBox implementation of a combined Yamaha YMF262 and Yamaha YM3812 emulator. + Enabling the opl3 bit will switch the emulator to stereo opl3 output instead of regular mono opl2 + Except for the table generation it's all integer math + Can choose different types of generators, using muls and bigger tables, try different ones for slower platforms + The generation was based on the MAME implementation but tried to have it use less memory and be faster in general + MAME uses much bigger envelope tables and this will be the biggest cause of it sounding different at times + + //TODO Don't delay first operator 1 sample in opl3 mode + //TODO Maybe not use class method pointers but a regular function pointers with operator as first parameter + //TODO Fix panning for the Percussion channels, would any opl3 player use it and actually really change it though? + //TODO Check if having the same accuracy in all frequency multipliers sounds better or not + + //DUNNO Keyon in 4op, switch to 2op without keyoff. +*/ + +/* $Id$ */ + + +#include +#include +#include +//#include "dosbox.h" +#include "dbopl.h" + + +#ifndef PI +#define PI 3.14159265358979323846 +#endif + +namespace DBOPL { + +#define OPLRATE ((double)(14318180.0 / 288.0)) +#define TREMOLO_TABLE 52 + +//Try to use most precision for frequencies +//Else try to keep different waves in synch +//#define WAVE_PRECISION 1 +#ifndef WAVE_PRECISION +//Wave bits available in the top of the 32bit range +//Original adlib uses 10.10, we use 10.22 +#define WAVE_BITS 10 +#else +//Need some extra bits at the top to have room for octaves and frequency multiplier +//We support to 8 times lower rate +//128 * 15 * 8 = 15350, 2^13.9, so need 14 bits +#define WAVE_BITS 14 +#endif +#define WAVE_SH ( 32 - WAVE_BITS ) +#define WAVE_MASK ( ( 1 << WAVE_SH ) - 1 ) + +//Use the same accuracy as the waves +#define LFO_SH ( WAVE_SH - 10 ) +//LFO is controlled by our tremolo 256 sample limit +#define LFO_MAX ( 256 << ( LFO_SH ) ) + + +//Maximum amount of attenuation bits +//Envelope goes to 511, 9 bits +#if (DBOPL_WAVE == WAVE_TABLEMUL ) +//Uses the value directly +#define ENV_BITS ( 9 ) +#else +//Add 3 bits here for more accuracy and would have to be shifted up either way +#define ENV_BITS ( 9 ) +#endif +//Limits of the envelope with those bits and when the envelope goes silent +#define ENV_MIN 0 +#define ENV_EXTRA ( ENV_BITS - 9 ) +#define ENV_MAX ( 511 << ENV_EXTRA ) +#define ENV_LIMIT ( ( 12 * 256) >> ( 3 - ENV_EXTRA ) ) +#define ENV_SILENT( _X_ ) ( (_X_) >= ENV_LIMIT ) + +//Attack/decay/release rate counter shift +#define RATE_SH 24 +#define RATE_MASK ( ( 1 << RATE_SH ) - 1 ) +//Has to fit within 16bit lookuptable +#define MUL_SH 16 + +//Check some ranges +#if ENV_EXTRA > 3 +#error Too many envelope bits +#endif + + +//How much to substract from the base value for the final attenuation +static const Bit8u KslCreateTable[16] = { + //0 will always be be lower than 7 * 8 + 64, 32, 24, 19, + 16, 12, 11, 10, + 8, 6, 5, 4, + 3, 2, 1, 0, +}; + +#define M(_X_) ((Bit8u)( (_X_) * 2)) +static const Bit8u FreqCreateTable[16] = { + M(0.5), M(1 ), M(2 ), M(3 ), M(4 ), M(5 ), M(6 ), M(7 ), + M(8 ), M(9 ), M(10), M(10), M(12), M(12), M(15), M(15) +}; +#undef M + +//We're not including the highest attack rate, that gets a special value +static const Bit8u AttackSamplesTable[13] = { + 69, 55, 46, 40, + 35, 29, 23, 20, + 19, 15, 11, 10, + 9 +}; +//On a real opl these values take 8 samples to reach and are based upon larger tables +static const Bit8u EnvelopeIncreaseTable[13] = { + 4, 5, 6, 7, + 8, 10, 12, 14, + 16, 20, 24, 28, + 32, +}; + +#if ( DBOPL_WAVE == WAVE_HANDLER ) || ( DBOPL_WAVE == WAVE_TABLELOG ) +static Bit16u ExpTable[ 256 ]; +#endif + +#if ( DBOPL_WAVE == WAVE_HANDLER ) +//PI table used by WAVEHANDLER +static Bit16u SinTable[ 512 ]; +#endif + +#if ( DBOPL_WAVE > WAVE_HANDLER ) +//Layout of the waveform table in 512 entry intervals +//With overlapping waves we reduce the table to half it's size + +// | |//\\|____|WAV7|//__|/\ |____|/\/\| +// |\\//| | |WAV7| | \/| | | +// |06 |0126|17 |7 |3 |4 |4 5 |5 | + +//6 is just 0 shifted and masked + +static Bit16s WaveTable[ 8 * 512 ]; +//Distance into WaveTable the wave starts +static const Bit16u WaveBaseTable[8] = { + 0x000, 0x200, 0x200, 0x800, + 0xa00, 0xc00, 0x100, 0x400, + +}; +//Mask the counter with this +static const Bit16u WaveMaskTable[8] = { + 1023, 1023, 511, 511, + 1023, 1023, 512, 1023, +}; + +//Where to start the counter on at keyon +static const Bit16u WaveStartTable[8] = { + 512, 0, 0, 0, + 0, 512, 512, 256, +}; +#endif + +#if ( DBOPL_WAVE == WAVE_TABLEMUL ) +static Bit16u MulTable[ 384 ]; +#endif + +static Bit8u KslTable[ 8 * 16 ]; +static Bit8u TremoloTable[ TREMOLO_TABLE ]; +//Start of a channel behind the chip struct start +static Bit16u ChanOffsetTable[32]; +//Start of an operator behind the chip struct start +static Bit16u OpOffsetTable[64]; + +//The lower bits are the shift of the operator vibrato value +//The highest bit is right shifted to generate -1 or 0 for negation +//So taking the highest input value of 7 this gives 3, 7, 3, 0, -3, -7, -3, 0 +static const Bit8s VibratoTable[ 8 ] = { + 1 - 0x00, 0 - 0x00, 1 - 0x00, 30 - 0x00, + 1 - 0x80, 0 - 0x80, 1 - 0x80, 30 - 0x80 +}; + +//Shift strength for the ksl value determined by ksl strength +static const Bit8u KslShiftTable[4] = { + 31,1,2,0 +}; + +//Generate a table index and table shift value using input value from a selected rate +static void EnvelopeSelect( Bit8u val, Bit8u& index, Bit8u& shift ) { + if ( val < 13 * 4 ) { //Rate 0 - 12 + shift = 12 - ( val >> 2 ); + index = val & 3; + } else if ( val < 15 * 4 ) { //rate 13 - 14 + shift = 0; + index = val - 12 * 4; + } else { //rate 15 and up + shift = 0; + index = 12; + } +} + +#if ( DBOPL_WAVE == WAVE_HANDLER ) +/* + Generate the different waveforms out of the sine/exponetial table using handlers +*/ +static inline Bits MakeVolume( Bitu wave, Bitu volume ) { + Bitu total = wave + volume; + Bitu index = total & 0xff; + Bitu sig = ExpTable[ index ]; + Bitu exp = total >> 8; +#if 0 + //Check if we overflow the 31 shift limit + if ( exp >= 32 ) { + LOG_MSG( "WTF %d %d", total, exp ); + } +#endif + return (sig >> exp); +}; + +static Bits DB_FASTCALL WaveForm0( Bitu i, Bitu volume ) { + Bits neg = 0 - (( i >> 9) & 1);//Create ~0 or 0 + Bitu wave = SinTable[i & 511]; + return (MakeVolume( wave, volume ) ^ neg) - neg; +} +static Bits DB_FASTCALL WaveForm1( Bitu i, Bitu volume ) { + Bit32u wave = SinTable[i & 511]; + wave |= ( ( (i ^ 512 ) & 512) - 1) >> ( 32 - 12 ); + return MakeVolume( wave, volume ); +} +static Bits DB_FASTCALL WaveForm2( Bitu i, Bitu volume ) { + Bitu wave = SinTable[i & 511]; + return MakeVolume( wave, volume ); +} +static Bits DB_FASTCALL WaveForm3( Bitu i, Bitu volume ) { + Bitu wave = SinTable[i & 255]; + wave |= ( ( (i ^ 256 ) & 256) - 1) >> ( 32 - 12 ); + return MakeVolume( wave, volume ); +} +static Bits DB_FASTCALL WaveForm4( Bitu i, Bitu volume ) { + //Twice as fast + i <<= 1; + Bits neg = 0 - (( i >> 9) & 1);//Create ~0 or 0 + Bitu wave = SinTable[i & 511]; + wave |= ( ( (i ^ 512 ) & 512) - 1) >> ( 32 - 12 ); + return (MakeVolume( wave, volume ) ^ neg) - neg; +} +static Bits DB_FASTCALL WaveForm5( Bitu i, Bitu volume ) { + //Twice as fast + i <<= 1; + Bitu wave = SinTable[i & 511]; + wave |= ( ( (i ^ 512 ) & 512) - 1) >> ( 32 - 12 ); + return MakeVolume( wave, volume ); +} +static Bits DB_FASTCALL WaveForm6( Bitu i, Bitu volume ) { + Bits neg = 0 - (( i >> 9) & 1);//Create ~0 or 0 + return (MakeVolume( 0, volume ) ^ neg) - neg; +} +static Bits DB_FASTCALL WaveForm7( Bitu i, Bitu volume ) { + //Negative is reversed here + Bits neg = (( i >> 9) & 1) - 1; + Bitu wave = (i << 3); + //When negative the volume also runs backwards + wave = ((wave ^ neg) - neg) & 4095; + return (MakeVolume( wave, volume ) ^ neg) - neg; +} + +static const WaveHandler WaveHandlerTable[8] = { + WaveForm0, WaveForm1, WaveForm2, WaveForm3, + WaveForm4, WaveForm5, WaveForm6, WaveForm7 +}; + +#endif + +/* + Operator +*/ + +//We zero out when rate == 0 +inline void Operator::UpdateAttack( const Chip* chip ) { + Bit8u rate = reg60 >> 4; + if ( rate ) { + Bit8u val = (rate << 2) + ksr; + attackAdd = chip->attackRates[ val ]; + rateZero &= ~(1 << ATTACK); + } else { + attackAdd = 0; + rateZero |= (1 << ATTACK); + } +} +inline void Operator::UpdateDecay( const Chip* chip ) { + Bit8u rate = reg60 & 0xf; + if ( rate ) { + Bit8u val = (rate << 2) + ksr; + decayAdd = chip->linearRates[ val ]; + rateZero &= ~(1 << DECAY); + } else { + decayAdd = 0; + rateZero |= (1 << DECAY); + } +} +inline void Operator::UpdateRelease( const Chip* chip ) { + Bit8u rate = reg80 & 0xf; + if ( rate ) { + Bit8u val = (rate << 2) + ksr; + releaseAdd = chip->linearRates[ val ]; + rateZero &= ~(1 << RELEASE); + if ( !(reg20 & MASK_SUSTAIN ) ) { + rateZero &= ~( 1 << SUSTAIN ); + } + } else { + rateZero |= (1 << RELEASE); + releaseAdd = 0; + if ( !(reg20 & MASK_SUSTAIN ) ) { + rateZero |= ( 1 << SUSTAIN ); + } + } +} + +inline void Operator::UpdateAttenuation( ) { + Bit8u kslBase = (Bit8u)((chanData >> SHIFT_KSLBASE) & 0xff); + Bit32u tl = reg40 & 0x3f; + Bit8u kslShift = KslShiftTable[ reg40 >> 6 ]; + //Make sure the attenuation goes to the right bits + totalLevel = tl << ( ENV_BITS - 7 ); //Total level goes 2 bits below max + totalLevel += ( kslBase << ENV_EXTRA ) >> kslShift; +} + +void Operator::UpdateFrequency( ) { + Bit32u freq = chanData & (( 1 << 10 ) - 1); + Bit32u block = (chanData >> 10) & 0xff; + +#ifdef WAVE_PRECISION + block = 7 - block; + waveAdd = ( freq * freqMul ) >> block; +#else + waveAdd = (freq << block) * freqMul; +#endif + if ( reg20 & MASK_VIBRATO ) { + vibStrength = (Bit8u)(freq >> 7); +#ifdef WAVE_PRECISION + vibrato = ( vibStrength * freqMul ) >> block; +#else + vibrato = ( vibStrength << block ) * freqMul; +#endif + } else { + vibStrength = 0; + vibrato = 0; + } +} + +void Operator::UpdateRates( const Chip* chip ) { + //Mame seems to reverse this where enabling ksr actually lowers + //the rate, but pdf manuals says otherwise? + Bit8u newKsr = (Bit8u)((chanData >> SHIFT_KEYCODE) & 0xff); + if ( !( reg20 & MASK_KSR ) ) { + newKsr >>= 2; + } + if ( ksr == newKsr ) + return; + ksr = newKsr; + UpdateAttack( chip ); + UpdateDecay( chip ); + UpdateRelease( chip ); +} + +INLINE Bit32s Operator::RateForward( Bit32u add ) { + rateIndex += add; + Bit32s ret = rateIndex >> RATE_SH; + rateIndex = rateIndex & RATE_MASK; + return ret; +} + +template< Operator::State yes> +Bits Operator::TemplateVolume( ) { + Bit32s vol = volume; + Bit32s change; + switch ( yes ) { + case OFF: + return ENV_MAX; + case ATTACK: + change = RateForward( attackAdd ); + if ( !change ) + return vol; + vol += ( (~vol) * change ) >> 3; + if ( vol < ENV_MIN ) { + volume = ENV_MIN; + rateIndex = 0; + SetState( DECAY ); + return ENV_MIN; + } + break; + case DECAY: + vol += RateForward( decayAdd ); + if ( vol >= sustainLevel ) { + //Check if we didn't overshoot max attenuation, then just go off + if ( vol >= ENV_MAX ) { + volume = ENV_MAX; + SetState( OFF ); + return ENV_MAX; + } + //Continue as sustain + rateIndex = 0; + SetState( SUSTAIN ); + } + break; + case SUSTAIN: + if ( reg20 & MASK_SUSTAIN ) { + return vol; + } + //In sustain phase, but not sustaining, do regular release + case RELEASE: + vol += RateForward( releaseAdd );; + if ( vol >= ENV_MAX ) { + volume = ENV_MAX; + SetState( OFF ); + return ENV_MAX; + } + break; + } + volume = vol; + return vol; +} + +static const VolumeHandler VolumeHandlerTable[5] = { + &Operator::TemplateVolume< Operator::OFF >, + &Operator::TemplateVolume< Operator::RELEASE >, + &Operator::TemplateVolume< Operator::SUSTAIN >, + &Operator::TemplateVolume< Operator::DECAY >, + &Operator::TemplateVolume< Operator::ATTACK > +}; + +INLINE Bitu Operator::ForwardVolume() { + return currentLevel + (this->*volHandler)(); +} + + +INLINE Bitu Operator::ForwardWave() { + waveIndex += waveCurrent; + return waveIndex >> WAVE_SH; +} + +void Operator::Write20( const Chip* chip, Bit8u val ) { + Bit8u change = (reg20 ^ val ); + if ( !change ) + return; + reg20 = val; + //Shift the tremolo bit over the entire register, saved a branch, YES! + tremoloMask = (Bit8s)(val) >> 7; + tremoloMask &= ~(( 1 << ENV_EXTRA ) -1); + //Update specific features based on changes + if ( change & MASK_KSR ) { + UpdateRates( chip ); + } + //With sustain enable the volume doesn't change + if ( reg20 & MASK_SUSTAIN || ( !releaseAdd ) ) { + rateZero |= ( 1 << SUSTAIN ); + } else { + rateZero &= ~( 1 << SUSTAIN ); + } + //Frequency multiplier or vibrato changed + if ( change & (0xf | MASK_VIBRATO) ) { + freqMul = chip->freqMul[ val & 0xf ]; + UpdateFrequency(); + } +} + +void Operator::Write40( const Chip* /*chip*/, Bit8u val ) { + if (!(reg40 ^ val )) + return; + reg40 = val; + UpdateAttenuation( ); +} + +void Operator::Write60( const Chip* chip, Bit8u val ) { + Bit8u change = reg60 ^ val; + reg60 = val; + if ( change & 0x0f ) { + UpdateDecay( chip ); + } + if ( change & 0xf0 ) { + UpdateAttack( chip ); + } +} + +void Operator::Write80( const Chip* chip, Bit8u val ) { + Bit8u change = (reg80 ^ val ); + if ( !change ) + return; + reg80 = val; + Bit8u sustain = val >> 4; + //Turn 0xf into 0x1f + sustain |= ( sustain + 1) & 0x10; + sustainLevel = sustain << ( ENV_BITS - 5 ); + if ( change & 0x0f ) { + UpdateRelease( chip ); + } +} + +void Operator::WriteE0( const Chip* chip, Bit8u val ) { + if ( !(regE0 ^ val) ) + return; + //in opl3 mode you can always selet 7 waveforms regardless of waveformselect + Bit8u waveForm = val & ( ( 0x3 & chip->waveFormMask ) | (0x7 & chip->opl3Active ) ); + regE0 = val; +#if ( DBOPL_WAVE == WAVE_HANDLER ) + waveHandler = WaveHandlerTable[ waveForm ]; +#else + waveBase = WaveTable + WaveBaseTable[ waveForm ]; + waveStart = WaveStartTable[ waveForm ] << WAVE_SH; + waveMask = WaveMaskTable[ waveForm ]; +#endif +} + +INLINE void Operator::SetState( Bit8u s ) { + state = s; + volHandler = VolumeHandlerTable[ s ]; +} + +INLINE bool Operator::Silent() const { + if ( !ENV_SILENT( totalLevel + volume ) ) + return false; + if ( !(rateZero & ( 1 << state ) ) ) + return false; + return true; +} + + INLINE void Operator::Prepare( const Chip* chip ) { + currentLevel = totalLevel + (chip->tremoloValue & tremoloMask); + waveCurrent = waveAdd; + if ( vibStrength >> chip->vibratoShift ) { + Bit32s add = vibrato >> chip->vibratoShift; + //Sign extend over the shift value + Bit32s neg = chip->vibratoSign; + //Negate the add with -1 or 0 + add = ( add ^ neg ) - neg; + waveCurrent += add; + } +} + +void Operator::KeyOn( Bit8u mask ) { + if ( !keyOn ) { + //Restart the frequency generator +#if ( DBOPL_WAVE > WAVE_HANDLER ) + waveIndex = waveStart; +#else + waveIndex = 0; +#endif + rateIndex = 0; + SetState( ATTACK ); + } + keyOn |= mask; +} + +void Operator::KeyOff( Bit8u mask ) { + keyOn &= ~mask; + if ( !keyOn ) { + if ( state != OFF ) { + SetState( RELEASE ); + } + } +} + +INLINE Bits Operator::GetWave( Bitu index, Bitu vol ) { +#if ( DBOPL_WAVE == WAVE_HANDLER ) + return waveHandler( index, vol << ( 3 - ENV_EXTRA ) ); +#elif ( DBOPL_WAVE == WAVE_TABLEMUL ) + return (waveBase[ index & waveMask ] * MulTable[ vol >> ENV_EXTRA ]) >> MUL_SH; +#elif ( DBOPL_WAVE == WAVE_TABLELOG ) + Bit32s wave = waveBase[ index & waveMask ]; + Bit32u total = ( wave & 0x7fff ) + vol << ( 3 - ENV_EXTRA ); + Bit32s sig = ExpTable[ total & 0xff ]; + Bit32u exp = total >> 8; + Bit32s neg = wave >> 16; + return ((sig ^ neg) - neg) >> exp; +#else +#error "No valid wave routine" +#endif +} + +Bits INLINE Operator::GetSample( Bits modulation ) { + Bitu vol = ForwardVolume(); + if ( ENV_SILENT( vol ) ) { + //Simply forward the wave + waveIndex += waveCurrent; + return 0; + } else { + Bitu index = ForwardWave(); + index += modulation; + return GetWave( index, vol ); + } +} + +Operator::Operator() { + chanData = 0; + freqMul = 0; + waveIndex = 0; + waveAdd = 0; + waveCurrent = 0; + keyOn = 0; + ksr = 0; + reg20 = 0; + reg40 = 0; + reg60 = 0; + reg80 = 0; + regE0 = 0; + SetState( OFF ); + rateZero = (1 << OFF); + sustainLevel = ENV_MAX; + currentLevel = ENV_MAX; + totalLevel = ENV_MAX; + volume = ENV_MAX; +} + +/* + Channel +*/ + +Channel::Channel() { + old[0] = old[1] = 0; + chanData = 0; + regB0 = 0; + regC0 = 0; + maskLeft = -1; + maskRight = -1; + feedback = 31; + fourMask = 0; + synthHandler = &Channel::BlockTemplate< sm2FM >; +} + +void Channel::SetChanData( const Chip* chip, Bit32u data ) { + Bit32u change = chanData ^ data; + chanData = data; + Op( 0 )->chanData = data; + Op( 1 )->chanData = data; + //Since a frequency update triggered this, always update frequency + Op( 0 )->UpdateFrequency(); + Op( 1 )->UpdateFrequency(); + if ( change & ( 0xff << SHIFT_KSLBASE ) ) { + Op( 0 )->UpdateAttenuation(); + Op( 1 )->UpdateAttenuation(); + } + if ( change & ( 0xff << SHIFT_KEYCODE ) ) { + Op( 0 )->UpdateRates( chip ); + Op( 1 )->UpdateRates( chip ); + } +} + +void Channel::UpdateFrequency( const Chip* chip, Bit8u fourOp ) { + //Extrace the frequency bits + Bit32u data = chanData & 0xffff; + Bit32u kslBase = KslTable[ data >> 6 ]; + Bit32u keyCode = ( data & 0x1c00) >> 9; + if ( chip->reg08 & 0x40 ) { + keyCode |= ( data & 0x100)>>8; /* notesel == 1 */ + } else { + keyCode |= ( data & 0x200)>>9; /* notesel == 0 */ + } + //Add the keycode and ksl into the highest bits of chanData + data |= (keyCode << SHIFT_KEYCODE) | ( kslBase << SHIFT_KSLBASE ); + ( this + 0 )->SetChanData( chip, data ); + if ( fourOp & 0x3f ) { + ( this + 1 )->SetChanData( chip, data ); + } +} + +void Channel::WriteA0( const Chip* chip, Bit8u val ) { + Bit8u fourOp = chip->reg104 & chip->opl3Active & fourMask; + //Don't handle writes to silent fourop channels + if ( fourOp > 0x80 ) + return; + Bit32u change = (chanData ^ val ) & 0xff; + if ( change ) { + chanData ^= change; + UpdateFrequency( chip, fourOp ); + } +} + +void Channel::WriteB0( const Chip* chip, Bit8u val ) { + Bit8u fourOp = chip->reg104 & chip->opl3Active & fourMask; + //Don't handle writes to silent fourop channels + if ( fourOp > 0x80 ) + return; + Bitu change = (chanData ^ ( val << 8 ) ) & 0x1f00; + if ( change ) { + chanData ^= change; + UpdateFrequency( chip, fourOp ); + } + //Check for a change in the keyon/off state + if ( !(( val ^ regB0) & 0x20)) + return; + regB0 = val; + if ( val & 0x20 ) { + Op(0)->KeyOn( 0x1 ); + Op(1)->KeyOn( 0x1 ); + if ( fourOp & 0x3f ) { + ( this + 1 )->Op(0)->KeyOn( 1 ); + ( this + 1 )->Op(1)->KeyOn( 1 ); + } + } else { + Op(0)->KeyOff( 0x1 ); + Op(1)->KeyOff( 0x1 ); + if ( fourOp & 0x3f ) { + ( this + 1 )->Op(0)->KeyOff( 1 ); + ( this + 1 )->Op(1)->KeyOff( 1 ); + } + } +} + +void Channel::WriteC0( const Chip* chip, Bit8u val ) { + Bit8u change = val ^ regC0; + if ( !change ) + return; + regC0 = val; + feedback = ( val >> 1 ) & 7; + if ( feedback ) { + //We shift the input to the right 10 bit wave index value + feedback = 9 - feedback; + } else { + feedback = 31; + } + //Select the new synth mode + if ( chip->opl3Active ) { + //4-op mode enabled for this channel + if ( (chip->reg104 & fourMask) & 0x3f ) { + Channel* chan0, *chan1; + //Check if it's the 2nd channel in a 4-op + if ( !(fourMask & 0x80 ) ) { + chan0 = this; + chan1 = this + 1; + } else { + chan0 = this - 1; + chan1 = this; + } + + Bit8u synth = ( (chan0->regC0 & 1) << 0 )| (( chan1->regC0 & 1) << 1 ); + switch ( synth ) { + case 0: + chan0->synthHandler = &Channel::BlockTemplate< sm3FMFM >; + break; + case 1: + chan0->synthHandler = &Channel::BlockTemplate< sm3AMFM >; + break; + case 2: + chan0->synthHandler = &Channel::BlockTemplate< sm3FMAM >; + break; + case 3: + chan0->synthHandler = &Channel::BlockTemplate< sm3AMAM >; + break; + } + //Disable updating percussion channels + } else if ((fourMask & 0x40) && ( chip->regBD & 0x20) ) { + + //Regular dual op, am or fm + } else if ( val & 1 ) { + synthHandler = &Channel::BlockTemplate< sm3AM >; + } else { + synthHandler = &Channel::BlockTemplate< sm3FM >; + } + maskLeft = ( val & 0x10 ) ? -1 : 0; + maskRight = ( val & 0x20 ) ? -1 : 0; + //opl2 active + } else { + //Disable updating percussion channels + if ( (fourMask & 0x40) && ( chip->regBD & 0x20 ) ) { + + //Regular dual op, am or fm + } else if ( val & 1 ) { + synthHandler = &Channel::BlockTemplate< sm2AM >; + } else { + synthHandler = &Channel::BlockTemplate< sm2FM >; + } + } +} + +void Channel::ResetC0( const Chip* chip ) { + Bit8u val = regC0; + regC0 ^= 0xff; + WriteC0( chip, val ); +} + +template< bool opl3Mode> +void Channel::GeneratePercussion( Chip* chip, Bit32s* output ) { + Channel* chan = this; + + //BassDrum + Bit32s mod = (Bit32u)((old[0] + old[1])) >> feedback; + old[0] = old[1]; + old[1] = Op(0)->GetSample( mod ); + + //When bassdrum is in AM mode first operator is ignoed + if ( chan->regC0 & 1 ) { + mod = 0; + } else { + mod = old[0]; + } + Bit32s sample = Op(1)->GetSample( mod ); + + + //Precalculate stuff used by other outputs + Bit32u noiseBit = chip->ForwardNoise() & 0x1; + Bit32u c2 = Op(2)->ForwardWave(); + Bit32u c5 = Op(5)->ForwardWave(); + Bit32u phaseBit = (((c2 & 0x88) ^ ((c2<<5) & 0x80)) | ((c5 ^ (c5<<2)) & 0x20)) ? 0x02 : 0x00; + + //Hi-Hat + Bit32u hhVol = Op(2)->ForwardVolume(); + if ( !ENV_SILENT( hhVol ) ) { + Bit32u hhIndex = (phaseBit<<8) | (0x34 << ( phaseBit ^ (noiseBit << 1 ))); + sample += Op(2)->GetWave( hhIndex, hhVol ); + } + //Snare Drum + Bit32u sdVol = Op(3)->ForwardVolume(); + if ( !ENV_SILENT( sdVol ) ) { + Bit32u sdIndex = ( 0x100 + (c2 & 0x100) ) ^ ( noiseBit << 8 ); + sample += Op(3)->GetWave( sdIndex, sdVol ); + } + //Tom-tom + sample += Op(4)->GetSample( 0 ); + + //Top-Cymbal + Bit32u tcVol = Op(5)->ForwardVolume(); + if ( !ENV_SILENT( tcVol ) ) { + Bit32u tcIndex = (1 + phaseBit) << 8; + sample += Op(5)->GetWave( tcIndex, tcVol ); + } + sample <<= 1; + if ( opl3Mode ) { + output[0] += sample; + output[1] += sample; + } else { + output[0] += sample; + } +} + +template +Channel* Channel::BlockTemplate( Chip* chip, Bit32u samples, Bit32s* output ) { + switch( mode ) { + case sm2AM: + case sm3AM: + if ( Op(0)->Silent() && Op(1)->Silent() ) { + old[0] = old[1] = 0; + return (this + 1); + } + break; + case sm2FM: + case sm3FM: + if ( Op(1)->Silent() ) { + old[0] = old[1] = 0; + return (this + 1); + } + break; + case sm3FMFM: + if ( Op(3)->Silent() ) { + old[0] = old[1] = 0; + return (this + 2); + } + break; + case sm3AMFM: + if ( Op(0)->Silent() && Op(3)->Silent() ) { + old[0] = old[1] = 0; + return (this + 2); + } + break; + case sm3FMAM: + if ( Op(1)->Silent() && Op(3)->Silent() ) { + old[0] = old[1] = 0; + return (this + 2); + } + break; + case sm3AMAM: + if ( Op(0)->Silent() && Op(2)->Silent() && Op(3)->Silent() ) { + old[0] = old[1] = 0; + return (this + 2); + } + break; + default: + break; + } + //Init the operators with the the current vibrato and tremolo values + Op( 0 )->Prepare( chip ); + Op( 1 )->Prepare( chip ); + if ( mode > sm4Start ) { + Op( 2 )->Prepare( chip ); + Op( 3 )->Prepare( chip ); + } + if ( mode > sm6Start ) { + Op( 4 )->Prepare( chip ); + Op( 5 )->Prepare( chip ); + } + for ( Bitu i = 0; i < samples; i++ ) { + //Early out for percussion handlers + if ( mode == sm2Percussion ) { + GeneratePercussion( chip, output + i ); + continue; //Prevent some unitialized value bitching + } else if ( mode == sm3Percussion ) { + GeneratePercussion( chip, output + i * 2 ); + continue; //Prevent some unitialized value bitching + } + + //Do unsigned shift so we can shift out all bits but still stay in 10 bit range otherwise + Bit32s mod = (Bit32u)((old[0] + old[1])) >> feedback; + old[0] = old[1]; + old[1] = Op(0)->GetSample( mod ); + Bit32s sample; + Bit32s out0 = old[0]; + if ( mode == sm2AM || mode == sm3AM ) { + sample = out0 + Op(1)->GetSample( 0 ); + } else if ( mode == sm2FM || mode == sm3FM ) { + sample = Op(1)->GetSample( out0 ); + } else if ( mode == sm3FMFM ) { + Bits next = Op(1)->GetSample( out0 ); + next = Op(2)->GetSample( next ); + sample = Op(3)->GetSample( next ); + } else if ( mode == sm3AMFM ) { + sample = out0; + Bits next = Op(1)->GetSample( 0 ); + next = Op(2)->GetSample( next ); + sample += Op(3)->GetSample( next ); + } else if ( mode == sm3FMAM ) { + sample = Op(1)->GetSample( out0 ); + Bits next = Op(2)->GetSample( 0 ); + sample += Op(3)->GetSample( next ); + } else if ( mode == sm3AMAM ) { + sample = out0; + Bits next = Op(1)->GetSample( 0 ); + sample += Op(2)->GetSample( next ); + sample += Op(3)->GetSample( 0 ); + } + switch( mode ) { + case sm2AM: + case sm2FM: + output[ i ] += sample; + break; + case sm3AM: + case sm3FM: + case sm3FMFM: + case sm3AMFM: + case sm3FMAM: + case sm3AMAM: + output[ i * 2 + 0 ] += sample & maskLeft; + output[ i * 2 + 1 ] += sample & maskRight; + break; + default: + break; + } + } + switch( mode ) { + case sm2AM: + case sm2FM: + case sm3AM: + case sm3FM: + return ( this + 1 ); + case sm3FMFM: + case sm3AMFM: + case sm3FMAM: + case sm3AMAM: + return( this + 2 ); + case sm2Percussion: + case sm3Percussion: + return( this + 3 ); + } + return 0; +} + +/* + Chip +*/ + +Chip::Chip() { + reg08 = 0; + reg04 = 0; + regBD = 0; + reg104 = 0; + opl3Active = 0; +} + +INLINE Bit32u Chip::ForwardNoise() { + noiseCounter += noiseAdd; + Bitu count = noiseCounter >> LFO_SH; + noiseCounter &= WAVE_MASK; + for ( ; count > 0; --count ) { + //Noise calculation from mame + noiseValue ^= ( 0x800302 ) & ( 0 - (noiseValue & 1 ) ); + noiseValue >>= 1; + } + return noiseValue; +} + +Bit32u Chip::ForwardLFO( Bit32u samples ) { + //Current vibrato value, runs 4x slower than tremolo + vibratoSign = ( VibratoTable[ vibratoIndex >> 2] ) >> 7; + vibratoShift = ( VibratoTable[ vibratoIndex >> 2] & 7) + vibratoStrength; + tremoloValue = TremoloTable[ tremoloIndex ] >> tremoloStrength; + + //Check hom many samples there can be done before the value changes + Bit32u todo = LFO_MAX - lfoCounter; + Bit32u count = (todo + lfoAdd - 1) / lfoAdd; + if ( count > samples ) { + count = samples; + lfoCounter += count * lfoAdd; + } else { + lfoCounter += count * lfoAdd; + lfoCounter &= (LFO_MAX - 1); + //Maximum of 7 vibrato value * 4 + vibratoIndex = ( vibratoIndex + 1 ) & 31; + //Clip tremolo to the the table size + if ( tremoloIndex + 1 < TREMOLO_TABLE ) + ++tremoloIndex; + else + tremoloIndex = 0; + } + return count; +} + +void Chip::WriteBD( Bit8u val ) { + Bit8u change = regBD ^ val; + if ( !change ) + return; + regBD = val; + //TODO could do this with shift and xor? + vibratoStrength = (val & 0x40) ? 0x00 : 0x01; + tremoloStrength = (val & 0x80) ? 0x00 : 0x02; + if ( val & 0x20 ) { + //Drum was just enabled, make sure channel 6 has the right synth + if ( change & 0x20 ) { + if ( opl3Active ) { + chan[6].synthHandler = &Channel::BlockTemplate< sm3Percussion >; + } else { + chan[6].synthHandler = &Channel::BlockTemplate< sm2Percussion >; + } + } + //Bass Drum + if ( val & 0x10 ) { + chan[6].op[0].KeyOn( 0x2 ); + chan[6].op[1].KeyOn( 0x2 ); + } else { + chan[6].op[0].KeyOff( 0x2 ); + chan[6].op[1].KeyOff( 0x2 ); + } + //Hi-Hat + if ( val & 0x1 ) { + chan[7].op[0].KeyOn( 0x2 ); + } else { + chan[7].op[0].KeyOff( 0x2 ); + } + //Snare + if ( val & 0x8 ) { + chan[7].op[1].KeyOn( 0x2 ); + } else { + chan[7].op[1].KeyOff( 0x2 ); + } + //Tom-Tom + if ( val & 0x4 ) { + chan[8].op[0].KeyOn( 0x2 ); + } else { + chan[8].op[0].KeyOff( 0x2 ); + } + //Top Cymbal + if ( val & 0x2 ) { + chan[8].op[1].KeyOn( 0x2 ); + } else { + chan[8].op[1].KeyOff( 0x2 ); + } + //Toggle keyoffs when we turn off the percussion + } else if ( change & 0x20 ) { + //Trigger a reset to setup the original synth handler + chan[6].ResetC0( this ); + chan[6].op[0].KeyOff( 0x2 ); + chan[6].op[1].KeyOff( 0x2 ); + chan[7].op[0].KeyOff( 0x2 ); + chan[7].op[1].KeyOff( 0x2 ); + chan[8].op[0].KeyOff( 0x2 ); + chan[8].op[1].KeyOff( 0x2 ); + } +} + + +#define REGOP( _FUNC_ ) \ + index = ( ( reg >> 3) & 0x20 ) | ( reg & 0x1f ); \ + if ( OpOffsetTable[ index ] ) { \ + Operator* regOp = (Operator*)( ((char *)this ) + OpOffsetTable[ index ] ); \ + regOp->_FUNC_( this, val ); \ + } + +#define REGCHAN( _FUNC_ ) \ + index = ( ( reg >> 4) & 0x10 ) | ( reg & 0xf ); \ + if ( ChanOffsetTable[ index ] ) { \ + Channel* regChan = (Channel*)( ((char *)this ) + ChanOffsetTable[ index ] ); \ + regChan->_FUNC_( this, val ); \ + } + +void Chip::WriteReg( Bit32u reg, Bit8u val ) { + Bitu index; + switch ( (reg & 0xf0) >> 4 ) { + case 0x00 >> 4: + if ( reg == 0x01 ) { + waveFormMask = ( val & 0x20 ) ? 0x7 : 0x0; + } else if ( reg == 0x104 ) { + //Only detect changes in lowest 6 bits + if ( !((reg104 ^ val) & 0x3f) ) + return; + //Always keep the highest bit enabled, for checking > 0x80 + reg104 = 0x80 | ( val & 0x3f ); + } else if ( reg == 0x105 ) { + //MAME says the real opl3 doesn't reset anything on opl3 disable/enable till the next write in another register + if ( !((opl3Active ^ val) & 1 ) ) + return; + opl3Active = ( val & 1 ) ? 0xff : 0; + //Update the 0xc0 register for all channels to signal the switch to mono/stereo handlers + for ( int i = 0; i < 18;i++ ) { + chan[i].ResetC0( this ); + } + } else if ( reg == 0x08 ) { + reg08 = val; + } + case 0x10 >> 4: + break; + case 0x20 >> 4: + case 0x30 >> 4: + REGOP( Write20 ); + break; + case 0x40 >> 4: + case 0x50 >> 4: + REGOP( Write40 ); + break; + case 0x60 >> 4: + case 0x70 >> 4: + REGOP( Write60 ); + break; + case 0x80 >> 4: + case 0x90 >> 4: + REGOP( Write80 ); + break; + case 0xa0 >> 4: + REGCHAN( WriteA0 ); + break; + case 0xb0 >> 4: + if ( reg == 0xbd ) { + WriteBD( val ); + } else { + REGCHAN( WriteB0 ); + } + break; + case 0xc0 >> 4: + REGCHAN( WriteC0 ); + case 0xd0 >> 4: + break; + case 0xe0 >> 4: + case 0xf0 >> 4: + REGOP( WriteE0 ); + break; + } +} + + +Bit32u Chip::WriteAddr( Bit32u port, Bit8u val ) { + switch ( port & 3 ) { + case 0: + return val; + case 2: + if ( opl3Active || (val == 0x05) ) + return 0x100 | val; + else + return val; + } + return 0; +} + +void Chip::GenerateBlock2( Bitu total, Bit32s* output ) { + while ( total > 0 ) { + Bit32u samples = ForwardLFO( total ); + for ( Bitu i = 0; i < samples; i++ ) { + output[i] = 0; + } + int count = 0; + for( Channel* ch = chan; ch < chan + 9; ) { + count++; + ch = (ch->*(ch->synthHandler))( this, samples, output ); + } + total -= samples; + output += samples; + } +} + +void Chip::GenerateBlock3( Bitu total, Bit32s* output ) { + while ( total > 0 ) { + Bit32u samples = ForwardLFO( total ); + for ( Bitu i = 0; i < samples; i++ ) { + output[i * 2 + 0 ] = 0; + output[i * 2 + 1 ] = 0; + } + int count = 0; + for( Channel* ch = chan; ch < chan + 18; ) { + count++; + ch = (ch->*(ch->synthHandler))( this, samples, output ); + } + total -= samples; + output += samples * 2; + } +} + +void Chip::Setup( Bit32u clock, Bit32u rate ) { + double original = (double)clock / 288.0; + double scale = original / (double)rate; + if (fabs(scale - 1.0) < 0.00001) + scale = 1.0; + + //Noise counter is run at the same precision as general waves + noiseAdd = (Bit32u)( 0.5 + scale * ( 1 << LFO_SH ) ); + noiseCounter = 0; + noiseValue = 1; //Make sure it triggers the noise xor the first time + //The low frequency oscillation counter + //Every time his overflows vibrato and tremoloindex are increased + lfoAdd = (Bit32u)( 0.5 + scale * ( 1 << LFO_SH ) ); + lfoCounter = 0; + vibratoIndex = 0; + tremoloIndex = 0; + + //With higher octave this gets shifted up + //-1 since the freqCreateTable = *2 +#ifdef WAVE_PRECISION + double freqScale = ( 1 << 7 ) * scale * ( 1 << ( WAVE_SH - 1 - 10)); + for ( int i = 0; i < 16; i++ ) { + //Use rounding with 0.5 + freqMul[i] = (Bit32u)( 0.5 + freqScale * FreqCreateTable[ i ] ); + } +#else + Bit32u freqScale = (Bit32u)( 0.5 + scale * ( 1 << ( WAVE_SH - 1 - 10))); + for ( int i = 0; i < 16; i++ ) { + freqMul[i] = freqScale * FreqCreateTable[ i ]; + } +#endif + + //-3 since the real envelope takes 8 steps to reach the single value we supply + for ( Bit8u i = 0; i < 76; i++ ) { + Bit8u index, shift; + EnvelopeSelect( i, index, shift ); + linearRates[i] = (Bit32u)( scale * (EnvelopeIncreaseTable[ index ] << ( RATE_SH + ENV_EXTRA - shift - 3 ))); + } + //Generate the best matching attack rate + for ( Bit8u i = 0; i < 62; i++ ) { + Bit8u index, shift; + EnvelopeSelect( i, index, shift ); + //Original amount of samples the attack would take + Bit32s original = (Bit32u)( (AttackSamplesTable[ index ] << shift) / scale); + + Bit32s guessAdd = (Bit32u)( scale * (EnvelopeIncreaseTable[ index ] << ( RATE_SH - shift - 3 ))); + Bit32s bestAdd = guessAdd; + Bit32u bestDiff = 1 << 30; + for( Bit32u passes = 0; passes < 16; passes ++ ) { + Bit32s volume = ENV_MAX; + Bit32s samples = 0; + Bit32u count = 0; + while ( volume > 0 && samples < original * 2 ) { + count += guessAdd; + Bit32s change = count >> RATE_SH; + count &= RATE_MASK; + if ( change ) { + volume += ( ~volume * change ) >> 3; + } + samples++; + + } + Bit32s diff = original - samples; + Bit32u lDiff = labs( diff ); + //Init last on first pass + if ( lDiff < bestDiff ) { + bestDiff = lDiff; + bestAdd = guessAdd; + if ( !bestDiff ) + break; + } + //Below our target + if ( diff < 0 ) { + //Better than the last time + Bit32s mul = ((original - diff) << 12) / original; + guessAdd = ((guessAdd * mul) >> 12); + guessAdd++; + } else if ( diff > 0 ) { + Bit32s mul = ((original - diff) << 12) / original; + guessAdd = (guessAdd * mul) >> 12; + guessAdd--; + } + } + attackRates[i] = bestAdd; + } + for ( Bit8u i = 62; i < 76; i++ ) { + //This should provide instant volume maximizing + attackRates[i] = 8 << RATE_SH; + } + //Setup the channels with the correct four op flags + //Channels are accessed through a table so they appear linear here + chan[ 0].fourMask = 0x00 | ( 1 << 0 ); + chan[ 1].fourMask = 0x80 | ( 1 << 0 ); + chan[ 2].fourMask = 0x00 | ( 1 << 1 ); + chan[ 3].fourMask = 0x80 | ( 1 << 1 ); + chan[ 4].fourMask = 0x00 | ( 1 << 2 ); + chan[ 5].fourMask = 0x80 | ( 1 << 2 ); + + chan[ 9].fourMask = 0x00 | ( 1 << 3 ); + chan[10].fourMask = 0x80 | ( 1 << 3 ); + chan[11].fourMask = 0x00 | ( 1 << 4 ); + chan[12].fourMask = 0x80 | ( 1 << 4 ); + chan[13].fourMask = 0x00 | ( 1 << 5 ); + chan[14].fourMask = 0x80 | ( 1 << 5 ); + + //mark the percussion channels + chan[ 6].fourMask = 0x40; + chan[ 7].fourMask = 0x40; + chan[ 8].fourMask = 0x40; + + //Clear Everything in opl3 mode + WriteReg( 0x105, 0x1 ); + for ( int i = 0; i < 512; i++ ) { + if ( i == 0x105 ) + continue; + WriteReg( i, 0xff ); + WriteReg( i, 0x0 ); + } + WriteReg( 0x105, 0x0 ); + //Clear everything in opl2 mode + for ( int i = 0; i < 256; i++ ) { + WriteReg( i, 0xff ); + WriteReg( i, 0x0 ); + } +} + +static bool doneTables = false; +void InitTables( void ) { + if ( doneTables ) + return; + doneTables = true; +#if ( DBOPL_WAVE == WAVE_HANDLER ) || ( DBOPL_WAVE == WAVE_TABLELOG ) + //Exponential volume table, same as the real adlib + for ( int i = 0; i < 256; i++ ) { + //Save them in reverse + ExpTable[i] = (int)( 0.5 + ( pow(2.0, ( 255 - i) * ( 1.0 /256 ) )-1) * 1024 ); + ExpTable[i] += 1024; //or remove the -1 oh well :) + //Preshift to the left once so the final volume can shift to the right + ExpTable[i] *= 2; + } +#endif +#if ( DBOPL_WAVE == WAVE_HANDLER ) + //Add 0.5 for the trunc rounding of the integer cast + //Do a PI sinetable instead of the original 0.5 PI + for ( int i = 0; i < 512; i++ ) { + SinTable[i] = (Bit16s)( 0.5 - log10( sin( (i + 0.5) * (PI / 512.0) ) ) / log10(2.0)*256 ); + } +#endif +#if ( DBOPL_WAVE == WAVE_TABLEMUL ) + //Multiplication based tables + for ( int i = 0; i < 384; i++ ) { + int s = i * 8; + //TODO maybe keep some of the precision errors of the original table? + double val = ( 0.5 + ( pow(2.0, -1.0 + ( 255 - s) * ( 1.0 /256 ) )) * ( 1 << MUL_SH )); + MulTable[i] = (Bit16u)(val); + } + + //Sine Wave Base + for ( int i = 0; i < 512; i++ ) { + WaveTable[ 0x0200 + i ] = (Bit16s)(sin( (i + 0.5) * (PI / 512.0) ) * 4084); + WaveTable[ 0x0000 + i ] = -WaveTable[ 0x200 + i ]; + } + //Exponential wave + for ( int i = 0; i < 256; i++ ) { + WaveTable[ 0x700 + i ] = (Bit16s)( 0.5 + ( pow(2.0, -1.0 + ( 255 - i * 8) * ( 1.0 /256 ) ) ) * 4085 ); + WaveTable[ 0x6ff - i ] = -WaveTable[ 0x700 + i ]; + } +#endif +#if ( DBOPL_WAVE == WAVE_TABLELOG ) + //Sine Wave Base + for ( int i = 0; i < 512; i++ ) { + WaveTable[ 0x0200 + i ] = (Bit16s)( 0.5 - log10( sin( (i + 0.5) * (PI / 512.0) ) ) / log10(2.0)*256 ); + WaveTable[ 0x0000 + i ] = ((Bit16s)0x8000) | WaveTable[ 0x200 + i]; + } + //Exponential wave + for ( int i = 0; i < 256; i++ ) { + WaveTable[ 0x700 + i ] = i * 8; + WaveTable[ 0x6ff - i ] = ((Bit16s)0x8000) | i * 8; + } +#endif + + // | |//\\|____|WAV7|//__|/\ |____|/\/\| + // |\\//| | |WAV7| | \/| | | + // |06 |0126|27 |7 |3 |4 |4 5 |5 | + +#if (( DBOPL_WAVE == WAVE_TABLELOG ) || ( DBOPL_WAVE == WAVE_TABLEMUL )) + for ( int i = 0; i < 256; i++ ) { + //Fill silence gaps + WaveTable[ 0x400 + i ] = WaveTable[0]; + WaveTable[ 0x500 + i ] = WaveTable[0]; + WaveTable[ 0x900 + i ] = WaveTable[0]; + WaveTable[ 0xc00 + i ] = WaveTable[0]; + WaveTable[ 0xd00 + i ] = WaveTable[0]; + //Replicate sines in other pieces + WaveTable[ 0x800 + i ] = WaveTable[ 0x200 + i ]; + //double speed sines + WaveTable[ 0xa00 + i ] = WaveTable[ 0x200 + i * 2 ]; + WaveTable[ 0xb00 + i ] = WaveTable[ 0x000 + i * 2 ]; + WaveTable[ 0xe00 + i ] = WaveTable[ 0x200 + i * 2 ]; + WaveTable[ 0xf00 + i ] = WaveTable[ 0x200 + i * 2 ]; + } +#endif + + //Create the ksl table + for ( int oct = 0; oct < 8; oct++ ) { + int base = oct * 8; + for ( int i = 0; i < 16; i++ ) { + int val = base - KslCreateTable[i]; + if ( val < 0 ) + val = 0; + //*4 for the final range to match attenuation range + KslTable[ oct * 16 + i ] = val * 4; + } + } + //Create the Tremolo table, just increase and decrease a triangle wave + for ( Bit8u i = 0; i < TREMOLO_TABLE / 2; i++ ) { + Bit8u val = i << ENV_EXTRA; + TremoloTable[i] = val; + TremoloTable[TREMOLO_TABLE - 1 - i] = val; + } + //Create a table with offsets of the channels from the start of the chip + DBOPL::Chip* chip = 0; + for ( Bitu i = 0; i < 32; i++ ) { + Bitu index = i & 0xf; + if ( index >= 9 ) { + ChanOffsetTable[i] = 0; + continue; + } + //Make sure the four op channels follow eachother + if ( index < 6 ) { + index = (index % 3) * 2 + ( index / 3 ); + } + //Add back the bits for highest ones + if ( i >= 16 ) + index += 9; + Bitu blah = static_cast( reinterpret_cast( &(chip->chan[ index ]) ) ); + ChanOffsetTable[i] = blah; + } + //Same for operators + for ( Bitu i = 0; i < 64; i++ ) { + if ( i % 8 >= 6 || ( (i / 8) % 4 == 3 ) ) { + OpOffsetTable[i] = 0; + continue; + } + Bitu chNum = (i / 8) * 3 + (i % 8) % 3; + //Make sure we use 16 and up for the 2nd range to match the chanoffset gap + if ( chNum >= 12 ) + chNum += 16 - 12; + Bitu opNum = ( i % 8 ) / 3; + DBOPL::Channel* chan = 0; + Bitu blah = static_cast( reinterpret_cast ( &(chan->op[opNum]) ) ); + OpOffsetTable[i] = ChanOffsetTable[ chNum ] + blah; + } +#if 0 + //Stupid checks if table's are correct + for ( Bitu i = 0; i < 18; i++ ) { + Bit32u find = (Bit16u)( &(chip->chan[ i ]) ); + for ( Bitu c = 0; c < 32; c++ ) { + if ( ChanOffsetTable[c] == find ) { + find = 0; + break; + } + } + if ( find ) { + find = find; + } + } + for ( Bitu i = 0; i < 36; i++ ) { + Bit32u find = (Bit16u)( &(chip->chan[ i / 2 ].op[i % 2]) ); + for ( Bitu c = 0; c < 64; c++ ) { + if ( OpOffsetTable[c] == find ) { + find = 0; + break; + } + } + if ( find ) { + find = find; + } + } +#endif +} + +/*Bit32u Handler::WriteAddr( Bit32u port, Bit8u val ) { + return chip.WriteAddr( port, val ); + +} +void Handler::WriteReg( Bit32u addr, Bit8u val ) { + chip.WriteReg( addr, val ); +} + +void Handler::Generate( MixerChannel* chan, Bitu samples ) { + Bit32s buffer[ 512 * 2 ]; + if ( samples > 512 ) + samples = 512; + if ( !chip.opl3Active ) { + chip.GenerateBlock2( samples, buffer ); + chan->AddSamples_m32( samples, buffer ); + } else { + chip.GenerateBlock3( samples, buffer ); + chan->AddSamples_s32( samples, buffer ); + } +} + +void Handler::Init( Bitu rate ) { + InitTables(); + chip.Setup( rate ); +}*/ + +static class init { +public: + init() { InitTables(); } +} init_stuff; + + +} //Namespace DBOPL diff --git a/Frameworks/GME/gme/dbopl.h b/Frameworks/GME/gme/dbopl.h new file mode 100644 index 000000000..7e24cc10e --- /dev/null +++ b/Frameworks/GME/gme/dbopl.h @@ -0,0 +1,292 @@ +/* + * Copyright (C) 2002-2009 The DOSBox Team + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifdef _WIN32 +#define INLINE __forceinline +#define DB_FASTCALL __fastcall +#else +#define INLINE inline +#define DB_FASTCALL __attribute__((fastcall)) +#endif + +typedef double Real64; +/* The internal types */ + +#ifdef HAVE_STDINT_H +#include + +typedef uint8_t Bit8u; +typedef int8_t Bit8s; +typedef uint16_t Bit16u; +typedef int16_t Bit16s; +typedef uint32_t Bit32u; +typedef int32_t Bit32s; +typedef uint64_t Bit64u; +typedef int64_t Bit64s; +#else +typedef unsigned char Bit8u; +typedef signed char Bit8s; +typedef unsigned short Bit16u; +typedef signed short Bit16s; +typedef unsigned long Bit32u; +typedef signed long Bit32s; +typedef unsigned __int64 Bit64u; +typedef signed __int64 Bit64s; +#endif + +typedef unsigned int Bitu; +typedef signed int Bits; + +#include "adlib.h" +//#include "dosbox.h" + +//Use 8 handlers based on a small logatirmic wavetabe and an exponential table for volume +#define WAVE_HANDLER 10 +//Use a logarithmic wavetable with an exponential table for volume +#define WAVE_TABLELOG 11 +//Use a linear wavetable with a multiply table for volume +#define WAVE_TABLEMUL 12 + +//Select the type of wave generator routine +#define DBOPL_WAVE WAVE_TABLEMUL + +namespace DBOPL { + +struct Chip; +struct Operator; +struct Channel; + +#if (DBOPL_WAVE == WAVE_HANDLER) +typedef Bits ( DB_FASTCALL *WaveHandler) ( Bitu i, Bitu volume ); +#endif + +typedef Bits ( DBOPL::Operator::*VolumeHandler) ( ); +typedef Channel* ( DBOPL::Channel::*SynthHandler) ( Chip* chip, Bit32u samples, Bit32s* output ); + +//Different synth modes that can generate blocks of data +typedef enum { + sm2AM, + sm2FM, + sm3AM, + sm3FM, + sm4Start, + sm3FMFM, + sm3AMFM, + sm3FMAM, + sm3AMAM, + sm6Start, + sm2Percussion, + sm3Percussion +} SynthMode; + +//Shifts for the values contained in chandata variable +enum { + SHIFT_KSLBASE = 16, + SHIFT_KEYCODE = 24 +}; + +struct Operator { +public: + //Masks for operator 20 values + enum { + MASK_KSR = 0x10, + MASK_SUSTAIN = 0x20, + MASK_VIBRATO = 0x40, + MASK_TREMOLO = 0x80 + }; + + typedef enum { + OFF, + RELEASE, + SUSTAIN, + DECAY, + ATTACK + } State; + + VolumeHandler volHandler; + +#if (DBOPL_WAVE == WAVE_HANDLER) + WaveHandler waveHandler; //Routine that generate a wave +#else + Bit16s* waveBase; + Bit32u waveMask; + Bit32u waveStart; +#endif + Bit32u waveIndex; //WAVE_BITS shifted counter of the frequency index + Bit32u waveAdd; //The base frequency without vibrato + Bit32u waveCurrent; //waveAdd + vibratao + + Bit32u chanData; //Frequency/octave and derived data coming from whatever channel controls this + Bit32u freqMul; //Scale channel frequency with this, TODO maybe remove? + Bit32u vibrato; //Scaled up vibrato strength + Bit32s sustainLevel; //When stopping at sustain level stop here + Bit32s totalLevel; //totalLevel is added to every generated volume + Bit32u currentLevel; //totalLevel + tremolo + Bit32s volume; //The currently active volume + + Bit32u attackAdd; //Timers for the different states of the envelope + Bit32u decayAdd; + Bit32u releaseAdd; + Bit32u rateIndex; //Current position of the evenlope + + Bit8u rateZero; //Bits for the different states of the envelope having no changes + Bit8u keyOn; //Bitmask of different values that can generate keyon + //Registers, also used to check for changes + Bit8u reg20, reg40, reg60, reg80, regE0; + //Active part of the envelope we're in + Bit8u state; + //0xff when tremolo is enabled + Bit8u tremoloMask; + //Strength of the vibrato + Bit8u vibStrength; + //Keep track of the calculated KSR so we can check for changes + Bit8u ksr; +private: + void SetState( Bit8u s ); + void UpdateAttack( const Chip* chip ); + void UpdateRelease( const Chip* chip ); + void UpdateDecay( const Chip* chip ); +public: + void UpdateAttenuation(); + void UpdateRates( const Chip* chip ); + void UpdateFrequency( ); + + void Write20( const Chip* chip, Bit8u val ); + void Write40( const Chip* chip, Bit8u val ); + void Write60( const Chip* chip, Bit8u val ); + void Write80( const Chip* chip, Bit8u val ); + void WriteE0( const Chip* chip, Bit8u val ); + + bool Silent() const; + void Prepare( const Chip* chip ); + + void KeyOn( Bit8u mask); + void KeyOff( Bit8u mask); + + template< State state> + Bits TemplateVolume( ); + + Bit32s RateForward( Bit32u add ); + Bitu ForwardWave(); + Bitu ForwardVolume(); + + Bits GetSample( Bits modulation ); + Bits GetWave( Bitu index, Bitu vol ); +public: + Operator(); +}; + +struct Channel { + Operator op[2]; + inline Operator* Op( Bitu index ) { + return &( ( this + (index >> 1) )->op[ index & 1 ]); + } + SynthHandler synthHandler; + Bit32u chanData; //Frequency/octave and derived values + Bit32s old[2]; //Old data for feedback + + Bit8u feedback; //Feedback shift + Bit8u regB0; //Register values to check for changes + Bit8u regC0; + //This should correspond with reg104, bit 6 indicates a Percussion channel, bit 7 indicates a silent channel + Bit8u fourMask; + Bit8s maskLeft; //Sign extended values for both channel's panning + Bit8s maskRight; + + //Forward the channel data to the operators of the channel + void SetChanData( const Chip* chip, Bit32u data ); + //Change in the chandata, check for new values and if we have to forward to operators + void UpdateFrequency( const Chip* chip, Bit8u fourOp ); + void WriteA0( const Chip* chip, Bit8u val ); + void WriteB0( const Chip* chip, Bit8u val ); + void WriteC0( const Chip* chip, Bit8u val ); + void ResetC0( const Chip* chip ); + + //call this for the first channel + template< bool opl3Mode > + void GeneratePercussion( Chip* chip, Bit32s* output ); + + //Generate blocks of data in specific modes + template + Channel* BlockTemplate( Chip* chip, Bit32u samples, Bit32s* output ); + Channel(); +}; + +struct Chip { + //This is used as the base counter for vibrato and tremolo + Bit32u lfoCounter; + Bit32u lfoAdd; + + + Bit32u noiseCounter; + Bit32u noiseAdd; + Bit32u noiseValue; + + //Frequency scales for the different multiplications + Bit32u freqMul[16]; + //Rates for decay and release for rate of this chip + Bit32u linearRates[76]; + //Best match attack rates for the rate of this chip + Bit32u attackRates[76]; + + //18 channels with 2 operators each + Channel chan[18]; + + Bit8u reg104; + Bit8u reg08; + Bit8u reg04; + Bit8u regBD; + Bit8u vibratoIndex; + Bit8u tremoloIndex; + Bit8s vibratoSign; + Bit8u vibratoShift; + Bit8u tremoloValue; + Bit8u vibratoStrength; + Bit8u tremoloStrength; + //Mask for allowed wave forms + Bit8u waveFormMask; + //0 or -1 when enabled + Bit8s opl3Active; + + //Return the maximum amount of samples before and LFO change + Bit32u ForwardLFO( Bit32u samples ); + Bit32u ForwardNoise(); + + void WriteBD( Bit8u val ); + void WriteReg(Bit32u reg, Bit8u val ); + + Bit32u WriteAddr( Bit32u port, Bit8u val ); + + void GenerateBlock2( Bitu samples, Bit32s* output ); + void GenerateBlock3( Bitu samples, Bit32s* output ); + + void Setup( Bit32u c, Bit32u r ); + + Chip(); +}; + +/*struct Handler : public Adlib::Handler { + DBOPL::Chip chip; + virtual Bit32u WriteAddr( Bit32u port, Bit8u val ); + virtual void WriteReg( Bit32u addr, Bit8u val ); + virtual void Generate( MixerChannel* chan, Bitu samples ); + virtual void Init( Bitu rate ); +};*/ + + +} //Namespace diff --git a/Frameworks/GME/gme/divfix.h b/Frameworks/GME/gme/divfix.h new file mode 100644 index 000000000..21212df3f --- /dev/null +++ b/Frameworks/GME/gme/divfix.h @@ -0,0 +1,18 @@ + +static Uint32 DivFix(Uint32 p1, Uint32 p2, Uint32 fix) +{ + Uint32 ret; + ret = p1 / p2; + p1 = p1 % p2;/* p1 = p1 - p2 * ret; */ + while (fix--) + { + p1 += p1; + ret += ret; + if (p1 >= p2) + { + p1 -= p2; + ret++; + } + } + return ret; +} diff --git a/Frameworks/GME/gme/emuconfig.h b/Frameworks/GME/gme/emuconfig.h new file mode 100644 index 000000000..9250e1cf9 --- /dev/null +++ b/Frameworks/GME/gme/emuconfig.h @@ -0,0 +1,78 @@ +///////////////////////////////////////////////////////////////////////////// +// +// Configuration for emulation libraries +// +///////////////////////////////////////////////////////////////////////////// + +#ifndef __EMUCONFIG_H__ +#define __EMUCONFIG_H__ + +///////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include + +///////////////////////////////////////////////////////////////////////////// +// +// WIN32 native project definitions +// +///////////////////////////////////////////////////////////////////////////// +#if defined(WIN32) && !defined(__GNUC__) + +#define EMU_CALL __fastcall +#define EMU_CALL_ __cdecl +#define EMU_INLINE __inline + +#define uint8 unsigned char +#define uint16 unsigned short +#define uint32 unsigned int +#define uint64 unsigned __int64 +#define sint8 signed char +#define sint16 signed short +#define sint32 signed int +#define sint64 signed __int64 + +///////////////////////////////////////////////////////////////////////////// +// +// LINUX / other platform definitions +// +///////////////////////////////////////////////////////////////////////////// +#else + +//#if defined(__GNUC__) && defined(__i386__) +//#define EMU_CALL __attribute__((__regparm__(2))) +//#else +#define EMU_CALL +//#endif + +#define EMU_CALL_ +#define EMU_INLINE __inline + +#ifdef HAVE_STDINT_H +#include +#define uint8 uint8_t +#define uint16 uint16_t +#define uint32 uint32_t +#define uint64 uint64_t +#define sint8 int8_t +#define sint16 int16_t +#define sint32 int32_t +#define sint64 int64_t +#else +#define uint8 unsigned char +#define uint16 unsigned short +#define uint32 unsigned int +#define uint64 unsigned long long +#define sint8 signed char +#define sint16 signed short +#define sint32 signed int +#define sint64 signed long long +#endif + +#endif + +///////////////////////////////////////////////////////////////////////////// + +#endif diff --git a/Frameworks/GME/gme/fm.c b/Frameworks/GME/gme/fm.c new file mode 100644 index 000000000..f069f396e --- /dev/null +++ b/Frameworks/GME/gme/fm.c @@ -0,0 +1,4501 @@ +#define YM2610B_WARNING + +/* +** +** File: fm.c -- software implementation of Yamaha FM sound generator +** +** Copyright Jarek Burczynski (bujar at mame dot net) +** Copyright Tatsuyuki Satoh , MultiArcadeMachineEmulator development +** +** Version 1.4.2 (final beta) +** +*/ + +/* +** History: +** +** 2006-2008 Eke-Eke (Genesis Plus GX), MAME backport by R. Belmont. +** - implemented PG overflow, aka "detune bug" (Ariel, Comix Zone, Shaq Fu, Spiderman,...), credits to Nemesis +** - fixed SSG-EG support, credits to Nemesis and additional fixes from Alone Coder +** - modified EG rates and frequency, tested by Nemesis on real hardware +** - implemented LFO phase update for CH3 special mode (Warlock birds, Alladin bug sound) +** - fixed Attack Rate update (Batman & Robin intro) +** - fixed attenuation level at the start of Substain (Gynoug explosions) +** - fixed EG decay->substain transition to handle special cases, like SL=0 and Decay rate is very slow (Mega Turrican tracks 03,09...) +** +** 06-23-2007 Zsolt Vasvari: +** - changed the timing not to require the use of floating point calculations +** +** 03-08-2003 Jarek Burczynski: +** - fixed YM2608 initial values (after the reset) +** - fixed flag and irqmask handling (YM2608) +** - fixed BUFRDY flag handling (YM2608) +** +** 14-06-2003 Jarek Burczynski: +** - implemented all of the YM2608 status register flags +** - implemented support for external memory read/write via YM2608 +** - implemented support for deltat memory limit register in YM2608 emulation +** +** 22-05-2003 Jarek Burczynski: +** - fixed LFO PM calculations (copy&paste bugfix) +** +** 08-05-2003 Jarek Burczynski: +** - fixed SSG support +** +** 22-04-2003 Jarek Burczynski: +** - implemented 100% correct LFO generator (verified on real YM2610 and YM2608) +** +** 15-04-2003 Jarek Burczynski: +** - added support for YM2608's register 0x110 - status mask +** +** 01-12-2002 Jarek Burczynski: +** - fixed register addressing in YM2608, YM2610, YM2610B chips. (verified on real YM2608) +** The addressing patch used for early Neo-Geo games can be removed now. +** +** 26-11-2002 Jarek Burczynski, Nicola Salmoria: +** - recreated YM2608 ADPCM ROM using data from real YM2608's output which leads to: +** - added emulation of YM2608 drums. +** - output of YM2608 is two times lower now - same as YM2610 (verified on real YM2608) +** +** 16-08-2002 Jarek Burczynski: +** - binary exact Envelope Generator (verified on real YM2203); +** identical to YM2151 +** - corrected 'off by one' error in feedback calculations (when feedback is off) +** - corrected connection (algorithm) calculation (verified on real YM2203 and YM2610) +** +** 18-12-2001 Jarek Burczynski: +** - added SSG-EG support (verified on real YM2203) +** +** 12-08-2001 Jarek Burczynski: +** - corrected sin_tab and tl_tab data (verified on real chip) +** - corrected feedback calculations (verified on real chip) +** - corrected phase generator calculations (verified on real chip) +** - corrected envelope generator calculations (verified on real chip) +** - corrected FM volume level (YM2610 and YM2610B). +** - changed YMxxxUpdateOne() functions (YM2203, YM2608, YM2610, YM2610B, YM2612) : +** this was needed to calculate YM2610 FM channels output correctly. +** (Each FM channel is calculated as in other chips, but the output of the channel +** gets shifted right by one *before* sending to accumulator. That was impossible to do +** with previous implementation). +** +** 23-07-2001 Jarek Burczynski, Nicola Salmoria: +** - corrected YM2610 ADPCM type A algorithm and tables (verified on real chip) +** +** 11-06-2001 Jarek Burczynski: +** - corrected end of sample bug in ADPCMA_calc_cha(). +** Real YM2610 checks for equality between current and end addresses (only 20 LSB bits). +** +** 08-12-98 hiro-shi: +** rename ADPCMA -> ADPCMB, ADPCMB -> ADPCMA +** move ROM limit check.(CALC_CH? -> 2610Write1/2) +** test program (ADPCMB_TEST) +** move ADPCM A/B end check. +** ADPCMB repeat flag(no check) +** change ADPCM volume rate (8->16) (32->48). +** +** 09-12-98 hiro-shi: +** change ADPCM volume. (8->16, 48->64) +** replace ym2610 ch0/3 (YM-2610B) +** change ADPCM_SHIFT (10->8) missing bank change 0x4000-0xffff. +** add ADPCM_SHIFT_MASK +** change ADPCMA_DECODE_MIN/MAX. +*/ + + + + +/************************************************************************/ +/* comment of hiro-shi(Hiromitsu Shioya) */ +/* YM2610(B) = OPN-B */ +/* YM2610 : PSG:3ch FM:4ch ADPCM(18.5KHz):6ch DeltaT ADPCM:1ch */ +/* YM2610B : PSG:3ch FM:6ch ADPCM(18.5KHz):6ch DeltaT ADPCM:1ch */ +/************************************************************************/ + +#include +#include +#include +#include +#include +#include "mathdefs.h" + + +#include "mamedef.h" +//#ifndef __RAINE__ +//#include "sndintrf.h" /* use M.A.M.E. */ +//#else +//#include "deftypes.h" /* use RAINE */ +//#include "support.h" /* use RAINE */ +//#endif +#include "fm.h" + + +/* include external DELTA-T unit (when needed) */ +#if (BUILD_YM2608||BUILD_YM2610||BUILD_YM2610B) + #include "ymdeltat.h" +#endif + +/* shared function building option */ +#define BUILD_OPN (BUILD_YM2203||BUILD_YM2608||BUILD_YM2610||BUILD_YM2610B) +#define BUILD_OPN_PRESCALER (BUILD_YM2203||BUILD_YM2608) + + +/* globals */ +#define TYPE_SSG 0x01 /* SSG support */ +#define TYPE_LFOPAN 0x02 /* OPN type LFO and PAN */ +#define TYPE_6CH 0x04 /* FM 6CH / 3CH */ +#define TYPE_DAC 0x08 /* YM2612's DAC device */ +#define TYPE_ADPCM 0x10 /* two ADPCM units */ +#define TYPE_2610 0x20 /* bogus flag to differentiate 2608 from 2610 */ + + +#define TYPE_YM2203 (TYPE_SSG) +#define TYPE_YM2608 (TYPE_SSG |TYPE_LFOPAN |TYPE_6CH |TYPE_ADPCM) +#define TYPE_YM2610 (TYPE_SSG |TYPE_LFOPAN |TYPE_6CH |TYPE_ADPCM |TYPE_2610) + + + +#define FREQ_SH 16 /* 16.16 fixed point (frequency calculations) */ +#define EG_SH 16 /* 16.16 fixed point (envelope generator timing) */ +#define LFO_SH 24 /* 8.24 fixed point (LFO calculations) */ +#define TIMER_SH 16 /* 16.16 fixed point (timers calculations) */ + +#define FREQ_MASK ((1<>3) + +/* sin waveform table in 'decibel' scale */ +static unsigned int sin_tab[SIN_LEN]; + +/* sustain level table (3dB per step) */ +/* bit0, bit1, bit2, bit3, bit4, bit5, bit6 */ +/* 1, 2, 4, 8, 16, 32, 64 (value)*/ +/* 0.75, 1.5, 3, 6, 12, 24, 48 (dB)*/ + +/* 0 - 15: 0, 3, 6, 9,12,15,18,21,24,27,30,33,36,39,42,93 (dB)*/ +#define SC(db) (UINT32) ( db * (4.0/ENV_STEP) ) +static const UINT32 sl_table[16]={ + SC( 0),SC( 1),SC( 2),SC(3 ),SC(4 ),SC(5 ),SC(6 ),SC( 7), + SC( 8),SC( 9),SC(10),SC(11),SC(12),SC(13),SC(14),SC(31) +}; +#undef SC + + +#define RATE_STEPS (8) +static const UINT8 eg_inc[19*RATE_STEPS]={ + +/*cycle:0 1 2 3 4 5 6 7*/ + +/* 0 */ 0,1, 0,1, 0,1, 0,1, /* rates 00..11 0 (increment by 0 or 1) */ +/* 1 */ 0,1, 0,1, 1,1, 0,1, /* rates 00..11 1 */ +/* 2 */ 0,1, 1,1, 0,1, 1,1, /* rates 00..11 2 */ +/* 3 */ 0,1, 1,1, 1,1, 1,1, /* rates 00..11 3 */ + +/* 4 */ 1,1, 1,1, 1,1, 1,1, /* rate 12 0 (increment by 1) */ +/* 5 */ 1,1, 1,2, 1,1, 1,2, /* rate 12 1 */ +/* 6 */ 1,2, 1,2, 1,2, 1,2, /* rate 12 2 */ +/* 7 */ 1,2, 2,2, 1,2, 2,2, /* rate 12 3 */ + +/* 8 */ 2,2, 2,2, 2,2, 2,2, /* rate 13 0 (increment by 2) */ +/* 9 */ 2,2, 2,4, 2,2, 2,4, /* rate 13 1 */ +/*10 */ 2,4, 2,4, 2,4, 2,4, /* rate 13 2 */ +/*11 */ 2,4, 4,4, 2,4, 4,4, /* rate 13 3 */ + +/*12 */ 4,4, 4,4, 4,4, 4,4, /* rate 14 0 (increment by 4) */ +/*13 */ 4,4, 4,8, 4,4, 4,8, /* rate 14 1 */ +/*14 */ 4,8, 4,8, 4,8, 4,8, /* rate 14 2 */ +/*15 */ 4,8, 8,8, 4,8, 8,8, /* rate 14 3 */ + +/*16 */ 8,8, 8,8, 8,8, 8,8, /* rates 15 0, 15 1, 15 2, 15 3 (increment by 8) */ +/*17 */ 16,16,16,16,16,16,16,16, /* rates 15 2, 15 3 for attack */ +/*18 */ 0,0, 0,0, 0,0, 0,0, /* infinity rates for attack and decay(s) */ +}; + + +#define O(a) (a*RATE_STEPS) + +/*note that there is no O(17) in this table - it's directly in the code */ +static const UINT8 eg_rate_select[32+64+32]={ /* Envelope Generator rates (32 + 64 rates + 32 RKS) */ +/* 32 infinite time rates */ +O(18),O(18),O(18),O(18),O(18),O(18),O(18),O(18), +O(18),O(18),O(18),O(18),O(18),O(18),O(18),O(18), +O(18),O(18),O(18),O(18),O(18),O(18),O(18),O(18), +O(18),O(18),O(18),O(18),O(18),O(18),O(18),O(18), + +/* rates 00-11 */ +O( 0),O( 1),O( 2),O( 3), +O( 0),O( 1),O( 2),O( 3), +O( 0),O( 1),O( 2),O( 3), +O( 0),O( 1),O( 2),O( 3), +O( 0),O( 1),O( 2),O( 3), +O( 0),O( 1),O( 2),O( 3), +O( 0),O( 1),O( 2),O( 3), +O( 0),O( 1),O( 2),O( 3), +O( 0),O( 1),O( 2),O( 3), +O( 0),O( 1),O( 2),O( 3), +O( 0),O( 1),O( 2),O( 3), +O( 0),O( 1),O( 2),O( 3), + +/* rate 12 */ +O( 4),O( 5),O( 6),O( 7), + +/* rate 13 */ +O( 8),O( 9),O(10),O(11), + +/* rate 14 */ +O(12),O(13),O(14),O(15), + +/* rate 15 */ +O(16),O(16),O(16),O(16), + +/* 32 dummy rates (same as 15 3) */ +O(16),O(16),O(16),O(16),O(16),O(16),O(16),O(16), +O(16),O(16),O(16),O(16),O(16),O(16),O(16),O(16), +O(16),O(16),O(16),O(16),O(16),O(16),O(16),O(16), +O(16),O(16),O(16),O(16),O(16),O(16),O(16),O(16) + +}; + +#undef O + +/*rate 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15*/ +/*shift 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 0, 0, 0, 0 */ +/*mask 2047, 1023, 511, 255, 127, 63, 31, 15, 7, 3, 1, 0, 0, 0, 0, 0 */ + +#define O(a) (a*1) +static const UINT8 eg_rate_shift[32+64+32]={ /* Envelope Generator counter shifts (32 + 64 rates + 32 RKS) */ +/* 32 infinite time rates */ +O(0),O(0),O(0),O(0),O(0),O(0),O(0),O(0), +O(0),O(0),O(0),O(0),O(0),O(0),O(0),O(0), +O(0),O(0),O(0),O(0),O(0),O(0),O(0),O(0), +O(0),O(0),O(0),O(0),O(0),O(0),O(0),O(0), + +/* rates 00-11 */ +O(11),O(11),O(11),O(11), +O(10),O(10),O(10),O(10), +O( 9),O( 9),O( 9),O( 9), +O( 8),O( 8),O( 8),O( 8), +O( 7),O( 7),O( 7),O( 7), +O( 6),O( 6),O( 6),O( 6), +O( 5),O( 5),O( 5),O( 5), +O( 4),O( 4),O( 4),O( 4), +O( 3),O( 3),O( 3),O( 3), +O( 2),O( 2),O( 2),O( 2), +O( 1),O( 1),O( 1),O( 1), +O( 0),O( 0),O( 0),O( 0), + +/* rate 12 */ +O( 0),O( 0),O( 0),O( 0), + +/* rate 13 */ +O( 0),O( 0),O( 0),O( 0), + +/* rate 14 */ +O( 0),O( 0),O( 0),O( 0), + +/* rate 15 */ +O( 0),O( 0),O( 0),O( 0), + +/* 32 dummy rates (same as 15 3) */ +O( 0),O( 0),O( 0),O( 0),O( 0),O( 0),O( 0),O( 0), +O( 0),O( 0),O( 0),O( 0),O( 0),O( 0),O( 0),O( 0), +O( 0),O( 0),O( 0),O( 0),O( 0),O( 0),O( 0),O( 0), +O( 0),O( 0),O( 0),O( 0),O( 0),O( 0),O( 0),O( 0) + +}; +#undef O + +static const UINT8 dt_tab[4 * 32]={ +/* this is YM2151 and YM2612 phase increment data (in 10.10 fixed point format)*/ +/* FD=0 */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +/* FD=1 */ + 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, + 2, 3, 3, 3, 4, 4, 4, 5, 5, 6, 6, 7, 8, 8, 8, 8, +/* FD=2 */ + 1, 1, 1, 1, 2, 2, 2, 2, 2, 3, 3, 3, 4, 4, 4, 5, + 5, 6, 6, 7, 8, 8, 9,10,11,12,13,14,16,16,16,16, +/* FD=3 */ + 2, 2, 2, 2, 2, 3, 3, 3, 4, 4, 4, 5, 5, 6, 6, 7, + 8 , 8, 9,10,11,12,13,14,16,17,19,20,22,22,22,22 +}; + + +/* OPN key frequency number -> key code follow table */ +/* fnum higher 4bit -> keycode lower 2bit */ +static const UINT8 opn_fktable[16] = {0,0,0,0,0,0,0,1,2,3,3,3,3,3,3,3}; + + +/* 8 LFO speed parameters */ +/* each value represents number of samples that one LFO level will last for */ +static const UINT32 lfo_samples_per_step[8] = {108, 77, 71, 67, 62, 44, 8, 5}; + + + +/*There are 4 different LFO AM depths available, they are: + 0 dB, 1.4 dB, 5.9 dB, 11.8 dB + Here is how it is generated (in EG steps): + + 11.8 dB = 0, 2, 4, 6, 8, 10,12,14,16...126,126,124,122,120,118,....4,2,0 + 5.9 dB = 0, 1, 2, 3, 4, 5, 6, 7, 8....63, 63, 62, 61, 60, 59,.....2,1,0 + 1.4 dB = 0, 0, 0, 0, 1, 1, 1, 1, 2,...15, 15, 15, 15, 14, 14,.....0,0,0 + + (1.4 dB is loosing precision as you can see) + + It's implemented as generator from 0..126 with step 2 then a shift + right N times, where N is: + 8 for 0 dB + 3 for 1.4 dB + 1 for 5.9 dB + 0 for 11.8 dB +*/ +static const UINT8 lfo_ams_depth_shift[4] = {8, 3, 1, 0}; + + + +/*There are 8 different LFO PM depths available, they are: + 0, 3.4, 6.7, 10, 14, 20, 40, 80 (cents) + + Modulation level at each depth depends on F-NUMBER bits: 4,5,6,7,8,9,10 + (bits 8,9,10 = FNUM MSB from OCT/FNUM register) + + Here we store only first quarter (positive one) of full waveform. + Full table (lfo_pm_table) containing all 128 waveforms is build + at run (init) time. + + One value in table below represents 4 (four) basic LFO steps + (1 PM step = 4 AM steps). + + For example: + at LFO SPEED=0 (which is 108 samples per basic LFO step) + one value from "lfo_pm_output" table lasts for 432 consecutive + samples (4*108=432) and one full LFO waveform cycle lasts for 13824 + samples (32*432=13824; 32 because we store only a quarter of whole + waveform in the table below) +*/ +static const UINT8 lfo_pm_output[7*8][8]={ /* 7 bits meaningful (of F-NUMBER), 8 LFO output levels per one depth (out of 32), 8 LFO depths */ +/* FNUM BIT 4: 000 0001xxxx */ +/* DEPTH 0 */ {0, 0, 0, 0, 0, 0, 0, 0}, +/* DEPTH 1 */ {0, 0, 0, 0, 0, 0, 0, 0}, +/* DEPTH 2 */ {0, 0, 0, 0, 0, 0, 0, 0}, +/* DEPTH 3 */ {0, 0, 0, 0, 0, 0, 0, 0}, +/* DEPTH 4 */ {0, 0, 0, 0, 0, 0, 0, 0}, +/* DEPTH 5 */ {0, 0, 0, 0, 0, 0, 0, 0}, +/* DEPTH 6 */ {0, 0, 0, 0, 0, 0, 0, 0}, +/* DEPTH 7 */ {0, 0, 0, 0, 1, 1, 1, 1}, + +/* FNUM BIT 5: 000 0010xxxx */ +/* DEPTH 0 */ {0, 0, 0, 0, 0, 0, 0, 0}, +/* DEPTH 1 */ {0, 0, 0, 0, 0, 0, 0, 0}, +/* DEPTH 2 */ {0, 0, 0, 0, 0, 0, 0, 0}, +/* DEPTH 3 */ {0, 0, 0, 0, 0, 0, 0, 0}, +/* DEPTH 4 */ {0, 0, 0, 0, 0, 0, 0, 0}, +/* DEPTH 5 */ {0, 0, 0, 0, 0, 0, 0, 0}, +/* DEPTH 6 */ {0, 0, 0, 0, 1, 1, 1, 1}, +/* DEPTH 7 */ {0, 0, 1, 1, 2, 2, 2, 3}, + +/* FNUM BIT 6: 000 0100xxxx */ +/* DEPTH 0 */ {0, 0, 0, 0, 0, 0, 0, 0}, +/* DEPTH 1 */ {0, 0, 0, 0, 0, 0, 0, 0}, +/* DEPTH 2 */ {0, 0, 0, 0, 0, 0, 0, 0}, +/* DEPTH 3 */ {0, 0, 0, 0, 0, 0, 0, 0}, +/* DEPTH 4 */ {0, 0, 0, 0, 0, 0, 0, 1}, +/* DEPTH 5 */ {0, 0, 0, 0, 1, 1, 1, 1}, +/* DEPTH 6 */ {0, 0, 1, 1, 2, 2, 2, 3}, +/* DEPTH 7 */ {0, 0, 2, 3, 4, 4, 5, 6}, + +/* FNUM BIT 7: 000 1000xxxx */ +/* DEPTH 0 */ {0, 0, 0, 0, 0, 0, 0, 0}, +/* DEPTH 1 */ {0, 0, 0, 0, 0, 0, 0, 0}, +/* DEPTH 2 */ {0, 0, 0, 0, 0, 0, 1, 1}, +/* DEPTH 3 */ {0, 0, 0, 0, 1, 1, 1, 1}, +/* DEPTH 4 */ {0, 0, 0, 1, 1, 1, 1, 2}, +/* DEPTH 5 */ {0, 0, 1, 1, 2, 2, 2, 3}, +/* DEPTH 6 */ {0, 0, 2, 3, 4, 4, 5, 6}, +/* DEPTH 7 */ {0, 0, 4, 6, 8, 8, 0xa, 0xc}, + +/* FNUM BIT 8: 001 0000xxxx */ +/* DEPTH 0 */ {0, 0, 0, 0, 0, 0, 0, 0}, +/* DEPTH 1 */ {0, 0, 0, 0, 1, 1, 1, 1}, +/* DEPTH 2 */ {0, 0, 0, 1, 1, 1, 2, 2}, +/* DEPTH 3 */ {0, 0, 1, 1, 2, 2, 3, 3}, +/* DEPTH 4 */ {0, 0, 1, 2, 2, 2, 3, 4}, +/* DEPTH 5 */ {0, 0, 2, 3, 4, 4, 5, 6}, +/* DEPTH 6 */ {0, 0, 4, 6, 8, 8, 0xa, 0xc}, +/* DEPTH 7 */ {0, 0, 8, 0xc,0x10,0x10,0x14,0x18}, + +/* FNUM BIT 9: 010 0000xxxx */ +/* DEPTH 0 */ {0, 0, 0, 0, 0, 0, 0, 0}, +/* DEPTH 1 */ {0, 0, 0, 0, 2, 2, 2, 2}, +/* DEPTH 2 */ {0, 0, 0, 2, 2, 2, 4, 4}, +/* DEPTH 3 */ {0, 0, 2, 2, 4, 4, 6, 6}, +/* DEPTH 4 */ {0, 0, 2, 4, 4, 4, 6, 8}, +/* DEPTH 5 */ {0, 0, 4, 6, 8, 8, 0xa, 0xc}, +/* DEPTH 6 */ {0, 0, 8, 0xc,0x10,0x10,0x14,0x18}, +/* DEPTH 7 */ {0, 0,0x10,0x18,0x20,0x20,0x28,0x30}, + +/* FNUM BIT10: 100 0000xxxx */ +/* DEPTH 0 */ {0, 0, 0, 0, 0, 0, 0, 0}, +/* DEPTH 1 */ {0, 0, 0, 0, 4, 4, 4, 4}, +/* DEPTH 2 */ {0, 0, 0, 4, 4, 4, 8, 8}, +/* DEPTH 3 */ {0, 0, 4, 4, 8, 8, 0xc, 0xc}, +/* DEPTH 4 */ {0, 0, 4, 8, 8, 8, 0xc,0x10}, +/* DEPTH 5 */ {0, 0, 8, 0xc,0x10,0x10,0x14,0x18}, +/* DEPTH 6 */ {0, 0,0x10,0x18,0x20,0x20,0x28,0x30}, +/* DEPTH 7 */ {0, 0,0x20,0x30,0x40,0x40,0x50,0x60}, + +}; + +/* all 128 LFO PM waveforms */ +static INT32 lfo_pm_table[128*8*32]; /* 128 combinations of 7 bits meaningful (of F-NUMBER), 8 LFO depths, 32 LFO output levels per one depth */ + + + + + +/* register number to channel number , slot offset */ +#define OPN_CHAN(N) (N&3) +#define OPN_SLOT(N) ((N>>2)&3) + +/* slot number */ +#define SLOT1 0 +#define SLOT2 2 +#define SLOT3 1 +#define SLOT4 3 + +/* bit0 = Right enable , bit1 = Left enable */ +#define OUTD_RIGHT 1 +#define OUTD_LEFT 2 +#define OUTD_CENTER 3 + + +/* save output as raw 16-bit sample */ +/* #define SAVE_SAMPLE */ + +#ifdef SAVE_SAMPLE +static FILE *sample[1]; + #if 1 /*save to MONO file */ + #define SAVE_ALL_CHANNELS \ + { signed int pom = lt; \ + fputc((unsigned short)pom&0xff,sample[0]); \ + fputc(((unsigned short)pom>>8)&0xff,sample[0]); \ + } + #else /*save to STEREO file */ + #define SAVE_ALL_CHANNELS \ + { signed int pom = lt; \ + fputc((unsigned short)pom&0xff,sample[0]); \ + fputc(((unsigned short)pom>>8)&0xff,sample[0]); \ + pom = rt; \ + fputc((unsigned short)pom&0xff,sample[0]); \ + fputc(((unsigned short)pom>>8)&0xff,sample[0]); \ + } + #endif +#endif + + +/* struct describing a single operator (SLOT) */ +typedef struct +{ + INT32 *DT; /* detune :dt_tab[DT] */ + UINT8 KSR; /* key scale rate :3-KSR */ + UINT32 ar; /* attack rate */ + UINT32 d1r; /* decay rate */ + UINT32 d2r; /* sustain rate */ + UINT32 rr; /* release rate */ + UINT8 ksr; /* key scale rate :kcode>>(3-KSR) */ + UINT32 mul; /* multiple :ML_TABLE[ML] */ + + /* Phase Generator */ + UINT32 phase; /* phase counter */ + INT32 Incr; /* phase step */ + + /* Envelope Generator */ + UINT8 state; /* phase type */ + UINT32 tl; /* total level: TL << 3 */ + INT32 volume; /* envelope counter */ + UINT32 sl; /* sustain level:sl_table[SL] */ + UINT32 vol_out; /* current output from EG circuit (without AM from LFO) */ + + UINT8 eg_sh_ar; /* (attack state) */ + UINT8 eg_sel_ar; /* (attack state) */ + UINT8 eg_sh_d1r; /* (decay state) */ + UINT8 eg_sel_d1r; /* (decay state) */ + UINT8 eg_sh_d2r; /* (sustain state) */ + UINT8 eg_sel_d2r; /* (sustain state) */ + UINT8 eg_sh_rr; /* (release state) */ + UINT8 eg_sel_rr; /* (release state) */ + + UINT8 ssg; /* SSG-EG waveform */ + UINT8 ssgn; /* SSG-EG negated output */ + + UINT32 key; /* 0=last key was KEY OFF, 1=KEY ON */ + + /* LFO */ + UINT32 AMmask; /* AM enable flag */ + +} FM_SLOT; + +typedef struct +{ + FM_SLOT SLOT[4]; /* four SLOTs (operators) */ + + UINT8 ALGO; /* algorithm */ + UINT8 FB; /* feedback shift */ + INT32 op1_out[2]; /* op1 output for feedback */ + + INT32 *connect1; /* SLOT1 output pointer */ + INT32 *connect3; /* SLOT3 output pointer */ + INT32 *connect2; /* SLOT2 output pointer */ + INT32 *connect4; /* SLOT4 output pointer */ + + INT32 *mem_connect;/* where to put the delayed sample (MEM) */ + INT32 mem_value; /* delayed sample (MEM) value */ + + INT32 pms; /* channel PMS */ + UINT8 ams; /* channel AMS */ + + UINT32 fc; /* fnum,blk:adjusted to sample rate */ + UINT8 kcode; /* key code: */ + UINT32 block_fnum; /* current blk/fnum value for this slot (can be different betweeen slots of one channel in 3slot mode) */ + UINT8 Muted; +} FM_CH; + + +typedef struct +{ + //const device_config *device; + void * param; /* this chip parameter */ + int clock; /* master clock (Hz) */ + int rate; /* sampling rate (Hz) */ + double freqbase; /* frequency base */ + int timer_prescaler; /* timer prescaler */ +#if FM_BUSY_FLAG_SUPPORT + TIME_TYPE busy_expiry_time; /* expiry time of the busy status */ +#endif + UINT8 address; /* address register */ + UINT8 irq; /* interrupt level */ + UINT8 irqmask; /* irq mask */ + UINT8 status; /* status flag */ + UINT32 mode; /* mode CSM / 3SLOT */ + UINT8 prescaler_sel; /* prescaler selector */ + UINT8 fn_h; /* freq latch */ + INT32 TA; /* timer a */ + INT32 TAC; /* timer a counter */ + UINT8 TB; /* timer b */ + INT32 TBC; /* timer b counter */ + /* local time tables */ + INT32 dt_tab[8][32]; /* DeTune table */ + /* Extention Timer and IRQ handler */ + const ssg_callbacks *SSG; +} FM_ST; + + + +/***********************************************************/ +/* OPN unit */ +/***********************************************************/ + +/* OPN 3slot struct */ +typedef struct +{ + UINT32 fc[3]; /* fnum3,blk3: calculated */ + UINT8 fn_h; /* freq3 latch */ + UINT8 kcode[3]; /* key code */ + UINT32 block_fnum[3]; /* current fnum value for this slot (can be different betweeen slots of one channel in 3slot mode) */ +} FM_3SLOT; + +/* OPN/A/B common state */ +typedef struct +{ + UINT8 type; /* chip type */ + FM_ST ST; /* general state */ + FM_3SLOT SL3; /* 3 slot mode state */ + FM_CH *P_CH; /* pointer of CH */ + unsigned int pan[6*2]; /* fm channels output masks (0xffffffff = enable) */ + + UINT32 eg_cnt; /* global envelope generator counter */ + UINT32 eg_timer; /* global envelope generator counter works at frequency = chipclock/64/3 */ + UINT32 eg_timer_add; /* step of eg_timer */ + UINT32 eg_timer_overflow;/* envelope generator timer overlfows every 3 samples (on real chip) */ + + + /* there are 2048 FNUMs that can be generated using FNUM/BLK registers + but LFO works with one more bit of a precision so we really need 4096 elements */ + + UINT32 fn_table[4096]; /* fnumber->increment counter */ + UINT32 fn_max; /* maximal phase increment (used for phase overflow) */ + + /* LFO */ + UINT32 LFO_AM; /* runtime LFO calculations helper */ + INT32 LFO_PM; /* runtime LFO calculations helper */ + + UINT32 lfo_cnt; + UINT32 lfo_inc; + + UINT32 lfo_freq[8]; /* LFO FREQ table */ + + INT32 m2,c1,c2; /* Phase Modulation input for operators 2,3,4 */ + INT32 mem; /* one sample delay memory */ + + INT32 out_fm[8]; /* outputs of working channels */ + +#if (BUILD_YM2608||BUILD_YM2610||BUILD_YM2610B) + INT32 out_adpcm[4]; /* channel output NONE,LEFT,RIGHT or CENTER for YM2608/YM2610 ADPCM */ + INT32 out_delta[4]; /* channel output NONE,LEFT,RIGHT or CENTER for YM2608/YM2610 DELTAT*/ +#endif +} FM_OPN; + + + +/* log output level */ +#define LOG_ERR 3 /* ERROR */ +#define LOG_WAR 2 /* WARNING */ +#define LOG_INF 1 /* INFORMATION */ +#define LOG_LEVEL LOG_INF + +#ifndef __RAINE__ +#define LOG(n,x) do { if( (n)>=LOG_LEVEL ) logerror x; } while (0) +#endif + +/* limitter */ +#define Limit(val, max,min) { \ + if ( val > max ) val = max; \ + else if ( val < min ) val = min; \ +} + + +/* status set and IRQ handling */ +INLINE void FM_STATUS_SET(FM_ST *ST,int flag) +{ + /* set status flag */ + ST->status |= flag; + if ( !(ST->irq) && (ST->status & ST->irqmask) ) + { + ST->irq = 1; + /* callback user interrupt handler (IRQ is OFF to ON) */ + } +} + +/* status reset and IRQ handling */ +INLINE void FM_STATUS_RESET(FM_ST *ST,int flag) +{ + /* reset status flag */ + ST->status &=~flag; + if ( (ST->irq) && !(ST->status & ST->irqmask) ) + { + ST->irq = 0; + /* callback user interrupt handler (IRQ is ON to OFF) */ + } +} + +/* IRQ mask set */ +INLINE void FM_IRQMASK_SET(FM_ST *ST,int flag) +{ + ST->irqmask = flag; + /* IRQ handling check */ + FM_STATUS_SET(ST,0); + FM_STATUS_RESET(ST,0); +} + +/* OPN Mode Register Write */ +INLINE void set_timers( FM_ST *ST, void *n, int v ) +{ + /* b7 = CSM MODE */ + /* b6 = 3 slot mode */ + /* b5 = reset b */ + /* b4 = reset a */ + /* b3 = timer enable b */ + /* b2 = timer enable a */ + /* b1 = load b */ + /* b0 = load a */ + ST->mode = v; + + /* reset Timer b flag */ + if( v & 0x20 ) + FM_STATUS_RESET(ST,0x02); + /* reset Timer a flag */ + if( v & 0x10 ) + FM_STATUS_RESET(ST,0x01); + /* load b */ + if( v & 0x02 ) + { + if( ST->TBC == 0 ) + { + ST->TBC = ( 256-ST->TB)<<4; + /* External timer handler */ + } + } + else + { /* stop timer b */ + if( ST->TBC != 0 ) + { + ST->TBC = 0; + } + } + /* load a */ + if( v & 0x01 ) + { + if( ST->TAC == 0 ) + { + ST->TAC = (1024-ST->TA); + /* External timer handler */ + } + } + else + { /* stop timer a */ + if( ST->TAC != 0 ) + { + ST->TAC = 0; + } + } +} + + +/* Timer A Overflow */ +INLINE void TimerAOver(FM_ST *ST) +{ + /* set status (if enabled) */ + if(ST->mode & 0x04) FM_STATUS_SET(ST,0x01); + /* clear or reload the counter */ + ST->TAC = (1024-ST->TA); +} +/* Timer B Overflow */ +INLINE void TimerBOver(FM_ST *ST) +{ + /* set status (if enabled) */ + if(ST->mode & 0x08) FM_STATUS_SET(ST,0x02); + /* clear or reload the counter */ + ST->TBC = ( 256-ST->TB)<<4; +} + + +#if FM_INTERNAL_TIMER +/* ----- internal timer mode , update timer */ + +/* ---------- calculate timer A ---------- */ + #define INTERNAL_TIMER_A(ST,CSM_CH) \ + { \ + if( (ST)->TAC && (1) ) \ + if( ((ST)->TAC -= (int)((ST)->freqbase*4096)) <= 0 ) \ + { \ + TimerAOver( ST ); \ + /* CSM mode total level latch and auto key on */ \ + if( (ST)->mode & 0x80 ) \ + CSMKeyControll( OPN->type, CSM_CH ); \ + } \ + } +/* ---------- calculate timer B ---------- */ + #define INTERNAL_TIMER_B(ST,step) \ + { \ + if( (ST)->TBC && (1) ) \ + if( ((ST)->TBC -= (int)((ST)->freqbase*4096*step)) <= 0 ) \ + TimerBOver( ST ); \ + } +#else /* FM_INTERNAL_TIMER */ +/* external timer mode */ +#define INTERNAL_TIMER_A(ST,CSM_CH) +#define INTERNAL_TIMER_B(ST,step) +#endif /* FM_INTERNAL_TIMER */ + + + +#if FM_BUSY_FLAG_SUPPORT +#define FM_BUSY_CLEAR(ST) ((ST)->busy_expiry_time = UNDEFINED_TIME) +INLINE UINT8 FM_STATUS_FLAG(FM_ST *ST) +{ + if( COMPARE_TIMES(ST->busy_expiry_time, UNDEFINED_TIME) != 0 ) + { + if (COMPARE_TIMES(ST->busy_expiry_time, FM_GET_TIME_NOW(ST->device->machine)) > 0) + return ST->status | 0x80; /* with busy */ + /* expire */ + FM_BUSY_CLEAR(ST); + } + return ST->status; +} +INLINE void FM_BUSY_SET(FM_ST *ST,int busyclock ) +{ + TIME_TYPE expiry_period = MULTIPLY_TIME_BY_INT(ATTOTIME_IN_HZ(ST->clock), busyclock * ST->timer_prescaler); + ST->busy_expiry_time = ADD_TIMES(FM_GET_TIME_NOW(ST->device->machine), expiry_period); +} +#else +#define FM_STATUS_FLAG(ST) ((ST)->status) +#define FM_BUSY_SET(ST,bclock) {} +#define FM_BUSY_CLEAR(ST) {} +#endif + + + + +INLINE void FM_KEYON(UINT8 type, FM_CH *CH , int s ) +{ + FM_SLOT *SLOT = &CH->SLOT[s]; + if( !SLOT->key ) + { + SLOT->key = 1; + SLOT->phase = 0; /* restart Phase Generator */ + SLOT->ssgn = (SLOT->ssg & 0x04) >> 1; + SLOT->state = EG_ATT; + } +} + +INLINE void FM_KEYOFF(FM_CH *CH , int s ) +{ + FM_SLOT *SLOT = &CH->SLOT[s]; + if( SLOT->key ) + { + SLOT->key = 0; + if (SLOT->state>EG_REL) + SLOT->state = EG_REL;/* phase -> Release */ + } +} + +/* set algorithm connection */ +static void setup_connection( FM_OPN *OPN, FM_CH *CH, int ch ) +{ + INT32 *carrier = &OPN->out_fm[ch]; + + INT32 **om1 = &CH->connect1; + INT32 **om2 = &CH->connect3; + INT32 **oc1 = &CH->connect2; + + INT32 **memc = &CH->mem_connect; + + switch( CH->ALGO ) + { + case 0: + /* M1---C1---MEM---M2---C2---OUT */ + *om1 = &OPN->c1; + *oc1 = &OPN->mem; + *om2 = &OPN->c2; + *memc= &OPN->m2; + break; + case 1: + /* M1------+-MEM---M2---C2---OUT */ + /* C1-+ */ + *om1 = &OPN->mem; + *oc1 = &OPN->mem; + *om2 = &OPN->c2; + *memc= &OPN->m2; + break; + case 2: + /* M1-----------------+-C2---OUT */ + /* C1---MEM---M2-+ */ + *om1 = &OPN->c2; + *oc1 = &OPN->mem; + *om2 = &OPN->c2; + *memc= &OPN->m2; + break; + case 3: + /* M1---C1---MEM------+-C2---OUT */ + /* M2-+ */ + *om1 = &OPN->c1; + *oc1 = &OPN->mem; + *om2 = &OPN->c2; + *memc= &OPN->c2; + break; + case 4: + /* M1---C1-+-OUT */ + /* M2---C2-+ */ + /* MEM: not used */ + *om1 = &OPN->c1; + *oc1 = carrier; + *om2 = &OPN->c2; + *memc= &OPN->mem; /* store it anywhere where it will not be used */ + break; + case 5: + /* +----C1----+ */ + /* M1-+-MEM---M2-+-OUT */ + /* +----C2----+ */ + *om1 = 0; /* special mark */ + *oc1 = carrier; + *om2 = carrier; + *memc= &OPN->m2; + break; + case 6: + /* M1---C1-+ */ + /* M2-+-OUT */ + /* C2-+ */ + /* MEM: not used */ + *om1 = &OPN->c1; + *oc1 = carrier; + *om2 = carrier; + *memc= &OPN->mem; /* store it anywhere where it will not be used */ + break; + case 7: + /* M1-+ */ + /* C1-+-OUT */ + /* M2-+ */ + /* C2-+ */ + /* MEM: not used*/ + *om1 = carrier; + *oc1 = carrier; + *om2 = carrier; + *memc= &OPN->mem; /* store it anywhere where it will not be used */ + break; + } + + CH->connect4 = carrier; +} + +/* set detune & multiple */ +INLINE void set_det_mul(FM_ST *ST,FM_CH *CH,FM_SLOT *SLOT,int v) +{ + SLOT->mul = (v&0x0f)? (v&0x0f)*2 : 1; + SLOT->DT = ST->dt_tab[(v>>4)&7]; + CH->SLOT[SLOT1].Incr=-1; +} + +/* set total level */ +INLINE void set_tl(FM_CH *CH,FM_SLOT *SLOT , int v) +{ + SLOT->tl = (v&0x7f)<<(ENV_BITS-7); /* 7bit TL */ +} + +/* set attack rate & key scale */ +INLINE void set_ar_ksr(UINT8 type, FM_CH *CH,FM_SLOT *SLOT,int v) +{ + UINT8 old_KSR = SLOT->KSR; + + SLOT->ar = (v&0x1f) ? 32 + ((v&0x1f)<<1) : 0; + + SLOT->KSR = 3-(v>>6); + if (SLOT->KSR != old_KSR) + { + CH->SLOT[SLOT1].Incr=-1; + } + + /* refresh Attack rate */ + if ((SLOT->ar + SLOT->ksr) < 32+62) + { + SLOT->eg_sh_ar = eg_rate_shift [SLOT->ar + SLOT->ksr ]; + SLOT->eg_sel_ar = eg_rate_select[SLOT->ar + SLOT->ksr ]; + } + else + { + SLOT->eg_sh_ar = 0; + SLOT->eg_sel_ar = 17*RATE_STEPS; + } +} + +/* set decay rate */ +INLINE void set_dr(UINT8 type, FM_SLOT *SLOT,int v) +{ + SLOT->d1r = (v&0x1f) ? 32 + ((v&0x1f)<<1) : 0; + + SLOT->eg_sh_d1r = eg_rate_shift [SLOT->d1r + SLOT->ksr]; + SLOT->eg_sel_d1r= eg_rate_select[SLOT->d1r + SLOT->ksr]; +} + +/* set sustain rate */ +INLINE void set_sr(UINT8 type, FM_SLOT *SLOT,int v) +{ + SLOT->d2r = (v&0x1f) ? 32 + ((v&0x1f)<<1) : 0; + + SLOT->eg_sh_d2r = eg_rate_shift [SLOT->d2r + SLOT->ksr]; + SLOT->eg_sel_d2r= eg_rate_select[SLOT->d2r + SLOT->ksr]; +} + +/* set release rate */ +INLINE void set_sl_rr(UINT8 type, FM_SLOT *SLOT,int v) +{ + SLOT->sl = sl_table[ v>>4 ]; + + SLOT->rr = 34 + ((v&0x0f)<<2); + + SLOT->eg_sh_rr = eg_rate_shift [SLOT->rr + SLOT->ksr]; + SLOT->eg_sel_rr = eg_rate_select[SLOT->rr + SLOT->ksr]; +} + + + +INLINE signed int op_calc(UINT32 phase, unsigned int env, signed int pm) +{ + UINT32 p; + + p = (env<<3) + sin_tab[ ( ((signed int)((phase & ~FREQ_MASK) + (pm<<15))) >> FREQ_SH ) & SIN_MASK ]; + + if (p >= TL_TAB_LEN) + return 0; + return tl_tab[p]; +} + +INLINE signed int op_calc1(UINT32 phase, unsigned int env, signed int pm) +{ + UINT32 p; + + p = (env<<3) + sin_tab[ ( ((signed int)((phase & ~FREQ_MASK) + pm )) >> FREQ_SH ) & SIN_MASK ]; + + if (p >= TL_TAB_LEN) + return 0; + return tl_tab[p]; +} + +/* advance LFO to next sample */ +INLINE void advance_lfo(FM_OPN *OPN) +{ + UINT8 pos; + + if (OPN->lfo_inc) /* LFO enabled ? */ + { + OPN->lfo_cnt += OPN->lfo_inc; + + pos = (OPN->lfo_cnt >> LFO_SH) & 127; + + + /* update AM when LFO output changes */ + + /* actually I can't optimize is this way without rewritting chan_calc() + to use chip->lfo_am instead of global lfo_am */ + { + + /* triangle */ + /* AM: 0 to 126 step +2, 126 to 0 step -2 */ + if (pos<64) + OPN->LFO_AM = (pos&63) * 2; + else + OPN->LFO_AM = 126 - ((pos&63) * 2); + } + + /* PM works with 4 times slower clock */ + pos >>= 2; + /* update PM when LFO output changes */ + /*if (prev_pos != pos)*/ /* can't use global lfo_pm for this optimization, must be chip->lfo_pm instead*/ + { + OPN->LFO_PM = pos; + } + + } + else + { + OPN->LFO_AM = 0; + OPN->LFO_PM = 0; + } +} + +/* changed from INLINE to static here to work around gcc 4.2.1 codegen bug */ +static void advance_eg_channel(FM_OPN *OPN, FM_SLOT *SLOT) +{ + unsigned int out; + unsigned int swap_flag = 0; + unsigned int i; + + + i = 4; /* four operators per channel */ + do + { + /* reset SSG-EG swap flag */ + swap_flag = 0; + + switch(SLOT->state) + { + case EG_ATT: /* attack phase */ + if ( !(OPN->eg_cnt & ((1<eg_sh_ar)-1) ) ) + { + SLOT->volume += (~SLOT->volume * + (eg_inc[SLOT->eg_sel_ar + ((OPN->eg_cnt>>SLOT->eg_sh_ar)&7)]) + ) >>4; + + if (SLOT->volume <= MIN_ATT_INDEX) + { + SLOT->volume = MIN_ATT_INDEX; + SLOT->state = EG_DEC; + } + } + break; + + case EG_DEC: /* decay phase */ + { + if (SLOT->ssg&0x08) /* SSG EG type envelope selected */ + { + if ( !(OPN->eg_cnt & ((1<eg_sh_d1r)-1) ) ) + { + SLOT->volume += 4 * eg_inc[SLOT->eg_sel_d1r + ((OPN->eg_cnt>>SLOT->eg_sh_d1r)&7)]; + + if ( SLOT->volume >= (INT32)(SLOT->sl) ) + SLOT->state = EG_SUS; + } + } + else + { + if ( !(OPN->eg_cnt & ((1<eg_sh_d1r)-1) ) ) + { + SLOT->volume += eg_inc[SLOT->eg_sel_d1r + ((OPN->eg_cnt>>SLOT->eg_sh_d1r)&7)]; + + if ( SLOT->volume >= (INT32)(SLOT->sl) ) + SLOT->state = EG_SUS; + } + } + } + break; + + case EG_SUS: /* sustain phase */ + if (SLOT->ssg&0x08) /* SSG EG type envelope selected */ + { + if ( !(OPN->eg_cnt & ((1<eg_sh_d2r)-1) ) ) + { + + SLOT->volume += 4 * eg_inc[SLOT->eg_sel_d2r + ((OPN->eg_cnt>>SLOT->eg_sh_d2r)&7)]; + + if ( SLOT->volume >= ENV_QUIET ) + { + SLOT->volume = MAX_ATT_INDEX; + + if (SLOT->ssg&0x01) /* bit 0 = hold */ + { + if (SLOT->ssgn&1) /* have we swapped once ??? */ + { + /* yes, so do nothing, just hold current level */ + } + else + swap_flag = (SLOT->ssg&0x02) | 1 ; /* bit 1 = alternate */ + + } + else + { + /* same as KEY-ON operation */ + + /* restart of the Phase Generator should be here */ + SLOT->phase = 0; + + { + /* phase -> Attack */ + SLOT->volume = 511; + SLOT->state = EG_ATT; + } + + swap_flag = (SLOT->ssg&0x02); /* bit 1 = alternate */ + } + } + } + } + else + { + if ( !(OPN->eg_cnt & ((1<eg_sh_d2r)-1) ) ) + { + SLOT->volume += eg_inc[SLOT->eg_sel_d2r + ((OPN->eg_cnt>>SLOT->eg_sh_d2r)&7)]; + + if ( SLOT->volume >= MAX_ATT_INDEX ) + { + SLOT->volume = MAX_ATT_INDEX; + /* do not change SLOT->state (verified on real chip) */ + } + } + + } + break; + + case EG_REL: /* release phase */ + if ( !(OPN->eg_cnt & ((1<eg_sh_rr)-1) ) ) + { + /* SSG-EG affects Release phase also (Nemesis) */ + SLOT->volume += eg_inc[SLOT->eg_sel_rr + ((OPN->eg_cnt>>SLOT->eg_sh_rr)&7)]; + + if ( SLOT->volume >= MAX_ATT_INDEX ) + { + SLOT->volume = MAX_ATT_INDEX; + SLOT->state = EG_OFF; + } + } + break; + + } + + + out = ((UINT32)SLOT->volume); + + /* negate output (changes come from alternate bit, init comes from attack bit) */ + if ((SLOT->ssg&0x08) && (SLOT->ssgn&2) && (SLOT->state > EG_REL)) + out ^= MAX_ATT_INDEX; + + /* we need to store the result here because we are going to change ssgn + in next instruction */ + SLOT->vol_out = out + SLOT->tl; + + /* reverse SLOT inversion flag */ + SLOT->ssgn ^= swap_flag; + + SLOT++; + i--; + }while (i); + +} + + + +#define volume_calc(OP) ((OP)->vol_out + (AM & (OP)->AMmask)) + +INLINE void update_phase_lfo_slot(FM_OPN *OPN, FM_SLOT *SLOT, INT32 pms, UINT32 block_fnum) +{ + UINT32 fnum_lfo = ((block_fnum & 0x7f0) >> 4) * 32 * 8; + INT32 lfo_fn_table_index_offset = lfo_pm_table[ fnum_lfo + pms + OPN->LFO_PM ]; + + if (lfo_fn_table_index_offset) /* LFO phase modulation active */ + { + UINT8 blk; + UINT32 fn; + int kc, fc; + + block_fnum = block_fnum*2 + lfo_fn_table_index_offset; + + blk = (block_fnum&0x7000) >> 12; + fn = block_fnum & 0xfff; + + /* keyscale code */ + kc = (blk<<2) | opn_fktable[fn >> 8]; + + /* phase increment counter */ + fc = (OPN->fn_table[fn]>>(7-blk)) + SLOT->DT[kc]; + + /* detects frequency overflow (credits to Nemesis) */ + if (fc < 0) fc += OPN->fn_max; + + /* update phase */ + SLOT->phase += (fc * SLOT->mul) >> 1; + } + else /* LFO phase modulation = zero */ + { + SLOT->phase += SLOT->Incr; + } +} + +INLINE void update_phase_lfo_channel(FM_OPN *OPN, FM_CH *CH) +{ + UINT32 block_fnum = CH->block_fnum; + + UINT32 fnum_lfo = ((block_fnum & 0x7f0) >> 4) * 32 * 8; + INT32 lfo_fn_table_index_offset = lfo_pm_table[ fnum_lfo + CH->pms + OPN->LFO_PM ]; + + if (lfo_fn_table_index_offset) /* LFO phase modulation active */ + { + UINT8 blk; + UINT32 fn; + int kc, fc, finc; + + block_fnum = block_fnum*2 + lfo_fn_table_index_offset; + + blk = (block_fnum&0x7000) >> 12; + fn = block_fnum & 0xfff; + + /* keyscale code */ + kc = (blk<<2) | opn_fktable[fn >> 8]; + + /* phase increment counter */ + fc = (OPN->fn_table[fn]>>(7-blk)); + + /* detects frequency overflow (credits to Nemesis) */ + finc = fc + CH->SLOT[SLOT1].DT[kc]; + + if (finc < 0) finc += OPN->fn_max; + CH->SLOT[SLOT1].phase += (finc*CH->SLOT[SLOT1].mul) >> 1; + + finc = fc + CH->SLOT[SLOT2].DT[kc]; + if (finc < 0) finc += OPN->fn_max; + CH->SLOT[SLOT2].phase += (finc*CH->SLOT[SLOT2].mul) >> 1; + + finc = fc + CH->SLOT[SLOT3].DT[kc]; + if (finc < 0) finc += OPN->fn_max; + CH->SLOT[SLOT3].phase += (finc*CH->SLOT[SLOT3].mul) >> 1; + + finc = fc + CH->SLOT[SLOT4].DT[kc]; + if (finc < 0) finc += OPN->fn_max; + CH->SLOT[SLOT4].phase += (finc*CH->SLOT[SLOT4].mul) >> 1; + } + else /* LFO phase modulation = zero */ + { + CH->SLOT[SLOT1].phase += CH->SLOT[SLOT1].Incr; + CH->SLOT[SLOT2].phase += CH->SLOT[SLOT2].Incr; + CH->SLOT[SLOT3].phase += CH->SLOT[SLOT3].Incr; + CH->SLOT[SLOT4].phase += CH->SLOT[SLOT4].Incr; + } +} + +INLINE void chan_calc(FM_OPN *OPN, FM_CH *CH, int chnum) +{ + unsigned int eg_out; + + UINT32 AM = OPN->LFO_AM >> CH->ams; + + if (CH->Muted) + return; + + + OPN->m2 = OPN->c1 = OPN->c2 = OPN->mem = 0; + + *CH->mem_connect = CH->mem_value; /* restore delayed sample (MEM) value to m2 or c2 */ + + eg_out = volume_calc(&CH->SLOT[SLOT1]); + { + INT32 out = CH->op1_out[0] + CH->op1_out[1]; + CH->op1_out[0] = CH->op1_out[1]; + + if( !CH->connect1 ) + { + /* algorithm 5 */ + OPN->mem = OPN->c1 = OPN->c2 = CH->op1_out[0]; + } + else + { + /* other algorithms */ + *CH->connect1 += CH->op1_out[0]; + } + + CH->op1_out[1] = 0; + if( eg_out < ENV_QUIET ) /* SLOT 1 */ + { + if (!CH->FB) + out=0; + + CH->op1_out[1] = op_calc1(CH->SLOT[SLOT1].phase, eg_out, (out<FB) ); + } + } + + eg_out = volume_calc(&CH->SLOT[SLOT3]); + if( eg_out < ENV_QUIET ) /* SLOT 3 */ + *CH->connect3 += op_calc(CH->SLOT[SLOT3].phase, eg_out, OPN->m2); + + eg_out = volume_calc(&CH->SLOT[SLOT2]); + if( eg_out < ENV_QUIET ) /* SLOT 2 */ + *CH->connect2 += op_calc(CH->SLOT[SLOT2].phase, eg_out, OPN->c1); + + eg_out = volume_calc(&CH->SLOT[SLOT4]); + if( eg_out < ENV_QUIET ) /* SLOT 4 */ + *CH->connect4 += op_calc(CH->SLOT[SLOT4].phase, eg_out, OPN->c2); + + + /* store current MEM */ + CH->mem_value = OPN->mem; + + /* update phase counters AFTER output calculations */ + if(CH->pms) + { + /* add support for 3 slot mode */ + if ((OPN->ST.mode & 0xC0) && (chnum == 2)) + { + update_phase_lfo_slot(OPN, &CH->SLOT[SLOT1], CH->pms, OPN->SL3.block_fnum[1]); + update_phase_lfo_slot(OPN, &CH->SLOT[SLOT2], CH->pms, OPN->SL3.block_fnum[2]); + update_phase_lfo_slot(OPN, &CH->SLOT[SLOT3], CH->pms, OPN->SL3.block_fnum[0]); + update_phase_lfo_slot(OPN, &CH->SLOT[SLOT4], CH->pms, CH->block_fnum); + } + else update_phase_lfo_channel(OPN, CH); + } + else /* no LFO phase modulation */ + { + CH->SLOT[SLOT1].phase += CH->SLOT[SLOT1].Incr; + CH->SLOT[SLOT2].phase += CH->SLOT[SLOT2].Incr; + CH->SLOT[SLOT3].phase += CH->SLOT[SLOT3].Incr; + CH->SLOT[SLOT4].phase += CH->SLOT[SLOT4].Incr; + } +} + +/* update phase increment and envelope generator */ +INLINE void refresh_fc_eg_slot(FM_OPN *OPN, FM_SLOT *SLOT , int fc , int kc ) +{ + int ksr = kc >> SLOT->KSR; + + fc += SLOT->DT[kc]; + + /* detects frequency overflow (credits to Nemesis) */ + if (fc < 0) fc += OPN->fn_max; + + /* (frequency) phase increment counter */ + SLOT->Incr = (fc * SLOT->mul) >> 1; + + if( SLOT->ksr != ksr ) + { + SLOT->ksr = ksr; + + /* calculate envelope generator rates */ + if ((SLOT->ar + SLOT->ksr) < 32+62) + { + SLOT->eg_sh_ar = eg_rate_shift [SLOT->ar + SLOT->ksr ]; + SLOT->eg_sel_ar = eg_rate_select[SLOT->ar + SLOT->ksr ]; + } + else + { + SLOT->eg_sh_ar = 0; + SLOT->eg_sel_ar = 17*RATE_STEPS; + } + + SLOT->eg_sh_d1r = eg_rate_shift [SLOT->d1r + SLOT->ksr]; + SLOT->eg_sh_d2r = eg_rate_shift [SLOT->d2r + SLOT->ksr]; + SLOT->eg_sh_rr = eg_rate_shift [SLOT->rr + SLOT->ksr]; + + SLOT->eg_sel_d1r= eg_rate_select[SLOT->d1r + SLOT->ksr]; + SLOT->eg_sel_d2r= eg_rate_select[SLOT->d2r + SLOT->ksr]; + SLOT->eg_sel_rr = eg_rate_select[SLOT->rr + SLOT->ksr]; + } +} + +/* update phase increment counters */ +/* Changed from INLINE to static to work around gcc 4.2.1 codegen bug */ +static void refresh_fc_eg_chan(FM_OPN *OPN, FM_CH *CH ) +{ + if( CH->SLOT[SLOT1].Incr==-1) + { + int fc = CH->fc; + int kc = CH->kcode; + refresh_fc_eg_slot(OPN, &CH->SLOT[SLOT1] , fc , kc ); + refresh_fc_eg_slot(OPN, &CH->SLOT[SLOT2] , fc , kc ); + refresh_fc_eg_slot(OPN, &CH->SLOT[SLOT3] , fc , kc ); + refresh_fc_eg_slot(OPN, &CH->SLOT[SLOT4] , fc , kc ); + } +} + +/* initialize time tables */ +static void init_timetables( FM_ST *ST , const UINT8 *dttable ) +{ + int i,d; + double rate; + +#if 0 + logerror("FM.C: samplerate=%8i chip clock=%8i freqbase=%f \n", + ST->rate, ST->clock, ST->freqbase ); +#endif + + /* DeTune table */ + for (d = 0;d <= 3;d++) + { + for (i = 0;i <= 31;i++) + { + rate = ((double)dttable[d*32 + i]) * SIN_LEN * ST->freqbase * (1<dt_tab[d][i] = (INT32) rate; + ST->dt_tab[d+4][i] = -ST->dt_tab[d][i]; +#if 0 + logerror("FM.C: DT [%2i %2i] = %8x \n", d, i, ST->dt_tab[d][i] ); +#endif + } + } + +} + + +static void reset_channels( FM_ST *ST , FM_CH *CH , int num ) +{ + int c,s; + + ST->mode = 0; /* normal mode */ + ST->TA = 0; + ST->TAC = 0; + ST->TB = 0; + ST->TBC = 0; + + for( c = 0 ; c < num ; c++ ) + { + //memset(&CH[c], 0x00, sizeof(FM_CH)); + CH[c].mem_value = 0; + CH[c].op1_out[0] = 0; + CH[c].op1_out[1] = 0; + CH[c].fc = 0; + for(s = 0 ; s < 4 ; s++ ) + { + //memset(&CH[c].SLOT[s], 0x00, sizeof(FM_SLOT)); + CH[c].SLOT[s].Incr = -1; + CH[c].SLOT[s].key = 0; + CH[c].SLOT[s].phase = 0; + CH[c].SLOT[s].ssg = 0; + CH[c].SLOT[s].ssgn = 0; + CH[c].SLOT[s].state= EG_OFF; + CH[c].SLOT[s].volume = MAX_ATT_INDEX; + CH[c].SLOT[s].vol_out= MAX_ATT_INDEX; + } + } +} + +/* initialize generic tables */ +static int init_tables(void) +{ + signed int i,x; + signed int n; + double o,m; + + for (x=0; x>= 4; /* 12 bits here */ + if (n&1) /* round to nearest */ + n = (n>>1)+1; + else + n = n>>1; + /* 11 bits here (rounded) */ + n <<= 2; /* 13 bits here (as in real chip) */ + tl_tab[ x*2 + 0 ] = n; + tl_tab[ x*2 + 1 ] = -tl_tab[ x*2 + 0 ]; + + for (i=1; i<13; i++) + { + tl_tab[ x*2+0 + i*2*TL_RES_LEN ] = tl_tab[ x*2+0 ]>>i; + tl_tab[ x*2+1 + i*2*TL_RES_LEN ] = -tl_tab[ x*2+0 + i*2*TL_RES_LEN ]; + } + #if 0 + logerror("tl %04i", x); + for (i=0; i<13; i++) + logerror(", [%02i] %4x", i*2, tl_tab[ x*2 /*+1*/ + i*2*TL_RES_LEN ]); + logerror("\n"); + #endif + } + /*logerror("FM.C: TL_TAB_LEN = %i elements (%i bytes)\n",TL_TAB_LEN, (int)sizeof(tl_tab));*/ + + + for (i=0; i0.0) + o = 8*log(1.0/m)/log(2.0); /* convert to 'decibels' */ + else + o = 8*log(-1.0/m)/log(2.0); /* convert to 'decibels' */ + + o = o / (ENV_STEP/4); + + n = (int)(2.0*o); + if (n&1) /* round to nearest */ + n = (n>>1)+1; + else + n = n>>1; + + sin_tab[ i ] = n*2 + (m>=0.0? 0: 1 ); + /*logerror("FM.C: sin [%4i]= %4i (tl_tab value=%5i)\n", i, sin_tab[i],tl_tab[sin_tab[i]]);*/ + } + + /*logerror("FM.C: ENV_QUIET= %08x\n",ENV_QUIET );*/ + + + /* build LFO PM modulation table */ + for(i = 0; i < 8; i++) /* 8 PM depths */ + { + UINT8 fnum; + for (fnum=0; fnum<128; fnum++) /* 7 bits meaningful of F-NUMBER */ + { + UINT8 value; + UINT8 step; + UINT32 offset_depth = i; + UINT32 offset_fnum_bit; + UINT32 bit_tmp; + + for (step=0; step<8; step++) + { + value = 0; + for (bit_tmp=0; bit_tmp<7; bit_tmp++) /* 7 bits */ + { + if (fnum & (1<SLOT[SLOT1].key) + { + FM_KEYON(type, CH,SLOT1); + FM_KEYOFF(CH, SLOT1); + } + if (!CH->SLOT[SLOT2].key) + { + FM_KEYON(type, CH,SLOT2); + FM_KEYOFF(CH, SLOT2); + } + if (!CH->SLOT[SLOT3].key) + { + FM_KEYON(type, CH,SLOT3); + FM_KEYOFF(CH, SLOT3); + } + if (!CH->SLOT[SLOT4].key) + { + FM_KEYON(type, CH,SLOT4); + FM_KEYOFF(CH, SLOT4); + } +} + +#ifdef __STATE_H__ +/* FM channel save , internal state only */ +static void FMsave_state_channel(const device_config *device,FM_CH *CH,int num_ch) +{ + int slot , ch; + + for(ch=0;chop1_out); + state_save_register_device_item(device, ch, CH->fc); + /* slots */ + for(slot=0;slot<4;slot++) + { + FM_SLOT *SLOT = &CH->SLOT[slot]; + state_save_register_device_item(device, ch * 4 + slot, SLOT->phase); + state_save_register_device_item(device, ch * 4 + slot, SLOT->state); + state_save_register_device_item(device, ch * 4 + slot, SLOT->volume); + } + } +} + +static void FMsave_state_st(const device_config *device,FM_ST *ST) +{ +#if FM_BUSY_FLAG_SUPPORT + state_save_register_device_item(device, 0, ST->busy_expiry_time.seconds ); + state_save_register_device_item(device, 0, ST->busy_expiry_time.attoseconds ); +#endif + state_save_register_device_item(device, 0, ST->address ); + state_save_register_device_item(device, 0, ST->irq ); + state_save_register_device_item(device, 0, ST->irqmask ); + state_save_register_device_item(device, 0, ST->status ); + state_save_register_device_item(device, 0, ST->mode ); + state_save_register_device_item(device, 0, ST->prescaler_sel ); + state_save_register_device_item(device, 0, ST->fn_h ); + state_save_register_device_item(device, 0, ST->TA ); + state_save_register_device_item(device, 0, ST->TAC ); + state_save_register_device_item(device, 0, ST->TB ); + state_save_register_device_item(device, 0, ST->TBC ); +} +#endif /* _STATE_H */ + +#if BUILD_OPN + + + +/* prescaler set (and make time tables) */ +static void OPNSetPres(FM_OPN *OPN, int pres, int timer_prescaler, int SSGpres) +{ + int i; + + /* frequency base */ + OPN->ST.freqbase = (OPN->ST.rate) ? ((double)OPN->ST.clock / OPN->ST.rate) / pres : 0; + +#if 0 + OPN->ST.rate = (double)OPN->ST.clock / pres; + OPN->ST.freqbase = 1.0; +#endif + + OPN->eg_timer_add = (1<ST.freqbase; + OPN->eg_timer_overflow = ( 3 ) * (1<ST.timer_prescaler = timer_prescaler; + + /* SSG part prescaler set */ + if( SSGpres ) (*OPN->ST.SSG->set_clock)( OPN->ST.param, OPN->ST.clock * 2 / SSGpres ); + + /* make time tables */ + init_timetables( &OPN->ST, dt_tab ); + + /* there are 2048 FNUMs that can be generated using FNUM/BLK registers + but LFO works with one more bit of a precision so we really need 4096 elements */ + /* calculate fnumber -> increment counter table */ + for(i = 0; i < 4096; i++) + { + /* freq table for octave 7 */ + /* OPN phase increment counter = 20bit */ + OPN->fn_table[i] = (UINT32)( (double)i * 32 * OPN->ST.freqbase * (1<<(FREQ_SH-10)) ); /* -10 because chip works with 10.10 fixed point, while we use 16.16 */ +#if 0 + logerror("FM.C: fn_table[%4i] = %08x (dec=%8i)\n", + i, OPN->fn_table[i]>>6,OPN->fn_table[i]>>6 ); +#endif + } + + /* maximal frequency is required for Phase overflow calculation, register size is 17 bits (Nemesis) */ + OPN->fn_max = (UINT32)( (double)0x20000 * OPN->ST.freqbase * (1<<(FREQ_SH-10)) ); + + /* LFO freq. table */ + for(i = 0; i < 8; i++) + { + /* Amplitude modulation: 64 output levels (triangle waveform); 1 level lasts for one of "lfo_samples_per_step" samples */ + /* Phase modulation: one entry from lfo_pm_output lasts for one of 4 * "lfo_samples_per_step" samples */ + OPN->lfo_freq[i] = (1.0 / lfo_samples_per_step[i]) * (1<ST.freqbase; +#if 0 + logerror("FM.C: lfo_freq[%i] = %08x (dec=%8i)\n", + i, OPN->lfo_freq[i],OPN->lfo_freq[i] ); +#endif + } +} + + + +/* write a OPN mode register 0x20-0x2f */ +static void OPNWriteMode(FM_OPN *OPN, int r, int v) +{ + UINT8 c; + FM_CH *CH; + + switch(r) + { + case 0x21: /* Test */ + break; + case 0x22: /* LFO FREQ (YM2608/YM2610/YM2610B/YM2612) */ + if( OPN->type & TYPE_LFOPAN ) + { + if (v&0x08) /* LFO enabled ? */ + { + OPN->lfo_inc = OPN->lfo_freq[v&7]; + } + else + { + OPN->lfo_inc = 0; + } + } + break; + case 0x24: /* timer A High 8*/ + OPN->ST.TA = (OPN->ST.TA & 0x03)|(((int)v)<<2); + break; + case 0x25: /* timer A Low 2*/ + OPN->ST.TA = (OPN->ST.TA & 0x3fc)|(v&3); + break; + case 0x26: /* timer B */ + OPN->ST.TB = v; + break; + case 0x27: /* mode, timer control */ + set_timers( &(OPN->ST),OPN->ST.param,v ); + break; + case 0x28: /* key on / off */ + c = v & 0x03; + if( c == 3 ) break; + if( (v&0x04) && (OPN->type & TYPE_6CH) ) c+=3; + CH = OPN->P_CH; + CH = &CH[c]; + if(v&0x10) FM_KEYON(OPN->type,CH,SLOT1); else FM_KEYOFF(CH,SLOT1); + if(v&0x20) FM_KEYON(OPN->type,CH,SLOT2); else FM_KEYOFF(CH,SLOT2); + if(v&0x40) FM_KEYON(OPN->type,CH,SLOT3); else FM_KEYOFF(CH,SLOT3); + if(v&0x80) FM_KEYON(OPN->type,CH,SLOT4); else FM_KEYOFF(CH,SLOT4); + break; + } +} + +/* write a OPN register (0x30-0xff) */ +static void OPNWriteReg(FM_OPN *OPN, int r, int v) +{ + FM_CH *CH; + FM_SLOT *SLOT; + + UINT8 c = OPN_CHAN(r); + + if (c == 3) return; /* 0xX3,0xX7,0xXB,0xXF */ + + if (r >= 0x100) c+=3; + + CH = OPN->P_CH; + CH = &CH[c]; + + SLOT = &(CH->SLOT[OPN_SLOT(r)]); + + switch( r & 0xf0 ) + { + case 0x30: /* DET , MUL */ + set_det_mul(&OPN->ST,CH,SLOT,v); + break; + + case 0x40: /* TL */ + set_tl(CH,SLOT,v); + break; + + case 0x50: /* KS, AR */ + set_ar_ksr(OPN->type,CH,SLOT,v); + break; + + case 0x60: /* bit7 = AM ENABLE, DR */ + set_dr(OPN->type, SLOT,v); + + if(OPN->type & TYPE_LFOPAN) /* YM2608/2610/2610B/2612 */ + { + SLOT->AMmask = (v&0x80) ? ~0 : 0; + } + break; + + case 0x70: /* SR */ + set_sr(OPN->type,SLOT,v); + break; + + case 0x80: /* SL, RR */ + set_sl_rr(OPN->type,SLOT,v); + break; + + case 0x90: /* SSG-EG */ + SLOT->ssg = v&0x0f; + SLOT->ssgn = (v&0x04)>>1; /* bit 1 in ssgn = attack */ + + /* SSG-EG envelope shapes : + + E AtAlH + 1 0 0 0 \\\\ + + 1 0 0 1 \___ + + 1 0 1 0 \/\/ + ___ + 1 0 1 1 \ + + 1 1 0 0 //// + ___ + 1 1 0 1 / + + 1 1 1 0 /\/\ + + 1 1 1 1 /___ + + + E = SSG-EG enable + + + The shapes are generated using Attack, Decay and Sustain phases. + + Each single character in the diagrams above represents this whole + sequence: + + - when KEY-ON = 1, normal Attack phase is generated (*without* any + difference when compared to normal mode), + + - later, when envelope level reaches minimum level (max volume), + the EG switches to Decay phase (which works with bigger steps + when compared to normal mode - see below), + + - later when envelope level passes the SL level, + the EG swithes to Sustain phase (which works with bigger steps + when compared to normal mode - see below), + + - finally when envelope level reaches maximum level (min volume), + the EG switches to Attack phase again (depends on actual waveform). + + Important is that when switch to Attack phase occurs, the phase counter + of that operator will be zeroed-out (as in normal KEY-ON) but not always. + (I havent found the rule for that - perhaps only when the output level is low) + + The difference (when compared to normal Envelope Generator mode) is + that the resolution in Decay and Sustain phases is 4 times lower; + this results in only 256 steps instead of normal 1024. + In other words: + when SSG-EG is disabled, the step inside of the EG is one, + when SSG-EG is enabled, the step is four (in Decay and Sustain phases). + + Times between the level changes are the same in both modes. + + + Important: + Decay 1 Level (so called SL) is compared to actual SSG-EG output, so + it is the same in both SSG and no-SSG modes, with this exception: + + when the SSG-EG is enabled and is generating raising levels + (when the EG output is inverted) the SL will be found at wrong level !!! + For example, when SL=02: + 0 -6 = -6dB in non-inverted EG output + 96-6 = -90dB in inverted EG output + Which means that EG compares its level to SL as usual, and that the + output is simply inverted afterall. + + + The Yamaha's manuals say that AR should be set to 0x1f (max speed). + That is not necessary, but then EG will be generating Attack phase. + + */ + + + break; + + case 0xa0: + switch( OPN_SLOT(r) ) + { + case 0: /* 0xa0-0xa2 : FNUM1 */ + { + UINT32 fn = (((UINT32)( (OPN->ST.fn_h)&7))<<8) + v; + UINT8 blk = OPN->ST.fn_h>>3; + /* keyscale code */ + CH->kcode = (blk<<2) | opn_fktable[fn >> 7]; + /* phase increment counter */ + CH->fc = OPN->fn_table[fn*2]>>(7-blk); + + /* store fnum in clear form for LFO PM calculations */ + CH->block_fnum = (blk<<11) | fn; + + CH->SLOT[SLOT1].Incr=-1; + } + break; + case 1: /* 0xa4-0xa6 : FNUM2,BLK */ + OPN->ST.fn_h = v&0x3f; + break; + case 2: /* 0xa8-0xaa : 3CH FNUM1 */ + if(r < 0x100) + { + UINT32 fn = (((UINT32)(OPN->SL3.fn_h&7))<<8) + v; + UINT8 blk = OPN->SL3.fn_h>>3; + /* keyscale code */ + OPN->SL3.kcode[c]= (blk<<2) | opn_fktable[fn >> 7]; + /* phase increment counter */ + OPN->SL3.fc[c] = OPN->fn_table[fn*2]>>(7-blk); + OPN->SL3.block_fnum[c] = (blk<<11) | fn; + (OPN->P_CH)[2].SLOT[SLOT1].Incr=-1; + } + break; + case 3: /* 0xac-0xae : 3CH FNUM2,BLK */ + if(r < 0x100) + OPN->SL3.fn_h = v&0x3f; + break; + } + break; + + case 0xb0: + switch( OPN_SLOT(r) ) + { + case 0: /* 0xb0-0xb2 : FB,ALGO */ + { + int feedback = (v>>3)&7; + CH->ALGO = v&7; + CH->FB = feedback ? feedback+6 : 0; + setup_connection( OPN, CH, c ); + } + break; + case 1: /* 0xb4-0xb6 : L , R , AMS , PMS (YM2612/YM2610B/YM2610/YM2608) */ + if( OPN->type & TYPE_LFOPAN) + { + /* b0-2 PMS */ + CH->pms = (v & 7) * 32; /* CH->pms = PM depth * 32 (index in lfo_pm_table) */ + + /* b4-5 AMS */ + CH->ams = lfo_ams_depth_shift[(v>>4) & 0x03]; + + /* PAN : b7 = L, b6 = R */ + OPN->pan[ c*2 ] = (v & 0x80) ? ~0 : 0; + OPN->pan[ c*2+1 ] = (v & 0x40) ? ~0 : 0; + + } + break; + } + break; + } +} + +#endif /* BUILD_OPN */ + +#if BUILD_OPN_PRESCALER +/* + prescaler circuit (best guess to verified chip behaviour) + + +--------------+ +-sel2-+ + | +--|in20 | + +---+ | +-sel1-+ | | +M-CLK -+-|1/2|-+--|in10 | +---+ | out|--INT_CLOCK + | +---+ | out|-|1/3|-|in21 | + +----------|in11 | +---+ +------+ + +------+ + +reg.2d : sel2 = in21 (select sel2) +reg.2e : sel1 = in11 (select sel1) +reg.2f : sel1 = in10 , sel2 = in20 (clear selector) +reset : sel1 = in11 , sel2 = in21 (clear both) + +*/ +static void OPNPrescaler_w(FM_OPN *OPN , int addr, int pre_divider) +{ + static const int opn_pres[4] = { 2*12 , 2*12 , 6*12 , 3*12 }; + static const int ssg_pres[4] = { 1 , 1 , 4 , 2 }; + int sel; + + switch(addr) + { + case 0: /* when reset */ + OPN->ST.prescaler_sel = 2; + break; + case 1: /* when postload */ + break; + case 0x2d: /* divider sel : select 1/1 for 1/3line */ + OPN->ST.prescaler_sel |= 0x02; + break; + case 0x2e: /* divider sel , select 1/3line for output */ + OPN->ST.prescaler_sel |= 0x01; + break; + case 0x2f: /* divider sel , clear both selector to 1/2,1/2 */ + OPN->ST.prescaler_sel = 0; + break; + } + sel = OPN->ST.prescaler_sel & 3; + /* update prescaler */ + OPNSetPres( OPN, opn_pres[sel]*pre_divider, + opn_pres[sel]*pre_divider, + ssg_pres[sel]*pre_divider ); +} +#endif /* BUILD_OPN_PRESCALER */ + +#if BUILD_YM2203 +/*****************************************************************************/ +/* YM2203 local section */ +/*****************************************************************************/ + +/* here's the virtual YM2203(OPN) */ +typedef struct +{ + UINT8 REGS[256]; /* registers */ + FM_OPN OPN; /* OPN state */ + FM_CH CH[3]; /* channel state */ +} YM2203; + +/* Generate samples for one of the YM2203s */ +void ym2203_update_one(void *chip, FMSAMPLE **buffer, int length) +{ + YM2203 *F2203 = (YM2203 *)chip; + FM_OPN *OPN = &F2203->OPN; + int i; + FMSAMPLE *bufL = buffer[0]; + FMSAMPLE *bufR = buffer[1]; + FM_CH *cch[3]; + + cch[0] = &F2203->CH[0]; + cch[1] = &F2203->CH[1]; + cch[2] = &F2203->CH[2]; + + + /* refresh PG and EG */ + refresh_fc_eg_chan( OPN, cch[0] ); + refresh_fc_eg_chan( OPN, cch[1] ); + if( (F2203->OPN.ST.mode & 0xc0) ) + { + /* 3SLOT MODE */ + if( cch[2]->SLOT[SLOT1].Incr==-1) + { + refresh_fc_eg_slot(OPN, &cch[2]->SLOT[SLOT1] , OPN->SL3.fc[1] , OPN->SL3.kcode[1] ); + refresh_fc_eg_slot(OPN, &cch[2]->SLOT[SLOT2] , OPN->SL3.fc[2] , OPN->SL3.kcode[2] ); + refresh_fc_eg_slot(OPN, &cch[2]->SLOT[SLOT3] , OPN->SL3.fc[0] , OPN->SL3.kcode[0] ); + refresh_fc_eg_slot(OPN, &cch[2]->SLOT[SLOT4] , cch[2]->fc , cch[2]->kcode ); + } + } + else + refresh_fc_eg_chan( OPN, cch[2] ); + + + /* YM2203 doesn't have LFO so we must keep these globals at 0 level */ + OPN->LFO_AM = 0; + OPN->LFO_PM = 0; + + /* buffering */ + for (i=0; i < length ; i++) + { + /* clear outputs */ + OPN->out_fm[0] = 0; + OPN->out_fm[1] = 0; + OPN->out_fm[2] = 0; + + /* advance envelope generator */ + OPN->eg_timer += OPN->eg_timer_add; + while (OPN->eg_timer >= OPN->eg_timer_overflow) + { + OPN->eg_timer -= OPN->eg_timer_overflow; + OPN->eg_cnt++; + + advance_eg_channel(OPN, &cch[0]->SLOT[SLOT1]); + advance_eg_channel(OPN, &cch[1]->SLOT[SLOT1]); + advance_eg_channel(OPN, &cch[2]->SLOT[SLOT1]); + } + + /* calculate FM */ + chan_calc(OPN, cch[0], 0 ); + chan_calc(OPN, cch[1], 1 ); + chan_calc(OPN, cch[2], 2 ); + + /* buffering */ + { + int lt; + + lt = OPN->out_fm[0] + OPN->out_fm[1] + OPN->out_fm[2]; + + lt >>= FINAL_SH; + + //Limit( lt , MAXOUT, MINOUT ); + + #ifdef SAVE_SAMPLE + SAVE_ALL_CHANNELS + #endif + + /* buffering */ + bufL[i] = lt; + bufR[i] = lt; + } + + /* timer A control */ + INTERNAL_TIMER_A( &F2203->OPN.ST , cch[2] ) + } + INTERNAL_TIMER_B(&F2203->OPN.ST,length) +} + +/* ---------- reset one of chip ---------- */ +void ym2203_reset_chip(void *chip) +{ + int i; + YM2203 *F2203 = (YM2203 *)chip; + FM_OPN *OPN = &F2203->OPN; + + /* Reset Prescaler */ + OPNPrescaler_w(OPN, 0 , 1 ); + /* reset SSG section */ + (*OPN->ST.SSG->reset)(OPN->ST.param); + /* status clear */ + FM_IRQMASK_SET(&OPN->ST,0x03); + FM_BUSY_CLEAR(&OPN->ST); + OPNWriteMode(OPN,0x27,0x30); /* mode 0 , timer reset */ + + OPN->eg_timer = 0; + OPN->eg_cnt = 0; + + FM_STATUS_RESET(&OPN->ST, 0xff); + + reset_channels( &OPN->ST , F2203->CH , 3 ); + /* reset OPerator paramater */ + for(i = 0xb2 ; i >= 0x30 ; i-- ) OPNWriteReg(OPN,i,0); + for(i = 0x26 ; i >= 0x20 ; i-- ) OPNWriteReg(OPN,i,0); +} + +#ifdef __STATE_H__ +static void YM2203_save_state(YM2203 *F2203, const device_config *device) +{ + state_save_register_device_item_array(device, 0, F2203->REGS); + FMsave_state_st(device,&F2203->OPN.ST); + FMsave_state_channel(device,F2203->CH,3); + /* 3slots */ + state_save_register_device_item_array (device, 0, F2203->OPN.SL3.fc); + state_save_register_device_item (device, 0, F2203->OPN.SL3.fn_h); + state_save_register_device_item_array (device, 0, F2203->OPN.SL3.kcode); +} +#endif /* _STATE_H */ + +/* ---------- Initialize YM2203 emulator(s) ---------- + 'num' is the number of virtual YM2203s to allocate + 'clock' is the chip clock in Hz + 'rate' is sampling rate +*/ +//void * ym2203_init(void *param, const device_config *device, int clock, int rate, +// FM_TIMERHANDLER timer_handler,FM_IRQHANDLER IRQHandler, const ssg_callbacks *ssg) +void * ym2203_init(void *param, int clock, int rate, const ssg_callbacks *ssg) +{ + YM2203 *F2203; + + /* allocate ym2203 state space */ + if( (F2203 = (YM2203 *)malloc(sizeof(YM2203)))==NULL) + return NULL; + /* clear */ + memset(F2203,0,sizeof(YM2203)); + + if( !init_tables() ) + { + free( F2203 ); + return NULL; + } + + F2203->OPN.ST.param = param; + F2203->OPN.type = TYPE_YM2203; + F2203->OPN.P_CH = F2203->CH; + //F2203->OPN.ST.device = device; + F2203->OPN.ST.clock = clock; + F2203->OPN.ST.rate = rate; + + F2203->OPN.ST.SSG = ssg; + +#ifdef __STATE_H__ + YM2203_save_state(F2203, device); +#endif + return F2203; +} + +/* shut down emulator */ +void ym2203_shutdown(void *chip) +{ + YM2203 *FM2203 = (YM2203 *)chip; + + FMCloseTable(); + free(FM2203); +} + +/* YM2203 I/O interface */ +int ym2203_write(void *chip,int a,UINT8 v) +{ + YM2203 *F2203 = (YM2203 *)chip; + FM_OPN *OPN = &F2203->OPN; + + if( !(a&1) ) + { /* address port */ + OPN->ST.address = (v &= 0xff); + + /* Write register to SSG emulator */ + if( v < 16 ) (*OPN->ST.SSG->write)(OPN->ST.param,0,v); + + /* prescaler select : 2d,2e,2f */ + if( v >= 0x2d && v <= 0x2f ) + OPNPrescaler_w(OPN , v , 1); + } + else + { /* data port */ + int addr = OPN->ST.address; + F2203->REGS[addr] = v; + switch( addr & 0xf0 ) + { + case 0x00: /* 0x00-0x0f : SSG section */ + /* Write data to SSG emulator */ + (*OPN->ST.SSG->write)(OPN->ST.param,a,v); + break; + case 0x20: /* 0x20-0x2f : Mode section */ + /* write register */ + OPNWriteMode(OPN,addr,v); + break; + default: /* 0x30-0xff : OPN section */ + /* write register */ + OPNWriteReg(OPN,addr,v); + } + FM_BUSY_SET(&OPN->ST,1); + } + return OPN->ST.irq; +} + +UINT8 ym2203_read(void *chip,int a) +{ + YM2203 *F2203 = (YM2203 *)chip; + int addr = F2203->OPN.ST.address; + UINT8 ret = 0; + + if( !(a&1) ) + { /* status port */ + ret = FM_STATUS_FLAG(&F2203->OPN.ST); + } + else + { /* data port (only SSG) */ + if( addr < 16 ) ret = (*F2203->OPN.ST.SSG->read)(F2203->OPN.ST.param); + } + return ret; +} + +int ym2203_timer_over(void *chip,int c) +{ + YM2203 *F2203 = (YM2203 *)chip; + + if( c ) + { /* Timer B */ + TimerBOver( &(F2203->OPN.ST) ); + } + else + { /* Timer A */ + /* timer update */ + TimerAOver( &(F2203->OPN.ST) ); + /* CSM mode key,TL control */ + if( F2203->OPN.ST.mode & 0x80 ) + { /* CSM mode auto key on */ + CSMKeyControll( F2203->OPN.type, &(F2203->CH[2]) ); + } + } + return F2203->OPN.ST.irq; +} + +void ym2203_set_mutemask(void *chip, UINT32 MuteMask) +{ + YM2203 *F2203 = (YM2203 *)chip; + UINT8 CurChn; + + for (CurChn = 0; CurChn < 3; CurChn ++) + F2203->CH[CurChn].Muted = (MuteMask >> CurChn) & 0x01; + + return; +} +#endif /* BUILD_YM2203 */ + + + +#if (BUILD_YM2608||BUILD_YM2610||BUILD_YM2610B) + +/* ADPCM type A channel struct */ +typedef struct +{ + UINT8 flag; /* port state */ + UINT8 flagMask; /* arrived flag mask */ + UINT8 now_data; /* current ROM data */ + UINT32 now_addr; /* current ROM address */ + UINT32 now_step; + UINT32 step; + UINT32 start; /* sample data start address*/ + UINT32 end; /* sample data end address */ + UINT8 IL; /* Instrument Level */ + INT32 adpcm_acc; /* accumulator */ + INT32 adpcm_step; /* step */ + INT32 adpcm_out; /* (speedup) hiro-shi!! */ + INT8 vol_mul; /* volume in "0.75dB" steps */ + UINT8 vol_shift; /* volume in "-6dB" steps */ + INT32 *pan; /* &out_adpcm[OPN_xxxx] */ + UINT8 Muted; +} ADPCM_CH; + +/* here's the virtual YM2610 */ +typedef struct +{ + UINT8 REGS[512]; /* registers */ + FM_OPN OPN; /* OPN state */ + FM_CH CH[6]; /* channel state */ + UINT8 addr_A1; /* address line A1 */ + + /* ADPCM-A unit */ + //const UINT8 *pcmbuf; /* pcm rom buffer */ + UINT8 *pcmbuf; /* pcm rom buffer */ + UINT32 pcm_size; /* size of pcm rom */ + UINT8 adpcmTL; /* adpcmA total level */ + ADPCM_CH adpcm[6]; /* adpcm channels */ + UINT32 adpcmreg[0x30]; /* registers */ + UINT8 adpcm_arrivedEndAddress; + YM_DELTAT deltaT; /* Delta-T ADPCM unit */ + UINT8 MuteDeltaT; + + UINT8 flagmask; /* YM2608 only */ + UINT8 irqmask; /* YM2608 only */ +} YM2610; + +/* here is the virtual YM2608 */ +typedef YM2610 YM2608; + + +/**** YM2610 ADPCM defines ****/ +#define ADPCM_SHIFT (16) /* frequency step rate */ +#define ADPCMA_ADDRESS_SHIFT 8 /* adpcm A address shift */ + +/* Algorithm and tables verified on real YM2608 and YM2610 */ + +/* usual ADPCM table (16 * 1.1^N) */ +static const int steps[49] = +{ + 16, 17, 19, 21, 23, 25, 28, + 31, 34, 37, 41, 45, 50, 55, + 60, 66, 73, 80, 88, 97, 107, + 118, 130, 143, 157, 173, 190, 209, + 230, 253, 279, 307, 337, 371, 408, + 449, 494, 544, 598, 658, 724, 796, + 876, 963, 1060, 1166, 1282, 1411, 1552 +}; + +/* different from the usual ADPCM table */ +static const int step_inc[8] = { -1*16, -1*16, -1*16, -1*16, 2*16, 5*16, 7*16, 9*16 }; + +/* speedup purposes only */ +static int jedi_table[ 49*16 ]; + + +static void Init_ADPCMATable(void) +{ + int step, nib; + + for (step = 0; step < 49; step++) + { + /* loop over all nibbles and compute the difference */ + for (nib = 0; nib < 16; nib++) + { + int value = (2*(nib & 0x07) + 1) * steps[step] / 8; + jedi_table[step*16 + nib] = (nib&0x08) ? -value : value; + } + } +} + +/* ADPCM A (Non control type) : calculate one channel output */ +INLINE void ADPCMA_calc_chan( YM2610 *F2610, ADPCM_CH *ch ) +{ + UINT32 step; + UINT8 data; + + if (ch->Muted) + return; + + + ch->now_step += ch->step; + if ( ch->now_step >= (1<now_step >> ADPCM_SHIFT; + ch->now_step &= (1< instead of == */ + /* YM2610 checks lower 20 bits only, the 4 MSB bits are sample bank */ + /* Here we use 1<<21 to compensate for nibble calculations */ + + if ( (ch->now_addr & ((1<<21)-1)) == ((ch->end<<1) & ((1<<21)-1)) ) + { + ch->flag = 0; + F2610->adpcm_arrivedEndAddress |= ch->flagMask; + return; + } +#if 0 + if ( ch->now_addr > (F2610->pcmsizeA<<1) ) + { +#ifdef _DEBUG + LOG(LOG_WAR,("YM2610: Attempting to play past adpcm rom size!\n" )); +#endif + return; + } +#endif + if ( ch->now_addr&1 ) + data = ch->now_data & 0x0f; + else + { + ch->now_data = *(F2610->pcmbuf+(ch->now_addr>>1)); + data = (ch->now_data >> 4) & 0x0f; + } + + ch->now_addr++; + + ch->adpcm_acc += jedi_table[ch->adpcm_step + data]; + + /* extend 12-bit signed int */ + if (ch->adpcm_acc & ~0x7ff) + ch->adpcm_acc |= ~0xfff; + else + ch->adpcm_acc &= 0xfff; + + ch->adpcm_step += step_inc[data & 7]; + Limit( ch->adpcm_step, 48*16, 0*16 ); + + }while(--step); + + /* calc pcm * volume data */ + ch->adpcm_out = ((ch->adpcm_acc * ch->vol_mul) >> ch->vol_shift) & ~3; /* multiply, shift and mask out 2 LSB bits */ + } + + /* output for work of output channels (out_adpcm[OPNxxxx])*/ + *(ch->pan) += ch->adpcm_out; +} + +/* ADPCM type A Write */ +static void FM_ADPCMAWrite(YM2610 *F2610,int r,int v) +{ + ADPCM_CH *adpcm = F2610->adpcm; + UINT8 c = r&0x07; + + F2610->adpcmreg[r] = v&0xff; /* stock data */ + switch( r ) + { + case 0x00: /* DM,--,C5,C4,C3,C2,C1,C0 */ + if( !(v&0x80) ) + { + /* KEY ON */ + for( c = 0; c < 6; c++ ) + { + if( (v>>c)&1 ) + { + /**** start adpcm ****/ + adpcm[c].step = (UINT32)((float)(1<OPN.ST.freqbase)/3.0); + adpcm[c].now_addr = adpcm[c].start<<1; + adpcm[c].now_step = 0; + adpcm[c].adpcm_acc = 0; + adpcm[c].adpcm_step= 0; + adpcm[c].adpcm_out = 0; + adpcm[c].flag = 1; + + if(F2610->pcmbuf==NULL) + { /* Check ROM Mapped */ +#ifdef _DEBUG + logerror("YM2608-YM2610: ADPCM-A rom not mapped\n"); +#endif + adpcm[c].flag = 0; + } + else + { + if(adpcm[c].end >= F2610->pcm_size) + { /* Check End in Range */ +#ifdef _DEBUG + logerror("YM2610: ADPCM-A end out of range: $%08x\n",adpcm[c].end); +#endif + /*adpcm[c].end = F2610->pcm_size-1;*/ /* JB: DO NOT uncomment this, otherwise you will break the comparison in the ADPCM_CALC_CHA() */ + } + if(adpcm[c].start >= F2610->pcm_size) /* Check Start in Range */ + { +#ifdef _DEBUG + logerror("YM2608-YM2610: ADPCM-A start out of range: $%08x\n",adpcm[c].start); +#endif + adpcm[c].flag = 0; + } + } + } + } + } + else + { + /* KEY OFF */ + for( c = 0; c < 6; c++ ) + if( (v>>c)&1 ) + adpcm[c].flag = 0; + } + break; + case 0x01: /* B0-5 = TL */ + F2610->adpcmTL = (v & 0x3f) ^ 0x3f; + for( c = 0; c < 6; c++ ) + { + int volume = F2610->adpcmTL + adpcm[c].IL; + + if ( volume >= 63 ) /* This is correct, 63 = quiet */ + { + adpcm[c].vol_mul = 0; + adpcm[c].vol_shift = 0; + } + else + { + adpcm[c].vol_mul = 15 - (volume & 7); /* so called 0.75 dB */ + adpcm[c].vol_shift = 1 + (volume >> 3); /* Yamaha engineers used the approximation: each -6 dB is close to divide by two (shift right) */ + } + + /* calc pcm * volume data */ + adpcm[c].adpcm_out = ((adpcm[c].adpcm_acc * adpcm[c].vol_mul) >> adpcm[c].vol_shift) & ~3; /* multiply, shift and mask out low 2 bits */ + } + break; + default: + c = r&0x07; + if( c >= 0x06 ) return; + switch( r&0x38 ) + { + case 0x08: /* B7=L,B6=R, B4-0=IL */ + { + int volume; + + adpcm[c].IL = (v & 0x1f) ^ 0x1f; + + volume = F2610->adpcmTL + adpcm[c].IL; + + if ( volume >= 63 ) /* This is correct, 63 = quiet */ + { + adpcm[c].vol_mul = 0; + adpcm[c].vol_shift = 0; + } + else + { + adpcm[c].vol_mul = 15 - (volume & 7); /* so called 0.75 dB */ + adpcm[c].vol_shift = 1 + (volume >> 3); /* Yamaha engineers used the approximation: each -6 dB is close to divide by two (shift right) */ + } + + adpcm[c].pan = &F2610->OPN.out_adpcm[(v>>6)&0x03]; + + /* calc pcm * volume data */ + adpcm[c].adpcm_out = ((adpcm[c].adpcm_acc * adpcm[c].vol_mul) >> adpcm[c].vol_shift) & ~3; /* multiply, shift and mask out low 2 bits */ + } + break; + case 0x10: + case 0x18: + adpcm[c].start = ( (F2610->adpcmreg[0x18 + c]*0x0100 | F2610->adpcmreg[0x10 + c]) << ADPCMA_ADDRESS_SHIFT); + break; + case 0x20: + case 0x28: + adpcm[c].end = ( (F2610->adpcmreg[0x28 + c]*0x0100 | F2610->adpcmreg[0x20 + c]) << ADPCMA_ADDRESS_SHIFT); + adpcm[c].end += (1<flag); + state_save_register_device_item(device, ch, adpcm->now_data); + state_save_register_device_item(device, ch, adpcm->now_addr); + state_save_register_device_item(device, ch, adpcm->now_step); + state_save_register_device_item(device, ch, adpcm->adpcm_acc); + state_save_register_device_item(device, ch, adpcm->adpcm_step); + state_save_register_device_item(device, ch, adpcm->adpcm_out); + } +} +#endif /* _STATE_H */ + +#endif /* (BUILD_YM2608||BUILD_YM2610||BUILD_YM2610B) */ + + +#if BUILD_YM2608 +/*****************************************************************************/ +/* YM2608 local section */ +/*****************************************************************************/ + + + +static const unsigned int YM2608_ADPCM_ROM_addr[2*6] = { +0x0000, 0x01bf, /* bass drum */ +0x01c0, 0x043f, /* snare drum */ +0x0440, 0x1b7f, /* top cymbal */ +0x1b80, 0x1cff, /* high hat */ +0x1d00, 0x1f7f, /* tom tom */ +0x1f80, 0x1fff /* rim shot */ +}; + + +/* + This data is derived from the chip's output - internal ROM can't be read. + It was verified, using real YM2608, that this ADPCM stream produces 100% correct output signal. +*/ + +static const unsigned char YM2608_ADPCM_ROM[0x2000] = { + +/* Source: 01BD.ROM */ +/* Length: 448 / 0x000001C0 */ + +0x88,0x08,0x08,0x08,0x00,0x88,0x16,0x76,0x99,0xB8,0x22,0x3A,0x84,0x3C,0xB1,0x54, +0x10,0xA9,0x98,0x32,0x80,0x33,0x9A,0xA7,0x4A,0xB4,0x58,0xBC,0x15,0x29,0x8A,0x97, +0x9B,0x44,0xAC,0x80,0x12,0xDE,0x13,0x1B,0xC0,0x58,0xC8,0x11,0x0A,0xA2,0x1A,0xA0, +0x00,0x98,0x0B,0x93,0x9E,0x92,0x0A,0x88,0xBE,0x14,0x1B,0x98,0x08,0xA1,0x4A,0xC1, +0x30,0xD9,0x33,0x98,0x10,0x89,0x17,0x1A,0x82,0x29,0x37,0x0C,0x83,0x50,0x9A,0x24, +0x1A,0x83,0x10,0x23,0x19,0xB3,0x72,0x8A,0x16,0x10,0x0A,0x93,0x70,0x99,0x23,0x99, +0x02,0x20,0x91,0x18,0x02,0x41,0xAB,0x24,0x18,0x81,0x99,0x4A,0xE8,0x28,0x9A,0x99, +0xA1,0x2F,0xA8,0x9D,0x90,0x08,0xCC,0xA3,0x1D,0xCA,0x82,0x0B,0xD8,0x08,0xB9,0x09, +0xBC,0xB8,0x00,0xBE,0x90,0x1B,0xCA,0x00,0x9B,0x8A,0xA8,0x91,0x0F,0xB3,0x3D,0xB8, +0x31,0x0B,0xA5,0x0A,0x11,0xA1,0x48,0x92,0x10,0x50,0x91,0x30,0x23,0x09,0x37,0x39, +0xA2,0x72,0x89,0x92,0x30,0x83,0x1C,0x96,0x28,0xB9,0x24,0x8C,0xA1,0x31,0xAD,0xA9, +0x13,0x9C,0xBA,0xA8,0x0B,0xBF,0xB8,0x9B,0xCA,0x88,0xDB,0xB8,0x19,0xFC,0x92,0x0A, +0xBA,0x89,0xAB,0xB8,0xAB,0xD8,0x08,0xAD,0xBA,0x33,0x9D,0xAA,0x83,0x3A,0xC0,0x40, +0xB9,0x15,0x39,0xA2,0x52,0x89,0x02,0x63,0x88,0x13,0x23,0x03,0x52,0x02,0x54,0x00, +0x11,0x23,0x23,0x35,0x20,0x01,0x44,0x41,0x80,0x24,0x40,0xA9,0x45,0x19,0x81,0x12, +0x81,0x02,0x11,0x21,0x19,0x02,0x61,0x8A,0x13,0x3A,0x10,0x12,0x23,0x8B,0x37,0x18, +0x91,0x24,0x10,0x81,0x34,0x20,0x05,0x32,0x82,0x53,0x20,0x14,0x33,0x31,0x34,0x52, +0x00,0x43,0x32,0x13,0x52,0x22,0x13,0x52,0x11,0x43,0x11,0x32,0x32,0x32,0x22,0x02, +0x13,0x12,0x89,0x22,0x19,0x81,0x81,0x08,0xA8,0x08,0x8B,0x90,0x1B,0xBA,0x8A,0x9B, +0xB9,0x89,0xCA,0xB9,0xAB,0xCA,0x9B,0xCA,0xB9,0xAB,0xDA,0x99,0xAC,0xBB,0x9B,0xAC, +0xAA,0xBA,0xAC,0xAB,0x9A,0xAA,0xAA,0xBA,0xB8,0xA9,0xBA,0x99,0xA9,0x9A,0xA0,0x8A, +0xA9,0x08,0x8A,0xA9,0x00,0x99,0x89,0x88,0x98,0x08,0x99,0x00,0x89,0x80,0x08,0x98, +0x00,0x88,0x88,0x80,0x90,0x80,0x90,0x80,0x81,0x99,0x08,0x88,0x99,0x09,0x00,0x1A, +0xA8,0x10,0x9A,0x88,0x08,0x0A,0x8A,0x89,0x99,0xA8,0x98,0xA9,0x99,0x99,0xA9,0x99, +0xAA,0x8A,0xAA,0x9B,0x8A,0x9A,0xA9,0x9A,0xBA,0x99,0x9A,0xAA,0x99,0x89,0xA9,0x99, +0x98,0x9A,0x98,0x88,0x09,0x89,0x09,0x08,0x08,0x09,0x18,0x18,0x00,0x12,0x00,0x11, +0x11,0x11,0x12,0x12,0x21,0x21,0x22,0x22,0x22,0x22,0x22,0x22,0x32,0x31,0x32,0x31, +0x32,0x32,0x21,0x31,0x21,0x32,0x21,0x12,0x00,0x80,0x80,0x80,0x80,0x80,0x80,0x80, + +/* Source: 02SD.ROM */ +/* Length: 640 / 0x00000280 */ + +0x0A,0xDC,0x14,0x0B,0xBA,0xBC,0x01,0x0F,0xF5,0x2F,0x87,0x19,0xC9,0x24,0x1B,0xA1, +0x31,0x99,0x90,0x32,0x32,0xFE,0x83,0x48,0xA8,0xA9,0x23,0x19,0xBC,0x91,0x02,0x41, +0xDE,0x81,0x28,0xA8,0x0A,0xB1,0x72,0xDA,0x23,0xBC,0x04,0x19,0xB8,0x21,0x8A,0x03, +0x29,0xBA,0x14,0x21,0x0B,0xC0,0x43,0x08,0x91,0x50,0x93,0x0F,0x86,0x1A,0x91,0x18, +0x21,0xCB,0x27,0x0A,0xA1,0x42,0x8C,0xA9,0x21,0x10,0x08,0xAB,0x94,0x2A,0xDA,0x02, +0x8B,0x91,0x09,0x98,0xAE,0x80,0xA9,0x02,0x0A,0xE9,0x21,0xBB,0x15,0x20,0xBE,0x92, +0x42,0x09,0xA9,0x11,0x34,0x08,0x12,0x0A,0x27,0x29,0xA1,0x52,0x12,0x8E,0x92,0x28, +0x92,0x2B,0xD1,0x23,0xBF,0x81,0x10,0x99,0xA8,0x0A,0xC4,0x3B,0xB9,0xB0,0x00,0x62, +0xCF,0x92,0x29,0x92,0x2B,0xB1,0x1C,0xB2,0x72,0xAA,0x88,0x11,0x18,0x80,0x13,0x9E, +0x03,0x18,0xB0,0x60,0xA1,0x28,0x88,0x08,0x04,0x10,0x8F,0x96,0x19,0x90,0x01,0x09, +0xC8,0x50,0x91,0x8A,0x01,0xAB,0x03,0x50,0xBA,0x9D,0x93,0x68,0xBA,0x80,0x22,0xCB, +0x41,0xBC,0x92,0x60,0xB9,0x1A,0x95,0x4A,0xC8,0x20,0x88,0x33,0xAC,0x92,0x38,0x83, +0x09,0x80,0x16,0x09,0x29,0xD0,0x54,0x8C,0xA2,0x28,0x91,0x89,0x93,0x60,0xCD,0x85, +0x1B,0xA1,0x49,0x90,0x8A,0x80,0x34,0x0C,0xC9,0x14,0x19,0x98,0xA0,0x40,0xA9,0x21, +0xD9,0x34,0x0A,0xA9,0x10,0x23,0xCB,0x25,0xAA,0x25,0x9B,0x13,0xCD,0x16,0x09,0xA0, +0x80,0x01,0x19,0x90,0x88,0x21,0xAC,0x33,0x8B,0xD8,0x27,0x3B,0xB8,0x81,0x31,0x80, +0xAF,0x97,0x0A,0x82,0x0A,0xA0,0x21,0x89,0x8A,0xA2,0x32,0x8D,0xBB,0x87,0x19,0x21, +0xC9,0xBC,0x45,0x09,0x90,0x09,0xA1,0x24,0x1A,0xD0,0x10,0x08,0x11,0xA9,0x21,0xE8, +0x60,0xA9,0x14,0x0C,0xD1,0x32,0xAB,0x04,0x0C,0x81,0x90,0x29,0x83,0x9B,0x01,0x8F, +0x97,0x0B,0x82,0x18,0x88,0xBA,0x06,0x39,0xC8,0x23,0xBC,0x04,0x09,0x92,0x08,0x1A, +0xBB,0x74,0x8C,0x81,0x18,0x81,0x9D,0x83,0x41,0xCD,0x81,0x40,0x9A,0x90,0x10,0x12, +0x9C,0xA1,0x68,0xD8,0x33,0x9C,0x91,0x01,0x12,0xBE,0x02,0x09,0x12,0x99,0x9A,0x36, +0x0A,0xB0,0x30,0x88,0xA3,0x2D,0x12,0xBC,0x03,0x3A,0x11,0xBD,0x08,0xC8,0x62,0x80, +0x8B,0xD8,0x23,0x38,0xF9,0x12,0x08,0x99,0x91,0x21,0x99,0x85,0x2F,0xB2,0x30,0x90, +0x88,0xD9,0x53,0xAC,0x82,0x19,0x91,0x20,0xCC,0x96,0x29,0xC9,0x24,0x89,0x80,0x99, +0x12,0x08,0x18,0x88,0x99,0x23,0xAB,0x73,0xCB,0x33,0x9F,0x04,0x2B,0xB1,0x08,0x03, +0x1B,0xC9,0x21,0x32,0xFA,0x33,0xDB,0x02,0x33,0xAE,0xB9,0x54,0x8B,0xA1,0x20,0x89, +0x90,0x11,0x88,0x09,0x98,0x23,0xBE,0x37,0x8D,0x81,0x20,0xAA,0x34,0xBB,0x13,0x18, +0xB9,0x40,0xB1,0x18,0x83,0x8E,0xB2,0x72,0xBC,0x82,0x30,0xA9,0x9A,0x24,0x8B,0x27, +0x0E,0x91,0x20,0x90,0x08,0xB0,0x32,0xB9,0x21,0xB0,0xAC,0x45,0x9A,0xA1,0x50,0xA9, +0x80,0x0A,0x26,0x9B,0x11,0xBB,0x23,0x71,0xCB,0x12,0x10,0xB8,0x40,0xA9,0xA5,0x39, +0xC0,0x30,0xB2,0x20,0xAA,0xBA,0x76,0x1C,0xC1,0x48,0x98,0x80,0x18,0x81,0xAA,0x23, +0x9C,0xA2,0x32,0xAC,0x9A,0x43,0x9C,0x12,0xAD,0x82,0x72,0xBC,0x00,0x82,0x39,0xD1, +0x3A,0xB8,0x35,0x9B,0x10,0x40,0xF9,0x22,0x0A,0xC0,0x51,0xB9,0x82,0x18,0x98,0xA3, +0x79,0xD0,0x20,0x88,0x09,0x01,0x99,0x82,0x11,0x38,0xFC,0x33,0x09,0xC8,0x40,0xA9, +0x11,0x29,0xAA,0x94,0x3A,0xC2,0x4A,0xC0,0x89,0x52,0xBC,0x11,0x08,0x09,0xB8,0x71, +0xA9,0x08,0xA8,0x62,0x8D,0x92,0x10,0x00,0x9E,0x94,0x38,0xBA,0x13,0x88,0x90,0x4A, +0xE2,0x30,0xBA,0x02,0x00,0x19,0xD9,0x62,0xBB,0x04,0x0B,0xA3,0x68,0xB9,0x21,0x88, +0x9D,0x04,0x10,0x8C,0xC8,0x62,0x99,0xAA,0x24,0x1A,0x80,0x9A,0x14,0x9B,0x26,0x8C, +0x92,0x30,0xB9,0x09,0xA3,0x71,0xBB,0x10,0x19,0x82,0x39,0xDB,0x02,0x44,0x9F,0x10, + +/* Source: 04TOP.ROM */ +/* Length: 5952 / 0x00001740 */ + +0x07,0xFF,0x7C,0x3C,0x31,0xC6,0xC4,0xBB,0x7F,0x7F,0x7B,0x82,0x8A,0x4D,0x5F,0x7C, +0x3E,0x44,0xD2,0xB3,0xA0,0x19,0x1B,0x6C,0x81,0x28,0xC4,0xA1,0x1C,0x4B,0x18,0x00, +0x2A,0xA2,0x0A,0x7C,0x2A,0x00,0x01,0x89,0x98,0x48,0x8A,0x3C,0x28,0x2A,0x5B,0x3E, +0x3A,0x1A,0x3B,0x3D,0x4B,0x3B,0x4A,0x08,0x2A,0x1A,0x2C,0x4A,0x3B,0x82,0x99,0x3C, +0x5D,0x29,0x2B,0x39,0x0B,0x23,0xAB,0x1A,0x4C,0x79,0xA3,0x01,0xC1,0x2A,0x0A,0x38, +0xA7,0xB9,0x12,0x1F,0x29,0x08,0x82,0xA1,0x08,0xA9,0x42,0xAA,0x95,0xB3,0x90,0x81, +0x09,0xD4,0x1A,0x80,0x1B,0x07,0xB8,0x12,0x8E,0x49,0x81,0x92,0xD3,0x90,0xA1,0x2A, +0x02,0xE1,0xA3,0x99,0x02,0xB3,0x94,0xB3,0xB0,0xF4,0x98,0x93,0x90,0x13,0xE1,0x81, +0x99,0x38,0x91,0xA6,0xD3,0x99,0x94,0xC1,0x83,0xB1,0x92,0x98,0x49,0xC4,0xB2,0xA4, +0xA3,0xD0,0x1A,0x30,0xBA,0x59,0x02,0xD4,0xA0,0xA4,0xA2,0x8A,0x01,0x00,0xB7,0xA8, +0x18,0x2A,0x2B,0x1E,0x23,0xC8,0x1A,0x00,0x39,0xA0,0x18,0x92,0x4F,0x2D,0x5A,0x10, +0x89,0x81,0x2A,0x8B,0x6A,0x02,0x09,0xB3,0x8D,0x48,0x1B,0x80,0x19,0x34,0xF8,0x29, +0x0A,0x7B,0x2A,0x28,0x81,0x0C,0x02,0x1E,0x29,0x09,0x12,0xC2,0x94,0xE1,0x18,0x98, +0x02,0xC4,0x89,0x91,0x1A,0x20,0xA9,0x02,0x1B,0x48,0x8E,0x20,0x88,0x2D,0x08,0x59, +0x1B,0x02,0xA3,0xB1,0x8A,0x1E,0x58,0x80,0xC2,0xB6,0x88,0x91,0x88,0x11,0xA1,0xA3, +0xE2,0x01,0xB0,0x19,0x11,0x09,0xF4,0x88,0x09,0x88,0x19,0x89,0x12,0xF1,0x2A,0x28, +0x8C,0x25,0x99,0xA4,0x98,0x39,0xA1,0x00,0xD0,0x58,0xAA,0x59,0x01,0x0C,0x00,0x2B, +0x00,0x08,0x89,0x6B,0x69,0x90,0x01,0x90,0x98,0x12,0xB3,0xF3,0xA0,0x89,0x02,0x3B, +0x0C,0x50,0xA9,0x4E,0x6B,0x19,0x28,0x09,0xA2,0x08,0x2F,0x20,0x88,0x92,0x8A,0x11, +0xC4,0x93,0xF1,0x18,0x88,0x11,0xF2,0x80,0x92,0xA8,0x02,0xA8,0xB7,0xB3,0xA3,0xA0, +0x88,0x1A,0x40,0xE2,0x91,0x19,0x88,0x18,0x91,0x83,0xC1,0xB5,0x92,0xA9,0xC6,0x90, +0x01,0xC2,0x81,0x98,0x03,0xF0,0x00,0x2C,0x2A,0x92,0x2C,0x83,0x1F,0x3A,0x29,0x00, +0xB8,0x70,0xAB,0x69,0x18,0x89,0x10,0x0D,0x12,0x0B,0x88,0x4A,0x3A,0x9B,0x70,0xA8, +0x28,0x2F,0x2A,0x3A,0x1B,0x85,0x88,0x8B,0x6A,0x29,0x00,0x91,0x91,0x1B,0x7C,0x29, +0x01,0x88,0x90,0x19,0x2B,0x2B,0x00,0x39,0xA8,0x5E,0x21,0x89,0x91,0x09,0x3A,0x6F, +0x2A,0x18,0x18,0x8B,0x50,0x89,0x2B,0x19,0x49,0x88,0x29,0xF5,0x89,0x08,0x09,0x12, +0xAA,0x15,0xB0,0x82,0xAC,0x38,0x00,0x3F,0x81,0x10,0xB0,0x49,0xA2,0x81,0x3A,0xC8, +0x87,0x90,0xC4,0xA3,0x99,0x19,0x83,0xE1,0x84,0xE2,0xA2,0x90,0x80,0x93,0xB5,0xC4, +0xB3,0xA1,0x0A,0x18,0x92,0xC4,0xA0,0x93,0x0C,0x3A,0x18,0x01,0x1E,0x20,0xB1,0x82, +0x8C,0x03,0xB5,0x2E,0x82,0x19,0xB2,0x1B,0x1B,0x6B,0x4C,0x19,0x12,0x8B,0x5A,0x11, +0x0C,0x3A,0x2C,0x18,0x3D,0x08,0x2A,0x5C,0x18,0x00,0x88,0x3D,0x29,0x80,0x2A,0x09, +0x00,0x7A,0x0A,0x10,0x0B,0x69,0x98,0x10,0x81,0x3F,0x00,0x18,0x19,0x91,0xB7,0x9A, +0x28,0x8A,0x48,0x92,0xF3,0xA2,0x88,0x98,0x87,0xA1,0x88,0x80,0x81,0x95,0xD1,0xA3, +0x1B,0x1C,0x39,0x10,0xA1,0x2A,0x0B,0x7A,0x4B,0x80,0x13,0xC1,0xD1,0x2B,0x2A,0x85, +0xB2,0xA2,0x93,0xB2,0xD3,0x80,0xD1,0x18,0x08,0x08,0xB7,0x98,0x81,0x3F,0x01,0x88, +0x01,0xE2,0x00,0x9A,0x59,0x08,0x10,0xC3,0x99,0x84,0xA9,0xA5,0x91,0x91,0x91,0x80, +0xB5,0x94,0xC0,0x01,0x98,0x09,0x84,0xB0,0x80,0x7A,0x08,0x18,0x90,0xA8,0x6A,0x1C, +0x39,0x2A,0xB7,0x98,0x19,0x10,0x2A,0xA1,0x10,0xBD,0x39,0x18,0x2D,0x39,0x3F,0x10, +0x3F,0x01,0x09,0x19,0x0A,0x38,0x8C,0x40,0xB3,0xB4,0x93,0xAD,0x20,0x2B,0xD4,0x81, +0xC3,0xB0,0x39,0xA0,0x23,0xD8,0x04,0xB1,0x9B,0xA7,0x1A,0x92,0x08,0xA5,0x88,0x81, +0xE2,0x01,0xB8,0x01,0x81,0xC1,0xC7,0x90,0x92,0x80,0xA1,0x97,0xA0,0xA2,0x82,0xB8, +0x18,0x00,0x9C,0x78,0x98,0x83,0x0B,0x0B,0x32,0x7D,0x19,0x10,0xA1,0x19,0x09,0x0A, +0x78,0xA8,0x10,0x1B,0x29,0x29,0x1A,0x14,0x2F,0x88,0x4A,0x1B,0x10,0x10,0xAB,0x79, +0x0D,0x49,0x18,0xA0,0x02,0x1F,0x19,0x3A,0x2B,0x11,0x8A,0x88,0x79,0x8A,0x20,0x49, +0x9B,0x58,0x0B,0x28,0x18,0xA9,0x3A,0x7D,0x00,0x29,0x88,0x82,0x3D,0x1A,0x38,0xBA, +0x15,0x09,0xAA,0x51,0x8B,0x83,0x3C,0x8A,0x58,0x1B,0xB5,0x01,0xBB,0x50,0x19,0x99, +0x24,0xCA,0x21,0x1B,0xA2,0x87,0xA8,0xB1,0x68,0xA1,0xA6,0xA2,0xA8,0x29,0x8B,0x24, +0xB4,0xE2,0x92,0x8A,0x00,0x19,0x93,0xB5,0xB4,0xB1,0x81,0xB1,0x03,0x9A,0x82,0xA7, +0x90,0xD6,0xA0,0x80,0x1B,0x29,0x01,0xA4,0xE1,0x18,0x0A,0x2A,0x29,0x92,0xC7,0xA8, +0x81,0x19,0x89,0x30,0x10,0xE0,0x30,0xB8,0x10,0x0C,0x1A,0x79,0x1B,0xA7,0x80,0xA0, +0x00,0x0B,0x28,0x18,0xB1,0x85,0x1E,0x00,0x20,0xA9,0x18,0x18,0x1C,0x13,0xBC,0x15, +0x99,0x2E,0x12,0x00,0xE1,0x00,0x0B,0x3B,0x21,0x90,0x06,0xC9,0x2A,0x49,0x0A,0x18, +0x20,0xD1,0x3C,0x08,0x00,0x83,0xC9,0x41,0x8E,0x18,0x08,0x02,0xA0,0x09,0xA4,0x7B, +0x90,0x19,0x2A,0x10,0x2A,0xA8,0x71,0xBA,0x10,0x4A,0x0E,0x22,0xB2,0xB2,0x1B,0x8C, +0x78,0x1A,0xB5,0x93,0xA9,0x1B,0x49,0x19,0x29,0xA3,0xC6,0x88,0xAA,0x32,0x0D,0x1B, +0x22,0x08,0xC2,0x18,0xB9,0x79,0x3F,0x01,0x10,0xA9,0x84,0x1C,0x09,0x21,0xB0,0xA7, +0x0A,0x99,0x50,0x0C,0x81,0x28,0x8B,0x48,0x2E,0x00,0x08,0x99,0x38,0x5B,0x88,0x14, +0xA9,0x08,0x11,0xAA,0x72,0xC1,0xB3,0x09,0x8A,0x05,0x91,0xF2,0x81,0xA1,0x09,0x02, +0xF2,0x92,0x99,0x1A,0x49,0x80,0xC5,0x90,0x90,0x18,0x09,0x12,0xA1,0xF2,0x81,0x98, +0xC6,0x91,0xA0,0x11,0xA0,0x94,0xB4,0xF2,0x81,0x8B,0x03,0x80,0xD2,0x93,0xA8,0x88, +0x69,0xA0,0x03,0xB8,0x88,0x32,0xBC,0x97,0x80,0xB1,0x3B,0x1A,0xA6,0x00,0xD1,0x01, +0x0B,0x3B,0x30,0x9B,0x31,0x3E,0x92,0x19,0x8A,0xD3,0x5C,0x1B,0x41,0xA0,0x93,0xA2, +0xAF,0x39,0x4C,0x01,0x92,0xA8,0x81,0x3C,0x0D,0x78,0x98,0x00,0x19,0x0A,0x20,0x2D, +0x29,0x3C,0x1B,0x48,0x88,0x99,0x7A,0x2D,0x29,0x2A,0x82,0x80,0xA8,0x49,0x3E,0x19, +0x11,0x98,0x82,0x9A,0x3B,0x28,0x2F,0x20,0x4C,0x90,0x29,0x19,0x9A,0x7A,0x29,0x28, +0x98,0x88,0x33,0xCD,0x11,0x3A,0xC1,0xA4,0xA0,0xC4,0x82,0xC8,0x50,0x98,0xB2,0x21, +0xC0,0xB6,0x98,0x82,0x80,0x9C,0x23,0x00,0xF8,0x30,0xA8,0x1A,0x68,0xA8,0x86,0x9A, +0x01,0x2A,0x0A,0x97,0x91,0xC1,0x18,0x89,0x02,0x83,0xE0,0x01,0x8B,0x29,0x30,0xE2, +0x91,0x0B,0x18,0x3B,0x1C,0x11,0x28,0xAC,0x78,0x80,0x93,0x91,0xA9,0x49,0x8B,0x87, +0x90,0x99,0x3D,0x5A,0x81,0x08,0xA1,0x11,0x2F,0x1A,0x21,0x9B,0x15,0xA2,0xB0,0x11, +0xC0,0x91,0x5B,0x98,0x24,0xA2,0xF2,0x92,0x8B,0x6A,0x18,0x81,0xB5,0xB1,0x88,0x4C, +0x00,0x00,0xA4,0xC1,0x2B,0x1A,0x59,0x0A,0x02,0x80,0x1E,0x02,0x08,0xB3,0x80,0x9A, +0x23,0xB8,0xF2,0x84,0xAB,0x01,0x48,0x90,0xA7,0x90,0x0A,0x29,0x09,0x95,0x99,0xA0, +0x59,0x2B,0x00,0x97,0xB0,0x29,0x89,0x2A,0x03,0xD0,0xB7,0x1B,0x81,0x00,0xA6,0xB1, +0x90,0x09,0x48,0xC0,0x11,0x00,0x8A,0x00,0x5B,0x83,0x9A,0x18,0x2F,0x3C,0x18,0x11, +0xA9,0x04,0x1A,0x4F,0x01,0x98,0x81,0x09,0x09,0x4A,0x18,0xB4,0xA2,0x0B,0x59,0x90, +0x3B,0x49,0xBC,0x40,0x6A,0x88,0x3A,0x08,0x3E,0x3A,0x80,0x93,0xB0,0xE1,0x5A,0x00, +0xA4,0xB3,0xE3,0x90,0x0D,0x38,0x09,0x82,0xC4,0xA1,0xB1,0x4C,0x18,0x10,0x91,0xB2, +0x13,0xEA,0x34,0x99,0x88,0xA6,0x89,0x92,0x91,0xC1,0x20,0xB2,0xC2,0x86,0xD2,0xB3, +0x80,0xB2,0x08,0x09,0x87,0x91,0xC0,0x11,0x89,0x90,0x28,0xB9,0x79,0x19,0xA4,0x82, +0xD0,0x03,0x0C,0xA3,0xA5,0xB2,0xB2,0x1B,0x29,0x13,0xF1,0xB4,0x81,0x9D,0x38,0x00, +0xC4,0xA1,0x89,0x59,0x1A,0x81,0xA4,0xA9,0x1C,0x6A,0x19,0x02,0xB1,0x1A,0x4A,0x0B, +0x78,0x89,0x81,0x1C,0x2A,0x29,0x4A,0xA3,0x3E,0x1C,0x49,0x1A,0x08,0x21,0xAE,0x28, +0x4B,0x19,0x20,0x8C,0x10,0x3A,0xAB,0x26,0x8B,0x18,0x59,0x99,0x13,0xA2,0xAB,0x79, +0x2F,0x18,0x10,0xB2,0x80,0x1B,0x4D,0x5A,0x80,0x82,0x98,0x81,0x80,0x09,0xA5,0x90, +0x91,0x03,0xC2,0xE2,0x81,0xA8,0x82,0x09,0xC6,0xA3,0xB1,0x08,0x5B,0x08,0x05,0xD1, +0xA2,0x89,0x2A,0x28,0x91,0xA6,0x88,0xB0,0x49,0x80,0x09,0x08,0x88,0x07,0xB8,0x05, +0x99,0x81,0x88,0x18,0xE2,0x00,0xC3,0x18,0x0D,0x10,0x30,0xD0,0x93,0x8A,0x09,0x10, +0x2F,0x11,0x90,0xA1,0x20,0x9B,0xB1,0x73,0xC8,0x94,0x98,0x3B,0x01,0x0C,0x30,0x19, +0xF8,0x12,0x90,0xBA,0x78,0x0A,0x11,0x98,0xA0,0x79,0x8A,0x30,0x2B,0xC2,0x11,0x0D, +0x09,0x7A,0x00,0x82,0xB9,0x01,0x7A,0x89,0x21,0x09,0xA1,0x0A,0x7C,0x10,0x88,0xB5, +0x88,0x0A,0x2B,0x69,0x1A,0x10,0xA0,0x5B,0x19,0x1A,0x10,0x19,0x1A,0x6C,0x20,0x90, +0xA5,0x98,0x1B,0x0A,0x69,0x82,0xD1,0x18,0x09,0x19,0x2A,0x93,0xD4,0x9A,0x01,0x49, +0xA2,0xA2,0x82,0xD8,0x22,0xAA,0x97,0xA9,0x2D,0x38,0x2A,0xB6,0x80,0x90,0x0A,0x3C, +0x82,0x94,0xB8,0x21,0x0E,0x2A,0x22,0xB8,0x00,0x4F,0x2B,0x3A,0x81,0xA1,0x29,0x2C, +0x6A,0x13,0xD1,0xA2,0x98,0x28,0x0C,0x01,0xD5,0x08,0xA9,0x31,0xB3,0xB0,0xA7,0xB0, +0x29,0x1B,0x87,0xA2,0xA1,0xB2,0x4A,0x89,0x11,0xC3,0xF3,0x98,0x08,0x03,0xA0,0xA3, +0xC5,0x90,0xB3,0xB5,0xB4,0xB8,0x02,0x91,0x91,0xD3,0xA4,0xC1,0x1B,0x82,0x28,0xA4, +0xD1,0x94,0x8A,0x28,0x08,0x03,0xE0,0x80,0xD4,0x90,0x91,0xA1,0x3B,0x3D,0x02,0xE4, +0xA1,0x92,0x89,0x1A,0x4B,0x95,0xB3,0x90,0x99,0x6A,0x0A,0x30,0xA1,0x93,0xA6,0xA9, +0x85,0x8B,0x82,0x10,0xB1,0xA3,0x94,0xF8,0x38,0x9A,0x30,0x1A,0x8B,0xA7,0x89,0x01, +0x5B,0x19,0x18,0x11,0xF0,0x18,0x1C,0x39,0x19,0x0C,0x12,0x1C,0x2A,0x7B,0x3A,0x88, +0x2B,0x18,0x2B,0x5C,0x20,0x92,0x8D,0x38,0x8A,0x3A,0x5B,0x2E,0x3A,0x2B,0x10,0x12, +0xBB,0x6A,0x4D,0x18,0x10,0xB1,0x81,0x2A,0x8B,0x79,0x80,0x01,0x0A,0x09,0x5B,0x2D, +0x84,0x8A,0x08,0x02,0xA2,0x91,0x82,0xE8,0x50,0x9B,0x85,0xA3,0xB0,0xA3,0x1B,0x02, +0x18,0xF3,0xA2,0x88,0xAB,0x53,0xD1,0xB4,0xA3,0x09,0x09,0x18,0xD4,0x08,0xB0,0x09, +0x58,0xD1,0x82,0x89,0x81,0x1A,0x18,0x05,0xB9,0xC3,0x30,0xC0,0x95,0x80,0xC3,0x89, +0x89,0x13,0x88,0xF2,0x93,0x0E,0x18,0x01,0x92,0xA5,0xB8,0x2A,0x39,0xAA,0x33,0x9A, +0xB1,0x11,0xF5,0xA1,0xA1,0x0A,0x50,0xB8,0x03,0xC4,0xA0,0x4E,0x29,0x10,0x88,0xC2, +0x1A,0x39,0x1D,0x28,0x98,0x94,0x0E,0x10,0x2A,0x3C,0x02,0x2D,0x1B,0x4B,0x3B,0x49, +0x19,0xA9,0x48,0x2F,0x29,0x10,0x89,0x02,0x0C,0x10,0x09,0xB9,0x70,0x1B,0x8A,0x50, +0xA8,0x2B,0x49,0x89,0x69,0x88,0x95,0x89,0x90,0x92,0x4C,0x19,0x82,0xC1,0x01,0x80, +0xA0,0x2B,0x7A,0x81,0x10,0xC2,0xB7,0x98,0x88,0x19,0x2C,0x03,0xB1,0xA4,0xA1,0x0C, +0x3B,0x78,0x88,0x85,0xB1,0xA0,0x1B,0x3A,0x4A,0x08,0x94,0x81,0xF1,0x80,0x00,0x0C, +0x59,0x09,0x18,0x90,0xA6,0x92,0x8C,0x1A,0x79,0x92,0xA8,0x00,0x81,0x2E,0x2A,0x13, +0xA2,0xB0,0xA5,0x88,0x88,0x89,0x11,0x19,0xA0,0xF3,0x82,0xB0,0x83,0x5F,0x2A,0x01, +0xA1,0x94,0xB0,0x09,0x78,0x98,0xA3,0xA6,0xA0,0x91,0x80,0x93,0x98,0xC1,0x12,0x18, +0xC9,0x17,0xA0,0xA0,0x1A,0x21,0x80,0x99,0xD4,0x30,0x9D,0x00,0x10,0x2F,0x08,0x1C, +0x21,0x08,0xB4,0xC3,0x2B,0xA9,0x52,0xD2,0xA3,0xD1,0x09,0x10,0x8B,0x24,0x92,0xD1, +0x80,0x19,0xA0,0x2C,0x12,0x49,0xAA,0xB6,0x95,0xB8,0x08,0x3A,0x2B,0x01,0xF3,0xB3, +0x0B,0x09,0x79,0x18,0xA2,0xA4,0xA0,0x18,0x0C,0x20,0x08,0xA9,0x16,0x0C,0x00,0x1B, +0x08,0x2B,0x7B,0x01,0x01,0xB9,0x59,0x19,0x8B,0x45,0xA8,0x80,0x0C,0x1A,0x41,0x1E, +0x00,0x28,0xA8,0x5A,0x00,0xC1,0x49,0x99,0x21,0x1D,0x08,0x85,0x99,0x95,0x89,0x90, +0x11,0x90,0xD1,0x28,0xB2,0xA7,0x99,0x81,0x02,0xAC,0x13,0x81,0xB2,0xA6,0xA9,0x28, +0x1C,0xB1,0x33,0xD1,0xC1,0x58,0xA8,0x14,0xB0,0xB7,0x91,0xA0,0x82,0x89,0xC2,0x28, +0xA1,0xB2,0x49,0xD2,0x94,0xC8,0x12,0x80,0x99,0x85,0x08,0xD3,0x09,0xA2,0xB3,0x1E, +0x08,0x21,0xB9,0x23,0xB4,0xAB,0x41,0xAC,0x87,0x09,0xA2,0xC5,0x0B,0x2A,0x5A,0x91, +0x20,0x9A,0x89,0x78,0x9B,0x31,0x89,0x80,0x29,0x0A,0xB7,0x3C,0x98,0x48,0x1D,0x00, +0x01,0xB0,0x20,0x2F,0x29,0x4A,0x89,0x94,0x1C,0x88,0x28,0x2B,0x10,0x88,0x9A,0x71, +0x9A,0x08,0x4A,0x2F,0x18,0x2B,0x18,0x02,0xA8,0x4B,0x7A,0x99,0x48,0x80,0xA8,0x20, +0x1D,0x40,0xA8,0x10,0x08,0xA8,0xC5,0x88,0xC2,0x18,0x88,0x2A,0x12,0xF3,0x82,0xD8, +0x20,0x0A,0x09,0xA6,0x98,0x04,0xB9,0x11,0x18,0xC3,0xE1,0x29,0xA1,0x11,0xC1,0x03, +0xE2,0x9A,0x33,0xA9,0xB5,0x98,0x92,0xA1,0x02,0xF8,0x21,0xA8,0x10,0x02,0xC1,0xB7, +0x1B,0x90,0x5B,0x3C,0x83,0x93,0xE0,0x19,0x1A,0x11,0x11,0xF1,0x92,0x89,0x19,0x2C, +0x2C,0x41,0x99,0x92,0x90,0x3F,0x18,0x4B,0x00,0x08,0xD2,0x01,0xB2,0xAA,0x78,0x09, +0x01,0x91,0xA2,0x98,0x2F,0x3A,0x2C,0x01,0x00,0x93,0xE0,0x28,0x2C,0x2B,0x01,0x12, +0xE1,0x80,0xB3,0x3D,0x3A,0x0A,0x50,0x98,0xC2,0xA0,0x11,0xAA,0x30,0x87,0x90,0xC2, +0x29,0x88,0x38,0xC8,0xB5,0x90,0xBA,0x70,0x1A,0x02,0x94,0xD0,0x80,0x1A,0x82,0xA6, +0xB0,0x91,0x18,0xB3,0x00,0x13,0xF1,0xA2,0xC1,0x82,0xB0,0x00,0x15,0x0B,0xD3,0x02, +0xA8,0x91,0x2B,0x1F,0x49,0x88,0xA6,0x80,0x88,0x08,0x1B,0xA5,0x80,0xB9,0x06,0x0B, +0x90,0x21,0x9D,0x48,0x18,0xA0,0x15,0xC9,0x82,0x2B,0x1A,0x42,0x9A,0xC4,0x39,0xBC, +0x69,0x00,0xA0,0x29,0x8C,0x39,0x59,0x08,0x09,0x49,0xA9,0x6B,0x81,0x00,0x98,0xB0, +0x68,0x3D,0x81,0x88,0x18,0x19,0x1D,0x12,0x80,0xB2,0x3A,0x3F,0x85,0x92,0xD0,0x00, +0x0A,0x19,0x12,0xF1,0x02,0x9B,0x19,0x40,0xB9,0x11,0x02,0xF2,0x1A,0x08,0x94,0x0A, +0xC2,0x83,0x0B,0xB4,0xA4,0xC0,0x32,0xD8,0x86,0x98,0x90,0x95,0x89,0xA3,0x83,0xC2, +0x92,0xE1,0x92,0x82,0xD9,0x03,0x08,0xA9,0x85,0x92,0xA2,0x80,0xE0,0x30,0x8B,0xB3, +0x87,0x89,0x90,0x83,0xA0,0x08,0x92,0x93,0x3E,0xAB,0x43,0x89,0xE3,0x80,0x83,0x2F, +0x00,0xA3,0x80,0xC9,0x22,0x3F,0x08,0x81,0x0B,0x33,0x9A,0xA3,0x7B,0x0C,0x29,0x4A, +0x1B,0x21,0xAA,0x70,0x1B,0x0D,0x48,0x1A,0x81,0x88,0xB1,0x39,0x3F,0x08,0x58,0xA0, +0x81,0x1A,0x1A,0x2B,0x6D,0x11,0x0A,0x91,0x01,0x1A,0x98,0x5A,0x0C,0x03,0xB1,0x84, +0xA3,0xAD,0x58,0x2A,0xA1,0x84,0xB1,0xA0,0x5C,0x2B,0x13,0xA8,0x95,0x83,0xE8,0x10, +0x81,0xB0,0x00,0xC2,0x96,0xA0,0x91,0x00,0x2C,0x90,0x30,0xF2,0x80,0xA8,0x39,0x21, +0xC1,0x03,0xAC,0x39,0x7C,0x29,0x91,0x1A,0x00,0x19,0x2C,0x3A,0x93,0xB0,0x29,0x8F, +0x28,0x02,0x93,0xF3,0xA9,0x01,0x03,0xE0,0x08,0x09,0x1D,0x58,0xA1,0x83,0xA9,0x6B, +0x2A,0x3C,0x21,0x89,0xC2,0x2C,0x4B,0x8A,0x50,0x81,0x98,0xA8,0x32,0x0C,0x8E,0x24, +0x0B,0x1A,0x81,0x92,0xA1,0x4F,0x18,0x3A,0x0A,0xB4,0x18,0x2E,0x39,0x82,0x19,0xD3, +0xD0,0x28,0x1B,0x11,0x98,0x07,0xAA,0x28,0x00,0x88,0xB4,0x89,0x1B,0x1F,0x22,0x00, +0xB3,0xC9,0x33,0xAB,0x2B,0xB5,0x48,0x98,0x98,0xA7,0x10,0xD2,0xC1,0x23,0xCA,0x93, +0xC6,0x80,0xA1,0x88,0x02,0x89,0xE2,0x09,0x38,0xBA,0x40,0x89,0x21,0xD8,0x49,0x10, +0x8D,0x02,0x90,0xC3,0x9A,0x24,0x89,0x08,0x84,0xA5,0x9C,0x10,0x11,0x9C,0x88,0x30, +0x3C,0xA1,0x94,0x58,0x8C,0x0B,0x69,0x29,0x9A,0x81,0x12,0x2B,0x8B,0x79,0x94,0xB0, +0xC1,0x84,0xC2,0x99,0x25,0x99,0x11,0xA2,0x93,0xE4,0x99,0x80,0x0A,0x00,0x10,0xB7, +0xB0,0x31,0xBA,0x3C,0x21,0xB3,0xF1,0x18,0xA0,0x2A,0x20,0xA3,0x06,0xE8,0x28,0xA1, +0xB4,0x08,0x0B,0x11,0x4B,0xB7,0x90,0xA5,0x98,0x3D,0x19,0x02,0xA1,0xC4,0xB2,0x19, +0x28,0xC0,0xA5,0x92,0xB1,0xA3,0x0A,0x0A,0x08,0x2B,0x70,0xC4,0xB3,0x00,0xBC,0x4B, +0x39,0x12,0xE3,0xA0,0x00,0x3F,0x18,0x29,0x94,0xD1,0x19,0x09,0x00,0xA1,0x83,0x99, +0x9B,0x35,0x80,0xC4,0xB1,0x6A,0x1A,0x1C,0x29,0x38,0x0E,0x19,0x5A,0x1A,0x82,0x8A, +0x59,0x2A,0x2E,0x20,0x88,0xA8,0x3A,0x38,0x3D,0x00,0xB3,0x29,0xAD,0x49,0x10,0x0C, +0x01,0x01,0xA3,0x8F,0x85,0x09,0x1B,0x88,0x10,0xA3,0xD2,0x90,0x3C,0x5C,0x39,0x03, +0xD1,0xA0,0x00,0x2A,0x0B,0x04,0xA7,0x90,0xA0,0x11,0x90,0x99,0x83,0xB4,0xB1,0xF1, +0x84,0x88,0x90,0x18,0x18,0xD3,0xD2,0xB3,0xA0,0x1A,0x21,0xA7,0xB2,0xB3,0x92,0x9A, +0x22,0xB9,0x28,0x38,0xBD,0x87,0x2A,0xB1,0x13,0x0D,0x0A,0x38,0xC9,0x24,0xC0,0x19, +0x23,0x0F,0x01,0x88,0xC0,0x2A,0x82,0x18,0x28,0xF0,0x18,0x2A,0x29,0x4B,0x35,0xB8, +0xA3,0x9D,0x18,0x1B,0x40,0x00,0x9A,0x5C,0x3A,0x09,0x2F,0x38,0x8A,0x3B,0x3B,0x11, +0x5C,0x19,0x2B,0x4A,0x08,0x0A,0x3D,0x20,0x4F,0x3A,0x19,0x2A,0x18,0x4D,0x1B,0x3A, +0x11,0x0D,0x3A,0x3C,0x4B,0x93,0x81,0xAA,0x6B,0x4A,0x18,0x00,0xC3,0xC3,0x9A,0x59, +0x2A,0x1B,0xA7,0xA1,0x81,0x88,0x88,0x58,0xB2,0xB1,0x2B,0x83,0xD4,0x81,0x08,0x0F, +0x00,0x20,0xC2,0xE2,0x80,0x08,0x1C,0x29,0x04,0xB1,0xA2,0x01,0x1C,0x91,0x00,0x0C, +0x49,0xB0,0x43,0xF2,0x99,0x39,0x3F,0x00,0x81,0x94,0xC1,0x09,0x1A,0x69,0x90,0x80, +0x94,0xAA,0x20,0x2A,0x91,0xB1,0x39,0x7A,0x38,0xD1,0x10,0x8A,0x8C,0x5A,0x01,0xB5, +0x98,0x80,0x2A,0x0B,0x32,0x92,0xF1,0x81,0x9A,0x23,0x8A,0xA3,0xB7,0x09,0x03,0x08, +0xD0,0x94,0x9A,0x09,0x01,0x93,0xB7,0xC2,0x8C,0x3A,0x83,0x99,0x05,0xA0,0x0B,0x29, +0x93,0xE5,0x80,0x89,0x38,0x90,0x8A,0xD7,0xA1,0x19,0x1B,0x48,0x98,0x92,0xC3,0xA1, +0x09,0x3F,0x02,0x0C,0x22,0xC3,0xB2,0xA1,0x01,0x9F,0x4A,0x01,0xA3,0xD3,0xB0,0x28, +0x3F,0x29,0x20,0xA2,0xC2,0xB1,0x08,0x5A,0x98,0x13,0xD2,0xC1,0x01,0xB2,0x80,0x3D, +0x03,0xC1,0x89,0x96,0x90,0x90,0x3A,0x1A,0x9A,0x32,0xB6,0xA2,0x8E,0x4A,0x28,0x8A, +0x84,0xA2,0x8A,0x2D,0x49,0x09,0x88,0x18,0x30,0x9D,0x2C,0x23,0xB1,0x0C,0x92,0x2D, +0x39,0x82,0xC4,0x2E,0x10,0x1A,0x10,0xB9,0x48,0x19,0x39,0xBA,0x34,0xDA,0x2D,0x48, +0x1A,0xA6,0x98,0x83,0x9A,0x1D,0x38,0x04,0xD0,0x18,0x90,0x2C,0x11,0x93,0xD3,0x9A, +0x11,0x08,0x82,0xF1,0x01,0xA0,0x2A,0x93,0xD3,0xB4,0xB8,0x82,0x2F,0x11,0xA3,0xB3, +0xA8,0x3B,0x09,0x23,0x96,0xC8,0x3B,0x3F,0x93,0x82,0xA1,0x90,0x3F,0x28,0x81,0xD1, +0x93,0x08,0x2D,0x18,0x91,0xB3,0xB5,0x98,0x2A,0x2B,0x84,0xB1,0x5B,0x8A,0x31,0x18, +0x80,0x8B,0x7E,0x39,0x2B,0x02,0xC1,0x8B,0x6C,0x49,0x09,0x10,0xA1,0x08,0x01,0x0C, +0x20,0xA1,0x09,0x4F,0x18,0x00,0x01,0xA0,0x5C,0x1B,0x5B,0x10,0x92,0x90,0x2B,0x5A, +0x3D,0x18,0x91,0x19,0x98,0x2D,0x39,0x89,0x2D,0x3A,0x48,0x2C,0x11,0xB5,0x9A,0x19, +0x5B,0x28,0x90,0x95,0x98,0x89,0x2B,0x40,0x08,0x90,0xF3,0x0A,0x08,0xA6,0x80,0x91, +0xB2,0xA0,0x02,0xF2,0xA1,0xB7,0x89,0x81,0x82,0x91,0xB1,0x21,0xAB,0x32,0xE9,0x04, +0xA2,0x8D,0x12,0x91,0xA3,0xA3,0xD2,0x8B,0x39,0xD1,0x84,0xE2,0x90,0x00,0x2B,0x29, +0xA3,0xD4,0xA1,0x91,0x1D,0x5A,0x08,0x19,0x11,0x99,0x08,0x18,0x49,0x0F,0x18,0x10, +0x82,0xF1,0x00,0x89,0x2F,0x3A,0x01,0xB3,0xC2,0x81,0x3F,0x29,0x08,0x10,0xA1,0xA1, +0x3B,0x5D,0x19,0x28,0x0B,0x38,0x82,0x91,0x19,0xBD,0x3B,0x7A,0x80,0x12,0xB3,0xE0, +0x0B,0x6A,0x01,0x88,0xA4,0x08,0x0B,0x08,0x59,0x80,0x80,0x1D,0x49,0x89,0x00,0x84, +0x99,0x1A,0x2B,0x32,0xE3,0xB4,0xA9,0x3A,0x99,0x31,0xE3,0xAA,0x58,0x3B,0x88,0x95, +0xC0,0x18,0x4A,0x09,0x30,0xF2,0xA3,0x1C,0x1B,0x49,0x00,0xD3,0xB2,0xA0,0x18,0x11, +0x92,0xD3,0xB2,0x91,0x80,0xE7,0xA1,0x91,0x98,0x19,0x22,0xC2,0xD2,0x18,0x8D,0x3B, +0x10,0xA5,0x91,0x98,0x02,0x3E,0x80,0x01,0x90,0xAA,0x13,0xF1,0x02,0xD1,0x08,0x19, +0x49,0xB4,0x91,0xB4,0x99,0x2A,0x0C,0x32,0xC0,0x05,0x88,0x0B,0x80,0x2C,0x81,0x10, +0x0B,0x51,0xA9,0x19,0x05,0xBF,0x28,0x20,0xE1,0x90,0x80,0x28,0x19,0x08,0x26,0xB1, +0xA1,0x18,0x88,0x2A,0xF0,0x12,0x8A,0xB3,0x14,0x1B,0xD4,0xD8,0x10,0x08,0x8A,0x17, +0xA0,0x98,0x2B,0x3A,0x29,0x48,0xA4,0x99,0x0E,0x4A,0x12,0x8B,0x31,0x8B,0x4E,0x1A, +0x11,0xB5,0x89,0x91,0x29,0x89,0xC2,0x97,0x90,0x0A,0x19,0x11,0x91,0xC1,0xD5,0x08, +0x89,0x20,0x91,0xB1,0x1A,0x2D,0x18,0x29,0xD2,0x3B,0x3E,0x3A,0x2A,0x90,0x82,0x1C, +0x49,0x3B,0x93,0xB6,0xC8,0x4C,0x02,0x91,0x93,0xF2,0x88,0x2D,0x28,0x81,0x82,0xC1, +0x89,0x2D,0x6B,0x19,0x82,0x80,0x18,0x8B,0x39,0x39,0xC8,0x3A,0x6A,0x0A,0x22,0xD2, +0x09,0x2C,0x1A,0x68,0x92,0xE2,0x89,0x2A,0x2A,0x30,0xC2,0xA3,0xB4,0x1D,0x2A,0x09, +0x93,0x18,0xF2,0x89,0x28,0xB3,0x01,0x8F,0x18,0x11,0xA1,0x93,0x90,0xD1,0x7A,0x20, +0xC3,0xA2,0xA8,0x88,0x1D,0x28,0xA5,0xA2,0xA2,0x0B,0x29,0x2B,0x87,0xC1,0x80,0x0A, +0x19,0x01,0x12,0xF1,0x10,0x80,0x0A,0x18,0x08,0x2F,0x4A,0x02,0x89,0x1B,0x29,0x5D, +0x4C,0x08,0x82,0xA1,0x0A,0x3A,0x4B,0x29,0xC6,0xC3,0x09,0x09,0x88,0x39,0x98,0x82, +0xA5,0x1A,0x30,0x11,0xBD,0x3F,0x12,0x8B,0x28,0xC3,0x88,0x3F,0x2B,0x3B,0x48,0xA1, +0x80,0x8A,0x4D,0x39,0x01,0x93,0xA2,0xF1,0x19,0x19,0x0A,0x02,0xB2,0x8B,0x24,0xD2, +0x4B,0x12,0xC8,0x2E,0x10,0xB5,0x89,0x01,0x09,0x1C,0x2A,0x03,0xD4,0x91,0x98,0x99, +0x11,0x2B,0xE4,0x00,0x00,0x01,0xE0,0xA5,0x89,0x99,0x31,0x18,0xD0,0xB7,0x98,0x18, +0x0A,0x10,0x94,0xC2,0x90,0x18,0x00,0x99,0x87,0xA0,0x90,0x2A,0x3C,0x02,0xB8,0xC1, +0x79,0x1A,0x20,0x08,0xA1,0xD2,0x1C,0x29,0x03,0xD1,0x29,0x99,0x2C,0x50,0xB3,0xD1, +0x08,0x09,0x3C,0x10,0x04,0xB2,0x0D,0x2B,0x59,0x80,0x90,0x01,0x0F,0x3A,0x18,0x01, +0xA2,0x9B,0x5B,0x3D,0x81,0x03,0xD2,0x98,0x59,0x90,0x81,0x92,0xB4,0x8B,0x1B,0x40, +0xB2,0xB5,0x08,0x4B,0x01,0x09,0xD1,0x91,0x8B,0x7A,0x10,0xB3,0xC3,0x99,0x49,0x1A, +0x29,0xB5,0xA2,0xAB,0x40,0x81,0x19,0xB7,0xB0,0x20,0x2B,0xD4,0x88,0xA1,0x91,0x3C, +0x82,0x37,0xD3,0xB1,0x8A,0x1B,0x30,0xB3,0xF4,0xA1,0x91,0x09,0x10,0x03,0xD0,0x83, +0xA9,0x8F,0x10,0x01,0x90,0x18,0x80,0x20,0x2B,0xF1,0x28,0x99,0x2A,0x41,0xF0,0x12, +0xAA,0x83,0x82,0xD1,0xC1,0x08,0x89,0x59,0x09,0x83,0x87,0xB0,0x2A,0x4D,0x18,0x09, +0x19,0xB3,0x4B,0x3F,0x39,0x19,0x09,0x01,0x89,0x03,0x1F,0x00,0x1A,0x0B,0x10,0x68, +0xA0,0x18,0x8C,0x6A,0x09,0x08,0x97,0xA1,0x81,0x1B,0x2B,0x4C,0x03,0xB4,0xA8,0x92, +0x4B,0x3C,0xA1,0x81,0x95,0xA8,0x81,0x12,0xBB,0x92,0x45,0xB9,0x93,0xF4,0x88,0x0A, +0x2D,0x28,0x00,0xA3,0xA3,0x8A,0x3F,0x48,0xB1,0x92,0xB4,0xA8,0x30,0x80,0xD3,0x80, +0xD1,0x19,0x3B,0xC4,0x81,0xC1,0x29,0x0D,0x20,0x13,0xC8,0xB4,0x4C,0x09,0x00,0x82, +0xC2,0x3B,0x0D,0x30,0x0B,0x12,0xF0,0x1B,0x20,0x0A,0xA6,0x80,0x0A,0x4A,0x4A,0x80, +0x94,0xB1,0x2E,0x3B,0x1A,0x10,0x93,0x10,0x4C,0x3D,0x08,0x82,0xC9,0x19,0x6A,0x2B, +0x38,0xD1,0x08,0x19,0x2A,0x5A,0x82,0xB1,0x8D,0x29,0x78,0x09,0x82,0x0A,0x2C,0x1B, +0x19,0x41,0xB8,0x8C,0x79,0x2B,0x11,0x88,0x82,0x91,0xDC,0x28,0x11,0xB0,0x11,0x18, +0xC9,0x62,0xA1,0x91,0x98,0x3B,0x3A,0xB0,0xF4,0x01,0xC0,0x29,0x39,0xF8,0x95,0x91, +0x88,0x88,0x91,0x03,0xA1,0xE2,0x18,0x82,0xD1,0xA2,0xD1,0x80,0x19,0x20,0x83,0xB1, +0xE3,0x80,0x91,0x4D,0x1A,0x03,0xB2,0x09,0x18,0xD1,0x19,0x09,0x92,0xA6,0xA0,0xB6, +0xB2,0x8B,0x38,0x10,0x42,0xD3,0xD0,0xA8,0x20,0x2C,0x10,0x01,0xB1,0xB4,0xAB,0x5B, +0x79,0x80,0x10,0x1A,0xA8,0x3D,0x18,0x20,0xB3,0x8F,0x18,0x01,0x00,0x09,0xF3,0x89, +0x69,0x88,0x81,0x91,0x08,0xE1,0x1A,0x08,0x11,0x81,0x1E,0x29,0xA0,0x01,0x00,0x90, +0x3E,0x7B,0x18,0x82,0xC3,0xA1,0x2A,0x2C,0x5B,0x81,0xA5,0x90,0x81,0x00,0x0B,0x1A, +0x1C,0x2C,0x32,0xC0,0xF3,0x80,0x2D,0x2A,0x10,0x02,0xE4,0xC1,0x89,0x4A,0x09,0x01, +0x03,0xD2,0x98,0x2A,0x39,0x8A,0x89,0x26,0xB1,0xB2,0x12,0xC0,0x0A,0x5A,0x18,0x98, +0xF3,0x92,0x99,0x99,0x79,0x01,0xB5,0xA1,0x80,0x80,0x90,0x83,0xA0,0xE2,0x81,0x29, +0x93,0x8A,0x0A,0x6A,0x1F,0x18,0x02,0xC8,0x01,0x19,0x3B,0x4A,0x98,0x17,0xA8,0x0D, +0x38,0xA1,0x91,0x10,0xA2,0x2B,0x4C,0xA6,0x81,0xBA,0x21,0x4C,0x80,0x21,0xD1,0x92, +0x2C,0x08,0x30,0x9F,0x93,0x2A,0x89,0x03,0x8B,0x87,0x0A,0x0D,0x12,0x98,0xA4,0x93, +0xBB,0x59,0x18,0xA1,0x32,0xE9,0x84,0x08,0x8A,0x02,0xA1,0x91,0x4B,0xB4,0x20,0x88, +0xF0,0x3A,0x1A,0x88,0x87,0xB1,0x92,0x0A,0x08,0x6B,0x83,0xC3,0x91,0xC0,0x2B,0x79, +0x08,0x8A,0x84,0xA0,0x89,0x40,0x1B,0xA1,0x39,0x98,0x17,0xC2,0xA2,0x12,0xCD,0x20, +0x89,0x92,0x25,0xB0,0x2D,0x3A,0x8B,0x58,0x2A,0xA0,0x4C,0x08,0x30,0xAE,0x82,0x59, +0x89,0x1A,0x10,0xC2,0x18,0x2C,0x40,0x1E,0x01,0xA3,0x8A,0x81,0x2C,0x29,0x29,0xA9, +0x13,0x51,0xAD,0x12,0x89,0x8F,0x18,0x2C,0x39,0x00,0xC1,0x10,0x3C,0x2A,0x41,0xC8, +0xA2,0x91,0x0A,0x6C,0x10,0x12,0x88,0xE8,0x30,0x91,0x81,0xD8,0x01,0x1B,0x0D,0x07, +0x00,0xA8,0x92,0x0A,0x28,0xD2,0xC3,0x02,0xAA,0x94,0x81,0xB4,0xB3,0x1A,0x0B,0x13, +0xF9,0x16,0xA1,0x8A,0x59,0x19,0x02,0xC1,0x91,0x8B,0x3D,0x18,0x3B,0xA4,0x94,0x80, +0x99,0x88,0x1C,0x79,0x0A,0x02,0x03,0xF8,0x90,0x39,0x5B,0x19,0x02,0xC3,0x90,0xBB, +0x58,0x6A,0x09,0x02,0x89,0x91,0x88,0x1A,0x69,0x8A,0x19,0x15,0xA0,0xA2,0x00,0x9A, +0x6B,0x49,0x88,0xA3,0x92,0xBB,0x6B,0x3D,0x38,0x01,0x98,0x91,0x3F,0x09,0x18,0x20, +0x90,0x80,0xAC,0x70,0x91,0x9B,0x51,0x09,0x88,0x99,0x14,0x8B,0x98,0x83,0x79,0xA0, +0x99,0x13,0x01,0x19,0xE0,0x83,0x0B,0xB0,0x0C,0x31,0x95,0xB5,0xC2,0x8A,0x39,0x20, +0x80,0x39,0xF3,0xB1,0x10,0x88,0x5E,0x18,0x94,0xA1,0x88,0xA1,0x98,0x15,0xAA,0x39, +0xD4,0x84,0xC0,0xA2,0xA2,0x0C,0x81,0x86,0xB5,0xA1,0xB1,0x14,0x1B,0xB1,0x02,0x92, +0xC3,0xE0,0x88,0x11,0xAA,0x69,0x18,0x81,0xA3,0xB0,0x01,0xBF,0x2A,0x31,0x93,0xF1, +0x00,0x89,0x18,0x19,0x11,0xD3,0xE0,0x10,0x18,0xB1,0x18,0x24,0x9A,0x2B,0xA4,0xC0, +0xB0,0x31,0x6C,0x19,0xB4,0x12,0xA8,0xEA,0x58,0x10,0x8B,0x93,0x82,0x88,0x9A,0x41, +0x10,0xC3,0xEA,0x41,0xA9,0x9C,0x34,0xA1,0x2A,0x79,0xA2,0x01,0xA8,0xB3,0x28,0xCC, +0x41,0x9A,0xB3,0x4B,0xB3,0x27,0x8B,0x83,0x2B,0x2F,0x08,0x28,0xB2,0x80,0x2C,0x30, +0x5E,0x09,0x12,0x9B,0x09,0x22,0x5B,0x19,0x8A,0x11,0x59,0x99,0xA4,0x32,0xCD,0x18, +0x08,0x10,0x85,0xB3,0xB4,0x1E,0x88,0x28,0x8A,0x11,0x09,0xC0,0x79,0x80,0x91,0x3B, +0x80,0x10,0x0F,0x01,0x80,0x91,0x19,0x3D,0x92,0x28,0xA8,0x37,0x9A,0x0A,0x3A,0x8A, +0x45,0xA9,0xA4,0x00,0xAA,0x09,0x3D,0x59,0x20,0xE1,0x08,0x98,0x90,0x59,0x10,0x09, +0xA3,0xC3,0x93,0x99,0x2B,0x69,0x11,0xD1,0xB1,0xA4,0x91,0x3C,0x89,0x83,0xF0,0x10, +0x91,0xA1,0x89,0x59,0x05,0x99,0x93,0x94,0xC8,0x08,0x0A,0x09,0x17,0xB1,0x83,0xC1, +0x91,0x40,0xA2,0xC2,0x98,0xC3,0xBA,0x28,0x23,0x0F,0x80,0x50,0xB8,0x19,0x10,0x96, +0x98,0x8C,0x05,0x98,0x19,0x29,0x2B,0x3B,0x0A,0xE2,0x01,0x0F,0x3C,0x38,0x08,0x09, +0x81,0x4A,0x6C,0x08,0x00,0x88,0x98,0x38,0x2C,0x5A,0x1B,0x20,0x1A,0x39,0xB0,0x09, +0xCB,0x5B,0x49,0x09,0x71,0x00,0xC1,0x0E,0x08,0x38,0x0C,0x02,0x10,0x0E,0x10,0x8A, +0x48,0x19,0x90,0x92,0x0D,0xA3,0x98,0x3B,0x79,0x19,0x01,0x10,0xE1,0x80,0x19,0x2B, +0x10,0xF2,0x02,0xAB,0x84,0x9A,0x29,0xB4,0x80,0x92,0x03,0x88,0x95,0xD0,0x03,0x90, +0xA0,0xC7,0xA1,0xB0,0xA2,0x02,0x18,0xB5,0xD4,0x01,0xC0,0x08,0xA2,0x93,0xA8,0xA0, +0xC3,0x20,0xF3,0x90,0x00,0xD5,0x08,0x89,0xA5,0x80,0xA0,0x81,0x82,0xC2,0x09,0xD1, +0x13,0xCB,0x03,0x84,0x91,0xE1,0x1B,0x12,0x08,0xAB,0x87,0x18,0xAB,0x58,0x89,0x28, +0x81,0xC9,0x33,0xA9,0x80,0x2E,0x20,0x83,0xB9,0x20,0x3B,0x9E,0x7A,0x08,0x81,0x18, +0x0B,0x88,0x79,0x80,0x8B,0x00,0x12,0x0E,0x89,0x51,0x1B,0x81,0xA0,0x3A,0x01,0xAF, +0x11,0x28,0xBA,0x35,0x98,0x88,0x52,0xC0,0x83,0x2F,0xA9,0x11,0x0A,0x19,0x25,0xD0, +0x30,0x9C,0x08,0x21,0x98,0x81,0x2A,0xF3,0x2A,0x80,0xB6,0x2B,0x08,0x93,0xE9,0x02, +0x81,0x8C,0x21,0x00,0xA6,0xA9,0x94,0x01,0x8F,0x80,0x94,0x98,0x93,0xB4,0x00,0x08, +0xC0,0x14,0x98,0xB3,0xB4,0xC1,0x09,0x18,0xA7,0x00,0xA3,0xC8,0x0A,0x3C,0x19,0x96, +0x83,0xC1,0x99,0x19,0x4A,0x85,0x80,0xC1,0x91,0x99,0x90,0x2A,0x17,0x95,0x99,0x88, +0x12,0xAE,0x39,0x08,0x92,0x84,0xB0,0xA8,0x79,0x09,0x19,0x01,0xB2,0xA3,0x8F,0x28, +0x2B,0xA2,0x40,0x82,0xA0,0x4C,0xA9,0x39,0x8D,0x81,0x70,0x88,0xA0,0x1A,0x49,0x2D, +0x1A,0x26,0xA8,0x98,0x08,0x29,0x0B,0x12,0x96,0xB1,0xB2,0x3A,0x13,0x9B,0x60,0xA0, +0x88,0xB2,0x34,0xEA,0x1A,0x2A,0x79,0x98,0x10,0x04,0x8C,0x1C,0x81,0x04,0x8C,0x83, +0x19,0x2F,0x81,0x93,0x98,0x10,0x08,0x30,0x2A,0xFA,0x05,0x08,0x2A,0x89,0x91,0xA3, +0xFA,0x11,0x11,0x00,0x8C,0x04,0x8A,0x2A,0xB5,0x10,0xA9,0xC2,0x3D,0x1B,0x32,0x04, +0x0A,0x1A,0x09,0x40,0x1F,0x92,0x1D,0x2A,0x91,0x10,0x30,0x2F,0x0B,0x68,0x99,0xA2, +0x92,0x88,0x78,0xA9,0x20,0x28,0xE2,0x92,0x1A,0x99,0x4B,0x19,0x22,0xA1,0xE2,0x21, +0x2F,0x98,0x29,0x18,0x91,0x08,0xB0,0x79,0x1A,0x82,0x3B,0xB1,0xA7,0x8A,0xB3,0x98, +0x5B,0x23,0xCA,0x42,0x83,0xF0,0x90,0x18,0x98,0x08,0xB4,0x20,0xA3,0xC0,0x43,0xD8, +0x80,0x81,0xA3,0x99,0xD9,0xA7,0x19,0x90,0x10,0x05,0xB1,0x8B,0x02,0xA4,0xBD,0x23, +0x93,0x8A,0x99,0x4B,0x03,0xC1,0xF8,0x38,0x09,0x2B,0x14,0xD0,0x03,0x8A,0x2A,0x39, +0xB9,0x97,0x90,0xAA,0x50,0x01,0x99,0x51,0xD1,0x09,0x1A,0xB5,0x00,0x8B,0x93,0x08, +0x98,0x11,0xF9,0x85,0x2B,0x08,0x96,0x89,0x90,0x2A,0x12,0x4A,0xD8,0x85,0x2B,0x0E, +0x10,0x00,0x01,0xB1,0x9B,0x69,0x1A,0x90,0x40,0xB8,0x01,0x08,0x0A,0x2C,0x09,0x14, +0x4B,0xE2,0x82,0x88,0xB1,0x78,0x0A,0x01,0xC2,0x93,0x19,0xCE,0x20,0x3C,0x82,0xB4, +0x1B,0x20,0x8C,0x3B,0x29,0xAB,0x86,0x23,0xD8,0x81,0x9A,0x5A,0x49,0xB0,0x16,0xA0, +0xB0,0x28,0x1B,0x13,0x93,0xE4,0xA2,0xA9,0x08,0x5A,0xB3,0x12,0xC1,0xE1,0x10,0x88, +0x01,0x0C,0x92,0x08,0x89,0xB7,0x88,0x81,0x10,0x9A,0x17,0xA0,0xB0,0x13,0x99,0xE0, +0x39,0x31,0xD2,0xB2,0x80,0x0B,0x2D,0x49,0x80,0x01,0xB0,0x06,0x09,0x0C,0x3A,0x69, +0xA0,0x08,0xB2,0xA1,0x69,0x2B,0x5A,0x81,0x92,0xBA,0x21,0xB1,0x7D,0x10,0x80,0x08, +0x88,0x82,0x32,0x0D,0xB0,0x1A,0x1C,0x21,0x94,0xA9,0x58,0xB9,0x5A,0x4A,0xA0,0x13, +0xA9,0x80,0x7C,0x00,0x20,0x8A,0x04,0x0C,0x00,0x82,0x2A,0xB2,0xAC,0x4B,0x69,0xA0, +0xA6,0x81,0x9B,0x19,0x38,0x8B,0x17,0xB2,0x81,0x2A,0xBB,0x94,0x29,0xA2,0x15,0xBA, +0x97,0xA3,0xB9,0x79,0x01,0xB2,0x02,0xF1,0x90,0x0A,0x29,0x11,0x88,0xE5,0xA0,0x81, +0x19,0x91,0x90,0x28,0xB3,0x14,0xD0,0xB5,0x91,0x9A,0x29,0x0B,0x07,0xA2,0xB3,0x01, +0x9D,0x28,0x41,0xD0,0x91,0x90,0x82,0x1A,0xA8,0x44,0x9A,0xA9,0x21,0xE3,0xA9,0x4B, +0x19,0x78,0x89,0x83,0xA3,0xB9,0x5A,0x3D,0x80,0x82,0xA2,0xA0,0x6C,0x10,0x20,0x8B, +0x93,0x8B,0x0E,0x33,0xA9,0xB1,0x68,0x8A,0x31,0xAC,0x94,0xB4,0x8B,0x32,0x0B,0xB4, +0x81,0x91,0x1D,0x33,0xD9,0x31,0xE1,0x8B,0x3B,0x30,0x12,0x49,0xD2,0x8E,0x29,0x18, +0x8A,0x92,0x02,0xAA,0x59,0x1C,0x32,0x88,0x01,0x23,0xFB,0x83,0x29,0xDA,0x59,0x01, +0x81,0x92,0xE1,0x18,0x8A,0x1D,0x30,0x93,0xF1,0x00,0x01,0x0B,0x39,0x92,0x89,0xA0, +0x11,0x5B,0xE0,0x82,0x09,0x13,0xAA,0xB4,0x16,0xD8,0x91,0x2A,0x29,0x84,0x1B,0xC5, +0x98,0x98,0x31,0x98,0x99,0x17,0xA9,0x20,0x92,0xC3,0x18,0x9D,0x20,0x3D,0x89,0x94, +0xA2,0x1C,0x5C,0x29,0x39,0xA0,0xB3,0x00,0x0C,0x4C,0x48,0x92,0x0A,0x91,0x85,0x9A, +0x01,0x82,0x1F,0x10,0x99,0x15,0xC1,0xA0,0x39,0x1A,0x1D,0x85,0xB4,0x90,0x1A,0x2A, +0x4B,0x01,0xB2,0x93,0xBE,0x12,0x83,0xC9,0x18,0x09,0x20,0x78,0xF1,0x08,0x19,0x88, +0x3A,0x83,0xB3,0xA9,0x93,0x7A,0x0A,0x96,0x98,0x00,0xA8,0x3A,0x30,0x92,0xF2,0x9B, +0x3D,0x38,0x92,0x92,0xC3,0xB8,0x6B,0x29,0x01,0x01,0xB2,0x2F,0x09,0x19,0x18,0x01, +0x3B,0x7B,0x10,0xA1,0x90,0x39,0x0F,0x38,0x0A,0xB5,0xA4,0x89,0x8B,0x6A,0x2B,0x12, +0xC8,0x90,0x40,0x2A,0x9E,0x22,0x88,0x18,0x09,0x3A,0xC3,0xE8,0x09,0x59,0x08,0x12, +0x94,0xD0,0x1A,0x2C,0x38,0x00,0xA1,0x83,0xE8,0x08,0x3A,0x08,0x10,0x9E,0x83,0x1D, +0x92,0x19,0x2C,0x39,0x3B,0x59,0x04,0xE1,0x80,0x08,0x8D,0x21,0x81,0xB2,0xB2,0x02, +0x99,0x91,0xA4,0xD6,0x98,0x99,0x03,0x80,0x98,0xA7,0x91,0x09,0xA1,0xB2,0xB3,0xE1, +0x12,0x92,0xB1,0x81,0x06,0x99,0x0A,0x23,0xC4,0xB1,0xF2,0x89,0x19,0x3A,0x94,0x82, +0xE0,0x89,0x38,0x0B,0xA4,0xA5,0x80,0x80,0x8C,0x34,0xB9,0xA9,0x23,0x13,0xB9,0xC1, +0xC7,0x1B,0x89,0x10,0x20,0x11,0xE3,0xA8,0x4B,0x0B,0x40,0x91,0x90,0x1B,0x5F,0x2A, +0x18,0x82,0x91,0x0B,0x4A,0x28,0xCA,0x40,0x80,0x5B,0x2C,0x13,0xB0,0x8A,0xA9,0x5A, +0x58,0x89,0x82,0x88,0x2E,0x3B,0x31,0xA1,0x9B,0x01,0x7A,0x2C,0x01,0x91,0x93,0x3F, +0x88,0x39,0x10,0xF1,0x91,0x8B,0x48,0x0A,0x12,0xE3,0xA8,0x18,0x28,0x92,0x97,0x98, +0x99,0x19,0xA1,0x11,0xB6,0x88,0x3B,0x10,0xD3,0xC3,0xA1,0x2A,0x8A,0x49,0x04,0xF1, +0x91,0x02,0x8A,0x89,0x04,0xF1,0x98,0x80,0x18,0x12,0xE3,0x81,0x98,0x80,0x01,0xB3, +0xF2,0x99,0x12,0x2A,0xB5,0xB3,0x92,0xAA,0x19,0x50,0xB2,0xC3,0x92,0xD0,0x2B,0x68, +0x93,0x99,0xC0,0x2C,0x3E,0x80,0x20,0x08,0x93,0x0D,0x2A,0x31,0x8D,0x02,0x2B,0x91, +0x08,0x0A,0x03,0x2C,0x3C,0x52,0xB9,0xA0,0x12,0xBF,0x3A,0x29,0x01,0x88,0xC0,0x6A, +0x3C,0x0A,0x49,0x18,0x0B,0x39,0x2B,0x69,0x0A,0x84,0x2A,0x2A,0x1C,0x2A,0xC3,0x8C, +0x19,0x50,0x09,0x91,0xA7,0x8D,0x18,0x1A,0x28,0x00,0xA0,0x94,0x10,0x1F,0x20,0x90, +0x8A,0x12,0xD0,0x1A,0x5A,0x81,0x04,0xBC,0x23,0x10,0xE0,0x90,0x90,0x18,0x1A,0xA6, +0x12,0xB1,0xD0,0x4A,0x08,0x82,0x92,0xB6,0x9A,0x0A,0x12,0x88,0xC3,0xC5,0x8A,0x89, +0x20,0xB5,0x93,0x0B,0x18,0x00,0x09,0xF2,0x88,0x2A,0x4A,0x08,0x05,0xB2,0xA9,0x3B, +0x5D,0x28,0xA4,0xB1,0x00,0x19,0x19,0x7A,0xA3,0xB3,0x0A,0x90,0xA1,0xC4,0x80,0xBA, +0x50,0x13,0xC1,0xC2,0x9A,0x2A,0x7B,0x28,0x84,0xC1,0x09,0x3B,0x4E,0x20,0x91,0xA1, +0x18,0xAB,0x79,0x10,0xB4,0x08,0x9A,0x11,0x2B,0xF0,0x93,0xAA,0x01,0x6A,0x01,0x93, +0x80,0xB8,0x2A,0x5B,0x10,0x80,0x89,0x4A,0x5B,0x92,0x15,0xB2,0xA0,0x2F,0x19,0x93, +0xB8,0x95,0x80,0x1C,0x21,0xA9,0x02,0x0B,0xA0,0x5A,0x18,0x98,0x39,0x1B,0x68,0x00, +0x91,0x91,0x9C,0x39,0x3E,0x18,0x84,0xB3,0x9B,0x7A,0x08,0x18,0x0A,0xB5,0x91,0x0B, +0x28,0x39,0x19,0x90,0x0A,0x50,0xAC,0x11,0x01,0xAB,0x88,0x52,0x1B,0x83,0xC4,0xA2, +0x9A,0xAB,0x03,0x90,0x19,0x93,0x81,0x08,0x92,0x9A,0x68,0x98,0x19,0x39,0xC1,0x92, +0x8A,0x38,0x4E,0x02,0xB1,0x90,0xC3,0x18,0x2B,0x04,0xC3,0xD2,0x91,0x90,0x81,0x89, +0x13,0xF1,0x88,0x93,0xA2,0x00,0x91,0xC0,0x5B,0x21,0x99,0x93,0x06,0x9A,0x1B,0x48, +0x99,0xB7,0x90,0x89,0x18,0x1B,0x11,0xA4,0xB2,0x81,0x9A,0x08,0x97,0x98,0x91,0x10, +0xB8,0x06,0xA2,0xA0,0x29,0x2B,0x21,0xC2,0xD1,0x10,0x1A,0x4A,0x29,0xF1,0x98,0x29, +0x1B,0x31,0x10,0xA0,0xA1,0x1D,0x5A,0x29,0xB2,0x82,0xA8,0x0F,0x28,0x21,0x09,0x91, +0x82,0x4D,0x10,0xA3,0xB0,0x89,0x4C,0x39,0xA0,0xA4,0xA1,0x89,0x1E,0x28,0x29,0xA3, +0xC3,0x2D,0x19,0x01,0x49,0x01,0x9B,0x0C,0x21,0xC2,0xA2,0x93,0x7C,0x2A,0x10,0x90, + +/* Source: 08HH.ROM */ +/* Length: 384 / 0x00000180 */ + +0x75,0xF2,0xAB,0x7D,0x7E,0x5C,0x3B,0x4B,0x3C,0x4D,0x4A,0x02,0xB3,0xC5,0xE7,0xE3, +0x92,0xB3,0xC4,0xB3,0xC3,0x8A,0x3B,0x5D,0x5C,0x3A,0x84,0xC2,0x91,0xA4,0xE7,0xF7, +0xF7,0xF4,0xA1,0x1B,0x49,0xA5,0xB1,0x1E,0x7F,0x5A,0x00,0x89,0x39,0xB7,0xA8,0x3D, +0x4A,0x84,0xE7,0xF7,0xE2,0x2D,0x4C,0x3A,0x4E,0x7D,0x04,0xB0,0x2D,0x4B,0x10,0x80, +0xA3,0x99,0x10,0x0E,0x59,0x93,0xC4,0xB1,0x81,0xC4,0xA2,0xB2,0x88,0x08,0x3F,0x3B, +0x28,0xA6,0xC3,0xA2,0xA2,0xC5,0xC1,0x3F,0x7E,0x39,0x81,0x93,0xC2,0xA3,0xE5,0xD2, +0x80,0x93,0xB8,0x6D,0x49,0x82,0xD4,0xA1,0x90,0x01,0xA0,0x09,0x04,0xE3,0xB2,0x91, +0xB7,0xB3,0xA8,0x2A,0x03,0xF3,0xA1,0x92,0xC5,0xC3,0xB2,0x0B,0x30,0xB3,0x8E,0x6D, +0x4A,0x01,0xB4,0xB4,0xC4,0xC3,0x99,0x3B,0x12,0xE3,0xA1,0x88,0x82,0xB4,0x9A,0x5C, +0x3A,0x18,0x93,0xC3,0xB3,0xB4,0xA8,0x19,0x04,0xF3,0xA8,0x3B,0x10,0xA2,0x88,0xA5, +0xB2,0x0B,0x6D,0x4B,0x10,0x91,0x89,0x3C,0x18,0x18,0xA6,0xC4,0xC3,0x98,0x19,0x2B, +0x20,0x91,0xA0,0x4E,0x28,0x93,0xB3,0xC2,0x92,0xA9,0x5A,0x96,0xC4,0xC2,0x09,0x01, +0xC4,0xA1,0x92,0xC4,0xA1,0x89,0x10,0xA3,0xA1,0x90,0x1C,0x5A,0x01,0xC5,0xA1,0x92, +0xD4,0xB3,0xC4,0xC4,0xC3,0xA1,0x88,0x1A,0x28,0x89,0x3C,0x3A,0x3D,0x29,0x00,0x93, +0xB0,0x3D,0x28,0x80,0x91,0x82,0xE3,0x99,0x2A,0x11,0xD6,0xC3,0x99,0x29,0x82,0xC4, +0xC3,0xA1,0x0A,0x3B,0x3D,0x3A,0x02,0xC3,0xA2,0x99,0x3B,0x2C,0x7C,0x28,0x81,0xA3, +0xB2,0xA3,0xB1,0x08,0x1A,0x3C,0x18,0x2E,0x4C,0x39,0xA5,0xB3,0xB4,0xC2,0x88,0x08, +0x19,0x0A,0x49,0xB7,0xB3,0xA2,0xA1,0x92,0xA1,0x93,0xB1,0x0C,0x7D,0x39,0x93,0xB3, +0xB1,0x1A,0x19,0x5D,0x28,0xA6,0xC4,0xB2,0x90,0x09,0x2A,0x18,0x1B,0x5B,0x28,0x88, +0x2C,0x29,0x82,0xA0,0x18,0x91,0x2D,0x29,0x2B,0x5C,0x4C,0x3B,0x4C,0x28,0x80,0x92, +0x90,0x09,0x2B,0x28,0x1D,0x6B,0x11,0xC5,0xB2,0x0B,0x39,0x09,0x4D,0x28,0x88,0x00, +0x1B,0x28,0x94,0xE3,0xA0,0x1A,0x28,0xB5,0xB4,0xB3,0xB2,0x93,0xE2,0x91,0x92,0xD4, +0xA0,0x1B,0x4A,0x01,0xA1,0x88,0x2D,0x5C,0x3B,0x28,0x08,0x93,0xD4,0xB2,0x91,0xB4, +0xA0,0x3E,0x3B,0x4B,0x3B,0x29,0x08,0x93,0x9B,0x7B,0x3A,0x19,0x00,0x80,0x80,0xA0, + +/* Source: 10TOM.ROM */ +/* Length: 640 / 0x00000280 */ + +0x77,0x27,0x87,0x01,0x2D,0x4F,0xC3,0xC1,0x92,0x91,0x89,0x59,0x83,0x1A,0x32,0xC2, +0x95,0xB1,0x81,0x88,0x81,0x4A,0x3D,0x11,0x9E,0x0B,0x88,0x0C,0x18,0x3B,0x11,0x11, +0x91,0x00,0xA0,0xE2,0x0A,0x48,0x13,0x24,0x81,0x48,0x1B,0x39,0x1C,0x83,0x84,0xA1, +0xD1,0x8E,0x8A,0x0B,0xC0,0x98,0x92,0xB8,0x39,0x90,0x10,0x92,0xF0,0xB5,0x88,0x32, +0x49,0x51,0x21,0x03,0x82,0x10,0x8A,0x7A,0x09,0x00,0xA2,0xCA,0x1B,0xCC,0x1C,0xB9, +0x8E,0x89,0x89,0xA1,0x89,0x92,0x29,0x11,0x60,0x40,0x14,0x22,0x32,0x78,0x40,0x01, +0x02,0x90,0x81,0xAB,0x0B,0x00,0xAF,0x99,0xCC,0xAB,0xDA,0xA9,0x99,0x1B,0x30,0x14, +0x92,0x22,0x19,0x68,0x32,0x14,0x26,0x13,0x23,0x23,0x20,0x12,0x9A,0xA8,0xB9,0xFA, +0xAA,0xCA,0xCC,0x0C,0xA8,0xAE,0x88,0xB9,0x88,0xA0,0x02,0x21,0x50,0x43,0x03,0x81, +0x2A,0x11,0x34,0x63,0x24,0x33,0x22,0x38,0x8B,0xEA,0xAE,0x99,0xA0,0x90,0x82,0x00, +0x89,0xBF,0x8A,0xE8,0xA9,0x90,0x01,0x12,0x13,0x12,0x08,0xA9,0xAA,0xC9,0x22,0x63, +0x63,0x12,0x44,0x00,0x10,0x88,0x9C,0x98,0xA1,0x85,0x03,0x32,0x36,0x80,0x89,0xDB, +0xDB,0xBB,0xB9,0xBA,0x01,0x81,0x28,0x19,0xCB,0xFA,0xBC,0x09,0x13,0x37,0x34,0x34, +0x23,0x31,0x20,0x10,0x00,0x00,0x28,0x38,0x10,0x88,0xEC,0x8D,0xCB,0xBC,0xCC,0xBB, +0xBB,0xC9,0x99,0x00,0x00,0x33,0x11,0x22,0x81,0x07,0x41,0x54,0x34,0x34,0x22,0x31, +0x00,0x88,0x9A,0x9B,0x98,0xAB,0x8E,0x9B,0xBD,0x9C,0xBC,0xBB,0xDA,0xAA,0xA9,0x99, +0x18,0x38,0x60,0x20,0x31,0x13,0x13,0x51,0x14,0x31,0x53,0x33,0x35,0x22,0x01,0x8A, +0x9C,0xA9,0xCA,0xC9,0xA8,0x00,0x10,0x81,0x9C,0x9E,0xAB,0xCC,0xAB,0xBA,0x98,0x30, +0x52,0x03,0x81,0x08,0x9C,0xAC,0xAC,0x18,0x11,0x03,0x51,0x61,0x41,0x31,0x31,0x02, +0x01,0x20,0x24,0x43,0x44,0x40,0x30,0x10,0xBC,0xBE,0xCB,0xDB,0xAB,0xBA,0x99,0x98, +0x99,0xAA,0xBD,0xAA,0xC8,0x90,0x11,0x53,0x37,0x23,0x43,0x34,0x33,0x33,0x33,0x11, +0x28,0x00,0x19,0xA9,0x9A,0xCB,0xCE,0xBB,0xEB,0xBC,0xBB,0xCA,0xBA,0xA8,0x88,0x11, +0x12,0x21,0x20,0x22,0x26,0x26,0x23,0x23,0x43,0x24,0x22,0x32,0x20,0x31,0x81,0x9A, +0xBC,0xBC,0xCB,0xBD,0x9A,0xA9,0x90,0x98,0xBA,0xCC,0xCB,0xBC,0x8B,0x88,0x22,0x35, +0x23,0x12,0x99,0x8B,0xAA,0xAA,0x89,0x82,0x93,0x31,0x42,0x23,0x23,0x21,0x32,0x11, +0x20,0x13,0x13,0x24,0x24,0x24,0x22,0x11,0x8A,0x9E,0xAC,0xAC,0xAA,0xBA,0xAA,0xAB, +0xBD,0xBC,0xCB,0xCB,0xA9,0xA8,0x91,0x12,0x44,0x43,0x44,0x34,0x34,0x42,0x33,0x42, +0x21,0x11,0x11,0x88,0x80,0xAA,0x0B,0xAC,0xCB,0xEC,0xAC,0xBA,0xCA,0xAB,0x9A,0x99, +0x80,0x91,0x09,0x08,0x10,0x22,0x44,0x43,0x44,0x33,0x43,0x22,0x13,0x21,0x22,0x20, +0x09,0x88,0xB9,0xC8,0xBB,0xAB,0xAB,0xA9,0xA9,0x9B,0x9B,0x99,0x90,0x90,0x00,0x81, +0x00,0x08,0x09,0x8A,0x9A,0xAA,0xA9,0xA9,0x99,0x90,0x80,0x01,0x80,0x00,0x09,0x31, +0x32,0x44,0x33,0x43,0x34,0x33,0x24,0x22,0x23,0x12,0x10,0x09,0x9B,0xAB,0xCA,0xCC, +0xBB,0xCB,0xDA,0xCA,0xAB,0xCA,0xAB,0xA9,0xA8,0x92,0x12,0x43,0x53,0x35,0x23,0x33, +0x43,0x43,0x52,0x22,0x22,0x21,0x01,0x09,0x89,0xA9,0xBB,0xBD,0xBC,0xCB,0xDA,0xAB, +0xAB,0xAB,0xAA,0xA9,0x99,0xA8,0x09,0x01,0x11,0x34,0x25,0x23,0x33,0x51,0x22,0x31, +0x12,0x20,0x21,0x12,0x10,0x80,0x99,0x9A,0x99,0x99,0x88,0x08,0x00,0x88,0xA9,0x99, +0x99,0x80,0x80,0x10,0x01,0x00,0x9A,0xAA,0xBB,0xBA,0xBA,0xA9,0x99,0x99,0x89,0x99, +0x99,0x00,0x01,0x33,0x35,0x24,0x23,0x34,0x23,0x33,0x34,0x33,0x43,0x32,0x21,0x88, +0xAB,0xBD,0xBB,0xDB,0xAB,0xBA,0xBB,0xDA,0xBB,0xCB,0xBB,0xBC,0xA8,0x90,0x01,0x12, +0x23,0x43,0x53,0x34,0x34,0x39,0x80,0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x00, + +/* Source: 20RIM.ROM */ +/* Length: 128 / 0x00000080 */ + +0x0F,0xFF,0x73,0x8E,0x71,0xCD,0x00,0x49,0x10,0x90,0x21,0x49,0xA0,0xDB,0x02,0x3A, +0xE3,0x0A,0x50,0x98,0xC0,0x59,0xA2,0x99,0x09,0x22,0xA2,0x80,0x10,0xA8,0x5B,0xD2, +0x88,0x21,0x09,0x96,0xA8,0x10,0x0A,0xE0,0x08,0x48,0x19,0xAB,0x52,0xA8,0x92,0x0C, +0x03,0x19,0xE2,0x0A,0x12,0xC2,0x81,0x1E,0x01,0xD0,0x48,0x88,0x98,0x01,0x49,0x91, +0xAA,0x2C,0x25,0x89,0x88,0xB5,0x81,0xA2,0x9A,0x12,0x9E,0x38,0x3B,0x81,0x9B,0x59, +0x01,0x93,0xCA,0x4A,0x21,0xA0,0x3D,0x0A,0x39,0x3D,0x12,0xA8,0x3F,0x18,0x01,0x92, +0x1C,0x00,0xB2,0x48,0xB9,0x94,0xA3,0x19,0x4F,0x19,0xB2,0x32,0x90,0xBA,0x01,0xE6, +0x91,0x80,0xC1,0xA4,0x2A,0x08,0xA1,0xB1,0x25,0xD2,0x88,0x99,0x21,0x80,0x88,0x80, +}; + + + +/* flag enable control 0x110 */ +INLINE void YM2608IRQFlagWrite(FM_OPN *OPN, YM2608 *F2608, int v) +{ + if( v & 0x80 ) + { /* Reset IRQ flag */ + FM_STATUS_RESET(&OPN->ST, 0xf7); /* don't touch BUFRDY flag otherwise we'd have to call ymdeltat module to set the flag back */ + } + else + { /* Set status flag mask */ + F2608->flagmask = (~(v&0x1f)); + FM_IRQMASK_SET(&OPN->ST, (F2608->irqmask & F2608->flagmask) ); + } +} + +/* compatible mode & IRQ enable control 0x29 */ +INLINE void YM2608IRQMaskWrite(FM_OPN *OPN, YM2608 *F2608, int v) +{ + /* SCH,xx,xxx,EN_ZERO,EN_BRDY,EN_EOS,EN_TB,EN_TA */ + + /* extend 3ch. enable/disable */ + if(v&0x80) + OPN->type |= TYPE_6CH; /* OPNA mode - 6 FM channels */ + else + OPN->type &= ~TYPE_6CH; /* OPN mode - 3 FM channels */ + + /* IRQ MASK store and set */ + F2608->irqmask = v&0x1f; + FM_IRQMASK_SET(&OPN->ST, (F2608->irqmask & F2608->flagmask) ); +} + +/* Generate samples for one of the YM2608s */ +void ym2608_update_one(void *chip, FMSAMPLE **buffer, int length) +{ + YM2608 *F2608 = (YM2608 *)chip; + FM_OPN *OPN = &F2608->OPN; + YM_DELTAT *DELTAT = &F2608->deltaT; + int i,j; + FMSAMPLE *bufL,*bufR; + FM_CH *cch[6]; + INT32 *out_fm = OPN->out_fm; + + /* set bufer */ + bufL = buffer[0]; + bufR = buffer[1]; + + cch[0] = &F2608->CH[0]; + cch[1] = &F2608->CH[1]; + cch[2] = &F2608->CH[2]; + cch[3] = &F2608->CH[3]; + cch[4] = &F2608->CH[4]; + cch[5] = &F2608->CH[5]; + + /* refresh PG and EG */ + refresh_fc_eg_chan( OPN, cch[0] ); + refresh_fc_eg_chan( OPN, cch[1] ); + if( (OPN->ST.mode & 0xc0) ) + { + /* 3SLOT MODE */ + if( cch[2]->SLOT[SLOT1].Incr==-1) + { + refresh_fc_eg_slot(OPN, &cch[2]->SLOT[SLOT1] , OPN->SL3.fc[1] , OPN->SL3.kcode[1] ); + refresh_fc_eg_slot(OPN, &cch[2]->SLOT[SLOT2] , OPN->SL3.fc[2] , OPN->SL3.kcode[2] ); + refresh_fc_eg_slot(OPN, &cch[2]->SLOT[SLOT3] , OPN->SL3.fc[0] , OPN->SL3.kcode[0] ); + refresh_fc_eg_slot(OPN, &cch[2]->SLOT[SLOT4] , cch[2]->fc , cch[2]->kcode ); + } + } + else + refresh_fc_eg_chan( OPN, cch[2] ); + refresh_fc_eg_chan( OPN, cch[3] ); + refresh_fc_eg_chan( OPN, cch[4] ); + refresh_fc_eg_chan( OPN, cch[5] ); + + + /* buffering */ + for(i=0; i < length ; i++) + { + + advance_lfo(OPN); + + /* clear output acc. */ + OPN->out_adpcm[OUTD_LEFT] = OPN->out_adpcm[OUTD_RIGHT] = OPN->out_adpcm[OUTD_CENTER] = 0; + OPN->out_delta[OUTD_LEFT] = OPN->out_delta[OUTD_RIGHT] = OPN->out_delta[OUTD_CENTER] = 0; + /* clear outputs */ + out_fm[0] = 0; + out_fm[1] = 0; + out_fm[2] = 0; + out_fm[3] = 0; + out_fm[4] = 0; + out_fm[5] = 0; + + /* calculate FM */ + chan_calc(OPN, cch[0], 0 ); + chan_calc(OPN, cch[1], 1 ); + chan_calc(OPN, cch[2], 2 ); + chan_calc(OPN, cch[3], 3 ); + chan_calc(OPN, cch[4], 4 ); + chan_calc(OPN, cch[5], 5 ); + + /* deltaT ADPCM */ + if( DELTAT->portstate&0x80 && ! F2608->MuteDeltaT ) + YM_DELTAT_ADPCM_CALC(DELTAT); + + /* ADPCMA */ + for( j = 0; j < 6; j++ ) + { + if( F2608->adpcm[j].flag ) + ADPCMA_calc_chan( F2608, &F2608->adpcm[j]); + } + + /* advance envelope generator */ + OPN->eg_timer += OPN->eg_timer_add; + while (OPN->eg_timer >= OPN->eg_timer_overflow) + { + OPN->eg_timer -= OPN->eg_timer_overflow; + OPN->eg_cnt++; + + advance_eg_channel(OPN, &cch[0]->SLOT[SLOT1]); + advance_eg_channel(OPN, &cch[1]->SLOT[SLOT1]); + advance_eg_channel(OPN, &cch[2]->SLOT[SLOT1]); + advance_eg_channel(OPN, &cch[3]->SLOT[SLOT1]); + advance_eg_channel(OPN, &cch[4]->SLOT[SLOT1]); + advance_eg_channel(OPN, &cch[5]->SLOT[SLOT1]); + } + + /* buffering */ + { + int lt,rt; + + /*lt = OPN->out_adpcm[OUTD_LEFT] + OPN->out_adpcm[OUTD_CENTER]; + rt = OPN->out_adpcm[OUTD_RIGHT] + OPN->out_adpcm[OUTD_CENTER]; + lt += (OPN->out_delta[OUTD_LEFT] + OPN->out_delta[OUTD_CENTER])>>9; + rt += (OPN->out_delta[OUTD_RIGHT] + OPN->out_delta[OUTD_CENTER])>>9; + lt += ((out_fm[0]>>1) & OPN->pan[0]); // shift right verified on real YM2608 + rt += ((out_fm[0]>>1) & OPN->pan[1]); + lt += ((out_fm[1]>>1) & OPN->pan[2]); + rt += ((out_fm[1]>>1) & OPN->pan[3]); + lt += ((out_fm[2]>>1) & OPN->pan[4]); + rt += ((out_fm[2]>>1) & OPN->pan[5]); + lt += ((out_fm[3]>>1) & OPN->pan[6]); + rt += ((out_fm[3]>>1) & OPN->pan[7]); + lt += ((out_fm[4]>>1) & OPN->pan[8]); + rt += ((out_fm[4]>>1) & OPN->pan[9]); + lt += ((out_fm[5]>>1) & OPN->pan[10]); + rt += ((out_fm[5]>>1) & OPN->pan[11]);*/ + // this way it's louder (and more accurate) + lt = (OPN->out_adpcm[OUTD_LEFT] + OPN->out_adpcm[OUTD_CENTER]) << 1; + rt = (OPN->out_adpcm[OUTD_RIGHT] + OPN->out_adpcm[OUTD_CENTER]) << 1; + lt += (OPN->out_delta[OUTD_LEFT] + OPN->out_delta[OUTD_CENTER])>>8; + rt += (OPN->out_delta[OUTD_RIGHT] + OPN->out_delta[OUTD_CENTER])>>8; + lt += (out_fm[0] & OPN->pan[0]); + rt += (out_fm[0] & OPN->pan[1]); + lt += (out_fm[1] & OPN->pan[2]); + rt += (out_fm[1] & OPN->pan[3]); + lt += (out_fm[2] & OPN->pan[4]); + rt += (out_fm[2] & OPN->pan[5]); + lt += (out_fm[3] & OPN->pan[6]); + rt += (out_fm[3] & OPN->pan[7]); + lt += (out_fm[4] & OPN->pan[8]); + rt += (out_fm[4] & OPN->pan[9]); + lt += (out_fm[5] & OPN->pan[10]); + rt += (out_fm[5] & OPN->pan[11]); + + lt >>= FINAL_SH; + rt >>= FINAL_SH; + + //Limit( lt, MAXOUT, MINOUT ); + //Limit( rt, MAXOUT, MINOUT ); + /* buffering */ + bufL[i] = lt; + bufR[i] = rt; + + #ifdef SAVE_SAMPLE + SAVE_ALL_CHANNELS + #endif + + } + + /* timer A control */ + INTERNAL_TIMER_A( &OPN->ST , cch[2] ) + } + INTERNAL_TIMER_B(&OPN->ST,length) + + + /* check IRQ for DELTA-T EOS */ + FM_STATUS_SET(&OPN->ST, 0); + +} + +static void YM2608_deltat_status_set(void *chip, UINT8 changebits) +{ + YM2608 *F2608 = (YM2608 *)chip; + FM_STATUS_SET(&(F2608->OPN.ST), changebits); +} +static void YM2608_deltat_status_reset(void *chip, UINT8 changebits) +{ + YM2608 *F2608 = (YM2608 *)chip; + FM_STATUS_RESET(&(F2608->OPN.ST), changebits); +} +/* YM2608(OPNA) */ +//void * ym2608_init(void *param, const device_config *device, int clock, int rate, +// void *pcmrom,int pcmsize, +// FM_TIMERHANDLER timer_handler,FM_IRQHANDLER IRQHandler, const ssg_callbacks *ssg) +void * ym2608_init(void *param, int clock, int rate, const ssg_callbacks *ssg) +{ + YM2608 *F2608; + + /* allocate extend state space */ + if( (F2608 = (YM2608 *)malloc(sizeof(YM2608)))==NULL) + return NULL; + /* clear */ + memset(F2608,0,sizeof(YM2608)); + /* allocate total level table (128kb space) */ + if( !init_tables() ) + { + free( F2608 ); + return NULL; + } + + F2608->OPN.ST.param = param; + F2608->OPN.type = TYPE_YM2608; + F2608->OPN.P_CH = F2608->CH; + //F2608->OPN.ST.device = device; + F2608->OPN.ST.clock = clock; + F2608->OPN.ST.rate = rate; + + /* External handlers */ + F2608->OPN.ST.SSG = ssg; + + /* DELTA-T */ + //F2608->deltaT.memory = (UINT8 *)pcmrom; + //F2608->deltaT.memory_size = pcmsize; + F2608->deltaT.memory = NULL; + F2608->deltaT.memory_size = 0x00; + + /*F2608->deltaT.write_time = 20.0 / clock;*/ /* a single byte write takes 20 cycles of main clock */ + /*F2608->deltaT.read_time = 18.0 / clock;*/ /* a single byte read takes 18 cycles of main clock */ + + F2608->deltaT.status_set_handler = YM2608_deltat_status_set; + F2608->deltaT.status_reset_handler = YM2608_deltat_status_reset; + F2608->deltaT.status_change_which_chip = F2608; + F2608->deltaT.status_change_EOS_bit = 0x04; /* status flag: set bit2 on End Of Sample */ + F2608->deltaT.status_change_BRDY_bit = 0x08; /* status flag: set bit3 on BRDY */ + F2608->deltaT.status_change_ZERO_bit = 0x10; /* status flag: set bit4 if silence continues for more than 290 miliseconds while recording the ADPCM */ + + /* ADPCM Rhythm */ + F2608->pcmbuf = (UINT8*)YM2608_ADPCM_ROM; + F2608->pcm_size = 0x2000; + + Init_ADPCMATable(); + +#ifdef __STATE_H__ + YM2608_save_state(F2608, device); +#endif + return F2608; +} + +/* shut down emulator */ +void ym2608_shutdown(void *chip) +{ + YM2608 *F2608 = (YM2608 *)chip; + + free(F2608->deltaT.memory); F2608->deltaT.memory = NULL; + + FMCloseTable(); + free(F2608); +} + +/* reset one of chips */ +void ym2608_reset_chip(void *chip) +{ + int i; + YM2608 *F2608 = (YM2608 *)chip; + FM_OPN *OPN = &F2608->OPN; + YM_DELTAT *DELTAT = &F2608->deltaT; + + /* Reset Prescaler */ + OPNPrescaler_w(OPN , 0 , 2); + F2608->deltaT.freqbase = OPN->ST.freqbase; + /* reset SSG section */ + (*OPN->ST.SSG->reset)(OPN->ST.param); + + /* status clear */ + FM_BUSY_CLEAR(&OPN->ST); + + /* register 0x29 - default value after reset is: + enable only 3 FM channels and enable all the status flags */ + YM2608IRQMaskWrite(OPN, F2608, 0x1f ); /* default value for D4-D0 is 1 */ + + /* register 0x10, A1=1 - default value is 1 for D4, D3, D2, 0 for the rest */ + YM2608IRQFlagWrite(OPN, F2608, 0x1c ); /* default: enable timer A and B, disable EOS, BRDY and ZERO */ + + OPNWriteMode(OPN,0x27,0x30); /* mode 0 , timer reset */ + + OPN->eg_timer = 0; + OPN->eg_cnt = 0; + + FM_STATUS_RESET(&OPN->ST, 0xff); + + reset_channels( &OPN->ST , F2608->CH , 6 ); + /* reset OPerator paramater */ + for(i = 0xb6 ; i >= 0xb4 ; i-- ) + { + OPNWriteReg(OPN,i ,0xc0); + OPNWriteReg(OPN,i|0x100,0xc0); + } + for(i = 0xb2 ; i >= 0x30 ; i-- ) + { + OPNWriteReg(OPN,i ,0); + OPNWriteReg(OPN,i|0x100,0); + } + for(i = 0x26 ; i >= 0x20 ; i-- ) OPNWriteReg(OPN,i,0); + + /* ADPCM - percussion sounds */ + for( i = 0; i < 6; i++ ) + { + if (i<=3) /* channels 0,1,2,3 */ + F2608->adpcm[i].step = (UINT32)((float)(1<OPN.ST.freqbase)/3.0); + else /* channels 4 and 5 work with slower clock */ + F2608->adpcm[i].step = (UINT32)((float)(1<OPN.ST.freqbase)/6.0); + + F2608->adpcm[i].start = YM2608_ADPCM_ROM_addr[i*2]; + F2608->adpcm[i].end = YM2608_ADPCM_ROM_addr[i*2+1]; + + F2608->adpcm[i].now_addr = 0; + F2608->adpcm[i].now_step = 0; + /* F2608->adpcm[i].delta = 21866; */ + F2608->adpcm[i].vol_mul = 0; + F2608->adpcm[i].pan = &OPN->out_adpcm[OUTD_CENTER]; /* default center */ + F2608->adpcm[i].flagMask = 0; + F2608->adpcm[i].flag = 0; + F2608->adpcm[i].adpcm_acc = 0; + F2608->adpcm[i].adpcm_step= 0; + F2608->adpcm[i].adpcm_out = 0; + } + F2608->adpcmTL = 0x3f; + + F2608->adpcm_arrivedEndAddress = 0; /* not used */ + + /* DELTA-T unit */ + DELTAT->freqbase = OPN->ST.freqbase; + DELTAT->output_pointer = OPN->out_delta; + DELTAT->portshift = 5; /* always 5bits shift */ /* ASG */ + DELTAT->output_range = 1<<23; + YM_DELTAT_ADPCM_Reset(DELTAT,OUTD_CENTER,YM_DELTAT_EMULATION_MODE_NORMAL); +} + +/* YM2608 write */ +/* n = number */ +/* a = address */ +/* v = value */ +int ym2608_write(void *chip, int a,UINT8 v) +{ + YM2608 *F2608 = (YM2608 *)chip; + FM_OPN *OPN = &F2608->OPN; + int addr; + + v &= 0xff; /*adjust to 8 bit bus */ + + + switch(a&3) + { + case 0: /* address port 0 */ + OPN->ST.address = v; + F2608->addr_A1 = 0; + + /* Write register to SSG emulator */ + if( v < 16 ) (*OPN->ST.SSG->write)(OPN->ST.param,0,v); + /* prescaler selecter : 2d,2e,2f */ + if( v >= 0x2d && v <= 0x2f ) + { + OPNPrescaler_w(OPN , v , 2); + F2608->deltaT.freqbase = OPN->ST.freqbase; + } + break; + + case 1: /* data port 0 */ + if (F2608->addr_A1 != 0) + break; /* verified on real YM2608 */ + + addr = OPN->ST.address; + F2608->REGS[addr] = v; + switch(addr & 0xf0) + { + case 0x00: /* SSG section */ + /* Write data to SSG emulator */ + (*OPN->ST.SSG->write)(OPN->ST.param,a,v); + break; + case 0x10: /* 0x10-0x1f : Rhythm section */ + FM_ADPCMAWrite(F2608,addr-0x10,v); + break; + case 0x20: /* Mode Register */ + switch(addr) + { + case 0x29: /* SCH,xx,xxx,EN_ZERO,EN_BRDY,EN_EOS,EN_TB,EN_TA */ + YM2608IRQMaskWrite(OPN, F2608, v); + break; + default: + OPNWriteMode(OPN,addr,v); + } + break; + default: /* OPN section */ + OPNWriteReg(OPN,addr,v); + } + break; + + case 2: /* address port 1 */ + OPN->ST.address = v; + F2608->addr_A1 = 1; + break; + + case 3: /* data port 1 */ + if (F2608->addr_A1 != 1) + break; /* verified on real YM2608 */ + + addr = OPN->ST.address; + F2608->REGS[addr | 0x100] = v; + switch( addr & 0xf0 ) + { + case 0x00: /* DELTAT PORT */ + switch( addr ) + { + case 0x0e: /* DAC data */ +#ifdef _DEBUG + logerror("YM2608: write to DAC data (unimplemented) value=%02x\n",v); +#endif + break; + default: + /* 0x00-0x0d */ + YM_DELTAT_ADPCM_Write(&F2608->deltaT,addr,v); + } + break; + case 0x10: /* IRQ Flag control */ + if( addr == 0x10 ) + { + YM2608IRQFlagWrite(OPN, F2608, v); + } + break; + default: + OPNWriteReg(OPN,addr | 0x100,v); + } + } + return OPN->ST.irq; +} + +UINT8 ym2608_read(void *chip,int a) +{ + YM2608 *F2608 = (YM2608 *)chip; + int addr = F2608->OPN.ST.address; + UINT8 ret = 0; + + switch( a&3 ) + { + case 0: /* status 0 : YM2203 compatible */ + /* BUSY:x:x:x:x:x:FLAGB:FLAGA */ + ret = FM_STATUS_FLAG(&F2608->OPN.ST) & 0x83; + break; + + case 1: /* status 0, ID */ + if( addr < 16 ) ret = (*F2608->OPN.ST.SSG->read)(F2608->OPN.ST.param); + else if(addr == 0xff) ret = 0x01; /* ID code */ + break; + + case 2: /* status 1 : status 0 + ADPCM status */ + /* BUSY : x : PCMBUSY : ZERO : BRDY : EOS : FLAGB : FLAGA */ + ret = (FM_STATUS_FLAG(&F2608->OPN.ST) & (F2608->flagmask|0x80)) | ((F2608->deltaT.PCM_BSY & 1)<<5) ; + break; + + case 3: + if(addr == 0x08) + { + ret = YM_DELTAT_ADPCM_Read(&F2608->deltaT); + } + else + { + if(addr == 0x0f) + { +#ifdef _DEBUG + logerror("YM2608 A/D convertion is accessed but not implemented !\n"); +#endif + ret = 0x80; /* 2's complement PCM data - result from A/D convertion */ + } + } + break; + } + return ret; +} + +int ym2608_timer_over(void *chip,int c) +{ + YM2608 *F2608 = (YM2608 *)chip; + + switch(c) + { +#if 0 + case 2: + { /* BUFRDY flag */ + YM_DELTAT_BRDY_callback( &F2608->deltaT ); + } + break; +#endif + case 1: + { /* Timer B */ + TimerBOver( &(F2608->OPN.ST) ); + } + break; + case 0: + { /* Timer A */ + /* timer update */ + TimerAOver( &(F2608->OPN.ST) ); + /* CSM mode key,TL controll */ + if( F2608->OPN.ST.mode & 0x80 ) + { /* CSM mode total level latch and auto key on */ + CSMKeyControll( F2608->OPN.type, &(F2608->CH[2]) ); + } + } + break; + default: + break; + } + + return F2608->OPN.ST.irq; +} + +void ym2608_write_pcmrom(void *chip, UINT8 rom_id, offs_t ROMSize, offs_t DataStart, + offs_t DataLength, const UINT8* ROMData) +{ + YM2608 *F2608 = (YM2608 *)chip; + + switch(rom_id) + { + case 0x01: // ADPCM + // unused, it's constant + break; + case 0x02: // DELTA-T + if (F2608->deltaT.memory_size != ROMSize) + { + F2608->deltaT.memory = (UINT8*)realloc(F2608->deltaT.memory, ROMSize); + F2608->deltaT.memory_size = ROMSize; + memset(F2608->deltaT.memory, 0xFF, ROMSize); + } + if (DataStart > ROMSize) + return; + if (DataStart + DataLength > ROMSize) + DataLength = ROMSize - DataStart; + + memcpy(F2608->deltaT.memory + DataStart, ROMData, DataLength); + break; + } + + return; +} + +void ym2608_set_mutemask(void *chip, UINT32 MuteMask) +{ + YM2608 *F2608 = (YM2608 *)chip; + UINT8 CurChn; + + for (CurChn = 0; CurChn < 6; CurChn ++) + F2608->CH[CurChn].Muted = (MuteMask >> CurChn) & 0x01; + for (CurChn = 0; CurChn < 6; CurChn ++) + F2608->adpcm[CurChn].Muted = (MuteMask >> (CurChn + 6)) & 0x01; + F2608->MuteDeltaT = (MuteMask >> 12) & 0x01; + + return; +} +#endif /* BUILD_YM2608 */ + + + +#if (BUILD_YM2610||BUILD_YM2610B) +/* YM2610(OPNB) */ + +/* Generate samples for one of the YM2610s */ +void ym2610_update_one(void *chip, FMSAMPLE **buffer, int length) +{ + YM2610 *F2610 = (YM2610 *)chip; + FM_OPN *OPN = &F2610->OPN; + YM_DELTAT *DELTAT = &F2610->deltaT; + int i,j; + FMSAMPLE *bufL,*bufR; + FM_CH *cch[4]; + INT32 *out_fm = OPN->out_fm; + + /* buffer setup */ + bufL = buffer[0]; + bufR = buffer[1]; + + cch[0] = &F2610->CH[1]; + cch[1] = &F2610->CH[2]; + cch[2] = &F2610->CH[4]; + cch[3] = &F2610->CH[5]; + +#ifdef YM2610B_WARNING +#define FM_KEY_IS(SLOT) ((SLOT)->key) +#define FM_MSG_YM2610B "YM2610-%p.CH%d is playing,Check whether the type of the chip is YM2610B\n" + /* Check YM2610B warning message */ + if( FM_KEY_IS(&F2610->CH[0].SLOT[3]) ) + { + LOG(LOG_WAR,(FM_MSG_YM2610B,F2610->OPN.ST.param,0)); + FM_KEY_IS(&F2610->CH[3].SLOT[3]) = 0; + } + if( FM_KEY_IS(&F2610->CH[3].SLOT[3]) ) + { + LOG(LOG_WAR,(FM_MSG_YM2610B,F2610->OPN.ST.param,3)); + FM_KEY_IS(&F2610->CH[3].SLOT[3]) = 0; + } +#endif + + /* refresh PG and EG */ + refresh_fc_eg_chan( OPN, cch[0] ); + if( (OPN->ST.mode & 0xc0) ) + { + /* 3SLOT MODE */ + if( cch[1]->SLOT[SLOT1].Incr==-1) + { + refresh_fc_eg_slot(OPN, &cch[1]->SLOT[SLOT1] , OPN->SL3.fc[1] , OPN->SL3.kcode[1] ); + refresh_fc_eg_slot(OPN, &cch[1]->SLOT[SLOT2] , OPN->SL3.fc[2] , OPN->SL3.kcode[2] ); + refresh_fc_eg_slot(OPN, &cch[1]->SLOT[SLOT3] , OPN->SL3.fc[0] , OPN->SL3.kcode[0] ); + refresh_fc_eg_slot(OPN, &cch[1]->SLOT[SLOT4] , cch[1]->fc , cch[1]->kcode ); + } + } + else + refresh_fc_eg_chan( OPN, cch[1] ); + refresh_fc_eg_chan( OPN, cch[2] ); + refresh_fc_eg_chan( OPN, cch[3] ); + + /* buffering */ + for(i=0; i < length ; i++) + { + + advance_lfo(OPN); + + /* clear output acc. */ + OPN->out_adpcm[OUTD_LEFT] = OPN->out_adpcm[OUTD_RIGHT] = OPN->out_adpcm[OUTD_CENTER] = 0; + OPN->out_delta[OUTD_LEFT] = OPN->out_delta[OUTD_RIGHT] = OPN->out_delta[OUTD_CENTER] = 0; + /* clear outputs */ + out_fm[1] = 0; + out_fm[2] = 0; + out_fm[4] = 0; + out_fm[5] = 0; + + /* advance envelope generator */ + OPN->eg_timer += OPN->eg_timer_add; + while (OPN->eg_timer >= OPN->eg_timer_overflow) + { + OPN->eg_timer -= OPN->eg_timer_overflow; + OPN->eg_cnt++; + + advance_eg_channel(OPN, &cch[0]->SLOT[SLOT1]); + advance_eg_channel(OPN, &cch[1]->SLOT[SLOT1]); + advance_eg_channel(OPN, &cch[2]->SLOT[SLOT1]); + advance_eg_channel(OPN, &cch[3]->SLOT[SLOT1]); + } + + /* calculate FM */ + chan_calc(OPN, cch[0], 1 ); /*remapped to 1*/ + chan_calc(OPN, cch[1], 2 ); /*remapped to 2*/ + chan_calc(OPN, cch[2], 4 ); /*remapped to 4*/ + chan_calc(OPN, cch[3], 5 ); /*remapped to 5*/ + + /* deltaT ADPCM */ + if( DELTAT->portstate&0x80 && ! F2610->MuteDeltaT ) + YM_DELTAT_ADPCM_CALC(DELTAT); + + /* ADPCMA */ + for( j = 0; j < 6; j++ ) + { + if( F2610->adpcm[j].flag ) + ADPCMA_calc_chan( F2610, &F2610->adpcm[j]); + } + + /* buffering */ + { + int lt,rt; + + /*lt = OPN->out_adpcm[OUTD_LEFT] + OPN->out_adpcm[OUTD_CENTER]; + rt = OPN->out_adpcm[OUTD_RIGHT] + OPN->out_adpcm[OUTD_CENTER]; + lt += (OPN->out_delta[OUTD_LEFT] + OPN->out_delta[OUTD_CENTER])>>9; + rt += (OPN->out_delta[OUTD_RIGHT] + OPN->out_delta[OUTD_CENTER])>>9; + + + lt += ((out_fm[1]>>1) & OPN->pan[2]); // the shift right was verified on real chip + rt += ((out_fm[1]>>1) & OPN->pan[3]); + lt += ((out_fm[2]>>1) & OPN->pan[4]); + rt += ((out_fm[2]>>1) & OPN->pan[5]); + + lt += ((out_fm[4]>>1) & OPN->pan[8]); + rt += ((out_fm[4]>>1) & OPN->pan[9]); + lt += ((out_fm[5]>>1) & OPN->pan[10]); + rt += ((out_fm[5]>>1) & OPN->pan[11]);*/ + + lt = (OPN->out_adpcm[OUTD_LEFT] + OPN->out_adpcm[OUTD_CENTER]) << 1; + rt = (OPN->out_adpcm[OUTD_RIGHT] + OPN->out_adpcm[OUTD_CENTER]) << 1; + lt += (OPN->out_delta[OUTD_LEFT] + OPN->out_delta[OUTD_CENTER])>>8; + rt += (OPN->out_delta[OUTD_RIGHT] + OPN->out_delta[OUTD_CENTER])>>8; + + + lt += (out_fm[1] & OPN->pan[2]); + rt += (out_fm[1] & OPN->pan[3]); + lt += (out_fm[2] & OPN->pan[4]); + rt += (out_fm[2] & OPN->pan[5]); + + lt += (out_fm[4] & OPN->pan[8]); + rt += (out_fm[4] & OPN->pan[9]); + lt += (out_fm[5] & OPN->pan[10]); + rt += (out_fm[5] & OPN->pan[11]); + + + lt >>= FINAL_SH; + rt >>= FINAL_SH; + + //Limit( lt, MAXOUT, MINOUT ); + //Limit( rt, MAXOUT, MINOUT ); + + #ifdef SAVE_SAMPLE + SAVE_ALL_CHANNELS + #endif + + /* buffering */ + bufL[i] = lt; + bufR[i] = rt; + } + + /* timer A control */ + INTERNAL_TIMER_A( &OPN->ST , cch[1] ) + } + INTERNAL_TIMER_B(&OPN->ST,length) + +} + +#if BUILD_YM2610B +/* Generate samples for one of the YM2610Bs */ +void ym2610b_update_one(void *chip, FMSAMPLE **buffer, int length) +{ + YM2610 *F2610 = (YM2610 *)chip; + FM_OPN *OPN = &F2610->OPN; + YM_DELTAT *DELTAT = &F2610->deltaT; + int i,j; + FMSAMPLE *bufL,*bufR; + FM_CH *cch[6]; + INT32 *out_fm = OPN->out_fm; + + /* buffer setup */ + bufL = buffer[0]; + bufR = buffer[1]; + + cch[0] = &F2610->CH[0]; + cch[1] = &F2610->CH[1]; + cch[2] = &F2610->CH[2]; + cch[3] = &F2610->CH[3]; + cch[4] = &F2610->CH[4]; + cch[5] = &F2610->CH[5]; + + /* refresh PG and EG */ + refresh_fc_eg_chan( OPN, cch[0] ); + refresh_fc_eg_chan( OPN, cch[1] ); + if( (OPN->ST.mode & 0xc0) ) + { + /* 3SLOT MODE */ + if( cch[2]->SLOT[SLOT1].Incr==-1) + { + refresh_fc_eg_slot(OPN, &cch[2]->SLOT[SLOT1] , OPN->SL3.fc[1] , OPN->SL3.kcode[1] ); + refresh_fc_eg_slot(OPN, &cch[2]->SLOT[SLOT2] , OPN->SL3.fc[2] , OPN->SL3.kcode[2] ); + refresh_fc_eg_slot(OPN, &cch[2]->SLOT[SLOT3] , OPN->SL3.fc[0] , OPN->SL3.kcode[0] ); + refresh_fc_eg_slot(OPN, &cch[2]->SLOT[SLOT4] , cch[2]->fc , cch[2]->kcode ); + } + } + else + refresh_fc_eg_chan( OPN, cch[2] ); + refresh_fc_eg_chan( OPN, cch[3] ); + refresh_fc_eg_chan( OPN, cch[4] ); + refresh_fc_eg_chan( OPN, cch[5] ); + + /* buffering */ + for(i=0; i < length ; i++) + { + + advance_lfo(OPN); + + /* clear output acc. */ + OPN->out_adpcm[OUTD_LEFT] = OPN->out_adpcm[OUTD_RIGHT] = OPN->out_adpcm[OUTD_CENTER] = 0; + OPN->out_delta[OUTD_LEFT] = OPN->out_delta[OUTD_RIGHT] = OPN->out_delta[OUTD_CENTER] = 0; + /* clear outputs */ + out_fm[0] = 0; + out_fm[1] = 0; + out_fm[2] = 0; + out_fm[3] = 0; + out_fm[4] = 0; + out_fm[5] = 0; + + /* advance envelope generator */ + OPN->eg_timer += OPN->eg_timer_add; + while (OPN->eg_timer >= OPN->eg_timer_overflow) + { + OPN->eg_timer -= OPN->eg_timer_overflow; + OPN->eg_cnt++; + + advance_eg_channel(OPN, &cch[0]->SLOT[SLOT1]); + advance_eg_channel(OPN, &cch[1]->SLOT[SLOT1]); + advance_eg_channel(OPN, &cch[2]->SLOT[SLOT1]); + advance_eg_channel(OPN, &cch[3]->SLOT[SLOT1]); + advance_eg_channel(OPN, &cch[4]->SLOT[SLOT1]); + advance_eg_channel(OPN, &cch[5]->SLOT[SLOT1]); + } + + /* calculate FM */ + chan_calc(OPN, cch[0], 0 ); + chan_calc(OPN, cch[1], 1 ); + chan_calc(OPN, cch[2], 2 ); + chan_calc(OPN, cch[3], 3 ); + chan_calc(OPN, cch[4], 4 ); + chan_calc(OPN, cch[5], 5 ); + + /* deltaT ADPCM */ + if( DELTAT->portstate&0x80 && ! F2610->MuteDeltaT ) + YM_DELTAT_ADPCM_CALC(DELTAT); + + /* ADPCMA */ + for( j = 0; j < 6; j++ ) + { + if( F2610->adpcm[j].flag ) + ADPCMA_calc_chan( F2610, &F2610->adpcm[j]); + } + + /* buffering */ + { + int lt,rt; + + /*lt = OPN->out_adpcm[OUTD_LEFT] + OPN->out_adpcm[OUTD_CENTER]; + rt = OPN->out_adpcm[OUTD_RIGHT] + OPN->out_adpcm[OUTD_CENTER]; + lt += (OPN->out_delta[OUTD_LEFT] + OPN->out_delta[OUTD_CENTER])>>9; + rt += (OPN->out_delta[OUTD_RIGHT] + OPN->out_delta[OUTD_CENTER])>>9; + + lt += ((out_fm[0]>>1) & OPN->pan[0]); // the shift right is verified on YM2610 + rt += ((out_fm[0]>>1) & OPN->pan[1]); + lt += ((out_fm[1]>>1) & OPN->pan[2]); + rt += ((out_fm[1]>>1) & OPN->pan[3]); + lt += ((out_fm[2]>>1) & OPN->pan[4]); + rt += ((out_fm[2]>>1) & OPN->pan[5]); + lt += ((out_fm[3]>>1) & OPN->pan[6]); + rt += ((out_fm[3]>>1) & OPN->pan[7]); + lt += ((out_fm[4]>>1) & OPN->pan[8]); + rt += ((out_fm[4]>>1) & OPN->pan[9]); + lt += ((out_fm[5]>>1) & OPN->pan[10]); + rt += ((out_fm[5]>>1) & OPN->pan[11]);*/ + lt = (OPN->out_adpcm[OUTD_LEFT] + OPN->out_adpcm[OUTD_CENTER]) << 1; + rt = (OPN->out_adpcm[OUTD_RIGHT] + OPN->out_adpcm[OUTD_CENTER]) << 1; + lt += (OPN->out_delta[OUTD_LEFT] + OPN->out_delta[OUTD_CENTER])>>8; + rt += (OPN->out_delta[OUTD_RIGHT] + OPN->out_delta[OUTD_CENTER])>>8; + + lt += (out_fm[0] & OPN->pan[0]); + rt += (out_fm[0] & OPN->pan[1]); + lt += (out_fm[1] & OPN->pan[2]); + rt += (out_fm[1] & OPN->pan[3]); + lt += (out_fm[2] & OPN->pan[4]); + rt += (out_fm[2] & OPN->pan[5]); + lt += (out_fm[3] & OPN->pan[6]); + rt += (out_fm[3] & OPN->pan[7]); + lt += (out_fm[4] & OPN->pan[8]); + rt += (out_fm[4] & OPN->pan[9]); + lt += (out_fm[5] & OPN->pan[10]); + rt += (out_fm[5] & OPN->pan[11]); + + + lt >>= FINAL_SH; + rt >>= FINAL_SH; + + //Limit( lt, MAXOUT, MINOUT ); + //Limit( rt, MAXOUT, MINOUT ); + + #ifdef SAVE_SAMPLE + SAVE_ALL_CHANNELS + #endif + + /* buffering */ + bufL[i] = lt; + bufR[i] = rt; + } + + /* timer A control */ + INTERNAL_TIMER_A( &OPN->ST , cch[2] ) + } + INTERNAL_TIMER_B(&OPN->ST,length) + +} +#endif /* BUILD_YM2610B */ + + +static void YM2610_deltat_status_set(void *chip, UINT8 changebits) +{ + YM2610 *F2610 = (YM2610 *)chip; + F2610->adpcm_arrivedEndAddress |= changebits; +} +static void YM2610_deltat_status_reset(void *chip, UINT8 changebits) +{ + YM2610 *F2610 = (YM2610 *)chip; + F2610->adpcm_arrivedEndAddress &= (~changebits); +} + +//void *ym2610_init(void *param, const device_config *device, int clock, int rate, +// void *pcmroma,int pcmsizea,void *pcmromb,int pcmsizeb, +// FM_TIMERHANDLER timer_handler,FM_IRQHANDLER IRQHandler, const ssg_callbacks *ssg) +void *ym2610_init(void *param, int clock, int rate, const ssg_callbacks *ssg) +{ + YM2610 *F2610; + + /* allocate extend state space */ + if( (F2610 = (YM2610 *)malloc(sizeof(YM2610)))==NULL) + return NULL; + /* clear */ + memset(F2610,0,sizeof(YM2610)); + /* allocate total level table (128kb space) */ + if( !init_tables() ) + { + free( F2610 ); + return NULL; + } + + /* FM */ + F2610->OPN.ST.param = param; + F2610->OPN.type = TYPE_YM2610; + F2610->OPN.P_CH = F2610->CH; + //F2610->OPN.ST.device = device; + F2610->OPN.ST.clock = clock; + F2610->OPN.ST.rate = rate; + /* Extend handler */ + F2610->OPN.ST.SSG = ssg; + /* ADPCM */ + //F2610->pcmbuf = (const UINT8 *)pcmroma; + //F2610->pcm_size = pcmsizea; + F2610->pcmbuf = NULL; + F2610->pcm_size = 0x00; + /* DELTA-T */ + //F2610->deltaT.memory = (UINT8 *)pcmromb; + //F2610->deltaT.memory_size = pcmsizeb; + F2610->deltaT.memory = NULL; + F2610->deltaT.memory_size = 0x00; + + F2610->deltaT.status_set_handler = YM2610_deltat_status_set; + F2610->deltaT.status_reset_handler = YM2610_deltat_status_reset; + F2610->deltaT.status_change_which_chip = F2610; + F2610->deltaT.status_change_EOS_bit = 0x80; /* status flag: set bit7 on End Of Sample */ + + Init_ADPCMATable(); +#ifdef __STATE_H__ + YM2610_save_state(F2610, device); +#endif + return F2610; +} + +/* shut down emulator */ +void ym2610_shutdown(void *chip) +{ + YM2610 *F2610 = (YM2610 *)chip; + + free(F2610->pcmbuf); F2610->pcmbuf = NULL; + free(F2610->deltaT.memory); F2610->deltaT.memory = NULL; + + FMCloseTable(); + free(F2610); +} + +/* reset one of chip */ +void ym2610_reset_chip(void *chip) +{ + int i; + YM2610 *F2610 = (YM2610 *)chip; + FM_OPN *OPN = &F2610->OPN; + YM_DELTAT *DELTAT = &F2610->deltaT; + + /*astring name; + device_t* dev = F2610->OPN.ST.device;*/ + + /* setup PCM buffers again */ + /*name.printf("%s",dev->tag()); + F2610->pcmbuf = (const UINT8 *)dev->machine->region(name)->base(); + F2610->pcm_size = dev->machine->region(name)->bytes(); + name.printf("%s.deltat",dev->tag()); + F2610->deltaT.memory = (UINT8 *)dev->machine->region(name)->base(); + if(F2610->deltaT.memory == NULL) + { + F2610->deltaT.memory = (UINT8*)F2610->pcmbuf; + F2610->deltaT.memory_size = F2610->pcm_size; + } + else + F2610->deltaT.memory_size = dev->machine->region(name)->bytes();*/ + F2610->pcmbuf = NULL; + F2610->pcm_size = 0x00; + F2610->deltaT.memory = NULL; + F2610->deltaT.memory_size = 0x00; + + /* Reset Prescaler */ + OPNSetPres( OPN, 6*24, 6*24, 4*2); /* OPN 1/6 , SSG 1/4 */ + /* reset SSG section */ + (*OPN->ST.SSG->reset)(OPN->ST.param); + /* status clear */ + FM_IRQMASK_SET(&OPN->ST,0x03); + FM_BUSY_CLEAR(&OPN->ST); + OPNWriteMode(OPN,0x27,0x30); /* mode 0 , timer reset */ + + OPN->eg_timer = 0; + OPN->eg_cnt = 0; + + FM_STATUS_RESET(&OPN->ST, 0xff); + + reset_channels( &OPN->ST , F2610->CH , 6 ); + /* reset OPerator paramater */ + for(i = 0xb6 ; i >= 0xb4 ; i-- ) + { + OPNWriteReg(OPN,i ,0xc0); + OPNWriteReg(OPN,i|0x100,0xc0); + } + for(i = 0xb2 ; i >= 0x30 ; i-- ) + { + OPNWriteReg(OPN,i ,0); + OPNWriteReg(OPN,i|0x100,0); + } + for(i = 0x26 ; i >= 0x20 ; i-- ) OPNWriteReg(OPN,i,0); + /**** ADPCM work initial ****/ + for( i = 0; i < 6 ; i++ ) + { + F2610->adpcm[i].step = (UINT32)((float)(1<OPN.ST.freqbase)/3.0); + F2610->adpcm[i].now_addr = 0; + F2610->adpcm[i].now_step = 0; + F2610->adpcm[i].start = 0; + F2610->adpcm[i].end = 0; + /* F2610->adpcm[i].delta = 21866; */ + F2610->adpcm[i].vol_mul = 0; + F2610->adpcm[i].pan = &OPN->out_adpcm[OUTD_CENTER]; /* default center */ + F2610->adpcm[i].flagMask = 1<adpcm[i].flag = 0; + F2610->adpcm[i].adpcm_acc = 0; + F2610->adpcm[i].adpcm_step= 0; + F2610->adpcm[i].adpcm_out = 0; + } + F2610->adpcmTL = 0x3f; + + F2610->adpcm_arrivedEndAddress = 0; + + /* DELTA-T unit */ + DELTAT->freqbase = OPN->ST.freqbase; + DELTAT->output_pointer = OPN->out_delta; + DELTAT->portshift = 8; /* allways 8bits shift */ + DELTAT->output_range = 1<<23; + YM_DELTAT_ADPCM_Reset(DELTAT,OUTD_CENTER,YM_DELTAT_EMULATION_MODE_YM2610); +} + +/* YM2610 write */ +/* n = number */ +/* a = address */ +/* v = value */ +int ym2610_write(void *chip, int a, UINT8 v) +{ + YM2610 *F2610 = (YM2610 *)chip; + FM_OPN *OPN = &F2610->OPN; + int addr; + int ch; + + v &= 0xff; /* adjust to 8 bit bus */ + + switch( a&3 ) + { + case 0: /* address port 0 */ + OPN->ST.address = v; + F2610->addr_A1 = 0; + + /* Write register to SSG emulator */ + if( v < 16 ) (*OPN->ST.SSG->write)(OPN->ST.param,0,v); + break; + + case 1: /* data port 0 */ + if (F2610->addr_A1 != 0) + break; /* verified on real YM2608 */ + + addr = OPN->ST.address; + F2610->REGS[addr] = v; + switch(addr & 0xf0) + { + case 0x00: /* SSG section */ + /* Write data to SSG emulator */ + (*OPN->ST.SSG->write)(OPN->ST.param,a,v); + break; + case 0x10: /* DeltaT ADPCM */ + switch(addr) + { + case 0x10: /* control 1 */ + case 0x11: /* control 2 */ + case 0x12: /* start address L */ + case 0x13: /* start address H */ + case 0x14: /* stop address L */ + case 0x15: /* stop address H */ + + case 0x19: /* delta-n L */ + case 0x1a: /* delta-n H */ + case 0x1b: /* volume */ + { + YM_DELTAT_ADPCM_Write(&F2610->deltaT,addr-0x10,v); + } + break; + + case 0x1c: /* FLAG CONTROL : Extend Status Clear/Mask */ + { + UINT8 statusmask = ~v; + /* set arrived flag mask */ + for(ch=0;ch<6;ch++) + F2610->adpcm[ch].flagMask = statusmask&(1<deltaT.status_change_EOS_bit = statusmask & 0x80; /* status flag: set bit7 on End Of Sample */ + + /* clear arrived flag */ + F2610->adpcm_arrivedEndAddress &= statusmask; + } + break; + + default: +#ifdef _DEBUG + logerror("YM2610: write to unknown deltat register %02x val=%02x\n",addr,v); +#endif + break; + } + + break; + case 0x20: /* Mode Register */ + OPNWriteMode(OPN,addr,v); + break; + default: /* OPN section */ + /* write register */ + OPNWriteReg(OPN,addr,v); + } + break; + + case 2: /* address port 1 */ + OPN->ST.address = v; + F2610->addr_A1 = 1; + break; + + case 3: /* data port 1 */ + if (F2610->addr_A1 != 1) + break; /* verified on real YM2608 */ + + addr = OPN->ST.address; + F2610->REGS[addr | 0x100] = v; + if( addr < 0x30 ) + /* 100-12f : ADPCM A section */ + FM_ADPCMAWrite(F2610,addr,v); + else + OPNWriteReg(OPN,addr | 0x100,v); + } + return OPN->ST.irq; +} + +UINT8 ym2610_read(void *chip,int a) +{ + YM2610 *F2610 = (YM2610 *)chip; + int addr = F2610->OPN.ST.address; + UINT8 ret = 0; + + switch( a&3) + { + case 0: /* status 0 : YM2203 compatible */ + ret = FM_STATUS_FLAG(&F2610->OPN.ST) & 0x83; + break; + case 1: /* data 0 */ + if( addr < 16 ) ret = (*F2610->OPN.ST.SSG->read)(F2610->OPN.ST.param); + if( addr == 0xff ) ret = 0x01; + break; + case 2: /* status 1 : ADPCM status */ + /* ADPCM STATUS (arrived End Address) */ + /* B,--,A5,A4,A3,A2,A1,A0 */ + /* B = ADPCM-B(DELTA-T) arrived end address */ + /* A0-A5 = ADPCM-A arrived end address */ + ret = F2610->adpcm_arrivedEndAddress; + break; + case 3: + ret = 0; + break; + } + return ret; +} + +int ym2610_timer_over(void *chip,int c) +{ + YM2610 *F2610 = (YM2610 *)chip; + + if( c ) + { /* Timer B */ + TimerBOver( &(F2610->OPN.ST) ); + } + else + { /* Timer A */ + /* timer update */ + TimerAOver( &(F2610->OPN.ST) ); + /* CSM mode key,TL controll */ + if( F2610->OPN.ST.mode & 0x80 ) + { /* CSM mode total level latch and auto key on */ + CSMKeyControll( F2610->OPN.type, &(F2610->CH[2]) ); + } + } + return F2610->OPN.ST.irq; +} + +void ym2610_write_pcmrom(void *chip, UINT8 rom_id, offs_t ROMSize, offs_t DataStart, + offs_t DataLength, const UINT8* ROMData) +{ + YM2610 *F2610 = (YM2610 *)chip; + + switch(rom_id) + { + case 0x01: // ADPCM + if (F2610->pcm_size != ROMSize) + { + F2610->pcmbuf = (UINT8*)realloc(F2610->pcmbuf, ROMSize); + F2610->pcm_size = ROMSize; + memset(F2610->pcmbuf, 0xFF, ROMSize); + } + if (DataStart > ROMSize) + return; + if (DataStart + DataLength > ROMSize) + DataLength = ROMSize - DataStart; + + memcpy(F2610->pcmbuf + DataStart, ROMData, DataLength); + break; + case 0x02: // DELTA-T + if (F2610->deltaT.memory_size != ROMSize) + { + F2610->deltaT.memory = (UINT8*)realloc(F2610->deltaT.memory, ROMSize); + F2610->deltaT.memory_size = ROMSize; + memset(F2610->deltaT.memory, 0xFF, ROMSize); + } + if (DataStart > ROMSize) + return; + if (DataStart + DataLength > ROMSize) + DataLength = ROMSize - DataStart; + + memcpy(F2610->deltaT.memory + DataStart, ROMData, DataLength); + break; + } + + return; +} + +void ym2610_set_mutemask(void *chip, UINT32 MuteMask) +{ + YM2610 *F2610 = (YM2610 *)chip; + UINT8 CurChn; + + for (CurChn = 0; CurChn < 6; CurChn ++) + F2610->CH[CurChn].Muted = (MuteMask >> CurChn) & 0x01; + for (CurChn = 0; CurChn < 6; CurChn ++) + F2610->adpcm[CurChn].Muted = (MuteMask >> (CurChn + 6)) & 0x01; + F2610->MuteDeltaT = (MuteMask >> 12) & 0x01; + + return; +} +#endif /* (BUILD_YM2610||BUILD_YM2610B) */ diff --git a/Frameworks/GME/gme/fm.h b/Frameworks/GME/gme/fm.h new file mode 100644 index 000000000..bee9f9095 --- /dev/null +++ b/Frameworks/GME/gme/fm.h @@ -0,0 +1,178 @@ +/* + File: fm.h -- header file for software emulation for FM sound generator + +*/ + +#pragma once + +#include "mamedef.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define BUILD_YM2203 1 +#define BUILD_YM2608 1 +#define BUILD_YM2610 1 +#define BUILD_YM2610B 1 +#define BUILD_YM2612 1 +#define BUILD_YM3438 1 + +/* select bit size of output : 8 or 16 */ +#define FM_SAMPLE_BITS 16 + +/* select timer system internal or external */ +#define FM_INTERNAL_TIMER 1 + +/* --- speedup optimize --- */ +/* busy flag enulation , The definition of FM_GET_TIME_NOW() is necessary. */ +//#define FM_BUSY_FLAG_SUPPORT 1 + + + +typedef stream_sample_t FMSAMPLE; +/* +#if (FM_SAMPLE_BITS==16) +typedef INT16 FMSAMPLE; +#endif +#if (FM_SAMPLE_BITS==8) +typedef unsigned char FMSAMPLE; +#endif +*/ + +/* FM_TIMERHANDLER : Stop or Start timer */ +/* int n = chip number */ +/* int c = Channel 0=TimerA,1=TimerB */ +/* int count = timer count (0=stop) */ +/* doube stepTime = step time of one count (sec.)*/ + +/* FM_IRQHHANDLER : IRQ level changing sense */ +/* int n = chip number */ +/* int irq = IRQ level 0=OFF,1=ON */ + +typedef struct _ssg_callbacks ssg_callbacks; +struct _ssg_callbacks +{ + void (*set_clock)(void *param, int clock); + void (*write)(void *param, int address, int data); + int (*read)(void *param); + void (*reset)(void *param); +}; + +#if BUILD_YM2203 +/* -------------------- YM2203(OPN) Interface -------------------- */ + +/* +** Initialize YM2203 emulator(s). +** +** 'num' is the number of virtual YM2203's to allocate +** 'baseclock' +** 'rate' is sampling rate +** 'TimerHandler' timer callback handler when timer start and clear +** 'IRQHandler' IRQ callback handler when changed IRQ level +** return 0 = success +*/ +//void * ym2203_init(void *param, const device_config *device, int baseclock, int rate, +// FM_TIMERHANDLER TimerHandler,FM_IRQHANDLER IRQHandler, const ssg_callbacks *ssg); +void * ym2203_init(void *param, int baseclock, int rate, const ssg_callbacks *ssg); + +/* +** shutdown the YM2203 emulators +*/ +void ym2203_shutdown(void *chip); + +/* +** reset all chip registers for YM2203 number 'num' +*/ +void ym2203_reset_chip(void *chip); + +/* +** update one of chip +*/ +void ym2203_update_one(void *chip, FMSAMPLE **buffer, int length); + +/* +** Write +** return : InterruptLevel +*/ +int ym2203_write(void *chip,int a,unsigned char v); + +/* +** Read +** return : InterruptLevel +*/ +unsigned char ym2203_read(void *chip,int a); + +/* +** Timer OverFlow +*/ +int ym2203_timer_over(void *chip, int c); + +/* +** State Save +*/ +void ym2203_postload(void *chip); + +void ym2203_set_mutemask(void *chip, UINT32 MuteMask); +#endif /* BUILD_YM2203 */ + +#if BUILD_YM2608 +/* -------------------- YM2608(OPNA) Interface -------------------- */ +//void * ym2608_init(void *param, const device_config *device, int baseclock, int rate, +// void *pcmroma,int pcmsizea, +// FM_TIMERHANDLER TimerHandler,FM_IRQHANDLER IRQHandler, const ssg_callbacks *ssg); +void * ym2608_init(void *param, int baseclock, int rate, const ssg_callbacks *ssg); +void ym2608_shutdown(void *chip); +void ym2608_reset_chip(void *chip); +void ym2608_update_one(void *chip, FMSAMPLE **buffer, int length); + +int ym2608_write(void *chip, int a,unsigned char v); +unsigned char ym2608_read(void *chip,int a); +void ym2608_write_pcmrom(void *chip, UINT8 rom_id, offs_t ROMSize, offs_t DataStart, + offs_t DataLength, const UINT8* ROMData); + +void ym2608_set_mutemask(void *chip, UINT32 MuteMask); +#endif /* BUILD_YM2608 */ + +#if (BUILD_YM2610||BUILD_YM2610B) +/* -------------------- YM2610(OPNB) Interface -------------------- */ +//void * ym2610_init(void *param, const device_config *device, int baseclock, int rate, +// void *pcmroma,int pcmasize,void *pcmromb,int pcmbsize, +// FM_TIMERHANDLER TimerHandler,FM_IRQHANDLER IRQHandler, const ssg_callbacks *ssg); +void * ym2610_init(void *param, int baseclock, int rate, const ssg_callbacks *ssg); +void ym2610_shutdown(void *chip); +void ym2610_reset_chip(void *chip); +void ym2610_update_one(void *chip, FMSAMPLE **buffer, int length); + +#if BUILD_YM2610B +void ym2610b_update_one(void *chip, FMSAMPLE **buffer, int length); +#endif /* BUILD_YM2610B */ + +int ym2610_write(void *chip, int a,unsigned char v); +unsigned char ym2610_read(void *chip,int a); +int ym2610_timer_over(void *chip, int c ); +void ym2610_postload(void *chip); +void ym2610_write_pcmrom(void *chip, UINT8 rom_id, offs_t ROMSize, offs_t DataStart, + offs_t DataLength, const UINT8* ROMData); + +void ym2610_set_mutemask(void *chip, UINT32 MuteMask); +#endif /* (BUILD_YM2610||BUILD_YM2610B) */ + +#if (BUILD_YM2612||BUILD_YM3438) +//void * ym2612_init(void *param, const device_config *device, int baseclock, int rate, +// FM_TIMERHANDLER TimerHandler,FM_IRQHANDLER IRQHandler); +void * ym2612_init(int baseclock, int rate); +void ym2612_shutdown(void *chip); +void ym2612_reset_chip(void *chip); +void ym2612_update_one(void *chip, FMSAMPLE **buffer, int length); + +int ym2612_write(void *chip, int a,unsigned char v); +unsigned char ym2612_read(void *chip,int a); + +void ym2612_set_mutemask(void *chip, UINT32 MuteMask); +void ym2612_setoptions(void *chip, UINT8 Flags); +#endif /* (BUILD_YM2612||BUILD_YM3438) */ + +#ifdef __cplusplus +} +#endif diff --git a/Frameworks/GME/gme/fm2612.c b/Frameworks/GME/gme/fm2612.c new file mode 100644 index 000000000..72621f94c --- /dev/null +++ b/Frameworks/GME/gme/fm2612.c @@ -0,0 +1,2634 @@ +/* +** +** File: fm2612.c -- software implementation of Yamaha YM2612 FM sound generator +** Split from fm.c to keep 2612 fixes from infecting other OPN chips +** +** Copyright Jarek Burczynski (bujar at mame dot net) +** Copyright Tatsuyuki Satoh , MultiArcadeMachineEmulator development +** +** Version 1.5.1 (Genesis Plus GX ym2612.c rev. 368) +** +*/ + +/* +** History: +** +** 2006~2009 Eke-Eke (Genesis Plus GX): +** Huge thanks to Nemesis, lot of those fixes came from his tests on Sega Genesis hardware +** More informations at http://gendev.spritesmind.net/forum/viewtopic.php?t=386 +** +** TODO: +** +** - core documentation +** - BUSY flag support +** +** CHANGELOG: +** +** - fixed LFO implementation: +** .added support for CH3 special mode: fixes various sound effects (birds in Warlock, bug sound in Aladdin...) +** .modified LFO behavior when switched off (AM/PM current level is held) and on (LFO step is reseted): fixes intro in Spider-Man & Venom : Separation Anxiety +** .improved LFO timing accuracy: now updated AFTER sample output, like EG/PG updates, and without any precision loss anymore. +** - improved internal timers emulation +** - adjusted lowest EG rates increment values +** - fixed Attack Rate not being updated in some specific cases (Batman & Robin intro) +** - fixed EG behavior when Attack Rate is maximal +** - fixed EG behavior when SL=0 (Mega Turrican tracks 03,09...) or/and Key ON occurs at minimal attenuation +** - implemented EG output immediate changes on register writes +** - fixed YM2612 initial values (after the reset): fixes missing intro in B.O.B +** - implemented Detune overflow (Ariel, Comix Zone, Shaq Fu, Spiderman & many other games using GEMS sound engine) +** - implemented accurate CSM mode emulation +** - implemented accurate SSG-EG emulation (Asterix, Beavis&Butthead, Bubba'n Stix & many other games) +** - implemented accurate address/data ports behavior +** +** 06-23-2007 Zsolt Vasvari: +** - changed the timing not to require the use of floating point calculations +** +** 03-08-2003 Jarek Burczynski: +** - fixed YM2608 initial values (after the reset) +** - fixed flag and irqmask handling (YM2608) +** - fixed BUFRDY flag handling (YM2608) +** +** 14-06-2003 Jarek Burczynski: +** - implemented all of the YM2608 status register flags +** - implemented support for external memory read/write via YM2608 +** - implemented support for deltat memory limit register in YM2608 emulation +** +** 22-05-2003 Jarek Burczynski: +** - fixed LFO PM calculations (copy&paste bugfix) +** +** 08-05-2003 Jarek Burczynski: +** - fixed SSG support +** +** 22-04-2003 Jarek Burczynski: +** - implemented 100% correct LFO generator (verified on real YM2610 and YM2608) +** +** 15-04-2003 Jarek Burczynski: +** - added support for YM2608's register 0x110 - status mask +** +** 01-12-2002 Jarek Burczynski: +** - fixed register addressing in YM2608, YM2610, YM2610B chips. (verified on real YM2608) +** The addressing patch used for early Neo-Geo games can be removed now. +** +** 26-11-2002 Jarek Burczynski, Nicola Salmoria: +** - recreated YM2608 ADPCM ROM using data from real YM2608's output which leads to: +** - added emulation of YM2608 drums. +** - output of YM2608 is two times lower now - same as YM2610 (verified on real YM2608) +** +** 16-08-2002 Jarek Burczynski: +** - binary exact Envelope Generator (verified on real YM2203); +** identical to YM2151 +** - corrected 'off by one' error in feedback calculations (when feedback is off) +** - corrected connection (algorithm) calculation (verified on real YM2203 and YM2610) +** +** 18-12-2001 Jarek Burczynski: +** - added SSG-EG support (verified on real YM2203) +** +** 12-08-2001 Jarek Burczynski: +** - corrected sin_tab and tl_tab data (verified on real chip) +** - corrected feedback calculations (verified on real chip) +** - corrected phase generator calculations (verified on real chip) +** - corrected envelope generator calculations (verified on real chip) +** - corrected FM volume level (YM2610 and YM2610B). +** - changed YMxxxUpdateOne() functions (YM2203, YM2608, YM2610, YM2610B, YM2612) : +** this was needed to calculate YM2610 FM channels output correctly. +** (Each FM channel is calculated as in other chips, but the output of the channel +** gets shifted right by one *before* sending to accumulator. That was impossible to do +** with previous implementation). +** +** 23-07-2001 Jarek Burczynski, Nicola Salmoria: +** - corrected YM2610 ADPCM type A algorithm and tables (verified on real chip) +** +** 11-06-2001 Jarek Burczynski: +** - corrected end of sample bug in ADPCMA_calc_cha(). +** Real YM2610 checks for equality between current and end addresses (only 20 LSB bits). +** +** 08-12-98 hiro-shi: +** rename ADPCMA -> ADPCMB, ADPCMB -> ADPCMA +** move ROM limit check.(CALC_CH? -> 2610Write1/2) +** test program (ADPCMB_TEST) +** move ADPCM A/B end check. +** ADPCMB repeat flag(no check) +** change ADPCM volume rate (8->16) (32->48). +** +** 09-12-98 hiro-shi: +** change ADPCM volume. (8->16, 48->64) +** replace ym2610 ch0/3 (YM-2610B) +** change ADPCM_SHIFT (10->8) missing bank change 0x4000-0xffff. +** add ADPCM_SHIFT_MASK +** change ADPCMA_DECODE_MIN/MAX. +*/ + + + + +/************************************************************************/ +/* comment of hiro-shi(Hiromitsu Shioya) */ +/* YM2610(B) = OPN-B */ +/* YM2610 : PSG:3ch FM:4ch ADPCM(18.5KHz):6ch DeltaT ADPCM:1ch */ +/* YM2610B : PSG:3ch FM:6ch ADPCM(18.5KHz):6ch DeltaT ADPCM:1ch */ +/************************************************************************/ + +//#include "emu.h" +#include +#include +#include "mathdefs.h" +#include "mamedef.h" +#include "fm.h" + +/* shared function building option */ +#define BUILD_OPN (BUILD_YM2203||BUILD_YM2608||BUILD_YM2610||BUILD_YM2610B||BUILD_YM2612||BUILD_YM3438) +#define BUILD_OPN_PRESCALER (BUILD_YM2203||BUILD_YM2608) + + +/* globals */ +#define TYPE_SSG 0x01 /* SSG support */ +#define TYPE_LFOPAN 0x02 /* OPN type LFO and PAN */ +#define TYPE_6CH 0x04 /* FM 6CH / 3CH */ +#define TYPE_DAC 0x08 /* YM2612's DAC device */ +#define TYPE_ADPCM 0x10 /* two ADPCM units */ +#define TYPE_2610 0x20 /* bogus flag to differentiate 2608 from 2610 */ + + +#define TYPE_YM2203 (TYPE_SSG) +#define TYPE_YM2608 (TYPE_SSG |TYPE_LFOPAN |TYPE_6CH |TYPE_ADPCM) +#define TYPE_YM2610 (TYPE_SSG |TYPE_LFOPAN |TYPE_6CH |TYPE_ADPCM |TYPE_2610) +#define TYPE_YM2612 (TYPE_DAC |TYPE_LFOPAN |TYPE_6CH) + + +/* globals */ +#define FREQ_SH 16 /* 16.16 fixed point (frequency calculations) */ +#define EG_SH 16 /* 16.16 fixed point (envelope generator timing) */ +#define LFO_SH 24 /* 8.24 fixed point (LFO calculations) */ +#define TIMER_SH 16 /* 16.16 fixed point (timers calculations) */ + +#define FREQ_MASK ((1<>3) + +/* sin waveform table in 'decibel' scale */ +static unsigned int sin_tab[SIN_LEN]; + +/* sustain level table (3dB per step) */ +/* bit0, bit1, bit2, bit3, bit4, bit5, bit6 */ +/* 1, 2, 4, 8, 16, 32, 64 (value)*/ +/* 0.75, 1.5, 3, 6, 12, 24, 48 (dB)*/ + +/* 0 - 15: 0, 3, 6, 9,12,15,18,21,24,27,30,33,36,39,42,93 (dB)*/ +/* attenuation value (10 bits) = (SL << 2) << 3 */ +#define SC(db) (UINT32) ( db * (4.0/ENV_STEP) ) +static const UINT32 sl_table[16]={ + SC( 0),SC( 1),SC( 2),SC(3 ),SC(4 ),SC(5 ),SC(6 ),SC( 7), + SC( 8),SC( 9),SC(10),SC(11),SC(12),SC(13),SC(14),SC(31) +}; +#undef SC + + +#define RATE_STEPS (8) +static const UINT8 eg_inc[19*RATE_STEPS]={ + +/*cycle:0 1 2 3 4 5 6 7*/ + +/* 0 */ 0,1, 0,1, 0,1, 0,1, /* rates 00..11 0 (increment by 0 or 1) */ +/* 1 */ 0,1, 0,1, 1,1, 0,1, /* rates 00..11 1 */ +/* 2 */ 0,1, 1,1, 0,1, 1,1, /* rates 00..11 2 */ +/* 3 */ 0,1, 1,1, 1,1, 1,1, /* rates 00..11 3 */ + +/* 4 */ 1,1, 1,1, 1,1, 1,1, /* rate 12 0 (increment by 1) */ +/* 5 */ 1,1, 1,2, 1,1, 1,2, /* rate 12 1 */ +/* 6 */ 1,2, 1,2, 1,2, 1,2, /* rate 12 2 */ +/* 7 */ 1,2, 2,2, 1,2, 2,2, /* rate 12 3 */ + +/* 8 */ 2,2, 2,2, 2,2, 2,2, /* rate 13 0 (increment by 2) */ +/* 9 */ 2,2, 2,4, 2,2, 2,4, /* rate 13 1 */ +/*10 */ 2,4, 2,4, 2,4, 2,4, /* rate 13 2 */ +/*11 */ 2,4, 4,4, 2,4, 4,4, /* rate 13 3 */ + +/*12 */ 4,4, 4,4, 4,4, 4,4, /* rate 14 0 (increment by 4) */ +/*13 */ 4,4, 4,8, 4,4, 4,8, /* rate 14 1 */ +/*14 */ 4,8, 4,8, 4,8, 4,8, /* rate 14 2 */ +/*15 */ 4,8, 8,8, 4,8, 8,8, /* rate 14 3 */ + +/*16 */ 8,8, 8,8, 8,8, 8,8, /* rates 15 0, 15 1, 15 2, 15 3 (increment by 8) */ +/*17 */ 16,16,16,16,16,16,16,16, /* rates 15 2, 15 3 for attack */ +/*18 */ 0,0, 0,0, 0,0, 0,0, /* infinity rates for attack and decay(s) */ +}; + + +#define O(a) (a*RATE_STEPS) + +/*note that there is no O(17) in this table - it's directly in the code */ +static const UINT8 eg_rate_select2612[32+64+32]={ /* Envelope Generator rates (32 + 64 rates + 32 RKS) */ +/* 32 infinite time rates (same as Rate 0) */ +O(18),O(18),O(18),O(18),O(18),O(18),O(18),O(18), +O(18),O(18),O(18),O(18),O(18),O(18),O(18),O(18), +O(18),O(18),O(18),O(18),O(18),O(18),O(18),O(18), +O(18),O(18),O(18),O(18),O(18),O(18),O(18),O(18), + +/* rates 00-11 */ +/* +O( 0),O( 1),O( 2),O( 3), +O( 0),O( 1),O( 2),O( 3), +*/ +O(18),O(18),O( 0),O( 0), +O( 0),O( 0),O( 2),O( 2), // Nemesis's tests + +O( 0),O( 1),O( 2),O( 3), +O( 0),O( 1),O( 2),O( 3), +O( 0),O( 1),O( 2),O( 3), +O( 0),O( 1),O( 2),O( 3), +O( 0),O( 1),O( 2),O( 3), +O( 0),O( 1),O( 2),O( 3), +O( 0),O( 1),O( 2),O( 3), +O( 0),O( 1),O( 2),O( 3), +O( 0),O( 1),O( 2),O( 3), +O( 0),O( 1),O( 2),O( 3), + +/* rate 12 */ +O( 4),O( 5),O( 6),O( 7), + +/* rate 13 */ +O( 8),O( 9),O(10),O(11), + +/* rate 14 */ +O(12),O(13),O(14),O(15), + +/* rate 15 */ +O(16),O(16),O(16),O(16), + +/* 32 dummy rates (same as 15 3) */ +O(16),O(16),O(16),O(16),O(16),O(16),O(16),O(16), +O(16),O(16),O(16),O(16),O(16),O(16),O(16),O(16), +O(16),O(16),O(16),O(16),O(16),O(16),O(16),O(16), +O(16),O(16),O(16),O(16),O(16),O(16),O(16),O(16) + +}; +#undef O + +/*rate 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15*/ +/*shift 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 0, 0, 0, 0 */ +/*mask 2047, 1023, 511, 255, 127, 63, 31, 15, 7, 3, 1, 0, 0, 0, 0, 0 */ + +#define O(a) (a*1) +static const UINT8 eg_rate_shift[32+64+32]={ /* Envelope Generator counter shifts (32 + 64 rates + 32 RKS) */ +/* 32 infinite time rates */ +/* O(0),O(0),O(0),O(0),O(0),O(0),O(0),O(0), +O(0),O(0),O(0),O(0),O(0),O(0),O(0),O(0), +O(0),O(0),O(0),O(0),O(0),O(0),O(0),O(0), +O(0),O(0),O(0),O(0),O(0),O(0),O(0),O(0), */ + +/* fixed (should be the same as rate 0, even if it makes no difference since increment value is 0 for these rates) */ +O(11),O(11),O(11),O(11),O(11),O(11),O(11),O(11), +O(11),O(11),O(11),O(11),O(11),O(11),O(11),O(11), +O(11),O(11),O(11),O(11),O(11),O(11),O(11),O(11), +O(11),O(11),O(11),O(11),O(11),O(11),O(11),O(11), + +/* rates 00-11 */ +O(11),O(11),O(11),O(11), +O(10),O(10),O(10),O(10), +O( 9),O( 9),O( 9),O( 9), +O( 8),O( 8),O( 8),O( 8), +O( 7),O( 7),O( 7),O( 7), +O( 6),O( 6),O( 6),O( 6), +O( 5),O( 5),O( 5),O( 5), +O( 4),O( 4),O( 4),O( 4), +O( 3),O( 3),O( 3),O( 3), +O( 2),O( 2),O( 2),O( 2), +O( 1),O( 1),O( 1),O( 1), +O( 0),O( 0),O( 0),O( 0), + +/* rate 12 */ +O( 0),O( 0),O( 0),O( 0), + +/* rate 13 */ +O( 0),O( 0),O( 0),O( 0), + +/* rate 14 */ +O( 0),O( 0),O( 0),O( 0), + +/* rate 15 */ +O( 0),O( 0),O( 0),O( 0), + +/* 32 dummy rates (same as 15 3) */ +O( 0),O( 0),O( 0),O( 0),O( 0),O( 0),O( 0),O( 0), +O( 0),O( 0),O( 0),O( 0),O( 0),O( 0),O( 0),O( 0), +O( 0),O( 0),O( 0),O( 0),O( 0),O( 0),O( 0),O( 0), +O( 0),O( 0),O( 0),O( 0),O( 0),O( 0),O( 0),O( 0) + +}; +#undef O + +static const UINT8 dt_tab[4 * 32]={ +/* this is YM2151 and YM2612 phase increment data (in 10.10 fixed point format)*/ +/* FD=0 */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +/* FD=1 */ + 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, + 2, 3, 3, 3, 4, 4, 4, 5, 5, 6, 6, 7, 8, 8, 8, 8, +/* FD=2 */ + 1, 1, 1, 1, 2, 2, 2, 2, 2, 3, 3, 3, 4, 4, 4, 5, + 5, 6, 6, 7, 8, 8, 9,10,11,12,13,14,16,16,16,16, +/* FD=3 */ + 2, 2, 2, 2, 2, 3, 3, 3, 4, 4, 4, 5, 5, 6, 6, 7, + 8 , 8, 9,10,11,12,13,14,16,17,19,20,22,22,22,22 +}; + + +/* OPN key frequency number -> key code follow table */ +/* fnum higher 4bit -> keycode lower 2bit */ +static const UINT8 opn_fktable[16] = {0,0,0,0,0,0,0,1,2,3,3,3,3,3,3,3}; + + +/* 8 LFO speed parameters */ +/* each value represents number of samples that one LFO level will last for */ +static const UINT32 lfo_samples_per_step[8] = {108, 77, 71, 67, 62, 44, 8, 5}; + + + +/*There are 4 different LFO AM depths available, they are: + 0 dB, 1.4 dB, 5.9 dB, 11.8 dB + Here is how it is generated (in EG steps): + + 11.8 dB = 0, 2, 4, 6, 8, 10,12,14,16...126,126,124,122,120,118,....4,2,0 + 5.9 dB = 0, 1, 2, 3, 4, 5, 6, 7, 8....63, 63, 62, 61, 60, 59,.....2,1,0 + 1.4 dB = 0, 0, 0, 0, 1, 1, 1, 1, 2,...15, 15, 15, 15, 14, 14,.....0,0,0 + + (1.4 dB is loosing precision as you can see) + + It's implemented as generator from 0..126 with step 2 then a shift + right N times, where N is: + 8 for 0 dB + 3 for 1.4 dB + 1 for 5.9 dB + 0 for 11.8 dB +*/ +static const UINT8 lfo_ams_depth_shift[4] = {8, 3, 1, 0}; + + + +/*There are 8 different LFO PM depths available, they are: + 0, 3.4, 6.7, 10, 14, 20, 40, 80 (cents) + + Modulation level at each depth depends on F-NUMBER bits: 4,5,6,7,8,9,10 + (bits 8,9,10 = FNUM MSB from OCT/FNUM register) + + Here we store only first quarter (positive one) of full waveform. + Full table (lfo_pm_table) containing all 128 waveforms is build + at run (init) time. + + One value in table below represents 4 (four) basic LFO steps + (1 PM step = 4 AM steps). + + For example: + at LFO SPEED=0 (which is 108 samples per basic LFO step) + one value from "lfo_pm_output" table lasts for 432 consecutive + samples (4*108=432) and one full LFO waveform cycle lasts for 13824 + samples (32*432=13824; 32 because we store only a quarter of whole + waveform in the table below) +*/ +static const UINT8 lfo_pm_output[7*8][8]={ /* 7 bits meaningful (of F-NUMBER), 8 LFO output levels per one depth (out of 32), 8 LFO depths */ +/* FNUM BIT 4: 000 0001xxxx */ +/* DEPTH 0 */ {0, 0, 0, 0, 0, 0, 0, 0}, +/* DEPTH 1 */ {0, 0, 0, 0, 0, 0, 0, 0}, +/* DEPTH 2 */ {0, 0, 0, 0, 0, 0, 0, 0}, +/* DEPTH 3 */ {0, 0, 0, 0, 0, 0, 0, 0}, +/* DEPTH 4 */ {0, 0, 0, 0, 0, 0, 0, 0}, +/* DEPTH 5 */ {0, 0, 0, 0, 0, 0, 0, 0}, +/* DEPTH 6 */ {0, 0, 0, 0, 0, 0, 0, 0}, +/* DEPTH 7 */ {0, 0, 0, 0, 1, 1, 1, 1}, + +/* FNUM BIT 5: 000 0010xxxx */ +/* DEPTH 0 */ {0, 0, 0, 0, 0, 0, 0, 0}, +/* DEPTH 1 */ {0, 0, 0, 0, 0, 0, 0, 0}, +/* DEPTH 2 */ {0, 0, 0, 0, 0, 0, 0, 0}, +/* DEPTH 3 */ {0, 0, 0, 0, 0, 0, 0, 0}, +/* DEPTH 4 */ {0, 0, 0, 0, 0, 0, 0, 0}, +/* DEPTH 5 */ {0, 0, 0, 0, 0, 0, 0, 0}, +/* DEPTH 6 */ {0, 0, 0, 0, 1, 1, 1, 1}, +/* DEPTH 7 */ {0, 0, 1, 1, 2, 2, 2, 3}, + +/* FNUM BIT 6: 000 0100xxxx */ +/* DEPTH 0 */ {0, 0, 0, 0, 0, 0, 0, 0}, +/* DEPTH 1 */ {0, 0, 0, 0, 0, 0, 0, 0}, +/* DEPTH 2 */ {0, 0, 0, 0, 0, 0, 0, 0}, +/* DEPTH 3 */ {0, 0, 0, 0, 0, 0, 0, 0}, +/* DEPTH 4 */ {0, 0, 0, 0, 0, 0, 0, 1}, +/* DEPTH 5 */ {0, 0, 0, 0, 1, 1, 1, 1}, +/* DEPTH 6 */ {0, 0, 1, 1, 2, 2, 2, 3}, +/* DEPTH 7 */ {0, 0, 2, 3, 4, 4, 5, 6}, + +/* FNUM BIT 7: 000 1000xxxx */ +/* DEPTH 0 */ {0, 0, 0, 0, 0, 0, 0, 0}, +/* DEPTH 1 */ {0, 0, 0, 0, 0, 0, 0, 0}, +/* DEPTH 2 */ {0, 0, 0, 0, 0, 0, 1, 1}, +/* DEPTH 3 */ {0, 0, 0, 0, 1, 1, 1, 1}, +/* DEPTH 4 */ {0, 0, 0, 1, 1, 1, 1, 2}, +/* DEPTH 5 */ {0, 0, 1, 1, 2, 2, 2, 3}, +/* DEPTH 6 */ {0, 0, 2, 3, 4, 4, 5, 6}, +/* DEPTH 7 */ {0, 0, 4, 6, 8, 8, 0xa, 0xc}, + +/* FNUM BIT 8: 001 0000xxxx */ +/* DEPTH 0 */ {0, 0, 0, 0, 0, 0, 0, 0}, +/* DEPTH 1 */ {0, 0, 0, 0, 1, 1, 1, 1}, +/* DEPTH 2 */ {0, 0, 0, 1, 1, 1, 2, 2}, +/* DEPTH 3 */ {0, 0, 1, 1, 2, 2, 3, 3}, +/* DEPTH 4 */ {0, 0, 1, 2, 2, 2, 3, 4}, +/* DEPTH 5 */ {0, 0, 2, 3, 4, 4, 5, 6}, +/* DEPTH 6 */ {0, 0, 4, 6, 8, 8, 0xa, 0xc}, +/* DEPTH 7 */ {0, 0, 8, 0xc,0x10,0x10,0x14,0x18}, + +/* FNUM BIT 9: 010 0000xxxx */ +/* DEPTH 0 */ {0, 0, 0, 0, 0, 0, 0, 0}, +/* DEPTH 1 */ {0, 0, 0, 0, 2, 2, 2, 2}, +/* DEPTH 2 */ {0, 0, 0, 2, 2, 2, 4, 4}, +/* DEPTH 3 */ {0, 0, 2, 2, 4, 4, 6, 6}, +/* DEPTH 4 */ {0, 0, 2, 4, 4, 4, 6, 8}, +/* DEPTH 5 */ {0, 0, 4, 6, 8, 8, 0xa, 0xc}, +/* DEPTH 6 */ {0, 0, 8, 0xc,0x10,0x10,0x14,0x18}, +/* DEPTH 7 */ {0, 0,0x10,0x18,0x20,0x20,0x28,0x30}, + +/* FNUM BIT10: 100 0000xxxx */ +/* DEPTH 0 */ {0, 0, 0, 0, 0, 0, 0, 0}, +/* DEPTH 1 */ {0, 0, 0, 0, 4, 4, 4, 4}, +/* DEPTH 2 */ {0, 0, 0, 4, 4, 4, 8, 8}, +/* DEPTH 3 */ {0, 0, 4, 4, 8, 8, 0xc, 0xc}, +/* DEPTH 4 */ {0, 0, 4, 8, 8, 8, 0xc,0x10}, +/* DEPTH 5 */ {0, 0, 8, 0xc,0x10,0x10,0x14,0x18}, +/* DEPTH 6 */ {0, 0,0x10,0x18,0x20,0x20,0x28,0x30}, +/* DEPTH 7 */ {0, 0,0x20,0x30,0x40,0x40,0x50,0x60}, + +}; + +/* all 128 LFO PM waveforms */ +static INT32 lfo_pm_table[128*8*32]; /* 128 combinations of 7 bits meaningful (of F-NUMBER), 8 LFO depths, 32 LFO output levels per one depth */ + +/* register number to channel number , slot offset */ +#define OPN_CHAN(N) (N&3) +#define OPN_SLOT(N) ((N>>2)&3) + +/* slot number */ +#define SLOT1 0 +#define SLOT2 2 +#define SLOT3 1 +#define SLOT4 3 + +/* bit0 = Right enable , bit1 = Left enable */ +#define OUTD_RIGHT 1 +#define OUTD_LEFT 2 +#define OUTD_CENTER 3 + + +/* save output as raw 16-bit sample */ +/* #define SAVE_SAMPLE */ + +#ifdef SAVE_SAMPLE +static FILE *sample[1]; + #if 1 /*save to MONO file */ + #define SAVE_ALL_CHANNELS \ + { signed int pom = lt; \ + fputc((unsigned short)pom&0xff,sample[0]); \ + fputc(((unsigned short)pom>>8)&0xff,sample[0]); \ + } + #else /*save to STEREO file */ + #define SAVE_ALL_CHANNELS \ + { signed int pom = lt; \ + fputc((unsigned short)pom&0xff,sample[0]); \ + fputc(((unsigned short)pom>>8)&0xff,sample[0]); \ + pom = rt; \ + fputc((unsigned short)pom&0xff,sample[0]); \ + fputc(((unsigned short)pom>>8)&0xff,sample[0]); \ + } + #endif +#endif + + +/* struct describing a single operator (SLOT) */ +typedef struct +{ + INT32 *DT; /* detune :dt_tab[DT] */ + UINT8 KSR; /* key scale rate :3-KSR */ + UINT32 ar; /* attack rate */ + UINT32 d1r; /* decay rate */ + UINT32 d2r; /* sustain rate */ + UINT32 rr; /* release rate */ + UINT8 ksr; /* key scale rate :kcode>>(3-KSR) */ + UINT32 mul; /* multiple :ML_TABLE[ML] */ + + /* Phase Generator */ + UINT32 phase; /* phase counter */ + INT32 Incr; /* phase step */ + + /* Envelope Generator */ + UINT8 state; /* phase type */ + UINT32 tl; /* total level: TL << 3 */ + INT32 volume; /* envelope counter */ + UINT32 sl; /* sustain level:sl_table[SL] */ + UINT32 vol_out; /* current output from EG circuit (without AM from LFO) */ + + UINT8 eg_sh_ar; /* (attack state) */ + UINT8 eg_sel_ar; /* (attack state) */ + UINT8 eg_sh_d1r; /* (decay state) */ + UINT8 eg_sel_d1r; /* (decay state) */ + UINT8 eg_sh_d2r; /* (sustain state) */ + UINT8 eg_sel_d2r; /* (sustain state) */ + UINT8 eg_sh_rr; /* (release state) */ + UINT8 eg_sel_rr; /* (release state) */ + + UINT8 ssg; /* SSG-EG waveform */ + UINT8 ssgn; /* SSG-EG negated output */ + + UINT8 key; /* 0=last key was KEY OFF, 1=KEY ON */ + + /* LFO */ + UINT32 AMmask; /* AM enable flag */ + +} FM_SLOT; + +typedef struct +{ + FM_SLOT SLOT[4]; /* four SLOTs (operators) */ + + UINT8 ALGO; /* algorithm */ + UINT8 FB; /* feedback shift */ + INT32 op1_out[2]; /* op1 output for feedback */ + + INT32 *connect1; /* SLOT1 output pointer */ + INT32 *connect3; /* SLOT3 output pointer */ + INT32 *connect2; /* SLOT2 output pointer */ + INT32 *connect4; /* SLOT4 output pointer */ + + INT32 *mem_connect;/* where to put the delayed sample (MEM) */ + INT32 mem_value; /* delayed sample (MEM) value */ + + INT32 pms; /* channel PMS */ + UINT8 ams; /* channel AMS */ + + UINT32 fc; /* fnum,blk:adjusted to sample rate */ + UINT8 kcode; /* key code: */ + UINT32 block_fnum; /* current blk/fnum value for this slot (can be different betweeen slots of one channel in 3slot mode) */ + UINT8 Muted; +} FM_CH; + + +typedef struct +{ + //running_device *device; + double freqbase; /* frequency base */ + int timer_prescaler; /* timer prescaler */ + UINT8 irq; /* interrupt level */ + UINT8 irqmask; /* irq mask */ +#if FM_BUSY_FLAG_SUPPORT + TIME_TYPE busy_expiry_time; /* expiry time of the busy status */ +#endif + UINT32 clock; /* master clock (Hz) */ + UINT32 rate; /* sampling rate (Hz) */ + UINT8 address; /* address register */ + UINT8 status; /* status flag */ + UINT32 mode; /* mode CSM / 3SLOT */ + UINT8 fn_h; /* freq latch */ + UINT8 prescaler_sel; /* prescaler selector */ + INT32 TA; /* timer a */ + INT32 TAC; /* timer a counter */ + UINT8 TB; /* timer b */ + INT32 TBC; /* timer b counter */ + /* local time tables */ + INT32 dt_tab[8][32]; /* DeTune table */ + /* Extention Timer and IRQ handler */ +} FM_ST; + + + +/***********************************************************/ +/* OPN unit */ +/***********************************************************/ + +/* OPN 3slot struct */ +typedef struct +{ + UINT32 fc[3]; /* fnum3,blk3: calculated */ + UINT8 fn_h; /* freq3 latch */ + UINT8 kcode[3]; /* key code */ + UINT32 block_fnum[3]; /* current fnum value for this slot (can be different betweeen slots of one channel in 3slot mode) */ + UINT8 key_csm; /* CSM mode Key-ON flag */ +} FM_3SLOT; + +/* OPN/A/B common state */ +typedef struct +{ + UINT8 type; /* chip type */ + FM_ST ST; /* general state */ + FM_3SLOT SL3; /* 3 slot mode state */ + FM_CH *P_CH; /* pointer of CH */ + unsigned int pan[6*2]; /* fm channels output masks (0xffffffff = enable) */ + + UINT32 eg_cnt; /* global envelope generator counter */ + UINT32 eg_timer; /* global envelope generator counter works at frequency = chipclock/144/3 */ + UINT32 eg_timer_add; /* step of eg_timer */ + UINT32 eg_timer_overflow;/* envelope generator timer overlfows every 3 samples (on real chip) */ + + + /* there are 2048 FNUMs that can be generated using FNUM/BLK registers + but LFO works with one more bit of a precision so we really need 4096 elements */ + UINT32 fn_table[4096]; /* fnumber->increment counter */ + UINT32 fn_max; /* maximal phase increment (used for phase overflow) */ + + /* LFO */ + UINT8 lfo_cnt; /* current LFO phase (out of 128) */ + UINT32 lfo_timer; /* current LFO phase runs at LFO frequency */ + UINT32 lfo_timer_add; /* step of lfo_timer */ + UINT32 lfo_timer_overflow; /* LFO timer overflows every N samples (depends on LFO frequency) */ + UINT32 LFO_AM; /* current LFO AM step */ + UINT32 LFO_PM; /* current LFO PM step */ + + INT32 m2,c1,c2; /* Phase Modulation input for operators 2,3,4 */ + INT32 mem; /* one sample delay memory */ + INT32 out_fm[6]; /* outputs of working channels */ + + UINT8 LFOResetZero; + +} FM_OPN; + +/* here's the virtual YM2612 */ +typedef struct +{ + UINT8 REGS[512]; /* registers */ + FM_OPN OPN; /* OPN state */ + FM_CH CH[6]; /* channel state */ + UINT8 addr_A1; /* address line A1 */ + + /* dac output (YM2612) */ + //int dacen; + UINT8 dacen; + UINT8 dac_test; + INT32 dacout; + UINT8 MuteDAC; + + UINT8 WaveOutMode; + INT32 WaveL; + INT32 WaveR; +} YM2612; + +/* log output level */ +#define LOG_ERR 3 /* ERROR */ +#define LOG_WAR 2 /* WARNING */ +#define LOG_INF 1 /* INFORMATION */ +#define LOG_LEVEL LOG_INF + +#ifndef __RAINE__ +#define LOG(n,x) do { if( (n)>=LOG_LEVEL ) logerror x; } while (0) +#endif + +/* limitter */ +#define Limit(val, max,min) { \ + if ( val > max ) val = max; \ + else if ( val < min ) val = min; \ +} + +/*#include +static FILE* hFile; +static UINT32 FileSample;*/ + +/* status set and IRQ handling */ +INLINE void FM_STATUS_SET(FM_ST *ST,int flag) +{ + /* set status flag */ + ST->status |= flag; + if ( !(ST->irq) && (ST->status & ST->irqmask) ) + { + ST->irq = 1; + /* callback user interrupt handler (IRQ is OFF to ON) */ + } +} + +/* status reset and IRQ handling */ +INLINE void FM_STATUS_RESET(FM_ST *ST,int flag) +{ + /* reset status flag */ + ST->status &=~flag; + if ( (ST->irq) && !(ST->status & ST->irqmask) ) + { + ST->irq = 0; + /* callback user interrupt handler (IRQ is ON to OFF) */ + } +} + +/* IRQ mask set */ +INLINE void FM_IRQMASK_SET(FM_ST *ST,int flag) +{ + ST->irqmask = flag; + /* IRQ handling check */ + FM_STATUS_SET(ST,0); + FM_STATUS_RESET(ST,0); +} + +INLINE void FM_KEYON(FM_OPN *OPN, FM_CH *CH , int s ) +{ + FM_SLOT *SLOT = &CH->SLOT[s]; + + if( !SLOT->key && !OPN->SL3.key_csm) + { + /* restart Phase Generator */ + SLOT->phase = 0; + + /* reset SSG-EG inversion flag */ + SLOT->ssgn = 0; + + if ((SLOT->ar + SLOT->ksr) < 94 /*32+62*/) + { + SLOT->state = (SLOT->volume <= MIN_ATT_INDEX) ? ((SLOT->sl == MIN_ATT_INDEX) ? EG_SUS : EG_DEC) : EG_ATT; + } + else + { + /* force attenuation level to 0 */ + SLOT->volume = MIN_ATT_INDEX; + + /* directly switch to Decay (or Sustain) */ + SLOT->state = (SLOT->sl == MIN_ATT_INDEX) ? EG_SUS : EG_DEC; + } + + /* recalculate EG output */ + if ((SLOT->ssg&0x08) && (SLOT->ssgn ^ (SLOT->ssg&0x04))) + SLOT->vol_out = ((UINT32)(0x200 - SLOT->volume) & MAX_ATT_INDEX) + SLOT->tl; + else + SLOT->vol_out = (UINT32)SLOT->volume + SLOT->tl; + } + + SLOT->key = 1; +} + +INLINE void FM_KEYOFF(FM_OPN *OPN, FM_CH *CH , int s ) +{ + FM_SLOT *SLOT = &CH->SLOT[s]; + + if (SLOT->key && !OPN->SL3.key_csm) + { + if (SLOT->state>EG_REL) + { + SLOT->state = EG_REL; /* phase -> Release */ + + /* SSG-EG specific update */ + if (SLOT->ssg&0x08) + { + /* convert EG attenuation level */ + if (SLOT->ssgn ^ (SLOT->ssg&0x04)) + SLOT->volume = (0x200 - SLOT->volume); + + /* force EG attenuation level */ + if (SLOT->volume >= 0x200) + { + SLOT->volume = MAX_ATT_INDEX; + SLOT->state = EG_OFF; + } + + /* recalculate EG output */ + SLOT->vol_out = (UINT32)SLOT->volume + SLOT->tl; + } + } + } + + SLOT->key = 0; +} + +INLINE void FM_KEYON_CSM(FM_OPN *OPN, FM_CH *CH , int s ) +{ + FM_SLOT *SLOT = &CH->SLOT[s]; + + if( !SLOT->key && !OPN->SL3.key_csm) + { + /* restart Phase Generator */ + SLOT->phase = 0; + + /* reset SSG-EG inversion flag */ + SLOT->ssgn = 0; + + if ((SLOT->ar + SLOT->ksr) < 94 /*32+62*/) + { + SLOT->state = (SLOT->volume <= MIN_ATT_INDEX) ? ((SLOT->sl == MIN_ATT_INDEX) ? EG_SUS : EG_DEC) : EG_ATT; + } + else + { + /* force attenuation level to 0 */ + SLOT->volume = MIN_ATT_INDEX; + + /* directly switch to Decay (or Sustain) */ + SLOT->state = (SLOT->sl == MIN_ATT_INDEX) ? EG_SUS : EG_DEC; + } + + /* recalculate EG output */ + if ((SLOT->ssg&0x08) && (SLOT->ssgn ^ (SLOT->ssg&0x04))) + SLOT->vol_out = ((UINT32)(0x200 - SLOT->volume) & MAX_ATT_INDEX) + SLOT->tl; + else + SLOT->vol_out = (UINT32)SLOT->volume + SLOT->tl; + } +} + +INLINE void FM_KEYOFF_CSM(FM_CH *CH , int s ) +{ + FM_SLOT *SLOT = &CH->SLOT[s]; + if (!SLOT->key) + { + if (SLOT->state>EG_REL) + { + SLOT->state = EG_REL; /* phase -> Release */ + + /* SSG-EG specific update */ + if (SLOT->ssg&0x08) + { + /* convert EG attenuation level */ + if (SLOT->ssgn ^ (SLOT->ssg&0x04)) + SLOT->volume = (0x200 - SLOT->volume); + + /* force EG attenuation level */ + if (SLOT->volume >= 0x200) + { + SLOT->volume = MAX_ATT_INDEX; + SLOT->state = EG_OFF; + } + + /* recalculate EG output */ + SLOT->vol_out = (UINT32)SLOT->volume + SLOT->tl; + } + } + } +} + +/* OPN Mode Register Write */ +INLINE void set_timers( FM_OPN *OPN, FM_ST *ST, int v ) +{ + /* b7 = CSM MODE */ + /* b6 = 3 slot mode */ + /* b5 = reset b */ + /* b4 = reset a */ + /* b3 = timer enable b */ + /* b2 = timer enable a */ + /* b1 = load b */ + /* b0 = load a */ + + if ((OPN->ST.mode ^ v) & 0xC0) + { + /* phase increment need to be recalculated */ + OPN->P_CH[2].SLOT[SLOT1].Incr=-1; + + /* CSM mode disabled and CSM key ON active*/ + if (((v & 0xC0) != 0x80) && OPN->SL3.key_csm) + { + /* CSM Mode Key OFF (verified by Nemesis on real hardware) */ + FM_KEYOFF_CSM(&OPN->P_CH[2],SLOT1); + FM_KEYOFF_CSM(&OPN->P_CH[2],SLOT2); + FM_KEYOFF_CSM(&OPN->P_CH[2],SLOT3); + FM_KEYOFF_CSM(&OPN->P_CH[2],SLOT4); + OPN->SL3.key_csm = 0; + } + } + + /* reset Timer b flag */ + if( v & 0x20 ) + FM_STATUS_RESET(ST,0x02); + /* reset Timer a flag */ + if( v & 0x10 ) + FM_STATUS_RESET(ST,0x01); + /* load b */ + if( v & 0x02 ) + { + if( ST->TBC == 0 ) + { + ST->TBC = ( 256-ST->TB)<<4; + /* External timer handler */ + } + } + else + { /* stop timer b */ + if( ST->TBC != 0 ) + { + ST->TBC = 0; + } + } + /* load a */ + if( v & 0x01 ) + { + if( ST->TAC == 0 ) + { + ST->TAC = (1024-ST->TA); + /* External timer handler */ + } + } + else + { /* stop timer a */ + if( ST->TAC != 0 ) + { + ST->TAC = 0; + } + } + ST->mode = v; +} + + +/* Timer A Overflow */ +INLINE void TimerAOver(FM_ST *ST) +{ + /* set status (if enabled) */ + if(ST->mode & 0x04) FM_STATUS_SET(ST,0x01); + /* clear or reload the counter */ + ST->TAC = (1024-ST->TA); +} +/* Timer B Overflow */ +INLINE void TimerBOver(FM_ST *ST) +{ + /* set status (if enabled) */ + if(ST->mode & 0x08) FM_STATUS_SET(ST,0x02); + /* clear or reload the counter */ + ST->TBC = ( 256-ST->TB)<<4; +} + + +#if FM_INTERNAL_TIMER +/* ----- internal timer mode , update timer */ +// Valley Bell: defines fixed + +/* ---------- calculate timer A ---------- */ + #define INTERNAL_TIMER_A(ST,CSM_CH) \ + { \ + if( (ST)->TAC && ((ST)->timer_handler==0) ) \ + if( ((ST)->TAC -= (int)((ST)->freqbase*4096)) <= 0 ) \ + { \ + TimerAOver( ST ); \ + /* CSM mode total level latch and auto key on */ \ + if( (ST)->mode & 0x80 ) \ + CSMKeyControll( OPN, CSM_CH ); \ + } \ + } +/* ---------- calculate timer B ---------- */ + #define INTERNAL_TIMER_B(ST,step) \ + { \ + if( (ST)->TBC && ((ST)->timer_handler==0) ) \ + if( ((ST)->TBC -= (int)((ST)->freqbase*4096*step)) <= 0 ) \ + TimerBOver( ST ); \ + } +#else /* FM_INTERNAL_TIMER */ +/* external timer mode */ +#define INTERNAL_TIMER_A(ST,CSM_CH) +#define INTERNAL_TIMER_B(ST,step) +#endif /* FM_INTERNAL_TIMER */ + + + +#if FM_BUSY_FLAG_SUPPORT +#define FM_BUSY_CLEAR(ST) ((ST)->busy_expiry_time = UNDEFINED_TIME) +INLINE UINT8 FM_STATUS_FLAG(FM_ST *ST) +{ + if( COMPARE_TIMES(ST->busy_expiry_time, UNDEFINED_TIME) != 0 ) + { + if (COMPARE_TIMES(ST->busy_expiry_time, FM_GET_TIME_NOW(ST->device->machine)) > 0) + return ST->status | 0x80; /* with busy */ + /* expire */ + FM_BUSY_CLEAR(ST); + } + return ST->status; +} +INLINE void FM_BUSY_SET(FM_ST *ST,int busyclock ) +{ + TIME_TYPE expiry_period = MULTIPLY_TIME_BY_INT(ATTOTIME_IN_HZ(ST->clock), busyclock * ST->timer_prescaler); + ST->busy_expiry_time = ADD_TIMES(FM_GET_TIME_NOW(ST->device->machine), expiry_period); +} +#else +#define FM_STATUS_FLAG(ST) ((ST)->status) +#define FM_BUSY_SET(ST,bclock) {} +#define FM_BUSY_CLEAR(ST) {} +#endif + + +/* set algorithm connection */ +INLINE void setup_connection( FM_OPN *OPN, FM_CH *CH, int ch ) +{ + INT32 *carrier = &OPN->out_fm[ch]; + + INT32 **om1 = &CH->connect1; + INT32 **om2 = &CH->connect3; + INT32 **oc1 = &CH->connect2; + + INT32 **memc = &CH->mem_connect; + + switch( CH->ALGO ) + { + case 0: + /* M1---C1---MEM---M2---C2---OUT */ + *om1 = &OPN->c1; + *oc1 = &OPN->mem; + *om2 = &OPN->c2; + *memc= &OPN->m2; + break; + case 1: + /* M1------+-MEM---M2---C2---OUT */ + /* C1-+ */ + *om1 = &OPN->mem; + *oc1 = &OPN->mem; + *om2 = &OPN->c2; + *memc= &OPN->m2; + break; + case 2: + /* M1-----------------+-C2---OUT */ + /* C1---MEM---M2-+ */ + *om1 = &OPN->c2; + *oc1 = &OPN->mem; + *om2 = &OPN->c2; + *memc= &OPN->m2; + break; + case 3: + /* M1---C1---MEM------+-C2---OUT */ + /* M2-+ */ + *om1 = &OPN->c1; + *oc1 = &OPN->mem; + *om2 = &OPN->c2; + *memc= &OPN->c2; + break; + case 4: + /* M1---C1-+-OUT */ + /* M2---C2-+ */ + /* MEM: not used */ + *om1 = &OPN->c1; + *oc1 = carrier; + *om2 = &OPN->c2; + *memc= &OPN->mem; /* store it anywhere where it will not be used */ + break; + case 5: + /* +----C1----+ */ + /* M1-+-MEM---M2-+-OUT */ + /* +----C2----+ */ + *om1 = 0; /* special mark */ + *oc1 = carrier; + *om2 = carrier; + *memc= &OPN->m2; + break; + case 6: + /* M1---C1-+ */ + /* M2-+-OUT */ + /* C2-+ */ + /* MEM: not used */ + *om1 = &OPN->c1; + *oc1 = carrier; + *om2 = carrier; + *memc= &OPN->mem; /* store it anywhere where it will not be used */ + break; + case 7: + /* M1-+ */ + /* C1-+-OUT */ + /* M2-+ */ + /* C2-+ */ + /* MEM: not used*/ + *om1 = carrier; + *oc1 = carrier; + *om2 = carrier; + *memc= &OPN->mem; /* store it anywhere where it will not be used */ + break; + } + + CH->connect4 = carrier; +} + +/* set detune & multiple */ +INLINE void set_det_mul(FM_ST *ST,FM_CH *CH,FM_SLOT *SLOT,int v) +{ + SLOT->mul = (v&0x0f)? (v&0x0f)*2 : 1; + SLOT->DT = ST->dt_tab[(v>>4)&7]; + CH->SLOT[SLOT1].Incr=-1; +} + +/* set total level */ +INLINE void set_tl(FM_CH *CH,FM_SLOT *SLOT , int v) +{ + SLOT->tl = (v&0x7f)<<(ENV_BITS-7); /* 7bit TL */ + + /* recalculate EG output */ + if ((SLOT->ssg&0x08) && (SLOT->ssgn ^ (SLOT->ssg&0x04)) && (SLOT->state > EG_REL)) + SLOT->vol_out = ((UINT32)(0x200 - SLOT->volume) & MAX_ATT_INDEX) + SLOT->tl; + else + SLOT->vol_out = (UINT32)SLOT->volume + SLOT->tl; +} + +/* set attack rate & key scale */ +INLINE void set_ar_ksr(UINT8 type, FM_CH *CH,FM_SLOT *SLOT,int v) +{ + UINT8 old_KSR = SLOT->KSR; + + SLOT->ar = (v&0x1f) ? 32 + ((v&0x1f)<<1) : 0; + + SLOT->KSR = 3-(v>>6); + if (SLOT->KSR != old_KSR) + { + CH->SLOT[SLOT1].Incr=-1; + } + + /* Even if it seems unnecessary, in some odd case, KSR and KC are both modified */ + /* and could result in SLOT->kc remaining unchanged. */ + /* In such case, AR values would not be recalculated despite SLOT->ar has changed */ + /* This fixes the introduction music of Batman & Robin (Eke-Eke) */ + if ((SLOT->ar + SLOT->ksr) < 94 /*32+62*/) + { + SLOT->eg_sh_ar = eg_rate_shift [SLOT->ar + SLOT->ksr ]; + SLOT->eg_sel_ar = eg_rate_select2612[SLOT->ar + SLOT->ksr ]; + } + else + { + SLOT->eg_sh_ar = 0; + SLOT->eg_sel_ar = 18*RATE_STEPS; /* verified by Nemesis on real hardware */ + } +} + +/* set decay rate */ +INLINE void set_dr(UINT8 type, FM_SLOT *SLOT,int v) +{ + SLOT->d1r = (v&0x1f) ? 32 + ((v&0x1f)<<1) : 0; + + SLOT->eg_sh_d1r = eg_rate_shift [SLOT->d1r + SLOT->ksr]; + SLOT->eg_sel_d1r= eg_rate_select2612[SLOT->d1r + SLOT->ksr]; +} + +/* set sustain rate */ +INLINE void set_sr(UINT8 type, FM_SLOT *SLOT,int v) +{ + SLOT->d2r = (v&0x1f) ? 32 + ((v&0x1f)<<1) : 0; + + SLOT->eg_sh_d2r = eg_rate_shift [SLOT->d2r + SLOT->ksr]; + SLOT->eg_sel_d2r= eg_rate_select2612[SLOT->d2r + SLOT->ksr]; +} + +/* set release rate */ +INLINE void set_sl_rr(UINT8 type, FM_SLOT *SLOT,int v) +{ + SLOT->sl = sl_table[ v>>4 ]; + + /* check EG state changes */ + if ((SLOT->state == EG_DEC) && (SLOT->volume >= (INT32)(SLOT->sl))) + SLOT->state = EG_SUS; + + SLOT->rr = 34 + ((v&0x0f)<<2); + + SLOT->eg_sh_rr = eg_rate_shift [SLOT->rr + SLOT->ksr]; + SLOT->eg_sel_rr = eg_rate_select2612[SLOT->rr + SLOT->ksr]; +} + +/* advance LFO to next sample */ +INLINE void advance_lfo(FM_OPN *OPN) +{ + if (OPN->lfo_timer_overflow) /* LFO enabled ? */ + { + /* increment LFO timer */ + OPN->lfo_timer += OPN->lfo_timer_add; + + /* when LFO is enabled, one level will last for 108, 77, 71, 67, 62, 44, 8 or 5 samples */ + while (OPN->lfo_timer >= OPN->lfo_timer_overflow) + { + OPN->lfo_timer -= OPN->lfo_timer_overflow; + + /* There are 128 LFO steps */ + OPN->lfo_cnt = ( OPN->lfo_cnt + 1 ) & 127; + + /* triangle */ + /* AM: 0 to 126 step +2, 126 to 0 step -2 */ + if (OPN->lfo_cnt<64) + OPN->LFO_AM = OPN->lfo_cnt * 2; + else + OPN->LFO_AM = 126 - ((OPN->lfo_cnt&63) * 2); + + /* PM works with 4 times slower clock */ + OPN->LFO_PM = OPN->lfo_cnt >> 2; + } + } +} + +INLINE void advance_eg_channel(FM_OPN *OPN, FM_SLOT *SLOT) +{ + //unsigned int out; + unsigned int i = 4; /* four operators per channel */ + + do + { + switch(SLOT->state) + { + case EG_ATT: /* attack phase */ + if (!(OPN->eg_cnt & ((1<eg_sh_ar)-1))) + { + /* update attenuation level */ + SLOT->volume += (~SLOT->volume * (eg_inc[SLOT->eg_sel_ar + ((OPN->eg_cnt>>SLOT->eg_sh_ar)&7)]))>>4; + + /* check phase transition*/ + if (SLOT->volume <= MIN_ATT_INDEX) + { + SLOT->volume = MIN_ATT_INDEX; + SLOT->state = (SLOT->sl == MIN_ATT_INDEX) ? EG_SUS : EG_DEC; /* special case where SL=0 */ + } + + /* recalculate EG output */ + if ((SLOT->ssg&0x08) && (SLOT->ssgn ^ (SLOT->ssg&0x04))) /* SSG-EG Output Inversion */ + SLOT->vol_out = ((UINT32)(0x200 - SLOT->volume) & MAX_ATT_INDEX) + SLOT->tl; + else + SLOT->vol_out = (UINT32)SLOT->volume + SLOT->tl; + } + break; + + case EG_DEC: /* decay phase */ + if (!(OPN->eg_cnt & ((1<eg_sh_d1r)-1))) + { + /* SSG EG type */ + if (SLOT->ssg&0x08) + { + /* update attenuation level */ + if (SLOT->volume < 0x200) + { + SLOT->volume += 4 * eg_inc[SLOT->eg_sel_d1r + ((OPN->eg_cnt>>SLOT->eg_sh_d1r)&7)]; + + /* recalculate EG output */ + if (SLOT->ssgn ^ (SLOT->ssg&0x04)) /* SSG-EG Output Inversion */ + SLOT->vol_out = ((UINT32)(0x200 - SLOT->volume) & MAX_ATT_INDEX) + SLOT->tl; + else + SLOT->vol_out = (UINT32)SLOT->volume + SLOT->tl; + } + + } + else + { + /* update attenuation level */ + SLOT->volume += eg_inc[SLOT->eg_sel_d1r + ((OPN->eg_cnt>>SLOT->eg_sh_d1r)&7)]; + + /* recalculate EG output */ + SLOT->vol_out = (UINT32)SLOT->volume + SLOT->tl; + } + + /* check phase transition*/ + if (SLOT->volume >= (INT32)(SLOT->sl)) + SLOT->state = EG_SUS; + } + break; + + case EG_SUS: /* sustain phase */ + if (!(OPN->eg_cnt & ((1<eg_sh_d2r)-1))) + { + /* SSG EG type */ + if (SLOT->ssg&0x08) + { + /* update attenuation level */ + if (SLOT->volume < 0x200) + { + SLOT->volume += 4 * eg_inc[SLOT->eg_sel_d2r + ((OPN->eg_cnt>>SLOT->eg_sh_d2r)&7)]; + + /* recalculate EG output */ + if (SLOT->ssgn ^ (SLOT->ssg&0x04)) /* SSG-EG Output Inversion */ + SLOT->vol_out = ((UINT32)(0x200 - SLOT->volume) & MAX_ATT_INDEX) + SLOT->tl; + else + SLOT->vol_out = (UINT32)SLOT->volume + SLOT->tl; + } + } + else + { + /* update attenuation level */ + SLOT->volume += eg_inc[SLOT->eg_sel_d2r + ((OPN->eg_cnt>>SLOT->eg_sh_d2r)&7)]; + + /* check phase transition*/ + if ( SLOT->volume >= MAX_ATT_INDEX ) + SLOT->volume = MAX_ATT_INDEX; + /* do not change SLOT->state (verified on real chip) */ + + /* recalculate EG output */ + SLOT->vol_out = (UINT32)SLOT->volume + SLOT->tl; + } + } + break; + + case EG_REL: /* release phase */ + if (!(OPN->eg_cnt & ((1<eg_sh_rr)-1))) + { + /* SSG EG type */ + if (SLOT->ssg&0x08) + { + /* update attenuation level */ + if (SLOT->volume < 0x200) + SLOT->volume += 4 * eg_inc[SLOT->eg_sel_rr + ((OPN->eg_cnt>>SLOT->eg_sh_rr)&7)]; + /* check phase transition */ + if (SLOT->volume >= 0x200) + { + SLOT->volume = MAX_ATT_INDEX; + SLOT->state = EG_OFF; + } + } + else + { + /* update attenuation level */ + SLOT->volume += eg_inc[SLOT->eg_sel_rr + ((OPN->eg_cnt>>SLOT->eg_sh_rr)&7)]; + + /* check phase transition*/ + if (SLOT->volume >= MAX_ATT_INDEX) + { + SLOT->volume = MAX_ATT_INDEX; + SLOT->state = EG_OFF; + } + } + + /* recalculate EG output */ + SLOT->vol_out = (UINT32)SLOT->volume + SLOT->tl; + + } + break; + } + + // Valley Bell: These few lines are missing in Genesis Plus GX' ym2612 core file. + // Disabling them fixes the SSG-EG. + // Additional Note: Asterix and the Great Rescue: Level 1 sounds "better" with these lines, + // but less accurate. + /*out = ((UINT32)SLOT->volume); + + // negate output (changes come from alternate bit, init comes from attack bit) + if ((SLOT->ssg&0x08) && (SLOT->ssgn&2) && (SLOT->state > EG_REL)) + out ^= MAX_ATT_INDEX; + + // we need to store the result here because we are going to change ssgn + // in next instruction + SLOT->vol_out = out + SLOT->tl;*/ + + SLOT++; + i--; + }while (i); + +} + +/* SSG-EG update process */ +/* The behavior is based upon Nemesis tests on real hardware */ +/* This is actually executed before each samples */ +INLINE void update_ssg_eg_channel(FM_SLOT *SLOT) +{ + unsigned int i = 4; /* four operators per channel */ + + do + { + /* detect SSG-EG transition */ + /* this is not required during release phase as the attenuation has been forced to MAX and output invert flag is not used */ + /* if an Attack Phase is programmed, inversion can occur on each sample */ + if ((SLOT->ssg & 0x08) && (SLOT->volume >= 0x200) && (SLOT->state > EG_REL)) + { + if (SLOT->ssg & 0x01) /* bit 0 = hold SSG-EG */ + { + /* set inversion flag */ + if (SLOT->ssg & 0x02) + SLOT->ssgn = 4; + + /* force attenuation level during decay phases */ + if ((SLOT->state != EG_ATT) && !(SLOT->ssgn ^ (SLOT->ssg & 0x04))) + SLOT->volume = MAX_ATT_INDEX; + } + else /* loop SSG-EG */ + { + /* toggle output inversion flag or reset Phase Generator */ + if (SLOT->ssg & 0x02) + SLOT->ssgn ^= 4; + else + SLOT->phase = 0; + + /* same as Key ON */ + if (SLOT->state != EG_ATT) + { + if ((SLOT->ar + SLOT->ksr) < 94 /*32+62*/) + { + SLOT->state = (SLOT->volume <= MIN_ATT_INDEX) ? ((SLOT->sl == MIN_ATT_INDEX) ? EG_SUS : EG_DEC) : EG_ATT; + } + else + { + /* Attack Rate is maximal: directly switch to Decay or Substain */ + SLOT->volume = MIN_ATT_INDEX; + SLOT->state = (SLOT->sl == MIN_ATT_INDEX) ? EG_SUS : EG_DEC; + } + } + } + + /* recalculate EG output */ + if (SLOT->ssgn ^ (SLOT->ssg&0x04)) + SLOT->vol_out = ((UINT32)(0x200 - SLOT->volume) & MAX_ATT_INDEX) + SLOT->tl; + else + SLOT->vol_out = (UINT32)SLOT->volume + SLOT->tl; + } + + /* next slot */ + SLOT++; + i--; + } while (i); +} + + +INLINE void update_phase_lfo_slot(FM_OPN *OPN, FM_SLOT *SLOT, INT32 pms, UINT32 block_fnum) +{ + UINT32 fnum_lfo = ((block_fnum & 0x7f0) >> 4) * 32 * 8; + INT32 lfo_fn_table_index_offset = lfo_pm_table[ fnum_lfo + pms + OPN->LFO_PM ]; + + block_fnum = block_fnum*2 + lfo_fn_table_index_offset; + + if (lfo_fn_table_index_offset) /* LFO phase modulation active */ + { + UINT8 blk = (block_fnum&0x7000) >> 12; + UINT32 fn = block_fnum & 0xfff; + + /* recalculate keyscale code */ + /*int kc = (blk<<2) | opn_fktable[fn >> 7];*/ + /* This really stupid bug caused a read outside of the + array [size 0x10] and returned invalid values. + This caused an annoying vibrato for some notes. + (Note: seems to be a copy-and-paste from OPNWriteReg -> case 0xA0) + Why are MAME cores always SOO buggy ?! */ + /* Oh, and before I forget: it's correct in fm.c */ + int kc = (blk<<2) | opn_fktable[fn >> 8]; + /* Thanks to Blargg - his patch that helped me to find this bug */ + + /* recalculate (frequency) phase increment counter */ + int fc = (OPN->fn_table[fn]>>(7-blk)) + SLOT->DT[kc]; + + /* (frequency) phase overflow (credits to Nemesis) */ + if (fc < 0) fc += OPN->fn_max; + + /* update phase */ + SLOT->phase += (fc * SLOT->mul) >> 1; + } + else /* LFO phase modulation = zero */ + { + SLOT->phase += SLOT->Incr; + } +} + +INLINE void update_phase_lfo_channel(FM_OPN *OPN, FM_CH *CH) +{ + UINT32 block_fnum = CH->block_fnum; + + UINT32 fnum_lfo = ((block_fnum & 0x7f0) >> 4) * 32 * 8; + INT32 lfo_fn_table_index_offset = lfo_pm_table[ fnum_lfo + CH->pms + OPN->LFO_PM ]; + + block_fnum = block_fnum*2 + lfo_fn_table_index_offset; + + if (lfo_fn_table_index_offset) /* LFO phase modulation active */ + { + UINT8 blk = (block_fnum&0x7000) >> 12; + UINT32 fn = block_fnum & 0xfff; + + /* recalculate keyscale code */ + /*int kc = (blk<<2) | opn_fktable[fn >> 7];*/ + /* the same stupid bug as above */ + int kc = (blk<<2) | opn_fktable[fn >> 8]; + + /* recalculate (frequency) phase increment counter */ + int fc = (OPN->fn_table[fn]>>(7-blk)); + + /* (frequency) phase overflow (credits to Nemesis) */ + int finc = fc + CH->SLOT[SLOT1].DT[kc]; + if (finc < 0) finc += OPN->fn_max; + CH->SLOT[SLOT1].phase += (finc*CH->SLOT[SLOT1].mul) >> 1; + + finc = fc + CH->SLOT[SLOT2].DT[kc]; + if (finc < 0) finc += OPN->fn_max; + CH->SLOT[SLOT2].phase += (finc*CH->SLOT[SLOT2].mul) >> 1; + + finc = fc + CH->SLOT[SLOT3].DT[kc]; + if (finc < 0) finc += OPN->fn_max; + CH->SLOT[SLOT3].phase += (finc*CH->SLOT[SLOT3].mul) >> 1; + + finc = fc + CH->SLOT[SLOT4].DT[kc]; + if (finc < 0) finc += OPN->fn_max; + CH->SLOT[SLOT4].phase += (finc*CH->SLOT[SLOT4].mul) >> 1; + } + else /* LFO phase modulation = zero */ + { + CH->SLOT[SLOT1].phase += CH->SLOT[SLOT1].Incr; + CH->SLOT[SLOT2].phase += CH->SLOT[SLOT2].Incr; + CH->SLOT[SLOT3].phase += CH->SLOT[SLOT3].Incr; + CH->SLOT[SLOT4].phase += CH->SLOT[SLOT4].Incr; + } +} + +/* update phase increment and envelope generator */ +INLINE void refresh_fc_eg_slot(FM_OPN *OPN, FM_SLOT *SLOT , int fc , int kc ) +{ + int ksr = kc >> SLOT->KSR; + + fc += SLOT->DT[kc]; + + /* detects frequency overflow (credits to Nemesis) */ + if (fc < 0) fc += OPN->fn_max; + + /* (frequency) phase increment counter */ + SLOT->Incr = (fc * SLOT->mul) >> 1; + + if( SLOT->ksr != ksr ) + { + SLOT->ksr = ksr; + + /* calculate envelope generator rates */ + if ((SLOT->ar + SLOT->ksr) < 32+62) + { + SLOT->eg_sh_ar = eg_rate_shift [SLOT->ar + SLOT->ksr ]; + SLOT->eg_sel_ar = eg_rate_select2612[SLOT->ar + SLOT->ksr ]; + } + else + { + SLOT->eg_sh_ar = 0; + SLOT->eg_sel_ar = 18*RATE_STEPS; /* verified by Nemesis on real hardware (Attack phase is blocked) */ + } + + SLOT->eg_sh_d1r = eg_rate_shift [SLOT->d1r + SLOT->ksr]; + SLOT->eg_sh_d2r = eg_rate_shift [SLOT->d2r + SLOT->ksr]; + SLOT->eg_sh_rr = eg_rate_shift [SLOT->rr + SLOT->ksr]; + + SLOT->eg_sel_d1r= eg_rate_select2612[SLOT->d1r + SLOT->ksr]; + SLOT->eg_sel_d2r= eg_rate_select2612[SLOT->d2r + SLOT->ksr]; + SLOT->eg_sel_rr = eg_rate_select2612[SLOT->rr + SLOT->ksr]; + } +} + +/* update phase increment counters */ +INLINE void refresh_fc_eg_chan(FM_OPN *OPN, FM_CH *CH ) +{ + if( CH->SLOT[SLOT1].Incr==-1) + { + int fc = CH->fc; + int kc = CH->kcode; + refresh_fc_eg_slot(OPN, &CH->SLOT[SLOT1] , fc , kc ); + refresh_fc_eg_slot(OPN, &CH->SLOT[SLOT2] , fc , kc ); + refresh_fc_eg_slot(OPN, &CH->SLOT[SLOT3] , fc , kc ); + refresh_fc_eg_slot(OPN, &CH->SLOT[SLOT4] , fc , kc ); + } +} + +#define volume_calc(OP) ((OP)->vol_out + (AM & (OP)->AMmask)) + +INLINE signed int op_calc(UINT32 phase, unsigned int env, signed int pm) +{ + UINT32 p; + + p = (env<<3) + sin_tab[ ( ((signed int)((phase & ~FREQ_MASK) + (pm<<15))) >> FREQ_SH ) & SIN_MASK ]; + + if (p >= TL_TAB_LEN) + return 0; + return tl_tab[p]; +} + +INLINE signed int op_calc1(UINT32 phase, unsigned int env, signed int pm) +{ + UINT32 p; + + p = (env<<3) + sin_tab[ ( ((signed int)((phase & ~FREQ_MASK) + pm )) >> FREQ_SH ) & SIN_MASK ]; + + if (p >= TL_TAB_LEN) + return 0; + return tl_tab[p]; +} + +INLINE void chan_calc(YM2612 *F2612, FM_OPN *OPN, FM_CH *CH) +{ + UINT32 AM = OPN->LFO_AM >> CH->ams; + unsigned int eg_out; + + if (CH->Muted) + return; + + OPN->m2 = OPN->c1 = OPN->c2 = OPN->mem = 0; + + *CH->mem_connect = CH->mem_value; /* restore delayed sample (MEM) value to m2 or c2 */ + + eg_out = volume_calc(&CH->SLOT[SLOT1]); + { + INT32 out = CH->op1_out[0] + CH->op1_out[1]; + CH->op1_out[0] = CH->op1_out[1]; + + if( !CH->connect1 ) + { + /* algorithm 5 */ + OPN->mem = OPN->c1 = OPN->c2 = CH->op1_out[0]; + } + else + { + /* other algorithms */ + *CH->connect1 += CH->op1_out[0]; + } + + + CH->op1_out[1] = 0; + if( eg_out < ENV_QUIET ) /* SLOT 1 */ + { + if (!CH->FB) + out=0; + + CH->op1_out[1] = op_calc1(CH->SLOT[SLOT1].phase, eg_out, (out<FB) ); + } + } + + eg_out = volume_calc(&CH->SLOT[SLOT3]); + if( eg_out < ENV_QUIET ) /* SLOT 3 */ + *CH->connect3 += op_calc(CH->SLOT[SLOT3].phase, eg_out, OPN->m2); + + eg_out = volume_calc(&CH->SLOT[SLOT2]); + if( eg_out < ENV_QUIET ) /* SLOT 2 */ + *CH->connect2 += op_calc(CH->SLOT[SLOT2].phase, eg_out, OPN->c1); + + eg_out = volume_calc(&CH->SLOT[SLOT4]); + if( eg_out < ENV_QUIET ) /* SLOT 4 */ + *CH->connect4 += op_calc(CH->SLOT[SLOT4].phase, eg_out, OPN->c2); + + + /* store current MEM */ + CH->mem_value = OPN->mem; + + /* update phase counters AFTER output calculations */ + if(CH->pms) + { + /* add support for 3 slot mode */ + if ((OPN->ST.mode & 0xC0) && (CH == &F2612->CH[2])) + { + update_phase_lfo_slot(OPN, &CH->SLOT[SLOT1], CH->pms, OPN->SL3.block_fnum[1]); + update_phase_lfo_slot(OPN, &CH->SLOT[SLOT2], CH->pms, OPN->SL3.block_fnum[2]); + update_phase_lfo_slot(OPN, &CH->SLOT[SLOT3], CH->pms, OPN->SL3.block_fnum[0]); + update_phase_lfo_slot(OPN, &CH->SLOT[SLOT4], CH->pms, CH->block_fnum); + } + else update_phase_lfo_channel(OPN, CH); + } + else /* no LFO phase modulation */ + { + CH->SLOT[SLOT1].phase += CH->SLOT[SLOT1].Incr; + CH->SLOT[SLOT2].phase += CH->SLOT[SLOT2].Incr; + CH->SLOT[SLOT3].phase += CH->SLOT[SLOT3].Incr; + CH->SLOT[SLOT4].phase += CH->SLOT[SLOT4].Incr; + } +} + +static void FMCloseTable( void ) +{ +#ifdef SAVE_SAMPLE + fclose(sample[0]); +#endif + return; +} + + +/* CSM Key Controll */ +INLINE void CSMKeyControll(FM_OPN *OPN, FM_CH *CH) +{ + /* all key ON (verified by Nemesis on real hardware) */ + FM_KEYON_CSM(OPN,CH,SLOT1); + FM_KEYON_CSM(OPN,CH,SLOT2); + FM_KEYON_CSM(OPN,CH,SLOT3); + FM_KEYON_CSM(OPN,CH,SLOT4); + OPN->SL3.key_csm = 1; +} + +#ifdef __STATE_H__ +/* FM channel save , internal state only */ +static void FMsave_state_channel(running_device *device,FM_CH *CH,int num_ch) +{ + int slot , ch; + + for(ch=0;chop1_out); + state_save_register_device_item(device, ch, CH->fc); + /* slots */ + for(slot=0;slot<4;slot++) + { + FM_SLOT *SLOT = &CH->SLOT[slot]; + state_save_register_device_item(device, ch * 4 + slot, SLOT->phase); + state_save_register_device_item(device, ch * 4 + slot, SLOT->state); + state_save_register_device_item(device, ch * 4 + slot, SLOT->volume); + } + } +} + +static void FMsave_state_st(running_device *device,FM_ST *ST) +{ +#if FM_BUSY_FLAG_SUPPORT + state_save_register_device_item(device, 0, ST->busy_expiry_time.seconds ); + state_save_register_device_item(device, 0, ST->busy_expiry_time.attoseconds ); +#endif + state_save_register_device_item(device, 0, ST->address ); + state_save_register_device_item(device, 0, ST->irq ); + state_save_register_device_item(device, 0, ST->irqmask ); + state_save_register_device_item(device, 0, ST->status ); + state_save_register_device_item(device, 0, ST->mode ); + state_save_register_device_item(device, 0, ST->prescaler_sel ); + state_save_register_device_item(device, 0, ST->fn_h ); + state_save_register_device_item(device, 0, ST->TA ); + state_save_register_device_item(device, 0, ST->TAC ); + state_save_register_device_item(device, 0, ST->TB ); + state_save_register_device_item(device, 0, ST->TBC ); +} +#endif /* _STATE_H */ + +#if BUILD_OPN +/* write a OPN mode register 0x20-0x2f */ +static void OPNWriteMode(FM_OPN *OPN, int r, int v) +{ + UINT8 c; + FM_CH *CH; + + switch(r) + { + case 0x21: /* Test */ + break; + case 0x22: /* LFO FREQ (YM2608/YM2610/YM2610B/YM2612) */ + if (v&8) /* LFO enabled ? */ + { + if (!OPN->lfo_timer_overflow) + { + /* restart LFO */ + OPN->lfo_cnt = 0; + OPN->lfo_timer = 0; + OPN->LFO_AM = 0; + OPN->LFO_PM = 0; + } + + OPN->lfo_timer_overflow = lfo_samples_per_step[v&7] << LFO_SH; + } + else + { + OPN->lfo_timer_overflow = 0; + if (OPN->LFOResetZero) + { + OPN->LFO_AM = 0; + OPN->LFO_PM = 0; + } + } + break; + case 0x24: /* timer A High 8*/ + OPN->ST.TA = (OPN->ST.TA & 0x03)|(((int)v)<<2); + break; + case 0x25: /* timer A Low 2*/ + OPN->ST.TA = (OPN->ST.TA & 0x3fc)|(v&3); + break; + case 0x26: /* timer B */ + OPN->ST.TB = v; + break; + case 0x27: /* mode, timer control */ + set_timers( OPN, &(OPN->ST),v ); + break; + case 0x28: /* key on / off */ + c = v & 0x03; + if( c == 3 ) break; + if( (v&0x04) && (OPN->type & TYPE_6CH) ) c+=3; + CH = OPN->P_CH; + CH = &CH[c]; + if(v&0x10) FM_KEYON(OPN,CH,SLOT1); else FM_KEYOFF(OPN,CH,SLOT1); + if(v&0x20) FM_KEYON(OPN,CH,SLOT2); else FM_KEYOFF(OPN,CH,SLOT2); + if(v&0x40) FM_KEYON(OPN,CH,SLOT3); else FM_KEYOFF(OPN,CH,SLOT3); + if(v&0x80) FM_KEYON(OPN,CH,SLOT4); else FM_KEYOFF(OPN,CH,SLOT4); + break; + } +} + +/* write a OPN register (0x30-0xff) */ +static void OPNWriteReg(FM_OPN *OPN, int r, int v) +{ + FM_CH *CH; + FM_SLOT *SLOT; + + UINT8 c = OPN_CHAN(r); + + if (c == 3) return; /* 0xX3,0xX7,0xXB,0xXF */ + + if (r >= 0x100) c+=3; + + CH = OPN->P_CH; + CH = &CH[c]; + + SLOT = &(CH->SLOT[OPN_SLOT(r)]); + + switch( r & 0xf0 ) { + case 0x30: /* DET , MUL */ + set_det_mul(&OPN->ST,CH,SLOT,v); + break; + + case 0x40: /* TL */ + set_tl(CH,SLOT,v); + break; + + case 0x50: /* KS, AR */ + set_ar_ksr(OPN->type,CH,SLOT,v); + break; + + case 0x60: /* bit7 = AM ENABLE, DR */ + set_dr(OPN->type, SLOT,v); + + if(OPN->type & TYPE_LFOPAN) /* YM2608/2610/2610B/2612 */ + { + SLOT->AMmask = (v&0x80) ? ~0 : 0; + } + break; + + case 0x70: /* SR */ + set_sr(OPN->type,SLOT,v); + break; + + case 0x80: /* SL, RR */ + set_sl_rr(OPN->type,SLOT,v); + break; + + case 0x90: /* SSG-EG */ + SLOT->ssg = v&0x0f; + + /* recalculate EG output */ + if (SLOT->state > EG_REL) + { + if ((SLOT->ssg&0x08) && (SLOT->ssgn ^ (SLOT->ssg&0x04))) + SLOT->vol_out = ((UINT32)(0x200 - SLOT->volume) & MAX_ATT_INDEX) + SLOT->tl; + else + SLOT->vol_out = (UINT32)SLOT->volume + SLOT->tl; + } + + /* SSG-EG envelope shapes : + + E AtAlH + 1 0 0 0 \\\\ + + 1 0 0 1 \___ + + 1 0 1 0 \/\/ + ___ + 1 0 1 1 \ + + 1 1 0 0 //// + ___ + 1 1 0 1 / + + 1 1 1 0 /\/\ + + 1 1 1 1 /___ + + + E = SSG-EG enable + + + The shapes are generated using Attack, Decay and Sustain phases. + + Each single character in the diagrams above represents this whole + sequence: + + - when KEY-ON = 1, normal Attack phase is generated (*without* any + difference when compared to normal mode), + + - later, when envelope level reaches minimum level (max volume), + the EG switches to Decay phase (which works with bigger steps + when compared to normal mode - see below), + + - later when envelope level passes the SL level, + the EG swithes to Sustain phase (which works with bigger steps + when compared to normal mode - see below), + + - finally when envelope level reaches maximum level (min volume), + the EG switches to Attack phase again (depends on actual waveform). + + Important is that when switch to Attack phase occurs, the phase counter + of that operator will be zeroed-out (as in normal KEY-ON) but not always. + (I havent found the rule for that - perhaps only when the output level is low) + + The difference (when compared to normal Envelope Generator mode) is + that the resolution in Decay and Sustain phases is 4 times lower; + this results in only 256 steps instead of normal 1024. + In other words: + when SSG-EG is disabled, the step inside of the EG is one, + when SSG-EG is enabled, the step is four (in Decay and Sustain phases). + + Times between the level changes are the same in both modes. + + + Important: + Decay 1 Level (so called SL) is compared to actual SSG-EG output, so + it is the same in both SSG and no-SSG modes, with this exception: + + when the SSG-EG is enabled and is generating raising levels + (when the EG output is inverted) the SL will be found at wrong level !!! + For example, when SL=02: + 0 -6 = -6dB in non-inverted EG output + 96-6 = -90dB in inverted EG output + Which means that EG compares its level to SL as usual, and that the + output is simply inverted afterall. + + + The Yamaha's manuals say that AR should be set to 0x1f (max speed). + That is not necessary, but then EG will be generating Attack phase. + + */ + + + break; + + case 0xa0: + switch( OPN_SLOT(r) ) + { + case 0: /* 0xa0-0xa2 : FNUM1 */ + { + UINT32 fn = (((UINT32)( (OPN->ST.fn_h)&7))<<8) + v; + UINT8 blk = OPN->ST.fn_h>>3; + /* keyscale code */ + CH->kcode = (blk<<2) | opn_fktable[fn >> 7]; + /* phase increment counter */ + CH->fc = OPN->fn_table[fn*2]>>(7-blk); + + /* store fnum in clear form for LFO PM calculations */ + CH->block_fnum = (blk<<11) | fn; + + CH->SLOT[SLOT1].Incr=-1; + } + break; + case 1: /* 0xa4-0xa6 : FNUM2,BLK */ + OPN->ST.fn_h = v&0x3f; + break; + case 2: /* 0xa8-0xaa : 3CH FNUM1 */ + if(r < 0x100) + { + UINT32 fn = (((UINT32)(OPN->SL3.fn_h&7))<<8) + v; + UINT8 blk = OPN->SL3.fn_h>>3; + /* keyscale code */ + OPN->SL3.kcode[c]= (blk<<2) | opn_fktable[fn >> 7]; + /* phase increment counter */ + OPN->SL3.fc[c] = OPN->fn_table[fn*2]>>(7-blk); + OPN->SL3.block_fnum[c] = (blk<<11) | fn; + (OPN->P_CH)[2].SLOT[SLOT1].Incr=-1; + } + break; + case 3: /* 0xac-0xae : 3CH FNUM2,BLK */ + if(r < 0x100) + OPN->SL3.fn_h = v&0x3f; + break; + } + break; + + case 0xb0: + switch( OPN_SLOT(r) ) + { + case 0: /* 0xb0-0xb2 : FB,ALGO */ + { + int feedback = (v>>3)&7; + CH->ALGO = v&7; + CH->FB = feedback ? feedback+6 : 0; + setup_connection( OPN, CH, c ); + } + break; + case 1: /* 0xb4-0xb6 : L , R , AMS , PMS (YM2612/YM2610B/YM2610/YM2608) */ + if( OPN->type & TYPE_LFOPAN) + { + /* b0-2 PMS */ + CH->pms = (v & 7) * 32; /* CH->pms = PM depth * 32 (index in lfo_pm_table) */ + + /* b4-5 AMS */ + CH->ams = lfo_ams_depth_shift[(v>>4) & 0x03]; + + /* PAN : b7 = L, b6 = R */ + OPN->pan[ c*2 ] = (v & 0x80) ? ~0 : 0; + OPN->pan[ c*2+1 ] = (v & 0x40) ? ~0 : 0; + + } + break; + } + break; + } +} + +/* initialize time tables */ +static void init_timetables(FM_OPN *OPN, double freqbase) +{ + int i,d; + double rate; + + /* DeTune table */ + for (d = 0;d <= 3;d++) + { + for (i = 0;i <= 31;i++) + { + rate = ((double)dt_tab[d*32 + i]) * freqbase * (1<<(FREQ_SH-10)); /* -10 because chip works with 10.10 fixed point, while we use 16.16 */ + OPN->ST.dt_tab[d][i] = (INT32) rate; + OPN->ST.dt_tab[d+4][i] = -OPN->ST.dt_tab[d][i]; + } + } + + /* there are 2048 FNUMs that can be generated using FNUM/BLK registers + but LFO works with one more bit of a precision so we really need 4096 elements */ + /* calculate fnumber -> increment counter table */ + for(i = 0; i < 4096; i++) + { + /* freq table for octave 7 */ + /* OPN phase increment counter = 20bit */ + /* the correct formula is : F-Number = (144 * fnote * 2^20 / M) / 2^(B-1) */ + /* where sample clock is M/144 */ + /* this means the increment value for one clock sample is FNUM * 2^(B-1) = FNUM * 64 for octave 7 */ + /* we also need to handle the ratio between the chip frequency and the emulated frequency (can be 1.0) */ + OPN->fn_table[i] = (UINT32)( (double)i * 32 * freqbase * (1<<(FREQ_SH-10)) ); /* -10 because chip works with 10.10 fixed point, while we use 16.16 */ + } + + /* maximal frequency is required for Phase overflow calculation, register size is 17 bits (Nemesis) */ + OPN->fn_max = (UINT32)( (double)0x20000 * freqbase * (1<<(FREQ_SH-10)) ); +} + +/* prescaler set (and make time tables) */ +static void OPNSetPres(FM_OPN *OPN, int pres, int timer_prescaler, int SSGpres) +{ + /* frequency base */ + OPN->ST.freqbase = (OPN->ST.rate) ? ((double)OPN->ST.clock / OPN->ST.rate) / pres : 0; + if ( fabs( OPN->ST.freqbase - 1.0 ) < 0.0001 ) + OPN->ST.freqbase = 1.0; + + /* EG is updated every 3 samples */ + OPN->eg_timer_add = (UINT32)((1<ST.freqbase); + OPN->eg_timer_overflow = ( 3 ) * (1<lfo_timer_add = (UINT32)((1<ST.freqbase); + + /* Timer base time */ + OPN->ST.timer_prescaler = timer_prescaler; + + /* make time tables */ + init_timetables(OPN, OPN->ST.freqbase); +} + +static void reset_channels( FM_ST *ST , FM_CH *CH , int num ) +{ + int c,s; + + for( c = 0 ; c < num ; c++ ) + { + //memset(&CH[c], 0x00, sizeof(FM_CH)); + CH[c].mem_value = 0; + CH[c].op1_out[0] = 0; + CH[c].op1_out[1] = 0; + CH[c].fc = 0; + for(s = 0 ; s < 4 ; s++ ) + { + //memset(&CH[c].SLOT[s], 0x00, sizeof(FM_SLOT)); + CH[c].SLOT[s].Incr = -1; + CH[c].SLOT[s].key = 0; + CH[c].SLOT[s].phase = 0; + CH[c].SLOT[s].ssg = 0; + CH[c].SLOT[s].ssgn = 0; + CH[c].SLOT[s].state= EG_OFF; + CH[c].SLOT[s].volume = MAX_ATT_INDEX; + CH[c].SLOT[s].vol_out= MAX_ATT_INDEX; + } + } +} + +/* initialize generic tables */ +static void init_tables(void) +{ + signed int i,x; + signed int n; + double o,m; + + /* build Linear Power Table */ + for (x=0; x>= 4; /* 12 bits here */ + if (n&1) /* round to nearest */ + n = (n>>1)+1; + else + n = n>>1; + /* 11 bits here (rounded) */ + n <<= 2; /* 13 bits here (as in real chip) */ + + + /* 14 bits (with sign bit) */ + tl_tab[ x*2 + 0 ] = n; + tl_tab[ x*2 + 1 ] = -tl_tab[ x*2 + 0 ]; + + /* one entry in the 'Power' table use the following format, xxxxxyyyyyyyys with: */ + /* s = sign bit */ + /* yyyyyyyy = 8-bits decimal part (0-TL_RES_LEN) */ + /* xxxxx = 5-bits integer 'shift' value (0-31) but, since Power table output is 13 bits, */ + /* any value above 13 (included) would be discarded. */ + for (i=1; i<13; i++) + { + tl_tab[ x*2+0 + i*2*TL_RES_LEN ] = tl_tab[ x*2+0 ]>>i; + tl_tab[ x*2+1 + i*2*TL_RES_LEN ] = -tl_tab[ x*2+0 + i*2*TL_RES_LEN ]; + } + } + + /* build Logarithmic Sinus table */ + for (i=0; i0.0) + o = 8*log(1.0/m)/log(2.0); /* convert to 'decibels' */ + else + o = 8*log(-1.0/m)/log(2.0); /* convert to 'decibels' */ + + o = o / (ENV_STEP/4); + + n = (int)(2.0*o); + if (n&1) /* round to nearest */ + n = (n>>1)+1; + else + n = n>>1; + + /* 13-bits (8.5) value is formatted for above 'Power' table */ + sin_tab[ i ] = n*2 + (m>=0.0? 0: 1 ); + } + + /* build LFO PM modulation table */ + for(i = 0; i < 8; i++) /* 8 PM depths */ + { + UINT8 fnum; + for (fnum=0; fnum<128; fnum++) /* 7 bits meaningful of F-NUMBER */ + { + UINT8 value; + UINT8 step; + UINT32 offset_depth = i; + UINT32 offset_fnum_bit; + UINT32 bit_tmp; + + for (step=0; step<8; step++) + { + value = 0; + for (bit_tmp=0; bit_tmp<7; bit_tmp++) /* 7 bits */ + { + if (fnum & (1<OPN; + INT32 *out_fm = OPN->out_fm; + int i; + FMSAMPLE *bufL,*bufR; + INT32 dacout; + FM_CH *cch[6]; + int lt,rt; + + /* set bufer */ + bufL = buffer[0]; + bufR = buffer[1]; + + cch[0] = &F2612->CH[0]; + cch[1] = &F2612->CH[1]; + cch[2] = &F2612->CH[2]; + cch[3] = &F2612->CH[3]; + cch[4] = &F2612->CH[4]; + cch[5] = &F2612->CH[5]; + + if (! F2612->MuteDAC) + dacout = F2612->dacout; + else + dacout = 0; + + /* refresh PG and EG */ + refresh_fc_eg_chan( OPN, cch[0] ); + refresh_fc_eg_chan( OPN, cch[1] ); + if( (OPN->ST.mode & 0xc0) ) + { + /* 3SLOT MODE */ + if( cch[2]->SLOT[SLOT1].Incr==-1) + { + refresh_fc_eg_slot(OPN, &cch[2]->SLOT[SLOT1] , OPN->SL3.fc[1] , OPN->SL3.kcode[1] ); + refresh_fc_eg_slot(OPN, &cch[2]->SLOT[SLOT2] , OPN->SL3.fc[2] , OPN->SL3.kcode[2] ); + refresh_fc_eg_slot(OPN, &cch[2]->SLOT[SLOT3] , OPN->SL3.fc[0] , OPN->SL3.kcode[0] ); + refresh_fc_eg_slot(OPN, &cch[2]->SLOT[SLOT4] , cch[2]->fc , cch[2]->kcode ); + } + }else refresh_fc_eg_chan( OPN, cch[2] ); + refresh_fc_eg_chan( OPN, cch[3] ); + refresh_fc_eg_chan( OPN, cch[4] ); + refresh_fc_eg_chan( OPN, cch[5] ); + if (! length) + { + update_ssg_eg_channel(&cch[0]->SLOT[SLOT1]); + update_ssg_eg_channel(&cch[1]->SLOT[SLOT1]); + update_ssg_eg_channel(&cch[2]->SLOT[SLOT1]); + update_ssg_eg_channel(&cch[3]->SLOT[SLOT1]); + update_ssg_eg_channel(&cch[4]->SLOT[SLOT1]); + update_ssg_eg_channel(&cch[5]->SLOT[SLOT1]); + } + + + /* buffering */ + for(i=0; i < length ; i++) + { + /* clear outputs */ + out_fm[0] = 0; + out_fm[1] = 0; + out_fm[2] = 0; + out_fm[3] = 0; + out_fm[4] = 0; + out_fm[5] = 0; + + /* update SSG-EG output */ + update_ssg_eg_channel(&cch[0]->SLOT[SLOT1]); + update_ssg_eg_channel(&cch[1]->SLOT[SLOT1]); + update_ssg_eg_channel(&cch[2]->SLOT[SLOT1]); + update_ssg_eg_channel(&cch[3]->SLOT[SLOT1]); + update_ssg_eg_channel(&cch[4]->SLOT[SLOT1]); + update_ssg_eg_channel(&cch[5]->SLOT[SLOT1]); + + /* calculate FM */ + if (! F2612->dac_test) + { + chan_calc(F2612, OPN, cch[0]); + chan_calc(F2612, OPN, cch[1]); + chan_calc(F2612, OPN, cch[2]); + chan_calc(F2612, OPN, cch[3]); + chan_calc(F2612, OPN, cch[4]); + if( F2612->dacen ) + *cch[5]->connect4 += dacout; + else + chan_calc(F2612, OPN, cch[5]); + } + else + { + out_fm[0] = out_fm[1] = dacout; + out_fm[2] = out_fm[3] = dacout; + out_fm[5] = dacout; + } + + /* advance LFO */ + advance_lfo(OPN); + + /* advance envelope generator */ + OPN->eg_timer += OPN->eg_timer_add; + while (OPN->eg_timer >= OPN->eg_timer_overflow) + { + OPN->eg_timer -= OPN->eg_timer_overflow; + OPN->eg_cnt++; + + advance_eg_channel(OPN, &cch[0]->SLOT[SLOT1]); + advance_eg_channel(OPN, &cch[1]->SLOT[SLOT1]); + advance_eg_channel(OPN, &cch[2]->SLOT[SLOT1]); + advance_eg_channel(OPN, &cch[3]->SLOT[SLOT1]); + advance_eg_channel(OPN, &cch[4]->SLOT[SLOT1]); + advance_eg_channel(OPN, &cch[5]->SLOT[SLOT1]); + } + + /*fprintf(hFile, "%u", FileSample, out_fm[0]); + for (lt = 0; lt < 6; lt ++) + fprintf(hFile, "\t%d", out_fm[lt]); + fprintf(hFile, "\n"); + FileSample ++;*/ + + if (out_fm[0] > 8192) out_fm[0] = 8192; + else if (out_fm[0] < -8192) out_fm[0] = -8192; + if (out_fm[1] > 8192) out_fm[1] = 8192; + else if (out_fm[1] < -8192) out_fm[1] = -8192; + if (out_fm[2] > 8192) out_fm[2] = 8192; + else if (out_fm[2] < -8192) out_fm[2] = -8192; + if (out_fm[3] > 8192) out_fm[3] = 8192; + else if (out_fm[3] < -8192) out_fm[3] = -8192; + if (out_fm[4] > 8192) out_fm[4] = 8192; + else if (out_fm[4] < -8192) out_fm[4] = -8192; + if (out_fm[5] > 8192) out_fm[5] = 8192; + else if (out_fm[5] < -8192) out_fm[5] = -8192; + + /* 6-channels mixing */ + lt = ((out_fm[0]>>0) & OPN->pan[0]); + rt = ((out_fm[0]>>0) & OPN->pan[1]); + lt += ((out_fm[1]>>0) & OPN->pan[2]); + rt += ((out_fm[1]>>0) & OPN->pan[3]); + lt += ((out_fm[2]>>0) & OPN->pan[4]); + rt += ((out_fm[2]>>0) & OPN->pan[5]); + lt += ((out_fm[3]>>0) & OPN->pan[6]); + rt += ((out_fm[3]>>0) & OPN->pan[7]); + if (! F2612->dac_test) + { + lt += ((out_fm[4]>>0) & OPN->pan[8]); + rt += ((out_fm[4]>>0) & OPN->pan[9]); + } + else + { + lt += dacout; + lt += dacout; + } + lt += ((out_fm[5]>>0) & OPN->pan[10]); + rt += ((out_fm[5]>>0) & OPN->pan[11]); + +// Limit( lt, MAXOUT, MINOUT ); +// Limit( rt, MAXOUT, MINOUT ); + + #ifdef SAVE_SAMPLE + SAVE_ALL_CHANNELS + #endif + + /* buffering */ + if (F2612->WaveOutMode & 0x01) + F2612->WaveL = lt; + if (F2612->WaveOutMode & 0x02) + F2612->WaveR = rt; + if (F2612->WaveOutMode ^ 0x03) + F2612->WaveOutMode ^= 0x03; + bufL[i] = F2612->WaveL; + bufR[i] = F2612->WaveR; + + /* CSM mode: if CSM Key ON has occured, CSM Key OFF need to be sent */ + /* only if Timer A does not overflow again (i.e CSM Key ON not set again) */ + OPN->SL3.key_csm <<= 1; + + /* CSM Mode Key ON still disabled */ + if (OPN->SL3.key_csm & 2) + { + /* CSM Mode Key OFF (verified by Nemesis on real hardware) */ + FM_KEYOFF_CSM(cch[2],SLOT1); + FM_KEYOFF_CSM(cch[2],SLOT2); + FM_KEYOFF_CSM(cch[2],SLOT3); + FM_KEYOFF_CSM(cch[2],SLOT4); + OPN->SL3.key_csm = 0; + } + } +} + +/* initialize YM2612 emulator(s) */ +//void * ym2612_init(void *param, running_device *device, int clock, int rate, +// FM_TIMERHANDLER timer_handler,FM_IRQHANDLER IRQHandler) +void * ym2612_init(int clock, int rate) +{ + YM2612 *F2612; + + /* allocate extend state space */ + //F2612 = auto_alloc_clear(device->machine, YM2612); + F2612 = (YM2612 *)malloc(sizeof(YM2612)); + if (F2612 == NULL) + return NULL; + memset(F2612, 0x00, sizeof(YM2612)); + /* allocate total level table (128kb space) */ + init_tables(); + + F2612->OPN.type = TYPE_YM2612; + F2612->OPN.P_CH = F2612->CH; + //F2612->OPN.ST.device = device; + F2612->OPN.ST.clock = clock; + F2612->OPN.ST.rate = rate; + /* F2612->OPN.ST.irq = 0; */ + /* F2612->OPN.ST.status = 0; */ + /* Extend handler */ + + if (rate > clock / 128) + F2612->WaveOutMode = 0x01; + else + F2612->WaveOutMode = 0x03; + /*hFile = fopen("YM2612.log", "wt"); + fprintf(hFile, "Clock: %d, Sample Rate: %d\n", clock, rate); + fprintf(hFile, "Sample\tCh 0\tCh 1\tCh 2\tCh 3\tCh 4\tCh 5\n"); + FileSample = 0;*/ + + return F2612; +} + +/* shut down emulator */ +void ym2612_shutdown(void *chip) +{ + YM2612 *F2612 = (YM2612 *)chip; + //fclose(hFile); + + FMCloseTable(); + //auto_free(F2612->OPN.ST.device->machine, F2612); + free(F2612); +} + +/* reset one of chip */ +void ym2612_reset_chip(void *chip) +{ + int i; + YM2612 *F2612 = (YM2612 *)chip; + FM_OPN *OPN = &F2612->OPN; + + OPNSetPres( OPN, 6*24, 6*24, 0); + /* status clear */ + FM_IRQMASK_SET(&OPN->ST,0x03); + FM_BUSY_CLEAR(&OPN->ST); + //OPNWriteMode(OPN,0x27,0x30); /* mode 0 , timer reset */ + + OPN->eg_timer = 0; + OPN->eg_cnt = 0; + + OPN->lfo_timer = 0; + OPN->lfo_cnt = 0; + OPN->LFO_AM = 0; + OPN->LFO_PM = 0; + + OPN->ST.TAC = 0; + OPN->ST.TBC = 0; + + OPN->SL3.key_csm = 0; + + OPN->ST.status = 0; + OPN->ST.mode = 0; + + memset(F2612->REGS, 0x00, sizeof(UINT8) * 512); + + OPNWriteMode(OPN,0x22,0x00); + + OPNWriteMode(OPN,0x27,0x30); + OPNWriteMode(OPN,0x26,0x00); + OPNWriteMode(OPN,0x25,0x00); + OPNWriteMode(OPN,0x24,0x00); + + reset_channels( &OPN->ST , &F2612->CH[0] , 6 ); + + for(i = 0xb6 ; i >= 0xb4 ; i-- ) + { + OPNWriteReg(OPN,i ,0xc0); + OPNWriteReg(OPN,i|0x100,0xc0); + } + for(i = 0xb2 ; i >= 0x30 ; i-- ) + { + OPNWriteReg(OPN,i ,0); + OPNWriteReg(OPN,i|0x100,0); + } + + /* DAC mode clear */ + F2612->dacen = 0; + F2612->dac_test = 0; + F2612->dacout = 0; + + if (F2612->WaveOutMode == 0x02) + F2612->WaveOutMode >>= 1; +} + +/* YM2612 write */ +/* n = number */ +/* a = address */ +/* v = value */ +int ym2612_write(void *chip, int a, UINT8 v) +{ + YM2612 *F2612 = (YM2612 *)chip; + int addr; + + v &= 0xff; /* adjust to 8 bit bus */ + + switch( a&3) + { + case 0: /* address port 0 */ + F2612->OPN.ST.address = v; + F2612->addr_A1 = 0; + break; + + case 1: /* data port 0 */ + if (F2612->addr_A1 != 0) + break; /* verified on real YM2608 */ + + addr = F2612->OPN.ST.address; + F2612->REGS[addr] = v; + switch( addr & 0xf0 ) + { + case 0x20: /* 0x20-0x2f Mode */ + switch( addr ) + { + case 0x2a: /* DAC data (YM2612) */ + F2612->dacout = ((int)v - 0x80) << 6; /* level unknown */ + break; + case 0x2b: /* DAC Sel (YM2612) */ + /* b7 = dac enable */ + F2612->dacen = v & 0x80; + break; + case 0x2C: // undocumented: DAC Test Reg + // b5 = volume enable + F2612->dac_test = v & 0x20; + break; + default: /* OPN section */ + /* write register */ + OPNWriteMode(&(F2612->OPN),addr,v); + } + break; + default: /* 0x30-0xff OPN section */ + /* write register */ + OPNWriteReg(&(F2612->OPN),addr,v); + } + break; + + case 2: /* address port 1 */ + F2612->OPN.ST.address = v; + F2612->addr_A1 = 1; + break; + + case 3: /* data port 1 */ + if (F2612->addr_A1 != 1) + break; /* verified on real YM2608 */ + + addr = F2612->OPN.ST.address; + F2612->REGS[addr | 0x100] = v; + OPNWriteReg(&(F2612->OPN),addr | 0x100,v); + break; + } + return F2612->OPN.ST.irq; +} + +UINT8 ym2612_read(void *chip,int a) +{ + YM2612 *F2612 = (YM2612 *)chip; + + switch( a&3) + { + case 0: /* status 0 */ + return FM_STATUS_FLAG(&F2612->OPN.ST); + case 1: + case 2: + case 3: + //LOG(LOG_WAR,("YM2612 #%p:A=%d read unmapped area\n",F2612->OPN.ST.param,a)); + return FM_STATUS_FLAG(&F2612->OPN.ST); + } + return 0; +} + +int ym2612_timer_over(void *chip,int c) +{ + YM2612 *F2612 = (YM2612 *)chip; + + if( c ) + { /* Timer B */ + TimerBOver( &(F2612->OPN.ST) ); + } + else + { /* Timer A */ + /* timer update */ + TimerAOver( &(F2612->OPN.ST) ); + /* CSM mode key,TL controll */ + if ((F2612->OPN.ST.mode & 0xc0) == 0x80) + { /* CSM mode total level latch and auto key on */ + CSMKeyControll( &F2612->OPN, &(F2612->CH[2]) ); + } + } + return F2612->OPN.ST.irq; +} + + +void ym2612_set_mutemask(void *chip, UINT32 MuteMask) +{ + YM2612 *F2612 = (YM2612 *)chip; + UINT8 CurChn; + + for (CurChn = 0; CurChn < 6; CurChn ++) + F2612->CH[CurChn].Muted = (MuteMask >> CurChn) & 0x01; + F2612->MuteDAC = (MuteMask >> 6) & 0x01; + + return; +} + +void ym2612_setoptions(void *chip, UINT8 Flags) +{ + YM2612 *F2612 = (YM2612 *)chip; + F2612->OPN.LFOResetZero = (Flags >> 2) & 0x01; + + return; +} +#endif /* (BUILD_YM2612||BUILD_YM3238) */ diff --git a/Frameworks/GME/gme/fmopl.cpp b/Frameworks/GME/gme/fmopl.cpp new file mode 100644 index 000000000..179410ff8 --- /dev/null +++ b/Frameworks/GME/gme/fmopl.cpp @@ -0,0 +1,2621 @@ +/* +** +** File: fmopl.c - software implementation of FM sound generator +** types OPL and OPL2 +** +** Copyright Jarek Burczynski (bujar at mame dot net) +** Copyright Tatsuyuki Satoh , MultiArcadeMachineEmulator development +** +** Version 0.72 +** + +Revision History: + +04-08-2003 Jarek Burczynski: + - removed BFRDY hack. BFRDY is busy flag, and it should be 0 only when the chip + handles memory read/write or during the adpcm synthesis when the chip + requests another byte of ADPCM data. + +24-07-2003 Jarek Burczynski: + - added a small hack for Y8950 status BFRDY flag (bit 3 should be set after + some (unknown) delay). Right now it's always set. + +14-06-2003 Jarek Burczynski: + - implemented all of the status register flags in Y8950 emulation + - renamed y8950_set_delta_t_memory() parameters from _rom_ to _mem_ since + they can be either RAM or ROM + +08-10-2002 Jarek Burczynski (thanks to Dox for the YM3526 chip) + - corrected ym3526_read() to always set bit 2 and bit 1 + to HIGH state - identical to ym3812_read (verified on real YM3526) + +04-28-2002 Jarek Burczynski: + - binary exact Envelope Generator (verified on real YM3812); + compared to YM2151: the EG clock is equal to internal_clock, + rates are 2 times slower and volume resolution is one bit less + - modified interface functions (they no longer return pointer - + that's internal to the emulator now): + - new wrapper functions for OPLCreate: ym3526_init(), ym3812_init() and y8950_init() + - corrected 'off by one' error in feedback calculations (when feedback is off) + - enabled waveform usage (credit goes to Vlad Romascanu and zazzal22) + - speeded up noise generator calculations (Nicola Salmoria) + +03-24-2002 Jarek Burczynski (thanks to Dox for the YM3812 chip) + Complete rewrite (all verified on real YM3812): + - corrected sin_tab and tl_tab data + - corrected operator output calculations + - corrected waveform_select_enable register; + simply: ignore all writes to waveform_select register when + waveform_select_enable == 0 and do not change the waveform previously selected. + - corrected KSR handling + - corrected Envelope Generator: attack shape, Sustain mode and + Percussive/Non-percussive modes handling + - Envelope Generator rates are two times slower now + - LFO amplitude (tremolo) and phase modulation (vibrato) + - rhythm sounds phase generation + - white noise generator (big thanks to Olivier Galibert for mentioning Berlekamp-Massey algorithm) + - corrected key on/off handling (the 'key' signal is ORed from three sources: FM, rhythm and CSM) + - funky details (like ignoring output of operator 1 in BD rhythm sound when connect == 1) + +12-28-2001 Acho A. Tang + - reflected Delta-T EOS status on Y8950 status port. + - fixed subscription range of attack/decay tables + + + To do: + add delay before key off in CSM mode (see CSMKeyControll) + verify volume of the FM part on the Y8950 +*/ + +#include +#include +#define _USE_MATH_DEFINES +#include +#include "fmopl.h" +#include "ymdeltat.h" + +#ifndef INLINE +#define INLINE __inline +#endif +#ifndef NULL + #define NULL ((void *)0) +#endif +#ifndef logerror +#define logerror (void) +#endif + +#ifndef M_PI + #define M_PI 3.14159265358979323846 +#endif + +/* output final shift */ +#if (OPL_SAMPLE_BITS==16) + #define FINAL_SH (0) + #define MAXOUT (+32767) + #define MINOUT (-32768) +#else + #define FINAL_SH (8) + #define MAXOUT (+127) + #define MINOUT (-128) +#endif + + +#define FREQ_SH 16 /* 16.16 fixed point (frequency calculations) */ +#define EG_SH 16 /* 16.16 fixed point (EG timing) */ +#define LFO_SH 24 /* 8.24 fixed point (LFO calculations) */ +#define TIMER_SH 16 /* 16.16 fixed point (timers calculations) */ + +#define FREQ_MASK ((1<=0) + { + if (value < 0x0200) + return (value & ~0); + if (value < 0x0400) + return (value & ~1); + if (value < 0x0800) + return (value & ~3); + if (value < 0x1000) + return (value & ~7); + if (value < 0x2000) + return (value & ~15); + if (value < 0x4000) + return (value & ~31); + return (value & ~63); + } + /*else value < 0*/ + if (value > -0x0200) + return (~abs(value) & ~0); + if (value > -0x0400) + return (~abs(value) & ~1); + if (value > -0x0800) + return (~abs(value) & ~3); + if (value > -0x1000) + return (~abs(value) & ~7); + if (value > -0x2000) + return (~abs(value) & ~15); + if (value > -0x4000) + return (~abs(value) & ~31); + return (~abs(value) & ~63); +} + + +static FILE *sample[1]; + #if 1 /*save to MONO file */ + #define SAVE_ALL_CHANNELS \ + { signed int pom = acc_calc(lt); \ + fputc((unsigned short)pom&0xff,sample[0]); \ + fputc(((unsigned short)pom>>8)&0xff,sample[0]); \ + } + #else /*save to STEREO file */ + #define SAVE_ALL_CHANNELS \ + { signed int pom = lt; \ + fputc((unsigned short)pom&0xff,sample[0]); \ + fputc(((unsigned short)pom>>8)&0xff,sample[0]); \ + pom = rt; \ + fputc((unsigned short)pom&0xff,sample[0]); \ + fputc(((unsigned short)pom>>8)&0xff,sample[0]); \ + } + #endif +#endif + +#define LOG_CYM_FILE 0 +//static FILE * cymfile = NULL; + + + +#define OPL_TYPE_WAVESEL 0x01 /* waveform select */ +#define OPL_TYPE_ADPCM 0x02 /* DELTA-T ADPCM unit */ +#define OPL_TYPE_KEYBOARD 0x04 /* keyboard interface */ +#define OPL_TYPE_IO 0x08 /* I/O port */ + +/* ---------- Generic interface section ---------- */ +#define OPL_TYPE_YM3526 (0) +#define OPL_TYPE_YM3812 (OPL_TYPE_WAVESEL) +#define OPL_TYPE_Y8950 (OPL_TYPE_ADPCM|OPL_TYPE_KEYBOARD|OPL_TYPE_IO) + + + +typedef struct{ + UINT32 ar; /* attack rate: AR<<2 */ + UINT32 dr; /* decay rate: DR<<2 */ + UINT32 rr; /* release rate:RR<<2 */ + UINT8 KSR; /* key scale rate */ + UINT8 ksl; /* keyscale level */ + UINT8 ksr; /* key scale rate: kcode>>KSR */ + UINT8 mul; /* multiple: mul_tab[ML] */ + + /* Phase Generator */ + UINT32 Cnt; /* frequency counter */ + UINT32 Incr; /* frequency counter step */ + UINT8 FB; /* feedback shift value */ + INT32 *connect1; /* slot1 output pointer */ + INT32 op1_out[2]; /* slot1 output for feedback */ + UINT8 CON; /* connection (algorithm) type */ + + /* Envelope Generator */ + UINT8 eg_type; /* percussive/non-percussive mode */ + UINT8 state; /* phase type */ + UINT32 TL; /* total level: TL << 2 */ + INT32 TLL; /* adjusted now TL */ + INT32 volume; /* envelope counter */ + UINT32 sl; /* sustain level: sl_tab[SL] */ + UINT8 eg_sh_ar; /* (attack state) */ + UINT8 eg_sel_ar; /* (attack state) */ + UINT8 eg_sh_dr; /* (decay state) */ + UINT8 eg_sel_dr; /* (decay state) */ + UINT8 eg_sh_rr; /* (release state) */ + UINT8 eg_sel_rr; /* (release state) */ + UINT32 key; /* 0 = KEY OFF, >0 = KEY ON */ + + /* LFO */ + UINT32 AMmask; /* LFO Amplitude Modulation enable mask */ + UINT8 vib; /* LFO Phase Modulation enable flag (active high)*/ + + /* waveform select */ + UINT16 wavetable; +} OPL_SLOT; + +typedef struct{ + OPL_SLOT SLOT[2]; + /* phase generator state */ + UINT32 block_fnum; /* block+fnum */ + UINT32 fc; /* Freq. Increment base */ + UINT32 ksl_base; /* KeyScaleLevel Base step */ + UINT8 kcode; /* key code (for key scaling) */ +} OPL_CH; + +/* OPL state */ +typedef struct fm_opl_f { + /* FM channel slots */ + OPL_CH P_CH[9]; /* OPL/OPL2 chips have 9 channels*/ + + UINT32 eg_cnt; /* global envelope generator counter */ + UINT32 eg_timer; /* global envelope generator counter works at frequency = chipclock/72 */ + UINT32 eg_timer_add; /* step of eg_timer */ + UINT32 eg_timer_overflow; /* envelope generator timer overlfows every 1 sample (on real chip) */ + + UINT8 rhythm; /* Rhythm mode */ + + UINT32 fn_tab[1024]; /* fnumber->increment counter */ + + /* LFO */ + UINT8 lfo_am_depth; + UINT8 lfo_pm_depth_range; + UINT32 lfo_am_cnt; + UINT32 lfo_am_inc; + UINT32 lfo_pm_cnt; + UINT32 lfo_pm_inc; + + UINT32 noise_rng; /* 23 bit noise shift register */ + UINT32 noise_p; /* current noise 'phase' */ + UINT32 noise_f; /* current noise period */ + + UINT8 wavesel; /* waveform select enable flag */ + + UINT32 T[2]; /* timer counters */ + UINT8 st[2]; /* timer enable */ + +#if BUILD_Y8950 + /* Delta-T ADPCM unit (Y8950) */ + + YM_DELTAT *deltat; + + /* Keyboard and I/O ports interface */ + UINT8 portDirection; + UINT8 portLatch; + OPL_PORTHANDLER_R porthandler_r; + OPL_PORTHANDLER_W porthandler_w; + void * port_param; + OPL_PORTHANDLER_R keyboardhandler_r; + OPL_PORTHANDLER_W keyboardhandler_w; + void * keyboard_param; +#endif + + /* external event callback handlers */ + //OPL_TIMERHANDLER timer_handler; /* TIMER handler */ + void *TimerParam; /* TIMER parameter */ + OPL_IRQHANDLER IRQHandler; /* IRQ handler */ + void *IRQParam; /* IRQ parameter */ + OPL_UPDATEHANDLER UpdateHandler;/* stream update handler */ + void *UpdateParam; /* stream update parameter */ + + UINT8 type; /* chip type */ + UINT8 address; /* address register */ + UINT8 status; /* status flag */ + UINT8 statusmask; /* status mask */ + UINT8 mode; /* Reg.08 : CSM,notesel,etc. */ + + UINT32 clock; /* master clock (Hz) */ + UINT32 rate; /* sampling rate (Hz) */ + double freqbase; /* frequency base */ + //attotime TimerBase; /* Timer base time (==sampling time)*/ + + OPL_SLOT *SLOT7_1, *SLOT7_2, *SLOT8_1, *SLOT8_2; + + signed int phase_modulation; /* phase modulation input (SLOT 2) */ + signed int output[1]; + +#if BUILD_Y8950 + INT32 output_deltat[4]; /* for Y8950 DELTA-T, chip is mono, that 4 here is just for safety */ +#endif + + UINT32 LFO_AM; + INT32 LFO_PM; +} FM_OPL; + + + +/* mapping of register number (offset) to slot number used by the emulator */ +static const int slot_array[32]= +{ + 0, 2, 4, 1, 3, 5,-1,-1, + 6, 8,10, 7, 9,11,-1,-1, + 12,14,16,13,15,17,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1 +}; + +/* key scale level */ +/* table is 3dB/octave , DV converts this into 6dB/octave */ +/* 0.1875 is bit 0 weight of the envelope counter (volume) expressed in the 'decibel' scale */ +#define DV (0.1875/2.0) +static const UINT32 ksl_tab[8*16]= +{ + /* OCT 0 */ + UINT32( 0.000/DV), UINT32( 0.000/DV), UINT32( 0.000/DV), UINT32( 0.000/DV), + UINT32( 0.000/DV), UINT32( 0.000/DV), UINT32( 0.000/DV), UINT32( 0.000/DV), + UINT32( 0.000/DV), UINT32( 0.000/DV), UINT32( 0.000/DV), UINT32( 0.000/DV), + UINT32( 0.000/DV), UINT32( 0.000/DV), UINT32( 0.000/DV), UINT32( 0.000/DV), + /* OCT 1 */ + UINT32( 0.000/DV), UINT32( 0.000/DV), UINT32( 0.000/DV), UINT32( 0.000/DV), + UINT32( 0.000/DV), UINT32( 0.000/DV), UINT32( 0.000/DV), UINT32( 0.000/DV), + UINT32( 0.000/DV), UINT32( 0.750/DV), UINT32( 1.125/DV), UINT32( 1.500/DV), + UINT32( 1.875/DV), UINT32( 2.250/DV), UINT32( 2.625/DV), UINT32( 3.000/DV), + /* OCT 2 */ + UINT32( 0.000/DV), UINT32( 0.000/DV), UINT32( 0.000/DV), UINT32( 0.000/DV), + UINT32( 0.000/DV), UINT32( 1.125/DV), UINT32( 1.875/DV), UINT32( 2.625/DV), + UINT32( 3.000/DV), UINT32( 3.750/DV), UINT32( 4.125/DV), UINT32( 4.500/DV), + UINT32( 4.875/DV), UINT32( 5.250/DV), UINT32( 5.625/DV), UINT32( 6.000/DV), + /* OCT 3 */ + UINT32( 0.000/DV), UINT32( 0.000/DV), UINT32( 0.000/DV), UINT32( 1.875/DV), + UINT32( 3.000/DV), UINT32( 4.125/DV), UINT32( 4.875/DV), UINT32( 5.625/DV), + UINT32( 6.000/DV), UINT32( 6.750/DV), UINT32( 7.125/DV), UINT32( 7.500/DV), + UINT32( 7.875/DV), UINT32( 8.250/DV), UINT32( 8.625/DV), UINT32( 9.000/DV), + /* OCT 4 */ + UINT32( 0.000/DV), UINT32( 0.000/DV), UINT32( 3.000/DV), UINT32( 4.875/DV), + UINT32( 6.000/DV), UINT32( 7.125/DV), UINT32( 7.875/DV), UINT32( 8.625/DV), + UINT32( 9.000/DV), UINT32( 9.750/DV), UINT32(10.125/DV), UINT32(10.500/DV), + UINT32(10.875/DV), UINT32(11.250/DV), UINT32(11.625/DV), UINT32(12.000/DV), + /* OCT 5 */ + UINT32( 0.000/DV), UINT32( 3.000/DV), UINT32( 6.000/DV), UINT32( 7.875/DV), + UINT32( 9.000/DV), UINT32(10.125/DV), UINT32(10.875/DV), UINT32(11.625/DV), + UINT32(12.000/DV), UINT32(12.750/DV), UINT32(13.125/DV), UINT32(13.500/DV), + UINT32(13.875/DV), UINT32(14.250/DV), UINT32(14.625/DV), UINT32(15.000/DV), + /* OCT 6 */ + UINT32( 0.000/DV), UINT32( 6.000/DV), UINT32( 9.000/DV), UINT32(10.875/DV), + UINT32(12.000/DV), UINT32(13.125/DV), UINT32(13.875/DV), UINT32(14.625/DV), + UINT32(15.000/DV), UINT32(15.750/DV), UINT32(16.125/DV), UINT32(16.500/DV), + UINT32(16.875/DV), UINT32(17.250/DV), UINT32(17.625/DV), UINT32(18.000/DV), + /* OCT 7 */ + UINT32( 0.000/DV), UINT32( 9.000/DV), UINT32(12.000/DV), UINT32(13.875/DV), + UINT32(15.000/DV), UINT32(16.125/DV), UINT32(16.875/DV), UINT32(17.625/DV), + UINT32(18.000/DV), UINT32(18.750/DV), UINT32(19.125/DV), UINT32(19.500/DV), + UINT32(19.875/DV), UINT32(20.250/DV), UINT32(20.625/DV), UINT32(21.000/DV) +}; +#undef DV + +/* sustain level table (3dB per step) */ +/* 0 - 15: 0, 3, 6, 9,12,15,18,21,24,27,30,33,36,39,42,93 (dB)*/ +#define SC(db) (UINT32) ( db * (2.0/ENV_STEP) ) +static const UINT32 sl_tab[16]={ + SC( 0),SC( 1),SC( 2),SC(3 ),SC(4 ),SC(5 ),SC(6 ),SC( 7), + SC( 8),SC( 9),SC(10),SC(11),SC(12),SC(13),SC(14),SC(31) +}; +#undef SC + + +#define RATE_STEPS (8) +static const unsigned char eg_inc[15*RATE_STEPS]={ + +/*cycle:0 1 2 3 4 5 6 7*/ + +/* 0 */ 0,1, 0,1, 0,1, 0,1, /* rates 00..12 0 (increment by 0 or 1) */ +/* 1 */ 0,1, 0,1, 1,1, 0,1, /* rates 00..12 1 */ +/* 2 */ 0,1, 1,1, 0,1, 1,1, /* rates 00..12 2 */ +/* 3 */ 0,1, 1,1, 1,1, 1,1, /* rates 00..12 3 */ + +/* 4 */ 1,1, 1,1, 1,1, 1,1, /* rate 13 0 (increment by 1) */ +/* 5 */ 1,1, 1,2, 1,1, 1,2, /* rate 13 1 */ +/* 6 */ 1,2, 1,2, 1,2, 1,2, /* rate 13 2 */ +/* 7 */ 1,2, 2,2, 1,2, 2,2, /* rate 13 3 */ + +/* 8 */ 2,2, 2,2, 2,2, 2,2, /* rate 14 0 (increment by 2) */ +/* 9 */ 2,2, 2,4, 2,2, 2,4, /* rate 14 1 */ +/*10 */ 2,4, 2,4, 2,4, 2,4, /* rate 14 2 */ +/*11 */ 2,4, 4,4, 2,4, 4,4, /* rate 14 3 */ + +/*12 */ 4,4, 4,4, 4,4, 4,4, /* rates 15 0, 15 1, 15 2, 15 3 (increment by 4) */ +/*13 */ 8,8, 8,8, 8,8, 8,8, /* rates 15 2, 15 3 for attack */ +/*14 */ 0,0, 0,0, 0,0, 0,0, /* infinity rates for attack and decay(s) */ +}; + + +#define O(a) (a*RATE_STEPS) + +/*note that there is no O(13) in this table - it's directly in the code */ +static const unsigned char eg_rate_select[16+64+16]={ /* Envelope Generator rates (16 + 64 rates + 16 RKS) */ +/* 16 infinite time rates */ +O(14),O(14),O(14),O(14),O(14),O(14),O(14),O(14), +O(14),O(14),O(14),O(14),O(14),O(14),O(14),O(14), + +/* rates 00-12 */ +O( 0),O( 1),O( 2),O( 3), +O( 0),O( 1),O( 2),O( 3), +O( 0),O( 1),O( 2),O( 3), +O( 0),O( 1),O( 2),O( 3), +O( 0),O( 1),O( 2),O( 3), +O( 0),O( 1),O( 2),O( 3), +O( 0),O( 1),O( 2),O( 3), +O( 0),O( 1),O( 2),O( 3), +O( 0),O( 1),O( 2),O( 3), +O( 0),O( 1),O( 2),O( 3), +O( 0),O( 1),O( 2),O( 3), +O( 0),O( 1),O( 2),O( 3), +O( 0),O( 1),O( 2),O( 3), + +/* rate 13 */ +O( 4),O( 5),O( 6),O( 7), + +/* rate 14 */ +O( 8),O( 9),O(10),O(11), + +/* rate 15 */ +O(12),O(12),O(12),O(12), + +/* 16 dummy rates (same as 15 3) */ +O(12),O(12),O(12),O(12),O(12),O(12),O(12),O(12), +O(12),O(12),O(12),O(12),O(12),O(12),O(12),O(12), + +}; +#undef O + +/*rate 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 */ +/*shift 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 0, 0, 0 */ +/*mask 4095, 2047, 1023, 511, 255, 127, 63, 31, 15, 7, 3, 1, 0, 0, 0, 0 */ + +#define O(a) (a*1) +static const unsigned char eg_rate_shift[16+64+16]={ /* Envelope Generator counter shifts (16 + 64 rates + 16 RKS) */ +/* 16 infinite time rates */ +O(0),O(0),O(0),O(0),O(0),O(0),O(0),O(0), +O(0),O(0),O(0),O(0),O(0),O(0),O(0),O(0), + +/* rates 00-12 */ +O(12),O(12),O(12),O(12), +O(11),O(11),O(11),O(11), +O(10),O(10),O(10),O(10), +O( 9),O( 9),O( 9),O( 9), +O( 8),O( 8),O( 8),O( 8), +O( 7),O( 7),O( 7),O( 7), +O( 6),O( 6),O( 6),O( 6), +O( 5),O( 5),O( 5),O( 5), +O( 4),O( 4),O( 4),O( 4), +O( 3),O( 3),O( 3),O( 3), +O( 2),O( 2),O( 2),O( 2), +O( 1),O( 1),O( 1),O( 1), +O( 0),O( 0),O( 0),O( 0), + +/* rate 13 */ +O( 0),O( 0),O( 0),O( 0), + +/* rate 14 */ +O( 0),O( 0),O( 0),O( 0), + +/* rate 15 */ +O( 0),O( 0),O( 0),O( 0), + +/* 16 dummy rates (same as 15 3) */ +O( 0),O( 0),O( 0),O( 0),O( 0),O( 0),O( 0),O( 0), +O( 0),O( 0),O( 0),O( 0),O( 0),O( 0),O( 0),O( 0), + +}; +#undef O + + +/* multiple table */ +#define ML 2 +static const UINT8 mul_tab[16]= { +/* 1/2, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,10,12,12,15,15 */ + UINT8( 0.50*ML),UINT8( 1.00*ML),UINT8( 2.00*ML),UINT8( 3.00*ML),UINT8( 4.00*ML),UINT8( 5.00*ML),UINT8( 6.00*ML),UINT8( 7.00*ML), + UINT8( 8.00*ML),UINT8( 9.00*ML),UINT8(10.00*ML),UINT8(10.00*ML),UINT8(12.00*ML),UINT8(12.00*ML),UINT8(15.00*ML),UINT8(15.00*ML) +}; +#undef ML + +/* TL_TAB_LEN is calculated as: +* 12 - sinus amplitude bits (Y axis) +* 2 - sinus sign bit (Y axis) +* TL_RES_LEN - sinus resolution (X axis) +*/ +#define TL_TAB_LEN (12*2*TL_RES_LEN) +static signed int tl_tab[TL_TAB_LEN]; + +#define ENV_QUIET (TL_TAB_LEN>>4) + +/* sin waveform table in 'decibel' scale */ +/* four waveforms on OPL2 type chips */ +static unsigned int sin_tab[SIN_LEN * 4]; + + +/* LFO Amplitude Modulation table (verified on real YM3812) + 27 output levels (triangle waveform); 1 level takes one of: 192, 256 or 448 samples + + Length: 210 elements. + + Each of the elements has to be repeated + exactly 64 times (on 64 consecutive samples). + The whole table takes: 64 * 210 = 13440 samples. + + When AM = 1 data is used directly + When AM = 0 data is divided by 4 before being used (loosing precision is important) +*/ + +#define LFO_AM_TAB_ELEMENTS 210 + +static const UINT8 lfo_am_table[LFO_AM_TAB_ELEMENTS] = { +0,0,0,0,0,0,0, +1,1,1,1, +2,2,2,2, +3,3,3,3, +4,4,4,4, +5,5,5,5, +6,6,6,6, +7,7,7,7, +8,8,8,8, +9,9,9,9, +10,10,10,10, +11,11,11,11, +12,12,12,12, +13,13,13,13, +14,14,14,14, +15,15,15,15, +16,16,16,16, +17,17,17,17, +18,18,18,18, +19,19,19,19, +20,20,20,20, +21,21,21,21, +22,22,22,22, +23,23,23,23, +24,24,24,24, +25,25,25,25, +26,26,26, +25,25,25,25, +24,24,24,24, +23,23,23,23, +22,22,22,22, +21,21,21,21, +20,20,20,20, +19,19,19,19, +18,18,18,18, +17,17,17,17, +16,16,16,16, +15,15,15,15, +14,14,14,14, +13,13,13,13, +12,12,12,12, +11,11,11,11, +10,10,10,10, +9,9,9,9, +8,8,8,8, +7,7,7,7, +6,6,6,6, +5,5,5,5, +4,4,4,4, +3,3,3,3, +2,2,2,2, +1,1,1,1 +}; + +/* LFO Phase Modulation table (verified on real YM3812) */ +static const INT8 lfo_pm_table[8*8*2] = { + +/* FNUM2/FNUM = 00 0xxxxxxx (0x0000) */ +0, 0, 0, 0, 0, 0, 0, 0, /*LFO PM depth = 0*/ +0, 0, 0, 0, 0, 0, 0, 0, /*LFO PM depth = 1*/ + +/* FNUM2/FNUM = 00 1xxxxxxx (0x0080) */ +0, 0, 0, 0, 0, 0, 0, 0, /*LFO PM depth = 0*/ +1, 0, 0, 0,-1, 0, 0, 0, /*LFO PM depth = 1*/ + +/* FNUM2/FNUM = 01 0xxxxxxx (0x0100) */ +1, 0, 0, 0,-1, 0, 0, 0, /*LFO PM depth = 0*/ +2, 1, 0,-1,-2,-1, 0, 1, /*LFO PM depth = 1*/ + +/* FNUM2/FNUM = 01 1xxxxxxx (0x0180) */ +1, 0, 0, 0,-1, 0, 0, 0, /*LFO PM depth = 0*/ +3, 1, 0,-1,-3,-1, 0, 1, /*LFO PM depth = 1*/ + +/* FNUM2/FNUM = 10 0xxxxxxx (0x0200) */ +2, 1, 0,-1,-2,-1, 0, 1, /*LFO PM depth = 0*/ +4, 2, 0,-2,-4,-2, 0, 2, /*LFO PM depth = 1*/ + +/* FNUM2/FNUM = 10 1xxxxxxx (0x0280) */ +2, 1, 0,-1,-2,-1, 0, 1, /*LFO PM depth = 0*/ +5, 2, 0,-2,-5,-2, 0, 2, /*LFO PM depth = 1*/ + +/* FNUM2/FNUM = 11 0xxxxxxx (0x0300) */ +3, 1, 0,-1,-3,-1, 0, 1, /*LFO PM depth = 0*/ +6, 3, 0,-3,-6,-3, 0, 3, /*LFO PM depth = 1*/ + +/* FNUM2/FNUM = 11 1xxxxxxx (0x0380) */ +3, 1, 0,-1,-3,-1, 0, 1, /*LFO PM depth = 0*/ +7, 3, 0,-3,-7,-3, 0, 3 /*LFO PM depth = 1*/ +}; + + +/* lock level of common table */ +//static int num_lock = 0; + + +//static void *cur_chip = NULL; /* current chip pointer */ + +INLINE int limit( int val, int max, int min ) { + if ( val > max ) + val = max; + else if ( val < min ) + val = min; + + return val; +} + + +/* status set and IRQ handling */ +INLINE void OPL_STATUS_SET(FM_OPL *OPL,int flag) +{ + /* set status flag */ + OPL->status |= flag; + if(!(OPL->status & 0x80)) + { + if(OPL->status & OPL->statusmask) + { /* IRQ on */ + OPL->status |= 0x80; + /* callback user interrupt handler (IRQ is OFF to ON) */ + if(OPL->IRQHandler) (OPL->IRQHandler)(OPL->IRQParam,1); + } + } +} + +/* status reset and IRQ handling */ +INLINE void OPL_STATUS_RESET(FM_OPL *OPL,int flag) +{ + /* reset status flag */ + OPL->status &=~flag; + if((OPL->status & 0x80)) + { + if (!(OPL->status & OPL->statusmask) ) + { + OPL->status &= 0x7f; + /* callback user interrupt handler (IRQ is ON to OFF) */ + if(OPL->IRQHandler) (OPL->IRQHandler)(OPL->IRQParam,0); + } + } +} + +/* IRQ mask set */ +INLINE void OPL_STATUSMASK_SET(FM_OPL *OPL,int flag) +{ + OPL->statusmask = flag; + /* IRQ handling check */ + OPL_STATUS_SET(OPL,0); + OPL_STATUS_RESET(OPL,0); +} + + +/* advance LFO to next sample */ +INLINE void advance_lfo(FM_OPL *OPL) +{ + UINT8 tmp; + + /* LFO */ + OPL->lfo_am_cnt += OPL->lfo_am_inc; + if (OPL->lfo_am_cnt >= ((UINT32)LFO_AM_TAB_ELEMENTS<lfo_am_cnt -= ((UINT32)LFO_AM_TAB_ELEMENTS<lfo_am_cnt >> LFO_SH ]; + + if (OPL->lfo_am_depth) + OPL->LFO_AM = tmp; + else + OPL->LFO_AM = tmp>>2; + + OPL->lfo_pm_cnt += OPL->lfo_pm_inc; + OPL->LFO_PM = ((OPL->lfo_pm_cnt>>LFO_SH) & 7) | OPL->lfo_pm_depth_range; +} + +/* advance to next sample */ +INLINE void advance(FM_OPL *OPL) +{ + OPL_CH *CH; + OPL_SLOT *op; + int i; + + OPL->eg_timer += OPL->eg_timer_add; + + while (OPL->eg_timer >= OPL->eg_timer_overflow) + { + OPL->eg_timer -= OPL->eg_timer_overflow; + + OPL->eg_cnt++; + + for (i=0; i<9*2; i++) + { + CH = &OPL->P_CH[i/2]; + op = &CH->SLOT[i&1]; + + /* Envelope Generator */ + switch(op->state) + { + case EG_ATT: /* attack phase */ + if ( !(OPL->eg_cnt & ((1<eg_sh_ar)-1) ) ) + { + op->volume += (~op->volume * + (eg_inc[op->eg_sel_ar + ((OPL->eg_cnt>>op->eg_sh_ar)&7)]) + ) >>3; + + if (op->volume <= MIN_ATT_INDEX) + { + op->volume = MIN_ATT_INDEX; + op->state = EG_DEC; + } + + } + break; + + case EG_DEC: /* decay phase */ + if ( !(OPL->eg_cnt & ((1<eg_sh_dr)-1) ) ) + { + op->volume += eg_inc[op->eg_sel_dr + ((OPL->eg_cnt>>op->eg_sh_dr)&7)]; + + if ( op->volume >= op->sl ) + op->state = EG_SUS; + + } + break; + + case EG_SUS: /* sustain phase */ + + /* this is important behaviour: + one can change percusive/non-percussive modes on the fly and + the chip will remain in sustain phase - verified on real YM3812 */ + + if(op->eg_type) /* non-percussive mode */ + { + /* do nothing */ + } + else /* percussive mode */ + { + /* during sustain phase chip adds Release Rate (in percussive mode) */ + if ( !(OPL->eg_cnt & ((1<eg_sh_rr)-1) ) ) + { + op->volume += eg_inc[op->eg_sel_rr + ((OPL->eg_cnt>>op->eg_sh_rr)&7)]; + + if ( op->volume >= MAX_ATT_INDEX ) + op->volume = MAX_ATT_INDEX; + } + /* else do nothing in sustain phase */ + } + break; + + case EG_REL: /* release phase */ + if ( !(OPL->eg_cnt & ((1<eg_sh_rr)-1) ) ) + { + op->volume += eg_inc[op->eg_sel_rr + ((OPL->eg_cnt>>op->eg_sh_rr)&7)]; + + if ( op->volume >= MAX_ATT_INDEX ) + { + op->volume = MAX_ATT_INDEX; + op->state = EG_OFF; + } + + } + break; + + default: + break; + } + } + } + + for (i=0; i<9*2; i++) + { + CH = &OPL->P_CH[i/2]; + op = &CH->SLOT[i&1]; + + /* Phase Generator */ + if(op->vib) + { + UINT8 block; + unsigned int block_fnum = CH->block_fnum; + + unsigned int fnum_lfo = (block_fnum&0x0380) >> 7; + + signed int lfo_fn_table_index_offset = lfo_pm_table[OPL->LFO_PM + 16*fnum_lfo ]; + + if (lfo_fn_table_index_offset) /* LFO phase modulation active */ + { + block_fnum += lfo_fn_table_index_offset; + block = (block_fnum&0x1c00) >> 10; + op->Cnt += (OPL->fn_tab[block_fnum&0x03ff] >> (7-block)) * op->mul; + } + else /* LFO phase modulation = zero */ + { + op->Cnt += op->Incr; + } + } + else /* LFO phase modulation disabled for this operator */ + { + op->Cnt += op->Incr; + } + } + + /* The Noise Generator of the YM3812 is 23-bit shift register. + * Period is equal to 2^23-2 samples. + * Register works at sampling frequency of the chip, so output + * can change on every sample. + * + * Output of the register and input to the bit 22 is: + * bit0 XOR bit14 XOR bit15 XOR bit22 + * + * Simply use bit 22 as the noise output. + */ + + OPL->noise_p += OPL->noise_f; + i = OPL->noise_p >> FREQ_SH; /* number of events (shifts of the shift register) */ + OPL->noise_p &= FREQ_MASK; + while (i) + { + /* + UINT32 j; + j = ( (OPL->noise_rng) ^ (OPL->noise_rng>>14) ^ (OPL->noise_rng>>15) ^ (OPL->noise_rng>>22) ) & 1; + OPL->noise_rng = (j<<22) | (OPL->noise_rng>>1); + */ + + /* + Instead of doing all the logic operations above, we + use a trick here (and use bit 0 as the noise output). + The difference is only that the noise bit changes one + step ahead. This doesn't matter since we don't know + what is real state of the noise_rng after the reset. + */ + + if (OPL->noise_rng & 1) OPL->noise_rng ^= 0x800302; + OPL->noise_rng >>= 1; + + i--; + } +} + + +INLINE signed int op_calc(UINT32 phase, unsigned int env, signed int pm, unsigned int wave_tab) +{ + UINT32 p; + + p = (env<<4) + sin_tab[wave_tab + ((((signed int)((phase & ~FREQ_MASK) + (pm<<16))) >> FREQ_SH ) & SIN_MASK) ]; + + if (p >= TL_TAB_LEN) + return 0; + return tl_tab[p]; +} + +INLINE signed int op_calc1(UINT32 phase, unsigned int env, signed int pm, unsigned int wave_tab) +{ + UINT32 p; + + p = (env<<4) + sin_tab[wave_tab + ((((signed int)((phase & ~FREQ_MASK) + pm )) >> FREQ_SH ) & SIN_MASK) ]; + + if (p >= TL_TAB_LEN) + return 0; + return tl_tab[p]; +} + + +#define volume_calc(OP) ((OP)->TLL + ((UINT32)(OP)->volume) + (OPL->LFO_AM & (OP)->AMmask)) + +/* calculate output */ +INLINE void OPL_CALC_CH( FM_OPL *OPL, OPL_CH *CH ) +{ + OPL_SLOT *SLOT; + unsigned int env; + signed int out; + + OPL->phase_modulation = 0; + + /* SLOT 1 */ + SLOT = &CH->SLOT[SLOT1]; + env = volume_calc(SLOT); + out = SLOT->op1_out[0] + SLOT->op1_out[1]; + SLOT->op1_out[0] = SLOT->op1_out[1]; + *SLOT->connect1 += SLOT->op1_out[0]; + SLOT->op1_out[1] = 0; + if( env < ENV_QUIET ) + { + if (!SLOT->FB) + out = 0; + SLOT->op1_out[1] = op_calc1(SLOT->Cnt, env, (out<FB), SLOT->wavetable ); + } + + /* SLOT 2 */ + SLOT++; + env = volume_calc(SLOT); + if( env < ENV_QUIET ) + OPL->output[0] += op_calc(SLOT->Cnt, env, OPL->phase_modulation, SLOT->wavetable); +} + +/* + operators used in the rhythm sounds generation process: + + Envelope Generator: + +channel operator register number Bass High Snare Tom Top +/ slot number TL ARDR SLRR Wave Drum Hat Drum Tom Cymbal + 6 / 0 12 50 70 90 f0 + + 6 / 1 15 53 73 93 f3 + + 7 / 0 13 51 71 91 f1 + + 7 / 1 16 54 74 94 f4 + + 8 / 0 14 52 72 92 f2 + + 8 / 1 17 55 75 95 f5 + + + Phase Generator: + +channel operator register number Bass High Snare Tom Top +/ slot number MULTIPLE Drum Hat Drum Tom Cymbal + 6 / 0 12 30 + + 6 / 1 15 33 + + 7 / 0 13 31 + + + + 7 / 1 16 34 ----- n o t u s e d ----- + 8 / 0 14 32 + + 8 / 1 17 35 + + + +channel operator register number Bass High Snare Tom Top +number number BLK/FNUM2 FNUM Drum Hat Drum Tom Cymbal + 6 12,15 B6 A6 + + + 7 13,16 B7 A7 + + + + + 8 14,17 B8 A8 + + + + +*/ + +/* calculate rhythm */ + +INLINE void OPL_CALC_RH( FM_OPL *OPL, OPL_CH *CH, unsigned int noise ) +{ + OPL_SLOT *SLOT; + signed int out; + unsigned int env; + + + /* Bass Drum (verified on real YM3812): + - depends on the channel 6 'connect' register: + when connect = 0 it works the same as in normal (non-rhythm) mode (op1->op2->out) + when connect = 1 _only_ operator 2 is present on output (op2->out), operator 1 is ignored + - output sample always is multiplied by 2 + */ + + OPL->phase_modulation = 0; + /* SLOT 1 */ + SLOT = &CH[6].SLOT[SLOT1]; + env = volume_calc(SLOT); + + out = SLOT->op1_out[0] + SLOT->op1_out[1]; + SLOT->op1_out[0] = SLOT->op1_out[1]; + + if (!SLOT->CON) + OPL->phase_modulation = SLOT->op1_out[0]; + /* else ignore output of operator 1 */ + + SLOT->op1_out[1] = 0; + if( env < ENV_QUIET ) + { + if (!SLOT->FB) + out = 0; + SLOT->op1_out[1] = op_calc1(SLOT->Cnt, env, (out<FB), SLOT->wavetable ); + } + + /* SLOT 2 */ + SLOT++; + env = volume_calc(SLOT); + if( env < ENV_QUIET ) + OPL->output[0] += op_calc(SLOT->Cnt, env, OPL->phase_modulation, SLOT->wavetable) * 2; + + + /* Phase generation is based on: */ + /* HH (13) channel 7->slot 1 combined with channel 8->slot 2 (same combination as TOP CYMBAL but different output phases) */ + /* SD (16) channel 7->slot 1 */ + /* TOM (14) channel 8->slot 1 */ + /* TOP (17) channel 7->slot 1 combined with channel 8->slot 2 (same combination as HIGH HAT but different output phases) */ + + /* Envelope generation based on: */ + /* HH channel 7->slot1 */ + /* SD channel 7->slot2 */ + /* TOM channel 8->slot1 */ + /* TOP channel 8->slot2 */ + + + /* The following formulas can be well optimized. + I leave them in direct form for now (in case I've missed something). + */ + + /* High Hat (verified on real YM3812) */ + env = volume_calc(OPL->SLOT7_1); + if( env < ENV_QUIET ) + { + + /* high hat phase generation: + phase = d0 or 234 (based on frequency only) + phase = 34 or 2d0 (based on noise) + */ + + /* base frequency derived from operator 1 in channel 7 */ + unsigned char bit7 = ((OPL->SLOT7_1->Cnt>>FREQ_SH)>>7)&1; + unsigned char bit3 = ((OPL->SLOT7_1->Cnt>>FREQ_SH)>>3)&1; + unsigned char bit2 = ((OPL->SLOT7_1->Cnt>>FREQ_SH)>>2)&1; + + unsigned char res1 = (bit2 ^ bit7) | bit3; + + /* when res1 = 0 phase = 0x000 | 0xd0; */ + /* when res1 = 1 phase = 0x200 | (0xd0>>2); */ + UINT32 phase = res1 ? (0x200|(0xd0>>2)) : 0xd0; + + /* enable gate based on frequency of operator 2 in channel 8 */ + unsigned char bit5e= ((OPL->SLOT8_2->Cnt>>FREQ_SH)>>5)&1; + unsigned char bit3e= ((OPL->SLOT8_2->Cnt>>FREQ_SH)>>3)&1; + + unsigned char res2 = (bit3e ^ bit5e); + + /* when res2 = 0 pass the phase from calculation above (res1); */ + /* when res2 = 1 phase = 0x200 | (0xd0>>2); */ + if (res2) + phase = (0x200|(0xd0>>2)); + + + /* when phase & 0x200 is set and noise=1 then phase = 0x200|0xd0 */ + /* when phase & 0x200 is set and noise=0 then phase = 0x200|(0xd0>>2), ie no change */ + if (phase&0x200) + { + if (noise) + phase = 0x200|0xd0; + } + else + /* when phase & 0x200 is clear and noise=1 then phase = 0xd0>>2 */ + /* when phase & 0x200 is clear and noise=0 then phase = 0xd0, ie no change */ + { + if (noise) + phase = 0xd0>>2; + } + + OPL->output[0] += op_calc(phase<SLOT7_1->wavetable) * 2; + } + + /* Snare Drum (verified on real YM3812) */ + env = volume_calc(OPL->SLOT7_2); + if( env < ENV_QUIET ) + { + /* base frequency derived from operator 1 in channel 7 */ + unsigned char bit8 = ((OPL->SLOT7_1->Cnt>>FREQ_SH)>>8)&1; + + /* when bit8 = 0 phase = 0x100; */ + /* when bit8 = 1 phase = 0x200; */ + UINT32 phase = bit8 ? 0x200 : 0x100; + + /* Noise bit XOR'es phase by 0x100 */ + /* when noisebit = 0 pass the phase from calculation above */ + /* when noisebit = 1 phase ^= 0x100; */ + /* in other words: phase ^= (noisebit<<8); */ + if (noise) + phase ^= 0x100; + + OPL->output[0] += op_calc(phase<SLOT7_2->wavetable) * 2; + } + + /* Tom Tom (verified on real YM3812) */ + env = volume_calc(OPL->SLOT8_1); + if( env < ENV_QUIET ) + OPL->output[0] += op_calc(OPL->SLOT8_1->Cnt, env, 0, OPL->SLOT8_1->wavetable) * 2; + + /* Top Cymbal (verified on real YM3812) */ + env = volume_calc(OPL->SLOT8_2); + if( env < ENV_QUIET ) + { + /* base frequency derived from operator 1 in channel 7 */ + unsigned char bit7 = ((OPL->SLOT7_1->Cnt>>FREQ_SH)>>7)&1; + unsigned char bit3 = ((OPL->SLOT7_1->Cnt>>FREQ_SH)>>3)&1; + unsigned char bit2 = ((OPL->SLOT7_1->Cnt>>FREQ_SH)>>2)&1; + + unsigned char res1 = (bit2 ^ bit7) | bit3; + + /* when res1 = 0 phase = 0x000 | 0x100; */ + /* when res1 = 1 phase = 0x200 | 0x100; */ + UINT32 phase = res1 ? 0x300 : 0x100; + + /* enable gate based on frequency of operator 2 in channel 8 */ + unsigned char bit5e= ((OPL->SLOT8_2->Cnt>>FREQ_SH)>>5)&1; + unsigned char bit3e= ((OPL->SLOT8_2->Cnt>>FREQ_SH)>>3)&1; + + unsigned char res2 = (bit3e ^ bit5e); + /* when res2 = 0 pass the phase from calculation above (res1); */ + /* when res2 = 1 phase = 0x200 | 0x100; */ + if (res2) + phase = 0x300; + + OPL->output[0] += op_calc(phase<SLOT8_2->wavetable) * 2; + } + +} + + +/* generic table initialize */ +static int init_tables(void) +{ + signed int i,x; + signed int n; + double o,m; + + + for (x=0; x>= 4; /* 12 bits here */ + if (n&1) /* round to nearest */ + n = (n>>1)+1; + else + n = n>>1; + /* 11 bits here (rounded) */ + n <<= 1; /* 12 bits here (as in real chip) */ + tl_tab[ x*2 + 0 ] = n; + tl_tab[ x*2 + 1 ] = -tl_tab[ x*2 + 0 ]; + + for (i=1; i<12; i++) + { + tl_tab[ x*2+0 + i*2*TL_RES_LEN ] = tl_tab[ x*2+0 ]>>i; + tl_tab[ x*2+1 + i*2*TL_RES_LEN ] = -tl_tab[ x*2+0 + i*2*TL_RES_LEN ]; + } + #if 0 + logerror("tl %04i", x*2); + for (i=0; i<12; i++) + logerror(", [%02i] %5i", i*2, tl_tab[ x*2 /*+1*/ + i*2*TL_RES_LEN ] ); + logerror("\n"); + #endif + } + /*logerror("FMOPL.C: TL_TAB_LEN = %i elements (%i bytes)\n",TL_TAB_LEN, (int)sizeof(tl_tab));*/ + + + for (i=0; i0.0) + o = 8*log(1.0/m)/log(2.0); /* convert to 'decibels' */ + else + o = 8*log(-1.0/m)/log(2.0); /* convert to 'decibels' */ + + o = o / (ENV_STEP/4); + + n = (int)(2.0*o); + if (n&1) /* round to nearest */ + n = (n>>1)+1; + else + n = n>>1; + + sin_tab[ i ] = n*2 + (m>=0.0? 0: 1 ); + + /*logerror("FMOPL.C: sin [%4i (hex=%03x)]= %4i (tl_tab value=%5i)\n", i, i, sin_tab[i], tl_tab[sin_tab[i]] );*/ + } + + for (i=0; i>1) ]; + + /* waveform 3: _ _ _ _ */ + /* / |_/ |_/ |_/ |_*/ + /* abs(output only first quarter of the sinus waveform) */ + + if (i & (1<<(SIN_BITS-2)) ) + sin_tab[3*SIN_LEN+i] = TL_TAB_LEN; + else + sin_tab[3*SIN_LEN+i] = sin_tab[i & (SIN_MASK>>2)]; + + /*logerror("FMOPL.C: sin1[%4i]= %4i (tl_tab value=%5i)\n", i, sin_tab[1*SIN_LEN+i], tl_tab[sin_tab[1*SIN_LEN+i]] ); + logerror("FMOPL.C: sin2[%4i]= %4i (tl_tab value=%5i)\n", i, sin_tab[2*SIN_LEN+i], tl_tab[sin_tab[2*SIN_LEN+i]] ); + logerror("FMOPL.C: sin3[%4i]= %4i (tl_tab value=%5i)\n", i, sin_tab[3*SIN_LEN+i], tl_tab[sin_tab[3*SIN_LEN+i]] );*/ + } + /*logerror("FMOPL.C: ENV_QUIET= %08x (dec*8=%i)\n", ENV_QUIET, ENV_QUIET*8 );*/ + + +#ifdef SAVE_SAMPLE + sample[0]=fopen("sampsum.pcm","wb"); +#endif + + return 1; +} + +static void OPLCloseTable( void ) +{ +#ifdef SAVE_SAMPLE + fclose(sample[0]); +#endif +} + + + +static void OPL_initalize(FM_OPL *OPL) +{ + int i; + + /* frequency base */ + OPL->freqbase = (OPL->rate) ? ((double)OPL->clock / 72.0) / OPL->rate : 0; +#if 0 + OPL->rate = (double)OPL->clock / 72.0; + OPL->freqbase = 1.0; +#endif + + /*logerror("freqbase=%f\n", OPL->freqbase);*/ + + /* Timer base time */ + //OPL->TimerBase = attotime_mul(ATTOTIME_IN_HZ(OPL->clock), 72); + + /* make fnumber -> increment counter table */ + for( i=0 ; i < 1024 ; i++ ) + { + /* opn phase increment counter = 20bit */ + OPL->fn_tab[i] = (UINT32)( (double)i * 64 * OPL->freqbase * (1<<(FREQ_SH-10)) ); /* -10 because chip works with 10.10 fixed point, while we use 16.16 */ +#if 0 + logerror("FMOPL.C: fn_tab[%4i] = %08x (dec=%8i)\n", + i, OPL->fn_tab[i]>>6, OPL->fn_tab[i]>>6 ); +#endif + } + +#if 0 + for( i=0 ; i < 16 ; i++ ) + { + logerror("FMOPL.C: sl_tab[%i] = %08x\n", + i, sl_tab[i] ); + } + for( i=0 ; i < 8 ; i++ ) + { + int j; + logerror("FMOPL.C: ksl_tab[oct=%2i] =",i); + for (j=0; j<16; j++) + { + logerror("%08x ", ksl_tab[i*16+j] ); + } + logerror("\n"); + } +#endif + + + /* Amplitude modulation: 27 output levels (triangle waveform); 1 level takes one of: 192, 256 or 448 samples */ + /* One entry from LFO_AM_TABLE lasts for 64 samples */ + OPL->lfo_am_inc = (1.0 / 64.0 ) * (1<freqbase; + + /* Vibrato: 8 output levels (triangle waveform); 1 level takes 1024 samples */ + OPL->lfo_pm_inc = (1.0 / 1024.0) * (1<freqbase; + + /*logerror ("OPL->lfo_am_inc = %8x ; OPL->lfo_pm_inc = %8x\n", OPL->lfo_am_inc, OPL->lfo_pm_inc);*/ + + /* Noise generator: a step takes 1 sample */ + OPL->noise_f = (1.0 / 1.0) * (1<freqbase; + + OPL->eg_timer_add = (1<freqbase; + OPL->eg_timer_overflow = ( 1 ) * (1<eg_timer_add, OPL->eg_timer_overflow);*/ + +} + +INLINE void FM_KEYON(OPL_SLOT *SLOT, UINT32 key_set) +{ + if( !SLOT->key ) + { + /* restart Phase Generator */ + SLOT->Cnt = 0; + /* phase -> Attack */ + SLOT->state = EG_ATT; + } + SLOT->key |= key_set; +} + +INLINE void FM_KEYOFF(OPL_SLOT *SLOT, UINT32 key_clr) +{ + if( SLOT->key ) + { + SLOT->key &= key_clr; + + if( !SLOT->key ) + { + /* phase -> Release */ + if (SLOT->state>EG_REL) + SLOT->state = EG_REL; + } + } +} + +/* update phase increment counter of operator (also update the EG rates if necessary) */ +INLINE void CALC_FCSLOT(OPL_CH *CH,OPL_SLOT *SLOT) +{ + int ksr; + + /* (frequency) phase increment counter */ + SLOT->Incr = CH->fc * SLOT->mul; + ksr = CH->kcode >> SLOT->KSR; + + if( SLOT->ksr != ksr ) + { + SLOT->ksr = ksr; + + /* calculate envelope generator rates */ + if ((SLOT->ar + SLOT->ksr) < 16+62) + { + SLOT->eg_sh_ar = eg_rate_shift [SLOT->ar + SLOT->ksr ]; + SLOT->eg_sel_ar = eg_rate_select[SLOT->ar + SLOT->ksr ]; + } + else + { + SLOT->eg_sh_ar = 0; + SLOT->eg_sel_ar = 13*RATE_STEPS; + } + SLOT->eg_sh_dr = eg_rate_shift [SLOT->dr + SLOT->ksr ]; + SLOT->eg_sel_dr = eg_rate_select[SLOT->dr + SLOT->ksr ]; + SLOT->eg_sh_rr = eg_rate_shift [SLOT->rr + SLOT->ksr ]; + SLOT->eg_sel_rr = eg_rate_select[SLOT->rr + SLOT->ksr ]; + } +} + +/* set multi,am,vib,EG-TYP,KSR,mul */ +INLINE void set_mul(FM_OPL *OPL,int slot,int v) +{ + OPL_CH *CH = &OPL->P_CH[slot/2]; + OPL_SLOT *SLOT = &CH->SLOT[slot&1]; + + SLOT->mul = mul_tab[v&0x0f]; + SLOT->KSR = (v&0x10) ? 0 : 2; + SLOT->eg_type = (v&0x20); + SLOT->vib = (v&0x40); + SLOT->AMmask = (v&0x80) ? ~0 : 0; + CALC_FCSLOT(CH,SLOT); +} + +/* set ksl & tl */ +INLINE void set_ksl_tl(FM_OPL *OPL,int slot,int v) +{ + OPL_CH *CH = &OPL->P_CH[slot/2]; + OPL_SLOT *SLOT = &CH->SLOT[slot&1]; + int ksl = v>>6; /* 0 / 1.5 / 3.0 / 6.0 dB/OCT */ + + SLOT->ksl = ksl ? 3-ksl : 31; + SLOT->TL = (v&0x3f)<<(ENV_BITS-1-7); /* 7 bits TL (bit 6 = always 0) */ + + SLOT->TLL = SLOT->TL + (CH->ksl_base>>SLOT->ksl); +} + +/* set attack rate & decay rate */ +INLINE void set_ar_dr(FM_OPL *OPL,int slot,int v) +{ + OPL_CH *CH = &OPL->P_CH[slot/2]; + OPL_SLOT *SLOT = &CH->SLOT[slot&1]; + + SLOT->ar = (v>>4) ? 16 + ((v>>4) <<2) : 0; + + if ((SLOT->ar + SLOT->ksr) < 16+62) + { + SLOT->eg_sh_ar = eg_rate_shift [SLOT->ar + SLOT->ksr ]; + SLOT->eg_sel_ar = eg_rate_select[SLOT->ar + SLOT->ksr ]; + } + else + { + SLOT->eg_sh_ar = 0; + SLOT->eg_sel_ar = 13*RATE_STEPS; + } + + SLOT->dr = (v&0x0f)? 16 + ((v&0x0f)<<2) : 0; + SLOT->eg_sh_dr = eg_rate_shift [SLOT->dr + SLOT->ksr ]; + SLOT->eg_sel_dr = eg_rate_select[SLOT->dr + SLOT->ksr ]; +} + +/* set sustain level & release rate */ +INLINE void set_sl_rr(FM_OPL *OPL,int slot,int v) +{ + OPL_CH *CH = &OPL->P_CH[slot/2]; + OPL_SLOT *SLOT = &CH->SLOT[slot&1]; + + SLOT->sl = sl_tab[ v>>4 ]; + + SLOT->rr = (v&0x0f)? 16 + ((v&0x0f)<<2) : 0; + SLOT->eg_sh_rr = eg_rate_shift [SLOT->rr + SLOT->ksr ]; + SLOT->eg_sel_rr = eg_rate_select[SLOT->rr + SLOT->ksr ]; +} + + +/* write a value v to register r on OPL chip */ +static void OPLWriteReg(FM_OPL *OPL, int r, int v) +{ + OPL_CH *CH; + int slot; + UINT32 block_fnum; + + + /* adjust bus to 8 bits */ + r &= 0xff; + v &= 0xff; + + /*if (LOG_CYM_FILE && (cymfile) && (r!=0) ) + { + fputc( (unsigned char)r, cymfile ); + fputc( (unsigned char)v, cymfile ); + }*/ + + + switch(r&0xe0) + { + case 0x00: /* 00-1f:control */ + switch(r&0x1f) + { + case 0x01: /* waveform select enable */ + if(OPL->type&OPL_TYPE_WAVESEL) + { + OPL->wavesel = v&0x20; + /* do not change the waveform previously selected */ + } + break; + case 0x02: /* Timer 1 */ + OPL->T[0] = (256-v)*4; + break; + case 0x03: /* Timer 2 */ + OPL->T[1] = (256-v)*16; + break; + case 0x04: /* IRQ clear / mask and Timer enable */ + if(v&0x80) + { /* IRQ flag clear */ + OPL_STATUS_RESET(OPL,0x7f-0x08); /* don't reset BFRDY flag or we will have to call deltat module to set the flag */ + } + else + { /* set IRQ mask ,timer enable*/ + UINT8 st1 = v&1; + UINT8 st2 = (v>>1)&1; + + /* IRQRST,T1MSK,t2MSK,EOSMSK,BRMSK,x,ST2,ST1 */ + OPL_STATUS_RESET(OPL, v & (0x78-0x08) ); + OPL_STATUSMASK_SET(OPL, (~v) & 0x78 ); + + /* timer 2 */ + /*if(OPL->st[1] != st2) + { + attotime period = st2 ? attotime_mul(OPL->TimerBase, OPL->T[1]) : attotime_zero; + OPL->st[1] = st2; + if (OPL->timer_handler) (OPL->timer_handler)(OPL->TimerParam,1,period); + }*/ + /* timer 1 */ + /*if(OPL->st[0] != st1) + { + attotime period = st1 ? attotime_mul(OPL->TimerBase, OPL->T[0]) : attotime_zero; + OPL->st[0] = st1; + if (OPL->timer_handler) (OPL->timer_handler)(OPL->TimerParam,0,period); + }*/ + } + break; +#if BUILD_Y8950 + case 0x06: /* Key Board OUT */ + if(OPL->type&OPL_TYPE_KEYBOARD) + { + if(OPL->keyboardhandler_w) + OPL->keyboardhandler_w(OPL->keyboard_param,v); + else + logerror("Y8950: write unmapped KEYBOARD port\n"); + } + break; + case 0x07: /* DELTA-T control 1 : START,REC,MEMDATA,REPT,SPOFF,x,x,RST */ + if(OPL->type&OPL_TYPE_ADPCM) + YM_DELTAT_ADPCM_Write(OPL->deltat,r-0x07,v); + break; +#endif + case 0x08: /* MODE,DELTA-T control 2 : CSM,NOTESEL,x,x,smpl,da/ad,64k,rom */ + OPL->mode = v; +#if BUILD_Y8950 + if(OPL->type&OPL_TYPE_ADPCM) + YM_DELTAT_ADPCM_Write(OPL->deltat,r-0x07,v&0x0f); /* mask 4 LSBs in register 08 for DELTA-T unit */ +#endif + break; + +#if BUILD_Y8950 + case 0x09: /* START ADD */ + case 0x0a: + case 0x0b: /* STOP ADD */ + case 0x0c: + case 0x0d: /* PRESCALE */ + case 0x0e: + case 0x0f: /* ADPCM data write */ + case 0x10: /* DELTA-N */ + case 0x11: /* DELTA-N */ + case 0x12: /* ADPCM volume */ + if(OPL->type&OPL_TYPE_ADPCM) + YM_DELTAT_ADPCM_Write(OPL->deltat,r-0x07,v); + break; + + case 0x15: /* DAC data high 8 bits (F7,F6...F2) */ + case 0x16: /* DAC data low 2 bits (F1, F0 in bits 7,6) */ + case 0x17: /* DAC data shift (S2,S1,S0 in bits 2,1,0) */ + logerror("FMOPL.C: DAC data register written, but not implemented reg=%02x val=%02x\n",r,v); + break; + + case 0x18: /* I/O CTRL (Direction) */ + if(OPL->type&OPL_TYPE_IO) + OPL->portDirection = v&0x0f; + break; + case 0x19: /* I/O DATA */ + if(OPL->type&OPL_TYPE_IO) + { + OPL->portLatch = v; + if(OPL->porthandler_w) + OPL->porthandler_w(OPL->port_param,v&OPL->portDirection); + } + break; +#endif + default: + logerror("FMOPL.C: write to unknown register: %02x\n",r); + break; + } + break; + case 0x20: /* am ON, vib ON, ksr, eg_type, mul */ + slot = slot_array[r&0x1f]; + if(slot < 0) return; + set_mul(OPL,slot,v); + break; + case 0x40: + slot = slot_array[r&0x1f]; + if(slot < 0) return; + set_ksl_tl(OPL,slot,v); + break; + case 0x60: + slot = slot_array[r&0x1f]; + if(slot < 0) return; + set_ar_dr(OPL,slot,v); + break; + case 0x80: + slot = slot_array[r&0x1f]; + if(slot < 0) return; + set_sl_rr(OPL,slot,v); + break; + case 0xa0: + if (r == 0xbd) /* am depth, vibrato depth, r,bd,sd,tom,tc,hh */ + { + OPL->lfo_am_depth = v & 0x80; + OPL->lfo_pm_depth_range = (v&0x40) ? 8 : 0; + + OPL->rhythm = v&0x3f; + + if(OPL->rhythm&0x20) + { + /* BD key on/off */ + if(v&0x10) + { + FM_KEYON (&OPL->P_CH[6].SLOT[SLOT1], 2); + FM_KEYON (&OPL->P_CH[6].SLOT[SLOT2], 2); + } + else + { + FM_KEYOFF(&OPL->P_CH[6].SLOT[SLOT1],~2); + FM_KEYOFF(&OPL->P_CH[6].SLOT[SLOT2],~2); + } + /* HH key on/off */ + if(v&0x01) FM_KEYON (&OPL->P_CH[7].SLOT[SLOT1], 2); + else FM_KEYOFF(&OPL->P_CH[7].SLOT[SLOT1],~2); + /* SD key on/off */ + if(v&0x08) FM_KEYON (&OPL->P_CH[7].SLOT[SLOT2], 2); + else FM_KEYOFF(&OPL->P_CH[7].SLOT[SLOT2],~2); + /* TOM key on/off */ + if(v&0x04) FM_KEYON (&OPL->P_CH[8].SLOT[SLOT1], 2); + else FM_KEYOFF(&OPL->P_CH[8].SLOT[SLOT1],~2); + /* TOP-CY key on/off */ + if(v&0x02) FM_KEYON (&OPL->P_CH[8].SLOT[SLOT2], 2); + else FM_KEYOFF(&OPL->P_CH[8].SLOT[SLOT2],~2); + } + else + { + /* BD key off */ + FM_KEYOFF(&OPL->P_CH[6].SLOT[SLOT1],~2); + FM_KEYOFF(&OPL->P_CH[6].SLOT[SLOT2],~2); + /* HH key off */ + FM_KEYOFF(&OPL->P_CH[7].SLOT[SLOT1],~2); + /* SD key off */ + FM_KEYOFF(&OPL->P_CH[7].SLOT[SLOT2],~2); + /* TOM key off */ + FM_KEYOFF(&OPL->P_CH[8].SLOT[SLOT1],~2); + /* TOP-CY off */ + FM_KEYOFF(&OPL->P_CH[8].SLOT[SLOT2],~2); + } + return; + } + /* keyon,block,fnum */ + if( (r&0x0f) > 8) return; + CH = &OPL->P_CH[r&0x0f]; + if(!(r&0x10)) + { /* a0-a8 */ + block_fnum = (CH->block_fnum&0x1f00) | v; + } + else + { /* b0-b8 */ + block_fnum = ((v&0x1f)<<8) | (CH->block_fnum&0xff); + + if(v&0x20) + { + FM_KEYON (&CH->SLOT[SLOT1], 1); + FM_KEYON (&CH->SLOT[SLOT2], 1); + } + else + { + FM_KEYOFF(&CH->SLOT[SLOT1],~1); + FM_KEYOFF(&CH->SLOT[SLOT2],~1); + } + } + /* update */ + if(CH->block_fnum != block_fnum) + { + UINT8 block = block_fnum >> 10; + + CH->block_fnum = block_fnum; + + CH->ksl_base = ksl_tab[block_fnum>>6]; + CH->fc = OPL->fn_tab[block_fnum&0x03ff] >> (7-block); + + /* BLK 2,1,0 bits -> bits 3,2,1 of kcode */ + CH->kcode = (CH->block_fnum&0x1c00)>>9; + + /* the info below is actually opposite to what is stated in the Manuals (verifed on real YM3812) */ + /* if notesel == 0 -> lsb of kcode is bit 10 (MSB) of fnum */ + /* if notesel == 1 -> lsb of kcode is bit 9 (MSB-1) of fnum */ + if (OPL->mode&0x40) + CH->kcode |= (CH->block_fnum&0x100)>>8; /* notesel == 1 */ + else + CH->kcode |= (CH->block_fnum&0x200)>>9; /* notesel == 0 */ + + /* refresh Total Level in both SLOTs of this channel */ + CH->SLOT[SLOT1].TLL = CH->SLOT[SLOT1].TL + (CH->ksl_base>>CH->SLOT[SLOT1].ksl); + CH->SLOT[SLOT2].TLL = CH->SLOT[SLOT2].TL + (CH->ksl_base>>CH->SLOT[SLOT2].ksl); + + /* refresh frequency counter in both SLOTs of this channel */ + CALC_FCSLOT(CH,&CH->SLOT[SLOT1]); + CALC_FCSLOT(CH,&CH->SLOT[SLOT2]); + } + break; + case 0xc0: + /* FB,C */ + if( (r&0x0f) > 8) return; + CH = &OPL->P_CH[r&0x0f]; + CH->SLOT[SLOT1].FB = (v>>1)&7 ? ((v>>1)&7) + 7 : 0; + CH->SLOT[SLOT1].CON = v&1; + CH->SLOT[SLOT1].connect1 = CH->SLOT[SLOT1].CON ? &OPL->output[0] : &OPL->phase_modulation; + break; + case 0xe0: /* waveform select */ + /* simply ignore write to the waveform select register if selecting not enabled in test register */ + if(OPL->wavesel) + { + slot = slot_array[r&0x1f]; + if(slot < 0) return; + CH = &OPL->P_CH[slot/2]; + + CH->SLOT[slot&1].wavetable = (v&0x03)*SIN_LEN; + } + break; + } +} + +/*static TIMER_CALLBACK( cymfile_callback ) +{ + if (cymfile) + { + fputc( (unsigned char)0, cymfile ); + } +}*/ + +/* lock/unlock for common table */ +#if 0 +static int OPL_LockTable(/*const device_config *device*/) +{ + num_lock++; + if(num_lock>1) return 0; + + /* first time */ + + cur_chip = NULL; + /* allocate total level table (128kb space) */ + if( !init_tables() ) + { + num_lock--; + return -1; + } + +#if 0 + if (LOG_CYM_FILE) + { + cymfile = fopen("3812_.cym","wb"); + if (cymfile) + timer_pulse ( device->machine, ATTOTIME_IN_HZ(110), NULL, 0, cymfile_callback); /*110 Hz pulse timer*/ + else + logerror("Could not create file 3812_.cym\n"); + } +#endif + + return 0; +} + +static void OPL_UnLockTable(void) +{ + if(num_lock) num_lock--; + if(num_lock) return; + + /* last time */ + + cur_chip = NULL; + OPLCloseTable(); + + /*if (cymfile) + fclose (cymfile); + cymfile = NULL;*/ +} +#endif + +static void OPLResetChip(FM_OPL *OPL) +{ + int c,s; + int i; + + OPL->eg_timer = 0; + OPL->eg_cnt = 0; + + OPL->noise_rng = 1; /* noise shift register */ + OPL->mode = 0; /* normal mode */ + OPL_STATUS_RESET(OPL,0x7f); + + /* reset with register write */ + OPLWriteReg(OPL,0x01,0); /* wavesel disable */ + OPLWriteReg(OPL,0x02,0); /* Timer1 */ + OPLWriteReg(OPL,0x03,0); /* Timer2 */ + OPLWriteReg(OPL,0x04,0); /* IRQ mask clear */ + for(i = 0xff ; i >= 0x20 ; i-- ) OPLWriteReg(OPL,i,0); + + /* reset operator parameters */ + for( c = 0 ; c < 9 ; c++ ) + { + OPL_CH *CH = &OPL->P_CH[c]; + for(s = 0 ; s < 2 ; s++ ) + { + /* wave table */ + CH->SLOT[s].wavetable = 0; + CH->SLOT[s].state = EG_OFF; + CH->SLOT[s].volume = MAX_ATT_INDEX; + } + } +#if BUILD_Y8950 + if(OPL->type&OPL_TYPE_ADPCM) + { + YM_DELTAT *DELTAT = OPL->deltat; + + DELTAT->freqbase = OPL->freqbase; + DELTAT->output_pointer = &OPL->output_deltat[0]; + DELTAT->portshift = 5; + DELTAT->output_range = 1<<23; + YM_DELTAT_ADPCM_Reset(DELTAT,0,YM_DELTAT_EMULATION_MODE_NORMAL); + } +#endif +} + + +#if 0 +static STATE_POSTLOAD( OPL_postload ) +{ + FM_OPL *OPL = (FM_OPL *)param; + int slot, ch; + + for( ch=0 ; ch < 9 ; ch++ ) + { + OPL_CH *CH = &OPL->P_CH[ch]; + + /* Look up key scale level */ + UINT32 block_fnum = CH->block_fnum; + CH->ksl_base = ksl_tab[block_fnum >> 6]; + CH->fc = OPL->fn_tab[block_fnum & 0x03ff] >> (7 - (block_fnum >> 10)); + + for( slot=0 ; slot < 2 ; slot++ ) + { + OPL_SLOT *SLOT = &CH->SLOT[slot]; + + /* Calculate key scale rate */ + SLOT->ksr = CH->kcode >> SLOT->KSR; + + /* Calculate attack, decay and release rates */ + if ((SLOT->ar + SLOT->ksr) < 16+62) + { + SLOT->eg_sh_ar = eg_rate_shift [SLOT->ar + SLOT->ksr ]; + SLOT->eg_sel_ar = eg_rate_select[SLOT->ar + SLOT->ksr ]; + } + else + { + SLOT->eg_sh_ar = 0; + SLOT->eg_sel_ar = 13*RATE_STEPS; + } + SLOT->eg_sh_dr = eg_rate_shift [SLOT->dr + SLOT->ksr ]; + SLOT->eg_sel_dr = eg_rate_select[SLOT->dr + SLOT->ksr ]; + SLOT->eg_sh_rr = eg_rate_shift [SLOT->rr + SLOT->ksr ]; + SLOT->eg_sel_rr = eg_rate_select[SLOT->rr + SLOT->ksr ]; + + /* Calculate phase increment */ + SLOT->Incr = CH->fc * SLOT->mul; + + /* Total level */ + SLOT->TLL = SLOT->TL + (CH->ksl_base >> SLOT->ksl); + + /* Connect output */ + SLOT->connect1 = SLOT->CON ? &OPL->output[0] : &OPL->phase_modulation; + } + } +#if BUILD_Y8950 + if ( (OPL->type & OPL_TYPE_ADPCM) && (OPL->deltat) ) + { + // We really should call the postlod function for the YM_DELTAT, but it's hard without registers + // (see the way the YM2610 does it) + //YM_DELTAT_postload(OPL->deltat, REGS); + } +#endif +} + + +static void OPLsave_state_channel(const device_config *device, OPL_CH *CH) +{ + int slot, ch; + + for( ch=0 ; ch < 9 ; ch++, CH++ ) + { + /* channel */ + state_save_register_device_item(device, ch, CH->block_fnum); + state_save_register_device_item(device, ch, CH->kcode); + /* slots */ + for( slot=0 ; slot < 2 ; slot++ ) + { + OPL_SLOT *SLOT = &CH->SLOT[slot]; + + state_save_register_device_item(device, ch * 2 + slot, SLOT->ar); + state_save_register_device_item(device, ch * 2 + slot, SLOT->dr); + state_save_register_device_item(device, ch * 2 + slot, SLOT->rr); + state_save_register_device_item(device, ch * 2 + slot, SLOT->KSR); + state_save_register_device_item(device, ch * 2 + slot, SLOT->ksl); + state_save_register_device_item(device, ch * 2 + slot, SLOT->mul); + + state_save_register_device_item(device, ch * 2 + slot, SLOT->Cnt); + state_save_register_device_item(device, ch * 2 + slot, SLOT->FB); + state_save_register_device_item_array(device, ch * 2 + slot, SLOT->op1_out); + state_save_register_device_item(device, ch * 2 + slot, SLOT->CON); + + state_save_register_device_item(device, ch * 2 + slot, SLOT->eg_type); + state_save_register_device_item(device, ch * 2 + slot, SLOT->state); + state_save_register_device_item(device, ch * 2 + slot, SLOT->TL); + state_save_register_device_item(device, ch * 2 + slot, SLOT->volume); + state_save_register_device_item(device, ch * 2 + slot, SLOT->sl); + state_save_register_device_item(device, ch * 2 + slot, SLOT->key); + + state_save_register_device_item(device, ch * 2 + slot, SLOT->AMmask); + state_save_register_device_item(device, ch * 2 + slot, SLOT->vib); + + state_save_register_device_item(device, ch * 2 + slot, SLOT->wavetable); + } + } +} + + +/* Register savestate for a virtual YM3812/YM3526Y8950 */ + +static void OPL_save_state(FM_OPL *OPL, const device_config *device) +{ + OPLsave_state_channel(device, OPL->P_CH); + + state_save_register_device_item(device, 0, OPL->eg_cnt); + state_save_register_device_item(device, 0, OPL->eg_timer); + + state_save_register_device_item(device, 0, OPL->rhythm); + + state_save_register_device_item(device, 0, OPL->lfo_am_depth); + state_save_register_device_item(device, 0, OPL->lfo_pm_depth_range); + state_save_register_device_item(device, 0, OPL->lfo_am_cnt); + state_save_register_device_item(device, 0, OPL->lfo_pm_cnt); + + state_save_register_device_item(device, 0, OPL->noise_rng); + state_save_register_device_item(device, 0, OPL->noise_p); + + if( OPL->type & OPL_TYPE_WAVESEL ) + { + state_save_register_device_item(device, 0, OPL->wavesel); + } + + state_save_register_device_item_array(device, 0, OPL->T); + state_save_register_device_item_array(device, 0, OPL->st); + +#if BUILD_Y8950 + if ( (OPL->type & OPL_TYPE_ADPCM) && (OPL->deltat) ) + { + YM_DELTAT_savestate(device, OPL->deltat); + } + + if ( OPL->type & OPL_TYPE_IO ) + { + state_save_register_device_item(device, 0, OPL->portDirection); + state_save_register_device_item(device, 0, OPL->portLatch); + } +#endif + + state_save_register_device_item(device, 0, OPL->address); + state_save_register_device_item(device, 0, OPL->status); + state_save_register_device_item(device, 0, OPL->statusmask); + state_save_register_device_item(device, 0, OPL->mode); + + state_save_register_postload(device->machine, OPL_postload, OPL); +} +#endif + + +/* Create one of virtual YM3812/YM3526/Y8950 */ +/* 'clock' is chip clock in Hz */ +/* 'rate' is sampling rate */ +static FM_OPL *OPLCreate(UINT32 clock, UINT32 rate, int type) +{ + char *ptr; + FM_OPL *OPL; + int state_size; + + //if (OPL_LockTable(device) == -1) return NULL; + init_tables(); + + /* calculate OPL state size */ + state_size = sizeof(FM_OPL); + +#if BUILD_Y8950 + if (type&OPL_TYPE_ADPCM) state_size+= sizeof(YM_DELTAT); +#endif + + /* allocate memory block */ + ptr = (char *)malloc(state_size); + + if (ptr==NULL) + return 0; + + /* clear */ + memset(ptr,0,state_size); + + OPL = (FM_OPL *)ptr; + + ptr += sizeof(FM_OPL); + +#if BUILD_Y8950 + if (type&OPL_TYPE_ADPCM) + { + OPL->deltat = (YM_DELTAT *)ptr; + } + ptr += sizeof(YM_DELTAT); +#endif + + OPL->type = type; + OPL->clock = clock; + OPL->rate = rate; + + /* init global tables */ + OPL_initalize(OPL); + + return OPL; +} + +/* Destroy one of virtual YM3812 */ +static void OPLDestroy(FM_OPL *OPL) +{ + //OPL_UnLockTable(); + free(OPL); +} + +/* Optional handlers */ + +/*static void OPLSetTimerHandler(FM_OPL *OPL,OPL_TIMERHANDLER timer_handler,void *param) +{ + OPL->timer_handler = timer_handler; + OPL->TimerParam = param; +}*/ +static void OPLSetIRQHandler(FM_OPL *OPL,OPL_IRQHANDLER IRQHandler,void *param) +{ + OPL->IRQHandler = IRQHandler; + OPL->IRQParam = param; +} +static void OPLSetUpdateHandler(FM_OPL *OPL,OPL_UPDATEHANDLER UpdateHandler,void *param) +{ + OPL->UpdateHandler = UpdateHandler; + OPL->UpdateParam = param; +} + +static int OPLWrite(FM_OPL *OPL,int a,int v) +{ + if( !(a&1) ) + { /* address port */ + OPL->address = v & 0xff; + } + else + { /* data port */ + if(OPL->UpdateHandler) OPL->UpdateHandler(OPL->UpdateParam,0); + OPLWriteReg(OPL,OPL->address,v); + } + return OPL->status>>7; +} + +static unsigned char OPLRead(FM_OPL *OPL,int a) +{ + if( !(a&1) ) + { + /* status port */ + + #if BUILD_Y8950 + + if(OPL->type&OPL_TYPE_ADPCM) /* Y8950 */ + { + return (OPL->status & (OPL->statusmask|0x80)) | (OPL->deltat->PCM_BSY&1); + } + + #endif + + /* OPL and OPL2 */ + return OPL->status & (OPL->statusmask|0x80); + } + +#if BUILD_Y8950 + /* data port */ + switch(OPL->address) + { + case 0x05: /* KeyBoard IN */ + if(OPL->type&OPL_TYPE_KEYBOARD) + { + if(OPL->keyboardhandler_r) + return OPL->keyboardhandler_r(OPL->keyboard_param); + else + logerror("Y8950: read unmapped KEYBOARD port\n"); + } + return 0; + + case 0x0f: /* ADPCM-DATA */ + if(OPL->type&OPL_TYPE_ADPCM) + { + UINT8 val; + + val = YM_DELTAT_ADPCM_Read(OPL->deltat); + /*logerror("Y8950: read ADPCM value read=%02x\n",val);*/ + return val; + } + return 0; + + case 0x19: /* I/O DATA */ + if(OPL->type&OPL_TYPE_IO) + { + if(OPL->porthandler_r) + return OPL->porthandler_r(OPL->port_param); + else + logerror("Y8950:read unmapped I/O port\n"); + } + return 0; + case 0x1a: /* PCM-DATA */ + if(OPL->type&OPL_TYPE_ADPCM) + { + logerror("Y8950 A/D convertion is accessed but not implemented !\n"); + return 0x80; /* 2's complement PCM data - result from A/D convertion */ + } + return 0; + } +#endif + + return 0xff; +} + +/* CSM Key Controll */ +INLINE void CSMKeyControll(OPL_CH *CH) +{ + FM_KEYON (&CH->SLOT[SLOT1], 4); + FM_KEYON (&CH->SLOT[SLOT2], 4); + + /* The key off should happen exactly one sample later - not implemented correctly yet */ + + FM_KEYOFF(&CH->SLOT[SLOT1], ~4); + FM_KEYOFF(&CH->SLOT[SLOT2], ~4); +} + + +static int OPLTimerOver(FM_OPL *OPL,int c) +{ + if( c ) + { /* Timer B */ + OPL_STATUS_SET(OPL,0x20); + } + else + { /* Timer A */ + OPL_STATUS_SET(OPL,0x40); + /* CSM mode key,TL controll */ + if( OPL->mode & 0x80 ) + { /* CSM mode total level latch and auto key on */ + int ch; + if(OPL->UpdateHandler) OPL->UpdateHandler(OPL->UpdateParam,0); + for(ch=0; ch<9; ch++) + CSMKeyControll( &OPL->P_CH[ch] ); + } + } + /* reload timer */ + //if (OPL->timer_handler) (OPL->timer_handler)(OPL->TimerParam,c,attotime_mul(OPL->TimerBase, OPL->T[c])); + return OPL->status>>7; +} + + +#define MAX_OPL_CHIPS 2 + + +#if (BUILD_YM3812) + +void * ym3812_init(UINT32 clock, UINT32 rate) +{ + /* emulator create */ + FM_OPL *YM3812 = OPLCreate(clock,rate,OPL_TYPE_YM3812); + if (YM3812) + { + //OPL_save_state(YM3812); + ym3812_reset_chip(YM3812); + } + return YM3812; +} + +void ym3812_shutdown(void *chip) +{ + FM_OPL *YM3812 = (FM_OPL *)chip; + + /* emulator shutdown */ + OPLDestroy(YM3812); +} +void ym3812_reset_chip(void *chip) +{ + FM_OPL *YM3812 = (FM_OPL *)chip; + OPLResetChip(YM3812); +} + +int ym3812_write(void *chip, int a, int v) +{ + FM_OPL *YM3812 = (FM_OPL *)chip; + return OPLWrite(YM3812, a, v); +} + +unsigned char ym3812_read(void *chip, int a) +{ + FM_OPL *YM3812 = (FM_OPL *)chip; + /* YM3812 always returns bit2 and bit1 in HIGH state */ + return OPLRead(YM3812, a) | 0x06 ; +} +int ym3812_timer_over(void *chip, int c) +{ + FM_OPL *YM3812 = (FM_OPL *)chip; + return OPLTimerOver(YM3812, c); +} + +/*void ym3812_set_timer_handler(void *chip, OPL_TIMERHANDLER timer_handler, void *param) +{ + FM_OPL *YM3812 = (FM_OPL *)chip; + OPLSetTimerHandler(YM3812, timer_handler, param); +}*/ +void ym3812_set_irq_handler(void *chip,OPL_IRQHANDLER IRQHandler,void *param) +{ + FM_OPL *YM3812 = (FM_OPL *)chip; + OPLSetIRQHandler(YM3812, IRQHandler, param); +} +void ym3812_set_update_handler(void *chip,OPL_UPDATEHANDLER UpdateHandler,void *param) +{ + FM_OPL *YM3812 = (FM_OPL *)chip; + OPLSetUpdateHandler(YM3812, UpdateHandler, param); +} + + +/* +** Generate samples for one of the YM3812's +** +** 'which' is the virtual YM3812 number +** '*buffer' is the output buffer pointer +** 'length' is the number of samples that should be generated +*/ +void ym3812_update_one(void *chip, OPLSAMPLE *buffer, int length) +{ + FM_OPL *OPL = (FM_OPL *)chip; + UINT8 rhythm = OPL->rhythm&0x20; + OPLSAMPLE *buf = buffer; + int i; + + /* rhythm slots */ + OPL->SLOT7_1 = &OPL->P_CH[7].SLOT[SLOT1]; + OPL->SLOT7_2 = &OPL->P_CH[7].SLOT[SLOT2]; + OPL->SLOT8_1 = &OPL->P_CH[8].SLOT[SLOT1]; + OPL->SLOT8_2 = &OPL->P_CH[8].SLOT[SLOT2]; + for( i=0; i < length ; i++ ) + { + int lt; + + OPL->output[0] = 0; + + advance_lfo(OPL); + + /* FM part */ + OPL_CALC_CH(OPL, &OPL->P_CH[0]); + OPL_CALC_CH(OPL, &OPL->P_CH[1]); + OPL_CALC_CH(OPL, &OPL->P_CH[2]); + OPL_CALC_CH(OPL, &OPL->P_CH[3]); + OPL_CALC_CH(OPL, &OPL->P_CH[4]); + OPL_CALC_CH(OPL, &OPL->P_CH[5]); + + if(!rhythm) + { + OPL_CALC_CH(OPL, &OPL->P_CH[6]); + OPL_CALC_CH(OPL, &OPL->P_CH[7]); + OPL_CALC_CH(OPL, &OPL->P_CH[8]); + } + else /* Rhythm part */ + { + OPL_CALC_RH(OPL, &OPL->P_CH[0], (OPL->noise_rng>>0)&1 ); + } + + lt = OPL->output[0]; + + lt >>= FINAL_SH; + + /* limit check */ + lt = limit( lt , MAXOUT, MINOUT ); + + #ifdef SAVE_SAMPLE + if (which==0) + { + SAVE_ALL_CHANNELS + } + #endif + + /* store to sound buffer */ + buf[i] = lt; + + advance(OPL); + } + +} +#endif /* BUILD_YM3812 */ + + + +#if (BUILD_YM3526) + +void *ym3526_init(UINT32 clock, UINT32 rate) +{ + /* emulator create */ + FM_OPL *YM3526 = OPLCreate(clock,rate,OPL_TYPE_YM3526); + if (YM3526) + { + /*OPL_save_state(YM3526);*/ + ym3526_reset_chip(YM3526); + } + return YM3526; +} + +void ym3526_shutdown(void *chip) +{ + FM_OPL *YM3526 = (FM_OPL *)chip; + /* emulator shutdown */ + OPLDestroy(YM3526); +} +void ym3526_reset_chip(void *chip) +{ + FM_OPL *YM3526 = (FM_OPL *)chip; + OPLResetChip(YM3526); +} + +int ym3526_write(void *chip, int a, int v) +{ + FM_OPL *YM3526 = (FM_OPL *)chip; + return OPLWrite(YM3526, a, v); +} + +unsigned char ym3526_read(void *chip, int a) +{ + FM_OPL *YM3526 = (FM_OPL *)chip; + /* YM3526 always returns bit2 and bit1 in HIGH state */ + return OPLRead(YM3526, a) | 0x06 ; +} +int ym3526_timer_over(void *chip, int c) +{ + FM_OPL *YM3526 = (FM_OPL *)chip; + return OPLTimerOver(YM3526, c); +} + +/*void ym3526_set_timer_handler(void *chip, OPL_TIMERHANDLER timer_handler, void *param) +{ + FM_OPL *YM3526 = (FM_OPL *)chip; + OPLSetTimerHandler(YM3526, timer_handler, param); +}*/ +void ym3526_set_irq_handler(void *chip,OPL_IRQHANDLER IRQHandler,void *param) +{ + FM_OPL *YM3526 = (FM_OPL *)chip; + OPLSetIRQHandler(YM3526, IRQHandler, param); +} +void ym3526_set_update_handler(void *chip,OPL_UPDATEHANDLER UpdateHandler,void *param) +{ + FM_OPL *YM3526 = (FM_OPL *)chip; + OPLSetUpdateHandler(YM3526, UpdateHandler, param); +} + + +/* +** Generate samples for one of the YM3526's +** +** 'which' is the virtual YM3526 number +** '*buffer' is the output buffer pointer +** 'length' is the number of samples that should be generated +*/ +void ym3526_update_one(void *chip, OPLSAMPLE *buffer, int length) +{ + FM_OPL *OPL = (FM_OPL *)chip; + UINT8 rhythm = OPL->rhythm&0x20; + OPLSAMPLE *buf = buffer; + int i; + + /* rhythm slots */ + OPL->SLOT7_1 = &OPL->P_CH[7].SLOT[SLOT1]; + OPL->SLOT7_2 = &OPL->P_CH[7].SLOT[SLOT2]; + OPL->SLOT8_1 = &OPL->P_CH[8].SLOT[SLOT1]; + OPL->SLOT8_2 = &OPL->P_CH[8].SLOT[SLOT2]; + for( i=0; i < length ; i++ ) + { + int lt; + + OPL->output[0] = 0; + + advance_lfo(OPL); + + /* FM part */ + OPL_CALC_CH(OPL, &OPL->P_CH[0]); + OPL_CALC_CH(OPL, &OPL->P_CH[1]); + OPL_CALC_CH(OPL, &OPL->P_CH[2]); + OPL_CALC_CH(OPL, &OPL->P_CH[3]); + OPL_CALC_CH(OPL, &OPL->P_CH[4]); + OPL_CALC_CH(OPL, &OPL->P_CH[5]); + + if(!rhythm) + { + OPL_CALC_CH(OPL, &OPL->P_CH[6]); + OPL_CALC_CH(OPL, &OPL->P_CH[7]); + OPL_CALC_CH(OPL, &OPL->P_CH[8]); + } + else /* Rhythm part */ + { + OPL_CALC_RH(OPL, &OPL->P_CH[0], (OPL->noise_rng>>0)&1 ); + } + + lt = OPL->output[0]; + + lt >>= FINAL_SH; + + /* limit check */ + lt = limit( lt , MAXOUT, MINOUT ); + + #ifdef SAVE_SAMPLE + if (which==0) + { + SAVE_ALL_CHANNELS + } + #endif + + /* store to sound buffer */ + buf[i] = lt; + + advance(OPL); + } + +} +#endif /* BUILD_YM3526 */ + + + + +#if BUILD_Y8950 + +static void Y8950_deltat_status_set(void *chip, UINT8 changebits) +{ + FM_OPL *Y8950 = (FM_OPL *)chip; + OPL_STATUS_SET(Y8950, changebits); +} +static void Y8950_deltat_status_reset(void *chip, UINT8 changebits) +{ + FM_OPL *Y8950 = (FM_OPL *)chip; + OPL_STATUS_RESET(Y8950, changebits); +} + +void *y8950_init(UINT32 clock, UINT32 rate) +{ + /* emulator create */ + FM_OPL *Y8950 = OPLCreate(clock,rate,OPL_TYPE_Y8950); + if (Y8950) + { + Y8950->deltat->status_set_handler = Y8950_deltat_status_set; + Y8950->deltat->status_reset_handler = Y8950_deltat_status_reset; + Y8950->deltat->status_change_which_chip = Y8950; + Y8950->deltat->status_change_EOS_bit = 0x10; /* status flag: set bit4 on End Of Sample */ + Y8950->deltat->status_change_BRDY_bit = 0x08; /* status flag: set bit3 on BRDY (End Of: ADPCM analysis/synthesis, memory reading/writing) */ + + /*Y8950->deltat->write_time = 10.0 / clock;*/ /* a single byte write takes 10 cycles of main clock */ + /*Y8950->deltat->read_time = 8.0 / clock;*/ /* a single byte read takes 8 cycles of main clock */ + /* reset */ + /*OPL_save_state(Y8950);*/ + y8950_reset_chip(Y8950); + } + + return Y8950; +} + +void y8950_shutdown(void *chip) +{ + FM_OPL *Y8950 = (FM_OPL *)chip; + /* emulator shutdown */ + OPLDestroy(Y8950); +} +void y8950_reset_chip(void *chip) +{ + FM_OPL *Y8950 = (FM_OPL *)chip; + OPLResetChip(Y8950); +} + +int y8950_write(void *chip, int a, int v) +{ + FM_OPL *Y8950 = (FM_OPL *)chip; + return OPLWrite(Y8950, a, v); +} + +unsigned char y8950_read(void *chip, int a) +{ + FM_OPL *Y8950 = (FM_OPL *)chip; + return OPLRead(Y8950, a); +} +int y8950_timer_over(void *chip, int c) +{ + FM_OPL *Y8950 = (FM_OPL *)chip; + return OPLTimerOver(Y8950, c); +} + +/*void y8950_set_timer_handler(void *chip, OPL_TIMERHANDLER timer_handler, void *param) +{ + FM_OPL *Y8950 = (FM_OPL *)chip; + OPLSetTimerHandler(Y8950, timer_handler, param); +}*/ +void y8950_set_irq_handler(void *chip,OPL_IRQHANDLER IRQHandler,void *param) +{ + FM_OPL *Y8950 = (FM_OPL *)chip; + OPLSetIRQHandler(Y8950, IRQHandler, param); +} +void y8950_set_update_handler(void *chip,OPL_UPDATEHANDLER UpdateHandler,void *param) +{ + FM_OPL *Y8950 = (FM_OPL *)chip; + OPLSetUpdateHandler(Y8950, UpdateHandler, param); +} + +void y8950_set_delta_t_memory(void *chip, void * deltat_mem_ptr, int deltat_mem_size ) +{ + FM_OPL *OPL = (FM_OPL *)chip; + OPL->deltat->memory = (UINT8 *)(deltat_mem_ptr); + OPL->deltat->memory_size = deltat_mem_size; +} + +/* +** Generate samples for one of the Y8950's +** +** 'which' is the virtual Y8950 number +** '*buffer' is the output buffer pointer +** 'length' is the number of samples that should be generated +*/ +void y8950_update_one(void *chip, OPLSAMPLE *buffer, int length) +{ + int i; + FM_OPL *OPL = (FM_OPL *)chip; + UINT8 rhythm = OPL->rhythm&0x20; + YM_DELTAT *DELTAT = OPL->deltat; + OPLSAMPLE *buf = buffer; + + /* rhythm slots */ + OPL->SLOT7_1 = &OPL->P_CH[7].SLOT[SLOT1]; + OPL->SLOT7_2 = &OPL->P_CH[7].SLOT[SLOT2]; + OPL->SLOT8_1 = &OPL->P_CH[8].SLOT[SLOT1]; + OPL->SLOT8_2 = &OPL->P_CH[8].SLOT[SLOT2]; + + for( i=0; i < length ; i++ ) + { + int lt; + + OPL->output[0] = 0; + OPL->output_deltat[0] = 0; + + advance_lfo(OPL); + + /* deltaT ADPCM */ + if( DELTAT->portstate&0x80 ) + YM_DELTAT_ADPCM_CALC(DELTAT); + + /* FM part */ + OPL_CALC_CH(OPL, &OPL->P_CH[0]); + OPL_CALC_CH(OPL, &OPL->P_CH[1]); + OPL_CALC_CH(OPL, &OPL->P_CH[2]); + OPL_CALC_CH(OPL, &OPL->P_CH[3]); + OPL_CALC_CH(OPL, &OPL->P_CH[4]); + OPL_CALC_CH(OPL, &OPL->P_CH[5]); + + if(!rhythm) + { + OPL_CALC_CH(OPL, &OPL->P_CH[6]); + OPL_CALC_CH(OPL, &OPL->P_CH[7]); + OPL_CALC_CH(OPL, &OPL->P_CH[8]); + } + else /* Rhythm part */ + { + OPL_CALC_RH(OPL, &OPL->P_CH[0], (OPL->noise_rng>>0)&1 ); + } + + lt = OPL->output[0] + (OPL->output_deltat[0]>>11); + + lt >>= FINAL_SH; + + /* limit check */ + lt = limit( lt , MAXOUT, MINOUT ); + + #ifdef SAVE_SAMPLE + if (which==0) + { + SAVE_ALL_CHANNELS + } + #endif + + /* store to sound buffer */ + buf[i] = lt; + + advance(OPL); + } + +} + +void y8950_set_port_handler(void *chip,OPL_PORTHANDLER_W PortHandler_w,OPL_PORTHANDLER_R PortHandler_r,void * param) +{ + FM_OPL *OPL = (FM_OPL *)chip; + OPL->porthandler_w = PortHandler_w; + OPL->porthandler_r = PortHandler_r; + OPL->port_param = param; +} + +void y8950_set_keyboard_handler(void *chip,OPL_PORTHANDLER_W KeyboardHandler_w,OPL_PORTHANDLER_R KeyboardHandler_r,void * param) +{ + FM_OPL *OPL = (FM_OPL *)chip; + OPL->keyboardhandler_w = KeyboardHandler_w; + OPL->keyboardhandler_r = KeyboardHandler_r; + OPL->keyboard_param = param; +} + +#endif + diff --git a/Frameworks/GME/gme/fmopl.h b/Frameworks/GME/gme/fmopl.h new file mode 100644 index 000000000..868d38bec --- /dev/null +++ b/Frameworks/GME/gme/fmopl.h @@ -0,0 +1,116 @@ +#pragma once + +#ifndef __FMOPL_H__ +#define __FMOPL_H__ + +/* --- select emulation chips --- */ +#define BUILD_YM3812 (1) +#define BUILD_YM3526 (1) +#define BUILD_Y8950 (1) + +/* select output bits size of output : 8 or 16 */ +#define OPL_SAMPLE_BITS 16 + +/* compiler dependence */ +#ifndef __OSDCOMM_H__ +#define __OSDCOMM_H__ +typedef unsigned char UINT8; /* unsigned 8bit */ +typedef unsigned short UINT16; /* unsigned 16bit */ +typedef unsigned int UINT32; /* unsigned 32bit */ +typedef signed char INT8; /* signed 8bit */ +typedef signed short INT16; /* signed 16bit */ +typedef signed int INT32; /* signed 32bit */ + +typedef INT32 stream_sample_t; + +#endif /* __OSDCOMM_H__ */ + +typedef stream_sample_t OPLSAMPLE; +/* +#if (OPL_SAMPLE_BITS==16) +typedef INT16 OPLSAMPLE; +#endif +#if (OPL_SAMPLE_BITS==8) +typedef INT8 OPLSAMPLE; +#endif +*/ + +//typedef void (*OPL_TIMERHANDLER)(void *param,int timer,attotime period); +typedef void (*OPL_IRQHANDLER)(void *param,int irq); +typedef void (*OPL_UPDATEHANDLER)(void *param,int min_interval_us); +typedef void (*OPL_PORTHANDLER_W)(void *param,unsigned char data); +typedef unsigned char (*OPL_PORTHANDLER_R)(void *param); + + +#if BUILD_YM3812 + +void *ym3812_init(UINT32 clock, UINT32 rate); +void ym3812_shutdown(void *chip); +void ym3812_reset_chip(void *chip); +int ym3812_write(void *chip, int a, int v); +unsigned char ym3812_read(void *chip, int a); +int ym3812_timer_over(void *chip, int c); +void ym3812_update_one(void *chip, OPLSAMPLE *buffer, int length); + +//void ym3812_set_timer_handler(void *chip, OPL_TIMERHANDLER TimerHandler, void *param); +void ym3812_set_irq_handler(void *chip, OPL_IRQHANDLER IRQHandler, void *param); +void ym3812_set_update_handler(void *chip, OPL_UPDATEHANDLER UpdateHandler, void *param); + +#endif /* BUILD_YM3812 */ + + +#if BUILD_YM3526 + +/* +** Initialize YM3526 emulator(s). +** +** 'num' is the number of virtual YM3526's to allocate +** 'clock' is the chip clock in Hz +** 'rate' is sampling rate +*/ +void *ym3526_init(UINT32 clock, UINT32 rate); +/* shutdown the YM3526 emulators*/ +void ym3526_shutdown(void *chip); +void ym3526_reset_chip(void *chip); +int ym3526_write(void *chip, int a, int v); +unsigned char ym3526_read(void *chip, int a); +int ym3526_timer_over(void *chip, int c); +/* +** Generate samples for one of the YM3526's +** +** 'which' is the virtual YM3526 number +** '*buffer' is the output buffer pointer +** 'length' is the number of samples that should be generated +*/ +void ym3526_update_one(void *chip, OPLSAMPLE *buffer, int length); + +//void ym3526_set_timer_handler(void *chip, OPL_TIMERHANDLER TimerHandler, void *param); +void ym3526_set_irq_handler(void *chip, OPL_IRQHANDLER IRQHandler, void *param); +void ym3526_set_update_handler(void *chip, OPL_UPDATEHANDLER UpdateHandler, void *param); + +#endif /* BUILD_YM3526 */ + + +#if BUILD_Y8950 + +/* Y8950 port handlers */ +void y8950_set_port_handler(void *chip, OPL_PORTHANDLER_W PortHandler_w, OPL_PORTHANDLER_R PortHandler_r, void *param); +void y8950_set_keyboard_handler(void *chip, OPL_PORTHANDLER_W KeyboardHandler_w, OPL_PORTHANDLER_R KeyboardHandler_r, void *param); +void y8950_set_delta_t_memory(void *chip, void * deltat_mem_ptr, int deltat_mem_size ); + +void * y8950_init(UINT32 clock, UINT32 rate); +void y8950_shutdown(void *chip); +void y8950_reset_chip(void *chip); +int y8950_write(void *chip, int a, int v); +unsigned char y8950_read (void *chip, int a); +int y8950_timer_over(void *chip, int c); +void y8950_update_one(void *chip, OPLSAMPLE *buffer, int length); + +//void y8950_set_timer_handler(void *chip, OPL_TIMERHANDLER TimerHandler, void *param); +void y8950_set_irq_handler(void *chip, OPL_IRQHANDLER IRQHandler, void *param); +void y8950_set_update_handler(void *chip, OPL_UPDATEHANDLER UpdateHandler, void *param); + +#endif /* BUILD_Y8950 */ + + +#endif /* __FMOPL_H__ */ diff --git a/Frameworks/GME/gme/gme_custom_dprintf.c b/Frameworks/GME/gme/gme_custom_dprintf.c new file mode 100644 index 000000000..0b4503948 --- /dev/null +++ b/Frameworks/GME/gme/gme_custom_dprintf.c @@ -0,0 +1,4 @@ +#include "gme_custom_dprintf.h" + + +gme_custom_dprintf_callback gme_custom_dprintf = 0; diff --git a/Frameworks/GME/gme/gme_custom_dprintf.h b/Frameworks/GME/gme/gme_custom_dprintf.h new file mode 100644 index 000000000..9f71baf1c --- /dev/null +++ b/Frameworks/GME/gme/gme_custom_dprintf.h @@ -0,0 +1,28 @@ +#ifndef GME_CUSTOM_DPRINTF_H +#define GME_CUSTOM_DPRINTF_H + + +#ifdef CUSTOM_DPRINTF_FUNCTION + + +#include + + +#ifdef _cplusplus +extern "C" { +#endif + + +typedef void (*gme_custom_dprintf_callback)( const char * fmt, va_list vl ); +extern gme_custom_dprintf_callback gme_custom_dprintf; + + +#ifdef _cplusplus +} +#endif + + +#endif + + +#endif diff --git a/Frameworks/GME/gme/i_fmpac.h b/Frameworks/GME/gme/i_fmpac.h new file mode 100644 index 000000000..fecba35d8 --- /dev/null +++ b/Frameworks/GME/gme/i_fmpac.h @@ -0,0 +1,38 @@ + 0x49, 0x4C, 0x4C, 0x32, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x33, 0x21, 0x09, 0x0E, 0x94, 0x90, 0x48, 0xF8, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x13, 0x41, 0x0F, 0x0D, 0xCE, 0xD3, 0x43, 0x13, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x12, 0x1B, 0x06, 0xFF, 0xD2, 0x00, 0x32, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x61, 0x61, 0x1B, 0x07, 0xAF, 0x63, 0x20, 0x28, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x22, 0x21, 0x1E, 0x06, 0xF0, 0x76, 0x08, 0x28, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x66, 0x21, 0x15, 0x00, 0x93, 0x94, 0x20, 0xF8, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x21, 0x61, 0x1C, 0x07, 0x82, 0x81, 0x10, 0x17, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x23, 0x21, 0x20, 0x1F, 0xC0, 0x71, 0x07, 0x47, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x25, 0x31, 0x26, 0x05, 0x64, 0x41, 0x18, 0xF8, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x17, 0x21, 0x28, 0x07, 0xFF, 0x83, 0x02, 0xF8, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x97, 0x81, 0x25, 0x07, 0xCF, 0xC8, 0x02, 0x14, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x21, 0x21, 0x54, 0x0F, 0x80, 0x7F, 0x07, 0x07, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x01, 0x56, 0x03, 0xD3, 0xB2, 0x43, 0x58, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x31, 0x21, 0x0C, 0x03, 0x82, 0xC0, 0x40, 0x07, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x21, 0x01, 0x0C, 0x03, 0xD4, 0xD3, 0x40, 0x84, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x04, 0x21, 0x28, 0x00, 0xDF, 0xF8, 0xFF, 0xF8, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x23, 0x22, 0x00, 0x00, 0xA8, 0xF8, 0xF8, 0xF8, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x25, 0x18, 0x00, 0x00, 0xF8, 0xA9, 0xF8, 0x55, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, diff --git a/Frameworks/GME/gme/i_fmunit.h b/Frameworks/GME/gme/i_fmunit.h new file mode 100644 index 000000000..3030810e5 --- /dev/null +++ b/Frameworks/GME/gme/i_fmunit.h @@ -0,0 +1,38 @@ + 0x49, 0x4C, 0x4C, 0x32, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x61, 0x61, 0x1E, 0x07, 0xF0, 0x7E, 0x07, 0x17, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x13, 0x41, 0x0F, 0x1D, 0xCE, 0xD2, 0x43, 0x13, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x03, 0x01, 0x99, 0x04, 0xFF, 0xC3, 0x03, 0x73, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x21, 0x61, 0x1B, 0x07, 0xAF, 0x63, 0x40, 0x28, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x22, 0x21, 0x1E, 0x06, 0xF0, 0x76, 0x08, 0x28, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x31, 0x22, 0x16, 0x05, 0x90, 0x71, 0x00, 0x18, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x31, 0x61, 0x1D, 0x07, 0x32, 0x81, 0x10, 0x17, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x23, 0x21, 0x2D, 0x16, 0xC0, 0x70, 0x07, 0x07, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x61, 0x21, 0x1B, 0x06, 0x64, 0x65, 0x18, 0x18, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x61, 0x61, 0x0C, 0x18, 0x85, 0xA0, 0x79, 0x07, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x23, 0x21, 0x87, 0x11, 0xF0, 0xA4, 0x00, 0xF7, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x97, 0xE1, 0x28, 0x07, 0xFF, 0xF3, 0x02, 0xF8, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x61, 0x10, 0x0C, 0x05, 0xF2, 0xC4, 0x40, 0xC8, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x01, 0x56, 0x03, 0xB4, 0xB2, 0x23, 0x58, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x61, 0x41, 0x89, 0x03, 0xF1, 0xF4, 0xF0, 0x13, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x04, 0x21, 0x28, 0x00, 0xDF, 0xF8, 0xFF, 0xF8, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x0F, 0x02, 0x00, 0x00, 0xA7, 0xF7, 0x07, 0x07, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x04, 0x1F, 0x00, 0x00, 0xF8, 0xA9, 0x08, 0x05, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, diff --git a/Frameworks/GME/gme/i_vrc7.h b/Frameworks/GME/gme/i_vrc7.h new file mode 100644 index 000000000..56bbfa9d4 --- /dev/null +++ b/Frameworks/GME/gme/i_vrc7.h @@ -0,0 +1,38 @@ + 0x49, 0x4C, 0x4C, 0x32, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x61, 0x61, 0x1E, 0x07, 0xF0, 0x7E, 0x07, 0x17, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x13, 0x41, 0x0F, 0x1D, 0xCE, 0xD2, 0x43, 0x13, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x03, 0x01, 0x99, 0x04, 0xFF, 0xC3, 0x03, 0x73, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x21, 0x61, 0x1B, 0x07, 0xAF, 0x63, 0x40, 0x28, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x22, 0x21, 0x1E, 0x06, 0xF0, 0x76, 0x08, 0x28, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x31, 0x22, 0x16, 0x05, 0x90, 0x71, 0x00, 0x18, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x31, 0x61, 0x1D, 0x07, 0x32, 0x81, 0x10, 0x17, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x23, 0x21, 0x2D, 0x16, 0xC0, 0x70, 0x07, 0x07, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x61, 0x21, 0x1B, 0x06, 0x64, 0x65, 0x18, 0x18, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x61, 0x61, 0x0C, 0x18, 0x85, 0xA0, 0x79, 0x07, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x23, 0x21, 0x87, 0x11, 0xF0, 0xA4, 0x00, 0xF7, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x97, 0xE1, 0x28, 0x07, 0xFF, 0xF3, 0x02, 0xF8, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x61, 0x10, 0x0C, 0x05, 0xF2, 0xC4, 0x40, 0xC8, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x01, 0x56, 0x03, 0xB4, 0xB2, 0x23, 0x58, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x61, 0x41, 0x89, 0x03, 0xF1, 0xF4, 0xF0, 0x13, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x04, 0x21, 0x28, 0x00, 0xDF, 0xF8, 0xFF, 0xF8, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x28, 0x21, 0x00, 0x00, 0xA8, 0xF8, 0xF8, 0xF8, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x25, 0x18, 0x00, 0x00, 0xF8, 0xA9, 0xF8, 0x55, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, diff --git a/Frameworks/GME/gme/k051649.c b/Frameworks/GME/gme/k051649.c new file mode 100644 index 000000000..599d9ecd2 --- /dev/null +++ b/Frameworks/GME/gme/k051649.c @@ -0,0 +1,298 @@ +/*************************************************************************** + + Konami 051649 - SCC1 sound as used in Haunted Castle, City Bomber + + This file is pieced together by Bryan McPhail from a combination of + Namco Sound, Amuse by Cab, Haunted Castle schematics and whoever first + figured out SCC! + + The 051649 is a 5 channel sound generator, each channel gets it's + waveform from RAM (32 bytes per waveform, 8 bit signed data). + + This sound chip is the same as the sound chip in some Konami + megaROM cartridges for the MSX. It is actually well researched + and documented: + + http://www.msxnet.org/tech/scc + + Thanks to Sean Young (sean@mess.org) for some bugfixes. + + K052539 is equivalent to this chip except channel 5 does not share + waveforms with channel 4. + +***************************************************************************/ + +#include "mamedef.h" +#include +#include +//#include "emu.h" +//#include "streams.h" +#include "k051649.h" + +#define FREQBASEBITS 16 + +/* this structure defines the parameters for a channel */ +typedef struct +{ + unsigned long counter; + int frequency; + int volume; + int key; + signed char waveform[32]; /* 19991207.CAB */ + UINT8 Muted; +} k051649_sound_channel; + +typedef struct _k051649_state k051649_state; +struct _k051649_state +{ + k051649_sound_channel channel_list[5]; + + /* global sound parameters */ + //sound_stream * stream; + int mclock,rate; + + /* mixer tables and internal buffers */ + INT16 *mixer_table; + INT16 *mixer_lookup; + short *mixer_buffer; + + int f[10]; + int cur_reg; +}; + +/*INLINE k051649_state *get_safe_token(running_device *device) +{ + assert(device != NULL); + assert(device->type() == K051649); + return (k051649_state *)downcast(device)->token(); +}*/ + +/* build a table to divide by the number of voices */ +static void make_mixer_table(/*running_machine *machine,*/ k051649_state *info, int voices) +{ + int count = voices * 256; + int i; + int gain = 8; + + /* allocate memory */ + //info->mixer_table = auto_alloc_array(machine, INT16, 512 * voices); + info->mixer_table = (INT16*)malloc(sizeof(INT16) * 512 * voices); + + /* find the middle of the table */ + info->mixer_lookup = info->mixer_table + (256 * voices); + + /* fill in the table - 16 bit case */ + for (i = 0; i < count; i++) + { + int val = i * gain * 16 / voices; + if (val > 32767) val = 32767; + info->mixer_lookup[ i] = val; + info->mixer_lookup[-i] = -val; + } +} + + +/* generate sound to the mix buffer */ +//static STREAM_UPDATE( k051649_update ) +void k051649_update(void *chip, stream_sample_t **outputs, int samples) +{ + k051649_state *info = (k051649_state *) chip; + k051649_sound_channel *voice=info->channel_list; + stream_sample_t *buffer = outputs[0]; + stream_sample_t *buffer2 = outputs[1]; + short *mix; + int i,v,f,j,k; + + /* zap the contents of the mixer buffer */ + memset(info->mixer_buffer, 0, samples * sizeof(short)); + + for (j=0; j<5; j++) { + v=voice[j].volume; + f=voice[j].frequency; + k=voice[j].key; + /* SY 20040109: the SCC produces no sound for freq < 9 */ + if (v && f > 8 && k && ! voice[j].Muted) + { + const signed char *w = voice[j].waveform; /* 19991207.CAB */ + int c=voice[j].counter; + + mix = info->mixer_buffer; + + /* add our contribution */ + for (i = 0; i < samples; i++) + { + int offs; + + /* Amuse source: Cab suggests this method gives greater resolution */ + /* Sean Young 20010417: the formula is really: f = clock/(16*(f+1))*/ + c+=(long)((((float)info->mclock / (float)((f+1) * 16))*(float)(1<rate / 32)); + offs = (c >> 16) & 0x1f; + *mix++ += (w[offs] * v)>>3; + } + + /* update the counter for this voice */ + voice[j].counter = c; + } + } + + /* mix it down */ + mix = info->mixer_buffer; + for (i = 0; i < samples; i++) + *buffer++ = *buffer2++ = info->mixer_lookup[*mix++]; +} + +//static DEVICE_START( k051649 ) +void * device_start_k051649(int clock) +{ + //k051649_state *info = get_safe_token(device); + k051649_state *info; + UINT8 CurChn; + + info = (k051649_state *) calloc(1, sizeof(k051649_state)); + /* get stream channels */ + //info->rate = device->clock()/16; + info->rate = clock/16; + //info->stream = stream_create(device, 0, 1, info->rate, info, k051649_update); + //info->mclock = device->clock(); + info->mclock = clock; + + /* allocate a buffer to mix into - 1 second's worth should be more than enough */ + //info->mixer_buffer = auto_alloc_array(device->machine, short, 2 * info->rate); + info->mixer_buffer = (short*)malloc(sizeof(short) * info->rate); + + /* build the mixer table */ + //make_mixer_table(device->machine, info, 5); + make_mixer_table(info, 5); + + for (CurChn = 0; CurChn < 5; CurChn ++) + info->channel_list[CurChn].Muted = 0x00; + + return info; //return info->rate; +} + +void device_stop_k051649(void *chip) +{ + k051649_state *info = (k051649_state *) chip; + + free(info->mixer_buffer); + free(info->mixer_table); + free(info); +} + +//static DEVICE_RESET( k051649 ) +void device_reset_k051649(void *chip) +{ + k051649_state *info = (k051649_state *) chip; + k051649_sound_channel *voice = info->channel_list; + int i; + + /* reset all the voices */ + for (i = 0; i < 5; i++) { + voice[i].frequency = 0; + voice[i].volume = 0; + voice[i].counter = 0; + } +} + +/********************************************************************************/ + +//WRITE8_DEVICE_HANDLER( k051649_waveform_w ) +void k051649_waveform_w(void *chip, offs_t offset, UINT8 data) +{ + k051649_state *info = (k051649_state *) chip; + //stream_update(info->stream); + info->channel_list[offset>>5].waveform[offset&0x1f]=data; + /* SY 20001114: Channel 5 shares the waveform with channel 4 */ + if (offset >= 0x60) + info->channel_list[4].waveform[offset&0x1f]=data; +} + +//READ8_DEVICE_HANDLER ( k051649_waveform_r ) +UINT8 k051649_waveform_r(void *chip, offs_t offset) +{ + k051649_state *info = (k051649_state *) chip; + return info->channel_list[offset>>5].waveform[offset&0x1f]; +} + +/* SY 20001114: Channel 5 doesn't share the waveform with channel 4 on this chip */ +//WRITE8_DEVICE_HANDLER( k052539_waveform_w ) +void k052539_waveform_w(void *chip, offs_t offset, UINT8 data) +{ + k051649_state *info = (k051649_state *) chip; + //stream_update(info->stream); + info->channel_list[offset>>5].waveform[offset&0x1f]=data; +} + +//WRITE8_DEVICE_HANDLER( k051649_volume_w ) +void k051649_volume_w(void *chip, offs_t offset, UINT8 data) +{ + k051649_state *info = (k051649_state *) chip; + //stream_update(info->stream); + info->channel_list[offset&0x7].volume=data&0xf; +} + +//WRITE8_DEVICE_HANDLER( k051649_frequency_w ) +void k051649_frequency_w(void *chip, offs_t offset, UINT8 data) +{ + k051649_state *info = (k051649_state *) chip; + info->f[offset]=data; + + //stream_update(info->stream); + info->channel_list[offset>>1].frequency=(info->f[offset&0xe] + (info->f[offset|1]<<8))&0xfff; +} + +//WRITE8_DEVICE_HANDLER( k051649_keyonoff_w ) +void k051649_keyonoff_w(void *chip, offs_t offset, UINT8 data) +{ + k051649_state *info = (k051649_state *) chip; + //stream_update(info->stream); + info->channel_list[0].key=data&1; + info->channel_list[1].key=data&2; + info->channel_list[2].key=data&4; + info->channel_list[3].key=data&8; + info->channel_list[4].key=data&16; +} + +void k051649_w(void *chip, offs_t offset, UINT8 data) +{ + k051649_state *info = (k051649_state *) chip; + + switch(offset & 1) + { + case 0x00: + info->cur_reg = data; + break; + case 0x01: + switch(offset >> 1) + { + case 0x00: + k051649_waveform_w(info, info->cur_reg, data); + break; + case 0x01: + k051649_frequency_w(info, info->cur_reg, data); + break; + case 0x02: + k051649_volume_w(info, info->cur_reg, data); + break; + case 0x03: + k051649_keyonoff_w(info, info->cur_reg, data); + break; + case 0x04: + k052539_waveform_w(info, info->cur_reg, data); + break; + } + break; + } + + return; +} + + +void k051649_set_mute_mask(void *chip, UINT32 MuteMask) +{ + k051649_state *info = (k051649_state *) chip; + UINT8 CurChn; + + for (CurChn = 0; CurChn < 5; CurChn ++) + info->channel_list[CurChn].Muted = (MuteMask >> CurChn) & 0x01; +} diff --git a/Frameworks/GME/gme/k051649.h b/Frameworks/GME/gme/k051649.h new file mode 100644 index 000000000..584bb7ff7 --- /dev/null +++ b/Frameworks/GME/gme/k051649.h @@ -0,0 +1,45 @@ +#pragma once + +#include "mamedef.h" + +#ifdef __cplusplus +extern "C" { +#endif + +//#ifndef __K051649_H__ +//#define __K051649_H__ + +//#include "devlegcy.h" + +/*WRITE8_DEVICE_HANDLER( k051649_waveform_w ); +READ8_DEVICE_HANDLER( k051649_waveform_r ); +WRITE8_DEVICE_HANDLER( k051649_volume_w ); +WRITE8_DEVICE_HANDLER( k051649_frequency_w ); +WRITE8_DEVICE_HANDLER( k051649_keyonoff_w ); + +WRITE8_DEVICE_HANDLER( k052539_waveform_w ); + +DECLARE_LEGACY_SOUND_DEVICE(K051649, k051649);*/ + +void k051649_update(void *, stream_sample_t **outputs, int samples); +void * device_start_k051649(int clock); +void device_stop_k051649(void *); +void device_reset_k051649(void *); + +void k051649_waveform_w(void *, offs_t offset, UINT8 data); +UINT8 k051649_waveform_r(void *, offs_t offset); +void k051649_volume_w(void *, offs_t offset, UINT8 data); +void k051649_frequency_w(void *, offs_t offset, UINT8 data); +void k051649_keyonoff_w(void *, offs_t offset, UINT8 data); + +void k052539_waveform_w(void *, offs_t offset, UINT8 data); + +void k051649_w(void *, offs_t offset, UINT8 data); + +void k051649_set_mute_mask(void *, UINT32 MuteMask); + +//#endif /* __K051649_H__ */ + +#ifdef __cplusplus +}; +#endif diff --git a/Frameworks/GME/gme/k053260.c b/Frameworks/GME/gme/k053260.c new file mode 100644 index 000000000..c00087957 --- /dev/null +++ b/Frameworks/GME/gme/k053260.c @@ -0,0 +1,507 @@ +/********************************************************* + + Konami 053260 PCM Sound Chip + +*********************************************************/ + +#include "mamedef.h" +//#include "emu.h" +#ifdef _DEBUG +#include +#endif +#include +#include +#include "k053260.h" + +#define NULL ((void *)0) + +/* 2004-02-28: Fixed PPCM decoding. Games sound much better now.*/ + +#define LOG 0 + +#define BASE_SHIFT 16 + +typedef struct _k053260_channel k053260_channel; +struct _k053260_channel +{ + UINT32 rate; + UINT32 size; + UINT32 start; + UINT32 bank; + UINT32 volume; + int play; + UINT32 pan; + UINT32 pos; + int loop; + int ppcm; /* packed PCM ( 4 bit signed ) */ + int ppcm_data; + UINT8 Muted; +}; + +typedef struct _k053260_state k053260_state; +struct _k053260_state +{ + //sound_stream * channel; + int mode; + int regs[0x30]; + UINT8 *rom; + //int rom_size; + UINT32 rom_size; + UINT32 *delta_table; + k053260_channel channels[4]; + //const k053260_interface *intf; + //device_t *device; +}; + +/*INLINE k053260_state *get_safe_token(device_t *device) +{ + assert(device != NULL); + assert(device->type() == K053260); + return (k053260_state *)downcast(device)->token(); +}*/ + + +static void InitDeltaTable( k053260_state *ic, int rate, int clock ) +{ + int i; + double base = ( double )rate; + double max = (double)(clock); /* Hz */ + UINT32 val; + + for( i = 0; i < 0x1000; i++ ) { + double v = ( double )( 0x1000 - i ); + double target = (max) / v; + double fixed = ( double )( 1 << BASE_SHIFT ); + + if ( target && base ) { + target = fixed / ( base / target ); + val = ( UINT32 )target; + if ( val == 0 ) + val = 1; + } else + val = 1; + + ic->delta_table[i] = val; + } +} + +//static DEVICE_RESET( k053260 ) +void device_reset_k053260(void *chip) +{ + //k053260_state *ic = get_safe_token(device); + k053260_state *ic = (k053260_state *) chip; + int i; + + for( i = 0; i < 4; i++ ) { + ic->channels[i].rate = 0; + ic->channels[i].size = 0; + ic->channels[i].start = 0; + ic->channels[i].bank = 0; + ic->channels[i].volume = 0; + ic->channels[i].play = 0; + ic->channels[i].pan = 0; + ic->channels[i].pos = 0; + ic->channels[i].loop = 0; + ic->channels[i].ppcm = 0; + ic->channels[i].ppcm_data = 0; + } +} + +INLINE int limit( int val, int max, int min ) +{ + if ( val > max ) + val = max; + else if ( val < min ) + val = min; + + return val; +} + +//#define MAXOUT 0x7fff +#define MAXOUT +0x8000 +#define MINOUT -0x8000 + +//static STREAM_UPDATE( k053260_update ) +void k053260_update(void *chip, stream_sample_t **outputs, int samples) +{ + static const long dpcmcnv[] = { 0,1,2,4,8,16,32,64, -128, -64, -32, -16, -8, -4, -2, -1}; + + int i, j, lvol[4], rvol[4], play[4], loop[4], ppcm_data[4], ppcm[4]; + unsigned char *rom[4]; + UINT32 delta[4], end[4], pos[4]; + int dataL, dataR; + signed char d; + k053260_state *ic = (k053260_state *) chip; + + /* precache some values */ + for ( i = 0; i < 4; i++ ) { + rom[i]= &ic->rom[ic->channels[i].start + ( ic->channels[i].bank << 16 )]; + delta[i] = ic->delta_table[ic->channels[i].rate]; + lvol[i] = ic->channels[i].volume * ic->channels[i].pan; + rvol[i] = ic->channels[i].volume * ( 8 - ic->channels[i].pan ); + end[i] = ic->channels[i].size; + pos[i] = ic->channels[i].pos; + play[i] = ic->channels[i].play; + loop[i] = ic->channels[i].loop; + ppcm[i] = ic->channels[i].ppcm; + ppcm_data[i] = ic->channels[i].ppcm_data; + if ( ppcm[i] ) + delta[i] /= 2; + } + + for ( j = 0; j < samples; j++ ) { + + dataL = dataR = 0; + + for ( i = 0; i < 4; i++ ) { + /* see if the voice is on */ + if ( play[i] ) { + /* see if we're done */ + if ( ( pos[i] >> BASE_SHIFT ) >= end[i] ) { + + ppcm_data[i] = 0; + if ( loop[i] ) + pos[i] = 0; + else { + play[i] = 0; + continue; + } + } + + if ( ppcm[i] ) { /* Packed PCM */ + /* we only update the signal if we're starting or a real sound sample has gone by */ + /* this is all due to the dynamic sample rate convertion */ + if ( pos[i] == 0 || ( ( pos[i] ^ ( pos[i] - delta[i] ) ) & 0x8000 ) == 0x8000 ) + + { + int newdata; + if ( pos[i] & 0x8000 ){ + + newdata = ((rom[i][pos[i] >> BASE_SHIFT]) >> 4) & 0x0f; /*high nybble*/ + } + else{ + newdata = ( ( rom[i][pos[i] >> BASE_SHIFT] ) ) & 0x0f; /*low nybble*/ + } + + ppcm_data[i] = (( ( ppcm_data[i] * 62 ) >> 6 ) + dpcmcnv[newdata]); + + if ( ppcm_data[i] > 127 ) + ppcm_data[i] = 127; + else + if ( ppcm_data[i] < -128 ) + ppcm_data[i] = -128; + } + + + + d = ppcm_data[i]; + + pos[i] += delta[i]; + } else { /* PCM */ + d = rom[i][pos[i] >> BASE_SHIFT]; + + pos[i] += delta[i]; + } + + if ( ic->mode & 2 ) { + dataL += ( d * lvol[i] ) >> 2; + dataR += ( d * rvol[i] ) >> 2; + } + } + } + + outputs[1][j] = limit( dataL, MAXOUT, MINOUT ); + outputs[0][j] = limit( dataR, MAXOUT, MINOUT ); + } + + /* update the regs now */ + for ( i = 0; i < 4; i++ ) { + ic->channels[i].pos = pos[i]; + ic->channels[i].play = play[i]; + ic->channels[i].ppcm_data = ppcm_data[i]; + } +} + +//static DEVICE_START( k053260 ) +void * device_start_k053260(int clock) +{ + //static const k053260_interface defintrf = { 0 }; + //k053260_state *ic = get_safe_token(device); + k053260_state *ic; + //int rate = device->clock() / 32; + int rate = clock / 32; + int i; + + ic = (k053260_state *) calloc(1, sizeof(k053260_state)); + + /* Initialize our chip structure */ + //ic->device = device; + //ic->intf = (device->static_config() != NULL) ? (const k053260_interface *)device->static_config() : &defintrf; + + ic->mode = 0; + + //const memory_region *region = (ic->intf->rgnoverride != NULL) ? device->machine().region(ic->intf->rgnoverride) : device->region(); + + //ic->rom = *region; + //ic->rom_size = region->bytes(); + ic->rom = NULL; + ic->rom_size = 0x00; + + // has to be done by the player after calling device_start + //DEVICE_RESET_CALL(k053260); + + for ( i = 0; i < 0x30; i++ ) + ic->regs[i] = 0; + + //ic->delta_table = auto_alloc_array( device->machine(), UINT32, 0x1000 ); + ic->delta_table = (UINT32*)malloc(0x1000 * sizeof(UINT32)); + + //ic->channel = device->machine().sound().stream_alloc( *device, 0, 2, rate, ic, k053260_update ); + + //InitDeltaTable( ic, rate, device->clock() ); + InitDeltaTable( ic, rate, clock ); + + /* register with the save state system */ + /*device->save_item(NAME(ic->mode)); + device->save_item(NAME(ic->regs)); + + for ( i = 0; i < 4; i++ ) + { + device->save_item(NAME(ic->channels[i].rate), i); + device->save_item(NAME(ic->channels[i].size), i); + device->save_item(NAME(ic->channels[i].start), i); + device->save_item(NAME(ic->channels[i].bank), i); + device->save_item(NAME(ic->channels[i].volume), i); + device->save_item(NAME(ic->channels[i].play), i); + device->save_item(NAME(ic->channels[i].pan), i); + device->save_item(NAME(ic->channels[i].pos), i); + device->save_item(NAME(ic->channels[i].loop), i); + device->save_item(NAME(ic->channels[i].ppcm), i); + device->save_item(NAME(ic->channels[i].ppcm_data), i); + }*/ + + /* setup SH1 timer if necessary */ + //if ( ic->intf->irq ) + // device->machine().scheduler().timer_pulse( attotime::from_hz(device->clock()) * 32, ic->intf->irq, "ic->intf->irq" ); + + return ic; //return rate; +} + +void device_stop_k053260(void *chip) +{ + k053260_state *ic = (k053260_state *) chip; + + free(ic->delta_table); + free(ic->rom); ic->rom = NULL; + free(ic); +} + +INLINE void check_bounds( k053260_state *ic, int channel ) +{ + + UINT32 channel_start = ( ic->channels[channel].bank << 16 ) + ic->channels[channel].start; + UINT32 channel_end = channel_start + ic->channels[channel].size - 1; + + if ( channel_start > ic->rom_size ) { + logerror("K53260: Attempting to start playing past the end of the ROM ( start = %06x, end = %06x ).\n", channel_start, channel_end ); + + ic->channels[channel].play = 0; + + return; + } + + if ( channel_end > ic->rom_size ) { + logerror("K53260: Attempting to play past the end of the ROM ( start = %06x, end = %06x ).\n", channel_start, channel_end ); + + ic->channels[channel].size = ic->rom_size - channel_start; + } + if (LOG) logerror("K053260: Sample Start = %06x, Sample End = %06x, Sample rate = %04x, PPCM = %s\n", channel_start, channel_end, ic->channels[channel].rate, ic->channels[channel].ppcm ? "yes" : "no" ); +} + +//WRITE8_DEVICE_HANDLER( k053260_w ) +void k053260_w(void *chip, offs_t offset, UINT8 data) +{ + int i, t; + int r = offset; + int v = data; + + k053260_state *ic = (k053260_state *) chip; + + if ( r > 0x2f ) { + logerror("K053260: Writing past registers\n" ); + return; + } + + //ic->channel->update(); + + /* before we update the regs, we need to check for a latched reg */ + if ( r == 0x28 ) { + t = ic->regs[r] ^ v; + + for ( i = 0; i < 4; i++ ) { + if ( t & ( 1 << i ) ) { + if ( v & ( 1 << i ) ) { + ic->channels[i].play = 1; + ic->channels[i].pos = 0; + ic->channels[i].ppcm_data = 0; + check_bounds( ic, i ); + } else + ic->channels[i].play = 0; + } + } + + ic->regs[r] = v; + return; + } + + /* update regs */ + ic->regs[r] = v; + + /* communication registers */ + if ( r < 8 ) + return; + + /* channel setup */ + if ( r < 0x28 ) { + int channel = ( r - 8 ) / 8; + + switch ( ( r - 8 ) & 0x07 ) { + case 0: /* sample rate low */ + ic->channels[channel].rate &= 0x0f00; + ic->channels[channel].rate |= v; + break; + + case 1: /* sample rate high */ + ic->channels[channel].rate &= 0x00ff; + ic->channels[channel].rate |= ( v & 0x0f ) << 8; + break; + + case 2: /* size low */ + ic->channels[channel].size &= 0xff00; + ic->channels[channel].size |= v; + break; + + case 3: /* size high */ + ic->channels[channel].size &= 0x00ff; + ic->channels[channel].size |= v << 8; + break; + + case 4: /* start low */ + ic->channels[channel].start &= 0xff00; + ic->channels[channel].start |= v; + break; + + case 5: /* start high */ + ic->channels[channel].start &= 0x00ff; + ic->channels[channel].start |= v << 8; + break; + + case 6: /* bank */ + ic->channels[channel].bank = v & 0xff; + break; + + case 7: /* volume is 7 bits. Convert to 8 bits now. */ + ic->channels[channel].volume = ( ( v & 0x7f ) << 1 ) | ( v & 1 ); + break; + } + + return; + } + + switch( r ) { + case 0x2a: /* loop, ppcm */ + for ( i = 0; i < 4; i++ ) + ic->channels[i].loop = ( v & ( 1 << i ) ) != 0; + + for ( i = 4; i < 8; i++ ) + ic->channels[i-4].ppcm = ( v & ( 1 << i ) ) != 0; + break; + + case 0x2c: /* pan */ + ic->channels[0].pan = v & 7; + ic->channels[1].pan = ( v >> 3 ) & 7; + break; + + case 0x2d: /* more pan */ + ic->channels[2].pan = v & 7; + ic->channels[3].pan = ( v >> 3 ) & 7; + break; + + case 0x2f: /* control */ + ic->mode = v & 7; + /* bit 0 = read ROM */ + /* bit 1 = enable sound output */ + /* bit 2 = unknown */ + break; + } +} + +//READ8_DEVICE_HANDLER( k053260_r ) +UINT8 k053260_r(void *chip, offs_t offset) +{ + k053260_state *ic = (k053260_state *) chip; + + switch ( offset ) { + case 0x29: /* channel status */ + { + int i, status = 0; + + for ( i = 0; i < 4; i++ ) + status |= ic->channels[i].play << i; + + return status; + } + break; + + case 0x2e: /* read ROM */ + if ( ic->mode & 1 ) + { + UINT32 offs = ic->channels[0].start + ( ic->channels[0].pos >> BASE_SHIFT ) + ( ic->channels[0].bank << 16 ); + + ic->channels[0].pos += ( 1 << 16 ); + + if ( offs > ic->rom_size ) { + //logerror("%s: K53260: Attempting to read past ROM size in ROM Read Mode (offs = %06x, size = %06x).\n", device->machine().describe_context(),offs,ic->rom_size ); + logerror("K53260: Attempting to read past ROM size in ROM Read Mode (offs = %06x, size = %06x).\n", offs,ic->rom_size ); + + return 0; + } + + return ic->rom[offs]; + } + break; + } + + return ic->regs[offset]; +} + +void k053260_write_rom(void *chip, offs_t ROMSize, offs_t DataStart, offs_t DataLength, + const UINT8* ROMData) +{ + k053260_state *info = (k053260_state *) chip; + + if (info->rom_size != ROMSize) + { + info->rom = (UINT8*)realloc(info->rom, ROMSize); + info->rom_size = ROMSize; + memset(info->rom, 0xFF, ROMSize); + } + if (DataStart > ROMSize) + return; + if (DataStart + DataLength > ROMSize) + DataLength = ROMSize - DataStart; + + memcpy(info->rom + DataStart, ROMData, DataLength); + + return; +} + + +void k053260_set_mute_mask(void *chip, UINT32 MuteMask) +{ + k053260_state *info = (k053260_state *) chip; + UINT8 CurChn; + + for (CurChn = 0; CurChn < 4; CurChn ++) + info->channels[CurChn].Muted = (MuteMask >> CurChn) & 0x01; +} diff --git a/Frameworks/GME/gme/k053260.h b/Frameworks/GME/gme/k053260.h new file mode 100644 index 000000000..25eb198ad --- /dev/null +++ b/Frameworks/GME/gme/k053260.h @@ -0,0 +1,42 @@ +/********************************************************* + + Konami 053260 PCM/ADPCM Sound Chip + +*********************************************************/ + +#pragma once + +#include "mamedef.h" + +#ifdef __cplusplus +extern "C" { +#endif + +//#include "devlegcy.h" + +/*typedef struct _k053260_interface k053260_interface; +struct _k053260_interface { + const char *rgnoverride; + timer_expired_func irq; // called on SH1 complete cycle ( clock / 32 ) // +};*/ + + +void k053260_update(void *, stream_sample_t **outputs, int samples); +void * device_start_k053260(int clock); +void device_stop_k053260(void *); +void device_reset_k053260(void *); + +//WRITE8_DEVICE_HANDLER( k053260_w ); +//READ8_DEVICE_HANDLER( k053260_r ); +void k053260_w(void *, offs_t offset, UINT8 data); +UINT8 k053260_r(void *, offs_t offset); + +void k053260_write_rom(void *, offs_t ROMSize, offs_t DataStart, offs_t DataLength, + const UINT8* ROMData); +void k053260_set_mute_mask(void *, UINT32 MuteMask); + +//DECLARE_LEGACY_SOUND_DEVICE(K053260, k053260); + +#ifdef __cplusplus +}; +#endif diff --git a/Frameworks/GME/gme/k054539.c b/Frameworks/GME/gme/k054539.c new file mode 100644 index 000000000..373c90bf0 --- /dev/null +++ b/Frameworks/GME/gme/k054539.c @@ -0,0 +1,787 @@ +/********************************************************* + + Konami 054539 PCM Sound Chip + + A lot of information comes from Amuse. + Big thanks to them. + + + +CHANNEL_DEBUG enables the following keys: + + PAD. : toggle debug mode + PAD0 : toggle chip (0 / 1) + PAD4,6 : select channel (0 - 7) + PAD8,2 : adjust gain (00=0.0 10=1.0, 20=2.0, etc.) + PAD5 : reset gain factor to 1.0 + +*********************************************************/ + +//#include "emu.h" +#include +#include +#define _USE_MATH_DEFINES +#include +#include "mamedef.h" +#ifdef _DEBUG +#include +#endif +#include "k054539.h" + +#define NULL ((void *)0) + +#define CHANNEL_DEBUG 0 +#define VERBOSE 0 +#define LOG(x) do { if (VERBOSE) logerror x; } while (0) + +/* Registers: + 00..ff: 20 bytes/channel, 8 channels + 00..02: pitch (lsb, mid, msb) + 03: volume (0=max, 0x40=-36dB) + 04: reverb volume (idem) + 05: pan (1-f right, 10 middle, 11-1f left) + 06..07: reverb delay (0=max, current computation non-trusted) + 08..0a: loop (lsb, mid, msb) + 0c..0e: start (lsb, mid, msb) (and current position ?) + + 100.1ff: effects? + 13f: pan of the analog input (1-1f) + + 200..20f: 2 bytes/channel, 8 channels + 00: type (b2-3), reverse (b5) + 01: loop (b0) + + 214: keyon (b0-7 = channel 0-7) + 215: keyoff "" + 22c: channel active? "" + 22d: data read/write port + 22e: rom/ram select (00..7f == rom banks, 80 = ram) + 22f: enable pcm (b0), disable register ram updating (b7) + + The chip has a 0x4000 bytes reverb buffer (the ram from 0x22e). + The reverb delay is actually an offset in this buffer. This driver + uses some tricks (doubling the buffer size so that the longest + reverbs don't fold over the sound to output, and adding a space at + the end to fold back overflows in) to be able to do frame-based + rendering instead of sample-based. +*/ + +typedef struct _k054539_channel k054539_channel; +struct _k054539_channel { + UINT32 pos; + UINT32 pfrac; + INT32 val; + INT32 pval; +}; + +typedef struct _k054539_state k054539_state; +struct _k054539_state { + //const k054539_interface *intf; + //device_t *device; + double voltab[256]; + double pantab[0xf]; + + double k054539_gain[8]; + UINT8 k054539_posreg_latch[8][3]; + int k054539_flags; + + unsigned char regs[0x230]; + unsigned char *ram; + int reverb_pos; + + INT32 cur_ptr; + int cur_limit; + unsigned char *cur_zone; + unsigned char *rom; + UINT32 rom_size; + UINT32 rom_mask; + //sound_stream * stream; + + k054539_channel channels[8]; + UINT8 Muted[8]; + + int clock; +}; + +/*INLINE k054539_state *get_safe_token(device_t *device) +{ + assert(device != NULL); + assert(device->type() == K054539); + return (k054539_state *)downcast(device)->token(); +}*/ + +//* + +//void k054539_init_flags(device_t *device, int flags) +void k054539_init_flags(void *chip, int flags) +{ + //k054539_state *info = get_safe_token(device); + k054539_state *info = (k054539_state *) chip; + info->k054539_flags = flags; +} + +//void k054539_set_gain(device_t *device, int channel, double gain) +void k054539_set_gain(void *chip, int channel, double gain) +{ + //k054539_state *info = get_safe_token(device); + k054539_state *info = (k054539_state *) chip; + if (gain >= 0) info->k054539_gain[channel] = gain; +} +//* + +static int k054539_regupdate(k054539_state *info) +{ + return !(info->regs[0x22f] & 0x80); +} + +static void k054539_keyon(k054539_state *info, int channel) +{ + if(k054539_regupdate(info)) + info->regs[0x22c] |= 1 << channel; +} + +static void k054539_keyoff(k054539_state *info, int channel) +{ + if(k054539_regupdate(info)) + info->regs[0x22c] &= ~(1 << channel); +} + +//static STREAM_UPDATE( k054539_update ) +void k054539_update(void *chip, stream_sample_t **outputs, int samples) +{ + //k054539_state *info = (k054539_state *)param; + k054539_state *info = (k054539_state *) chip; +#define VOL_CAP 1.80 + + static const INT16 dpcm[16] = { + 0<<8, 1<<8, 4<<8, 9<<8, 16<<8, 25<<8, 36<<8, 49<<8, + -64<<8, -49<<8, -36<<8, -25<<8, -16<<8, -9<<8, -4<<8, -1<<8 + }; + + int ch, reverb_pos; + short *rbase; + unsigned char *rom; + UINT32 rom_mask; + + unsigned char *base1, *base2; + k054539_channel *chan; + stream_sample_t *bufl, *bufr; + UINT32 cur_pos, cur_pfrac; + int cur_val, cur_pval; + int delta, rdelta, fdelta, pdelta; + int vol, bval, pan, i; + + double gain, lvol, rvol, rbvol; + + reverb_pos = info->reverb_pos; + rbase = (short *)(info->ram); + + memset(outputs[0], 0, samples*sizeof(*outputs[0])); + memset(outputs[1], 0, samples*sizeof(*outputs[1])); + + rom = info->rom; + rom_mask = info->rom_mask; + + if(!(info->regs[0x22f] & 1)) return; + + info->reverb_pos = (reverb_pos + samples) & 0x3fff; + + + for(ch=0; ch<8; ch++) + if ((info->regs[0x22c] & (1<Muted[ch]) + { + base1 = info->regs + 0x20*ch; + base2 = info->regs + 0x200 + 0x2*ch; + chan = info->channels + ch; +//* + delta = base1[0x00] | (base1[0x01] << 8) | (base1[0x02] << 16); + + vol = base1[0x03]; + + bval = vol + base1[0x04]; + if (bval > 255) bval = 255; + + pan = base1[0x05]; +// DJ Main: 81-87 right, 88 middle, 89-8f left +if (pan >= 0x81 && pan <= 0x8f) +pan -= 0x81; +else + if (pan >= 0x11 && pan <= 0x1f) pan -= 0x11; else pan = 0x18 - 0x11; + + gain = info->k054539_gain[ch]; + + lvol = info->voltab[vol] * info->pantab[pan] * gain; + if (lvol > VOL_CAP) lvol = VOL_CAP; + + rvol = info->voltab[vol] * info->pantab[0xe - pan] * gain; + if (rvol > VOL_CAP) rvol = VOL_CAP; + + rbvol= info->voltab[bval] * gain / 2; + if (rbvol > VOL_CAP) rbvol = VOL_CAP; + +/* + INT x FLOAT could be interpreted as INT x (int)FLOAT instead of (float)INT x FLOAT on some compilers + causing precision loss. (rdelta - 0x2000) wraps around on zero reverb and the scale factor should + actually be 1/freq_ratio because the target is an offset to the reverb buffer not sample source. +*/ + rdelta = (base1[6] | (base1[7] << 8)) >> 3; +// rdelta = (reverb_pos + (int)((rdelta - 0x2000) * info->freq_ratio)) & 0x3fff; + rdelta = (int)(rdelta + reverb_pos) & 0x3fff; + + cur_pos = (base1[0x0c] | (base1[0x0d] << 8) | (base1[0x0e] << 16)) & rom_mask; + + bufl = outputs[0]; + bufr = outputs[1]; +//* + + if(base2[0] & 0x20) { + delta = -delta; + fdelta = +0x10000; + pdelta = -1; + } else { + fdelta = -0x10000; + pdelta = +1; + } + + if(cur_pos != chan->pos) { + chan->pos = cur_pos; + cur_pfrac = 0; + cur_val = 0; + cur_pval = 0; + } else { + cur_pfrac = chan->pfrac; + cur_val = chan->val; + cur_pval = chan->pval; + } + +#define UPDATE_CHANNELS \ + do { \ + *bufl++ += (INT16)(cur_val*lvol); \ + *bufr++ += (INT16)(cur_val*rvol); \ + rbase[rdelta++] += (INT16)(cur_val*rbvol); \ + rdelta &= 0x3fff; \ + } while(0) + + switch(base2[0] & 0xc) { + case 0x0: { // 8bit pcm + for(i=0; i>1]; + if(cur_val == 0x88) { + if(base2[1] & 1) { + cur_pos = ((base1[0x08] | (base1[0x09] << 8) | (base1[0x0a] << 16)) & rom_mask) << 1; + cur_val = rom[cur_pos>>1]; + if(cur_val != 0x88) + goto next_iter; + } + k054539_keyoff(info, ch); + goto end_channel_8; + } + next_iter: + if(cur_pos & 1) + cur_val >>= 4; + else + cur_val &= 15; + cur_val = cur_pval + dpcm[cur_val]; + if(cur_val < -32768) + cur_val = -32768; + else if(cur_val > 32767) + cur_val = 32767; + } + + UPDATE_CHANNELS; + } + end_channel_8: + cur_pfrac >>= 1; + if(cur_pos & 1) + cur_pfrac |= 0x8000; + cur_pos >>= 1; + break; + } + default: + LOG(("Unknown sample type %x for channel %d\n", base2[0] & 0xc, ch)); + break; + } + chan->pos = cur_pos; + chan->pfrac = cur_pfrac; + chan->pval = cur_pval; + chan->val = cur_val; + if(k054539_regupdate(info)) { + base1[0x0c] = cur_pos & 0xff; + base1[0x0d] = cur_pos>> 8 & 0xff; + base1[0x0e] = cur_pos>>16 & 0xff; + } + } + + //* drivers should be given the option to disable reverb when things go terribly wrong + if(!(info->k054539_flags & K054539_DISABLE_REVERB)) + { + for(i=0; i 0x4000) { + i = 0x4000 - reverb_pos; + memset(rbase + reverb_pos, 0, i*2); + memset(rbase, 0, (samples-i)*2); + } else + memset(rbase + reverb_pos, 0, samples*2); + + #if CHANNEL_DEBUG + { + static const char gc_msg[32] = "chip : "; + static int gc_active=0, gc_chip=0, gc_pos[2]={0,0}; + double *gc_fptr; + char *gc_cptr; + double gc_f0; + int gc_i, gc_j, gc_k, gc_l; + + if (device->machine().input().code_pressed_once(KEYCODE_DEL_PAD)) + { + gc_active ^= 1; + if (!gc_active) popmessage(NULL); + } + + if (gc_active) + { + if (device->machine().input().code_pressed_once(KEYCODE_0_PAD)) gc_chip ^= 1; + + gc_i = gc_pos[gc_chip]; + gc_j = 0; + if (device->machine().input().code_pressed_once(KEYCODE_4_PAD)) { gc_i--; gc_j = 1; } + if (device->machine().input().code_pressed_once(KEYCODE_6_PAD)) { gc_i++; gc_j = 1; } + if (gc_j) { gc_i &= 7; gc_pos[gc_chip] = gc_i; } + + if (device->machine().input().code_pressed_once(KEYCODE_5_PAD)) + info->k054539_gain[gc_i] = 1.0; + else + { + gc_fptr = &info->k054539_gain[gc_i]; + gc_f0 = *gc_fptr; + gc_j = 0; + if (device->machine().input().code_pressed_once(KEYCODE_2_PAD)) { gc_f0 -= 0.1; gc_j = 1; } + if (device->machine().input().code_pressed_once(KEYCODE_8_PAD)) { gc_f0 += 0.1; gc_j = 1; } + if (gc_j) { if (gc_f0 < 0) gc_f0 = 0; *gc_fptr = gc_f0; } + } + + gc_fptr = &info->k054539_gain[0] + 8; + gc_cptr = gc_msg + 7; + for (gc_j=-8; gc_j; gc_j++) + { + gc_k = (int)(gc_fptr[gc_j] * 10); + gc_l = gc_k / 10; + gc_k = gc_k % 10; + gc_cptr[0] = gc_l + '0'; + gc_cptr[1] = gc_k + '0'; + gc_cptr += 3; + } + gc_i = (gc_i + gc_i*2 + 6); + gc_msg[4] = gc_chip + '0'; + gc_msg[gc_i ] = '['; + gc_msg[gc_i+3] = ']'; + popmessage("%s", gc_msg); + gc_msg[gc_i+3] = gc_msg[gc_i] = ' '; + } + } + #endif +} + + +/*static TIMER_CALLBACK( k054539_irq ) +{ + k054539_state *info = (k054539_state *)ptr; + if(info->regs[0x22f] & 0x20) + info->intf->irq(info->device); +}*/ + +//static void k054539_init_chip(device_t *device, k054539_state *info) +static int k054539_init_chip(k054539_state *info, int clock) +{ + //int i; + + info->clock = clock; + // most of these are done in device_reset +// memset(info->regs, 0, sizeof(info->regs)); +// memset(info->k054539_posreg_latch, 0, sizeof(info->k054539_posreg_latch)); //* + info->k054539_flags |= K054539_UPDATE_AT_KEYON; //* make it default until proven otherwise + + // Real size of 0x4000, the addon is to simplify the reverb buffer computations + //info->ram = auto_alloc_array(device->machine(), unsigned char, 0x4000*2+device->clock()/50*2); + info->ram = (unsigned char*)malloc(0x4000 * 2 + info->clock / 50 * 2); +// info->reverb_pos = 0; +// info->cur_ptr = 0; + //memset(info->ram, 0, 0x4000*2+device->clock()/50*2); +// memset(info->ram, 0, 0x4000 * 2 + info->clock / 50 * 2); + + /*const memory_region *region = (info->intf->rgnoverride != NULL) ? device->machine().region(info->intf->rgnoverride) : device->region(); + info->rom = *region; + info->rom_size = region->bytes(); + info->rom_mask = 0xffffffffU; + for(i=0; i<32; i++) + if((1U<= info->rom_size) { + info->rom_mask = (1U<rom = NULL; + info->rom_size = 0; + info->rom_mask = 0x00; + + //if(info->intf->irq) + // One or more of the registers must be the timer period + // And anyway, this particular frequency is probably wrong + // 480 hz is TRUSTED by gokuparo disco stage - the looping sample doesn't line up otherwise + // device->machine().scheduler().timer_pulse(attotime::from_hz(480), FUNC(k054539_irq), 0, info); + + //info->stream = device->machine().sound().stream_alloc(*device, 0, 2, device->clock(), info, k054539_update); + + //device->save_item(NAME(info->regs)); + //device->save_pointer(NAME(info->ram), 0x4000); + //device->save_item(NAME(info->cur_ptr)); + + return info->clock; +} + +//WRITE8_DEVICE_HANDLER( k054539_w ) +void k054539_w(void *chip, offs_t offset, UINT8 data) +{ + //k054539_state *info = get_safe_token(device); + k054539_state *info = (k054539_state *) chip; + +#if 0 + int voice, reg; + + /* The K054539 has behavior like many other wavetable chips including + the Ensoniq 550x and Gravis GF-1: if a voice is active, writing + to it's current position is silently ignored. + + Dadandaan depends on this or the vocals go wrong. + */ + if (offset < 8*0x20) + { + voice = offset / 0x20; + reg = offset & ~0x20; + + if(info->regs[0x22c] & (1<= 0xc && reg <= 0xe) + { + return; + } + } + } +#endif + + int latch, offs, ch, pan; + UINT8 *regbase, *regptr, *posptr; + + regbase = info->regs; + latch = (info->k054539_flags & K054539_UPDATE_AT_KEYON) && (regbase[0x22f] & 1); + + if (latch && offset < 0x100) + { + offs = (offset & 0x1f) - 0xc; + ch = offset >> 5; + + if (offs >= 0 && offs <= 2) + { + // latch writes to the position index registers + info->k054539_posreg_latch[ch][offs] = data; + return; + } + } + + else switch(offset) + { + case 0x13f: + pan = data >= 0x11 && data <= 0x1f ? data - 0x11 : 0x18 - 0x11; + //if(info->intf->apan) + // info->intf->apan(info->device, info->pantab[pan], info->pantab[0xe - pan]); + break; + + case 0x214: + if (latch) + { + for(ch=0; ch<8; ch++) + { + if(data & (1<k054539_posreg_latch[ch][0]; + regptr = regbase + (ch<<5) + 0xc; + + // update the chip at key-on + regptr[0] = posptr[0]; + regptr[1] = posptr[1]; + regptr[2] = posptr[2]; + + k054539_keyon(info, ch); + } + } + } + else + { + for(ch=0; ch<8; ch++) + if(data & (1<cur_zone[info->cur_ptr] = data; + info->cur_ptr++; + if(info->cur_ptr == info->cur_limit) + info->cur_ptr = 0; + break; + + case 0x22e: + info->cur_zone = + data == 0x80 ? info->ram : + info->rom + 0x20000*data; + info->cur_limit = data == 0x80 ? 0x4000 : 0x20000; + info->cur_ptr = 0; + break; + + default: +#if 0 + if(regbase[offset] != data) { + if((offset & 0xff00) == 0) { + chanoff = offset & 0x1f; + if(chanoff < 4 || chanoff == 5 || + (chanoff >=8 && chanoff <= 0xa) || + (chanoff >= 0xc && chanoff <= 0xe)) + break; + } + if(1 || ((offset >= 0x200) && (offset <= 0x210))) + break; + logerror("K054539 %03x = %02x\n", offset, data); + } +#endif + break; + } + + regbase[offset] = data; +} + +static void reset_zones(k054539_state *info) +{ + int data = info->regs[0x22e]; + info->cur_zone = + data == 0x80 ? info->ram : + info->rom + 0x20000*data; + info->cur_limit = data == 0x80 ? 0x4000 : 0x20000; +} + +//READ8_DEVICE_HANDLER( k054539_r ) +UINT8 k054539_r(void *chip, offs_t offset) +{ + //k054539_state *info = get_safe_token(device); + k054539_state *info = (k054539_state *) chip; + switch(offset) { + case 0x22d: + if(info->regs[0x22f] & 0x10) { + UINT8 res = info->cur_zone[info->cur_ptr]; + info->cur_ptr++; + if(info->cur_ptr == info->cur_limit) + info->cur_ptr = 0; + return res; + } else + return 0; + case 0x22c: + break; + default: + LOG(("K054539 read %03x\n", offset)); + break; + } + return info->regs[offset]; +} + +//static DEVICE_START( k054539 ) +void * device_start_k054539(int clock) +{ + //static const k054539_interface defintrf = { 0 }; + int i; + //k054539_state *info = get_safe_token(device); + k054539_state *info; + + info = (k054539_state *) calloc(1, sizeof(k054539_state)); + //info->device = device; + + for (i = 0; i < 8; i++) + info->k054539_gain[i] = 1.0; + info->k054539_flags = K054539_RESET_FLAGS; + + //info->intf = (device->static_config() != NULL) ? (const k054539_interface *)device->static_config() : &defintrf; + + /* + I've tried various equations on volume control but none worked consistently. + The upper four channels in most MW/GX games simply need a significant boost + to sound right. For example, the bass and smash sound volumes in Violent Storm + have roughly the same values and the voices in Tokimeki Puzzledama are given + values smaller than those of the hihats. Needless to say the two K054539 chips + in Mystic Warriors are completely out of balance. Rather than forcing a + "one size fits all" function to the voltab the current invert exponential + appraoch seems most appropriate. + */ + // Factor the 1/4 for the number of channels in the volume (1/8 is too harsh, 1/2 gives clipping) + // vol=0 -> no attenuation, vol=0x40 -> -36dB + for(i=0; i<256; i++) + info->voltab[i] = pow(10.0, (-36.0 * (double)i / (double)0x40) / 20.0) / 4.0; + + // Pan table for the left channel + // Right channel is identical with inverted index + // Formula is such that pan[i]**2+pan[0xe-i]**2 = 1 (constant output power) + // and pan[0xe] = 1 (full panning) + for(i=0; i<0xf; i++) + info->pantab[i] = sqrt((double)i) / sqrt((double)0xe); + + //k054539_init_chip(device, info); + + //device->machine().save().register_postload(save_prepost_delegate(FUNC(reset_zones), info)); + + for (i = 0; i < 8; i ++) + info->Muted[i] = 0x00; + + k054539_init_chip(info, clock); + + return info; +} + +void device_stop_k054539(void *chip) +{ + k054539_state *info = (k054539_state *) chip; + + free(info->rom); info->rom = NULL; + free(info->ram); + free(info); +} + +void device_reset_k054539(void *chip) +{ + k054539_state *info = (k054539_state *) chip; + + memset(info->regs, 0, sizeof(info->regs)); + memset(info->k054539_posreg_latch, 0, sizeof(info->k054539_posreg_latch)); + //info->k054539_flags |= K054539_UPDATE_AT_KEYON; + + info->reverb_pos = 0; + info->cur_ptr = 0; + memset(info->ram, 0, 0x4000 * 2 + info->clock / 50 * 2); +} + +void k054539_write_rom(void *chip, offs_t ROMSize, offs_t DataStart, offs_t DataLength, + const UINT8* ROMData) +{ + k054539_state *info = (k054539_state *) chip; + + if (info->rom_size != ROMSize) + { + UINT8 i; + + info->rom = (UINT8*)realloc(info->rom, ROMSize); + info->rom_size = ROMSize; + memset(info->rom, 0xFF, ROMSize); + + info->rom_mask = 0xFFFFFFFF; + for (i = 0; i < 32; i ++) + { + if ((1U << i) >= info->rom_size) + { + info->rom_mask = (1 << i) - 1; + break; + } + } + } + if (DataStart > ROMSize) + return; + if (DataStart + DataLength > ROMSize) + DataLength = ROMSize - DataStart; + + memcpy(info->rom + DataStart, ROMData, DataLength); +} + + +void k054539_set_mute_mask(void *chip, UINT32 MuteMask) +{ + k054539_state *info = (k054539_state *) chip; + UINT8 CurChn; + + for (CurChn = 0; CurChn < 8; CurChn ++) + info->Muted[CurChn] = (MuteMask >> CurChn) & 0x01; + + return; +} diff --git a/Frameworks/GME/gme/k054539.h b/Frameworks/GME/gme/k054539.h new file mode 100644 index 000000000..5090403a4 --- /dev/null +++ b/Frameworks/GME/gme/k054539.h @@ -0,0 +1,70 @@ +/********************************************************* + + Konami 054539 PCM Sound Chip + +*********************************************************/ + +#pragma once + +#include "mamedef.h" + +//#include "devlegcy.h" + +/*typedef struct _k054539_interface k054539_interface; +struct _k054539_interface +{ + const char *rgnoverride; + void (*apan)(device_t *, double, double); // Callback for analog output mixing levels (0..1 for each channel) + void (*irq)(device_t *); +};*/ + +#ifdef __cplusplus +extern "C" { +#endif + +void k054539_update(void *, stream_sample_t **outputs, int samples); +void * device_start_k054539(int clock); +void device_stop_k054539(void *); +void device_reset_k054539(void *); + + +//WRITE8_DEVICE_HANDLER( k054539_w ); +//READ8_DEVICE_HANDLER( k054539_r ); +void k054539_w(void *, offs_t offset, UINT8 data); +UINT8 k054539_r(void *, offs_t offset); + +//* control flags, may be set at DRIVER_INIT(). +#define K054539_RESET_FLAGS 0 +#define K054539_REVERSE_STEREO 1 +#define K054539_DISABLE_REVERB 2 +#define K054539_UPDATE_AT_KEYON 4 + +//void k054539_init_flags(device_t *device, int flags); +void k054539_init_flags(void *, int flags); + +/* + Note that the eight PCM channels of a K054539 do not have separate + volume controls. Considering the global attenuation equation may not + be entirely accurate, k054539_set_gain() provides means to control + channel gain. It can be called anywhere but preferrably from + DRIVER_INIT(). + + Parameters: + chip : 0 / 1 + channel : 0 - 7 + gain : 0.0=silent, 1.0=no gain, 2.0=twice as loud, etc. +*/ +//void k054539_set_gain(device_t *device, int channel, double gain); +void k054539_set_gain(void *, int channel, double gain); + + +void k054539_write_rom(void *, offs_t ROMSize, offs_t DataStart, offs_t DataLength, + const UINT8* ROMData); +void k054539_set_mute_mask(void *, UINT32 MuteMask); + + +//DECLARE_LEGACY_SOUND_DEVICE(K054539, k054539); + +#ifdef __cplusplus +}; +#endif diff --git a/Frameworks/GME/gme/kmsnddev.h b/Frameworks/GME/gme/kmsnddev.h new file mode 100644 index 000000000..ec7552b1b --- /dev/null +++ b/Frameworks/GME/gme/kmsnddev.h @@ -0,0 +1,31 @@ +/* libnezp by Mamiya */ + +#ifndef KMSNDDEV_H__ +#define KMSNDDEV_H__ +#ifdef __cplusplus +extern "C" { +#endif + +#include "nestypes.h" + +typedef struct KMIF_SOUND_DEVICE { + void *ctx; + void (*release)(void *ctx); + void (*reset)(void *ctx, Uint32 clock, Uint32 freq); + int (*synth)(void *ctx); + void (*volume)(void *ctx, Int32 v); + void (*write)(void *ctx, Uint32 a, Uint32 v); + Uint32 (*read)(void *ctx, Uint32 a); + void (*setinst)(void *ctx, Uint32 n, void *p, Uint32 l); +#if 0 + void (*setrate)(void *ctx, Uint32 clock, Uint32 freq); + void (*getinfo)(void *ctx, KMCH_INFO *cip, ); + void (*volume2)(void *ctx, Uint8 *volp, Uint32 numch); + /* 0x00(mute),0x70(x1/2),0x80(x1),0x90(x2) */ +#endif +} KMIF_SOUND_DEVICE; + +#ifdef __cplusplus +} +#endif +#endif /* KMSNDDEV_H__ */ diff --git a/Frameworks/GME/gme/mamedef.h b/Frameworks/GME/gme/mamedef.h new file mode 100644 index 000000000..3c5ad267d --- /dev/null +++ b/Frameworks/GME/gme/mamedef.h @@ -0,0 +1,55 @@ +#ifndef GME_MAMEDEFS_H +#define GME_MAMEDEFS_H + + +// typedefs to use MAME's (U)INTxx types (copied from MAME\src\ods\odscomm.h) +/* 8-bit values */ +typedef unsigned char UINT8; +typedef signed char INT8; + +/* 16-bit values */ +typedef unsigned short UINT16; +typedef signed short INT16; + +/* 32-bit values */ +#ifndef _WINDOWS_H +typedef unsigned int UINT32; +typedef signed int INT32; +#endif + +/* 64-bit values */ +#ifndef _WINDOWS_H +#ifdef _MSC_VER +typedef signed __int64 INT64; +typedef unsigned __int64 UINT64; +#else +__extension__ typedef unsigned long long UINT64; +__extension__ typedef signed long long INT64; +#endif +#endif + +/* offsets and addresses are 32-bit (for now...) */ +typedef UINT32 offs_t; + +/* stream_sample_t is used to represent a single sample in a sound stream */ +typedef INT32 stream_sample_t; + +#ifndef NULL +#define NULL ((void *)0) +#endif + +#if defined(_MSC_VER) +//#define INLINE static __forceinline +#define INLINE static __inline +#elif defined(__GNUC__) +#define INLINE static __inline__ +#else +#define INLINE static inline +#endif +#define _USE_MATH_DEFINES +#include + +#define logerror + + +#endif diff --git a/Frameworks/GME/gme/mathdefs.h b/Frameworks/GME/gme/mathdefs.h new file mode 100644 index 000000000..b12dfd0c7 --- /dev/null +++ b/Frameworks/GME/gme/mathdefs.h @@ -0,0 +1,15 @@ +#ifndef MATHDEFS_H +#define MATHDEFS_H + + +#ifndef _USE_MATH_DEFINES +#define _USE_MATH_DEFINES +#endif +#include + +#ifndef M_PI +#define M_PI 3.14159265358979323846 +#endif + + +#endif diff --git a/Frameworks/GME/gme/nestypes.h b/Frameworks/GME/gme/nestypes.h new file mode 100644 index 000000000..1fd3582b0 --- /dev/null +++ b/Frameworks/GME/gme/nestypes.h @@ -0,0 +1,39 @@ +#ifndef NESTYPES_H__ +#define NESTYPES_H__ + +#if defined(_MSC_VER) +#define NEVER_REACH __assume(0); +#define inline __inline +#elif defined(__BORLANDC__) +#define __fastcall __msfastcall +#elif defined(__GNUC__) +#define __inline __inline__ +#define __fastcall +#else +#define __inline +#define __fastcall +#endif +#ifndef NEVER_REACH +#define NEVER_REACH +#endif + +typedef int Int; +typedef unsigned int Uint; +typedef signed int Int32; +typedef unsigned int Uint32; +typedef signed short Int16; +typedef unsigned short Uint16; +typedef signed char Int8; +typedef unsigned char Uint8; +typedef char Char; + +#include + +#define XSLEEP(n) ((void)0) +#define XMALLOC(s) malloc(s) +#define XREALLOC(p,s) realloc(p,s) +#define XFREE(p) free(p) +#define XMEMCPY(d,s,n) memcpy(d,s,n) +#define XMEMSET(d,c,n) memset(d,c,n) + +#endif /* NESTYPES_H__ */ diff --git a/Frameworks/GME/gme/okim6258.c b/Frameworks/GME/gme/okim6258.c new file mode 100644 index 000000000..e333f247b --- /dev/null +++ b/Frameworks/GME/gme/okim6258.c @@ -0,0 +1,446 @@ +/********************************************************************************************** + * + * OKI MSM6258 ADPCM + * + * TODO: + * 3-bit ADPCM support + * Recording? + * + **********************************************************************************************/ + + +//#include "emu.h" +#include "mamedef.h" +#ifdef _DEBUG +#include +#endif +//#include "streams.h" +#include +#include +#include "okim6258.h" + +#define COMMAND_STOP (1 << 0) +#define COMMAND_PLAY (1 << 1) +#define COMMAND_RECORD (1 << 2) + +#define STATUS_PLAYING (1 << 1) +#define STATUS_RECORDING (1 << 2) + +static const int dividers[4] = { 1024, 768, 512, 512 }; + +typedef struct _okim6258_state okim6258_state; +struct _okim6258_state +{ + UINT8 status; + + UINT32 master_clock; /* master clock frequency */ + UINT32 divider; /* master clock divider */ + UINT8 adpcm_type; /* 3/4 bit ADPCM select */ + UINT8 data_in; /* ADPCM data-in register */ + UINT8 nibble_shift; /* nibble select */ + //sound_stream *stream; /* which stream are we playing on? */ + + UINT8 output_bits; + + INT32 signal; + INT32 step; + + UINT8 clock_buffer[0x04]; +}; + +/* step size index shift table */ +static const int index_shift[8] = { -1, -1, -1, -1, 2, 4, 6, 8 }; + +/* lookup table for the precomputed difference */ +static int diff_lookup[49*16]; + +/* tables computed? */ +static int tables_computed = 0; + +/*INLINE okim6258_state *get_safe_token(running_device *device) +{ + assert(device != NULL); + assert(device->type() == OKIM6258); + return (okim6258_state *)downcast(device)->token(); +}*/ + +/********************************************************************************************** + + compute_tables -- compute the difference tables + +***********************************************************************************************/ + +static void compute_tables(void) +{ + /* nibble to bit map */ + static const int nbl2bit[16][4] = + { + { 1, 0, 0, 0}, { 1, 0, 0, 1}, { 1, 0, 1, 0}, { 1, 0, 1, 1}, + { 1, 1, 0, 0}, { 1, 1, 0, 1}, { 1, 1, 1, 0}, { 1, 1, 1, 1}, + {-1, 0, 0, 0}, {-1, 0, 0, 1}, {-1, 0, 1, 0}, {-1, 0, 1, 1}, + {-1, 1, 0, 0}, {-1, 1, 0, 1}, {-1, 1, 1, 0}, {-1, 1, 1, 1} + }; + + int step, nib; + + if (tables_computed) + return; + + /* loop over all possible steps */ + for (step = 0; step <= 48; step++) + { + /* compute the step value */ + int stepval = floor(16.0 * pow(11.0 / 10.0, (double)step)); + + /* loop over all nibbles and compute the difference */ + for (nib = 0; nib < 16; nib++) + { + diff_lookup[step*16 + nib] = nbl2bit[nib][0] * + (stepval * nbl2bit[nib][1] + + stepval/2 * nbl2bit[nib][2] + + stepval/4 * nbl2bit[nib][3] + + stepval/8); + } + } + + tables_computed = 1; +} + + +static INT16 clock_adpcm(okim6258_state *chip, UINT8 nibble) +{ + INT32 max = (1 << (chip->output_bits - 1)) - 1; + INT32 min = -(1 << (chip->output_bits - 1)); + + chip->signal += diff_lookup[chip->step * 16 + (nibble & 15)]; + + /* clamp to the maximum */ + if (chip->signal > max) + chip->signal = max; + else if (chip->signal < min) + chip->signal = min; + + /* adjust the step size and clamp */ + chip->step += index_shift[nibble & 7]; + if (chip->step > 48) + chip->step = 48; + else if (chip->step < 0) + chip->step = 0; + + /* return the signal scaled up to 32767 */ + return chip->signal << 4; +} + +/********************************************************************************************** + + okim6258_update -- update the sound chip so that it is in sync with CPU execution + +***********************************************************************************************/ + +//static STREAM_UPDATE( okim6258_update ) +void okim6258_update(void *_chip, stream_sample_t **outputs, int samples) +{ + okim6258_state *chip = (okim6258_state *)_chip; + //stream_sample_t *buffer = outputs[0]; + stream_sample_t *bufL = outputs[0]; + stream_sample_t *bufR = outputs[1]; + + //memset(outputs[0], 0, samples * sizeof(*outputs[0])); + + if (chip->status & STATUS_PLAYING) + { + int nibble_shift = chip->nibble_shift; + + while (samples) + { + /* Compute the new amplitude and update the current step */ + int nibble = (chip->data_in >> nibble_shift) & 0xf; + + /* Output to the buffer */ + INT16 sample = clock_adpcm(chip, nibble); + + nibble_shift ^= 4; + + //*buffer++ = sample; + *bufL++ = sample; + *bufR++ = sample; + samples--; + } + + /* Update the parameters */ + chip->nibble_shift = nibble_shift; + } + else + { + /* Fill with 0 */ + while (samples--) + { + //*buffer++ = 0; + *bufL++ = 0; + *bufR++ = 0; + } + } +} + + + +/********************************************************************************************** + + state save support for MAME + +***********************************************************************************************/ + +/*static void okim6258_state_save_register(okim6258_state *info, running_device *device) +{ + state_save_register_device_item(device, 0, info->status); + state_save_register_device_item(device, 0, info->master_clock); + state_save_register_device_item(device, 0, info->divider); + state_save_register_device_item(device, 0, info->data_in); + state_save_register_device_item(device, 0, info->nibble_shift); + state_save_register_device_item(device, 0, info->signal); + state_save_register_device_item(device, 0, info->step); +}*/ + + +/********************************************************************************************** + + OKIM6258_start -- start emulation of an OKIM6258-compatible chip + +***********************************************************************************************/ + +//static DEVICE_START( okim6258 ) +void * device_start_okim6258(int clock, int divider, int adpcm_type, int output_12bits) +{ + //const okim6258_interface *intf = (const okim6258_interface *)device->baseconfig().static_config(); + //okim6258_state *info = get_safe_token(device); + okim6258_state *info; + + info = (okim6258_state *) calloc(1, sizeof(okim6258_state)); + + compute_tables(); + + //info->master_clock = device->clock(); + info->master_clock = clock; + info->adpcm_type = /*intf->*/adpcm_type; + info->clock_buffer[0x00] = (clock & 0x000000FF) >> 0; + info->clock_buffer[0x01] = (clock & 0x0000FF00) >> 8; + info->clock_buffer[0x02] = (clock & 0x00FF0000) >> 16; + info->clock_buffer[0x03] = (clock & 0xFF000000) >> 24; + + /* D/A precision is 10-bits but 12-bit data can be output serially to an external DAC */ + info->output_bits = /*intf->*/output_12bits ? 12 : 10; + info->divider = dividers[/*intf->*/divider]; + + //info->stream = stream_create(device, 0, 1, device->clock()/info->divider, info, okim6258_update); + + info->signal = -2; + info->step = 0; + + //okim6258_state_save_register(info, device); + + return info; //return info->master_clock / info->divider; +} + + +/********************************************************************************************** + + OKIM6258_stop -- stop emulation of an OKIM6258-compatible chip + +***********************************************************************************************/ + +void device_stop_okim6258(void *chip) +{ + okim6258_state *info = (okim6258_state *) chip; + + free(info); +} + +//static DEVICE_RESET( okim6258 ) +void device_reset_okim6258(void *chip) +{ + //okim6258_state *info = get_safe_token(device); + okim6258_state *info = (okim6258_state *) chip; + + //stream_update(info->stream); + + info->signal = -2; + info->step = 0; + info->status = 0; +} + + +/********************************************************************************************** + + okim6258_set_divider -- set the master clock divider + +***********************************************************************************************/ + +//void okim6258_set_divider(running_device *device, int val) +void okim6258_set_divider(void *chip, int val) +{ + okim6258_state *info = (okim6258_state *) chip; + int divider = dividers[val]; + + info->divider = dividers[val]; + //stream_set_sample_rate(info->stream, info->master_clock / divider); +} + + +/********************************************************************************************** + + okim6258_set_clock -- set the master clock + +***********************************************************************************************/ + +//void okim6258_set_clock(running_device *device, int val) +void okim6258_set_clock(void *chip, int val) +{ + okim6258_state *info = (okim6258_state *) chip; + + if (val) + { + info->master_clock = val; + } + else + { + info->master_clock = (info->clock_buffer[0x00] << 0) | + (info->clock_buffer[0x01] << 8) | + (info->clock_buffer[0x02] << 16) | + (info->clock_buffer[0x03] << 24); + } +} + + +/********************************************************************************************** + + okim6258_get_vclk -- get the VCLK/sampling frequency + +***********************************************************************************************/ + +//int okim6258_get_vclk(running_device *device) +int okim6258_get_vclk(void *chip) +{ + okim6258_state *info = (okim6258_state *) chip; + + return (info->master_clock / info->divider); +} + + +/********************************************************************************************** + + okim6258_status_r -- read the status port of an OKIM6258-compatible chip + +***********************************************************************************************/ + +//READ8_DEVICE_HANDLER( okim6258_status_r ) +UINT8 okim6258_status_r(void *chip, offs_t offset) +{ + //okim6258_state *info = get_safe_token(device); + okim6258_state *info = (okim6258_state *) chip; + + //stream_update(info->stream); + + return (info->status & STATUS_PLAYING) ? 0x00 : 0x80; +} + + +/********************************************************************************************** + + okim6258_data_w -- write to the control port of an OKIM6258-compatible chip + +***********************************************************************************************/ +//WRITE8_DEVICE_HANDLER( okim6258_data_w ) +void okim6258_data_w(void *chip, offs_t offset, UINT8 data) +{ + //okim6258_state *info = get_safe_token(device); + okim6258_state *info = (okim6258_state *) chip; + + /* update the stream */ + //stream_update(info->stream); + + info->data_in = data; + info->nibble_shift = 0; +} + + +/********************************************************************************************** + + okim6258_ctrl_w -- write to the control port of an OKIM6258-compatible chip + +***********************************************************************************************/ + +//WRITE8_DEVICE_HANDLER( okim6258_ctrl_w ) +void okim6258_ctrl_w(void *chip, offs_t offset, UINT8 data) +{ + //okim6258_state *info = get_safe_token(device); + okim6258_state *info = (okim6258_state *) chip; + + //stream_update(info->stream); + + if (data & COMMAND_STOP) + { + info->status &= ~(STATUS_PLAYING | STATUS_RECORDING); + return; + } + + if (data & COMMAND_PLAY) + { + if (!(info->status & STATUS_PLAYING)) + { + info->status |= STATUS_PLAYING; + + /* Also reset the ADPCM parameters */ + info->signal = -2; + info->step = 0; + info->nibble_shift = 0; + } + } + else + { + info->status &= ~STATUS_PLAYING; + } + + if (data & COMMAND_RECORD) + { + logerror("M6258: Record enabled\n"); + info->status |= STATUS_RECORDING; + } + else + { + info->status &= ~STATUS_RECORDING; + } +} + +void okim6258_set_clock_byte(void *chip, UINT8 Byte, UINT8 val) +{ + okim6258_state *info = (okim6258_state *) chip; + + info->clock_buffer[Byte] = val; + + return; +} + +void okim6258_write(void *chip, UINT8 Port, UINT8 Data) +{ + switch(Port) + { + case 0x00: + okim6258_ctrl_w(chip, 0x00, Data); + break; + case 0x01: + okim6258_data_w(chip, 0x00, Data); + break; + case 0x08: + case 0x09: + case 0x0A: + okim6258_set_clock_byte(chip, Port & 0x03, Data); + break; + case 0x0B: + okim6258_set_clock_byte(chip, Port & 0x03, Data); + okim6258_set_clock(chip, 0); + break; + case 0x0C: + okim6258_set_divider(chip, Data); + break; + } +} diff --git a/Frameworks/GME/gme/okim6258.h b/Frameworks/GME/gme/okim6258.h new file mode 100644 index 000000000..1383a2b70 --- /dev/null +++ b/Frameworks/GME/gme/okim6258.h @@ -0,0 +1,57 @@ +#pragma once + +#include "mamedef.h" + +//#include "devlegcy.h" + +/* an interface for the OKIM6258 and similar chips */ + +/*typedef struct _okim6258_interface okim6258_interface; +struct _okim6258_interface +{ + int divider; + int adpcm_type; + int output_12bits; +};*/ + +#ifdef __cplusplus +extern "C" { +#endif + +#define FOSC_DIV_BY_1024 0 +#define FOSC_DIV_BY_768 1 +#define FOSC_DIV_BY_512 2 + +#define TYPE_3BITS 0 +#define TYPE_4BITS 1 + +#define OUTPUT_10BITS 0 +#define OUTPUT_12BITS 1 + +void okim6258_update(void *, stream_sample_t **outputs, int samples); +void * device_start_okim6258(int clock, int divider, int adpcm_type, int output_12bits); +void device_stop_okim6258(void *); +void device_reset_okim6258(void *); + +//void okim6258_set_divider(running_device *device, int val); +//void okim6258_set_clock(running_device *device, int val); +//int okim6258_get_vclk(running_device *device); + +void okim6258_set_divider(void *, int val); +void okim6258_set_clock(void *, int val); +int okim6258_get_vclk(void *); + +//READ8_DEVICE_HANDLER( okim6258_status_r ); +//WRITE8_DEVICE_HANDLER( okim6258_data_w ); +//WRITE8_DEVICE_HANDLER( okim6258_ctrl_w ); + +UINT8 okim6258_status_r(void *, offs_t offset); +void okim6258_data_w(void *, offs_t offset, UINT8 data); +void okim6258_ctrl_w(void *, offs_t offset, UINT8 data); +void okim6258_write(void *, UINT8 Port, UINT8 Data); + +//DECLARE_LEGACY_SOUND_DEVICE(OKIM6258, okim6258); + +#ifdef __cplusplus +} +#endif diff --git a/Frameworks/GME/gme/okim6295.c b/Frameworks/GME/gme/okim6295.c new file mode 100644 index 000000000..1c2c2de21 --- /dev/null +++ b/Frameworks/GME/gme/okim6295.c @@ -0,0 +1,709 @@ +/********************************************************************************************** + * + * streaming ADPCM driver + * by Aaron Giles + * + * Library to transcode from an ADPCM source to raw PCM. + * Written by Buffoni Mirko in 08/06/97 + * References: various sources and documents. + * + * HJB 08/31/98 + * modified to use an automatically selected oversampling factor + * for the current sample rate + * + * Mish 21/7/99 + * Updated to allow multiple OKI chips with different sample rates + * + * R. Belmont 31/10/2003 + * Updated to allow a driver to use both MSM6295s and "raw" ADPCM voices (gcpinbal) + * Also added some error trapping for MAME_DEBUG builds + * + **********************************************************************************************/ + + +#include "mamedef.h" +//#include "emu.h" +//#include "streams.h" +#include +#include +#include +#define _USE_MATH_DEFINES +#include +#include "okim6295.h" + +#define FALSE 0 +#define TRUE 1 + +//#define MAX_SAMPLE_CHUNK 10000 +#define MAX_SAMPLE_CHUNK 0x10 // that's enough for VGMPlay's update rate + + +/* struct describing a single playing ADPCM voice */ +struct ADPCMVoice +{ + UINT8 playing; /* 1 if we are actively playing */ + + UINT32 base_offset; /* pointer to the base memory location */ + UINT32 sample; /* current sample number */ + UINT32 count; /* total samples to play */ + + struct adpcm_state adpcm;/* current ADPCM state */ + UINT32 volume; /* output volume */ + UINT8 Muted; +}; + +typedef struct _okim6295_state okim6295_state; +struct _okim6295_state +{ + #define OKIM6295_VOICES 4 + struct ADPCMVoice voice[OKIM6295_VOICES]; + //running_device *device; + INT32 command; + UINT8 bank_installed; + INT32 bank_offs; + UINT8 pin7_state; + //sound_stream *stream; /* which stream are we playing on? */ + UINT32 master_clock; /* master clock frequency */ + + UINT32 ROMSize; + UINT8* ROM; +}; + +/* step size index shift table */ +static const int index_shift[8] = { -1, -1, -1, -1, 2, 4, 6, 8 }; + +/* lookup table for the precomputed difference */ +static int diff_lookup[49*16]; + +/* volume lookup table. The manual lists only 9 steps, ~3dB per step. Given the dB values, + that seems to map to a 5-bit volume control. Any volume parameter beyond the 9th index + results in silent playback. */ +static const int volume_table[16] = +{ + 0x20, // 0 dB + 0x16, // -3.2 dB + 0x10, // -6.0 dB + 0x0b, // -9.2 dB + 0x08, // -12.0 dB + 0x06, // -14.5 dB + 0x04, // -18.0 dB + 0x03, // -20.5 dB + 0x02, // -24.0 dB + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, +}; + +/* tables computed? */ +static int tables_computed = 0; + +/* useful interfaces */ +//const okim6295_interface okim6295_interface_pin7high = { 1 }; +//const okim6295_interface okim6295_interface_pin7low = { 0 }; + +/* default address map */ +/*static ADDRESS_MAP_START( okim6295, 0, 8 ) + AM_RANGE(0x00000, 0x3ffff) AM_ROM +ADDRESS_MAP_END*/ + + +/*INLINE okim6295_state *get_safe_token(running_device *device) +{ + assert(device != NULL); + assert(device->token != NULL); + assert(device->type == SOUND); + assert(sound_get_type(device) == SOUND_OKIM6295); + return (okim6295_state *)device->token; +}*/ + + +/********************************************************************************************** + + compute_tables -- compute the difference tables + +***********************************************************************************************/ + +static void compute_tables(void) +{ + /* nibble to bit map */ + static const int nbl2bit[16][4] = + { + { 1, 0, 0, 0}, { 1, 0, 0, 1}, { 1, 0, 1, 0}, { 1, 0, 1, 1}, + { 1, 1, 0, 0}, { 1, 1, 0, 1}, { 1, 1, 1, 0}, { 1, 1, 1, 1}, + {-1, 0, 0, 0}, {-1, 0, 0, 1}, {-1, 0, 1, 0}, {-1, 0, 1, 1}, + {-1, 1, 0, 0}, {-1, 1, 0, 1}, {-1, 1, 1, 0}, {-1, 1, 1, 1} + }; + + int step, nib; + + /* loop over all possible steps */ + for (step = 0; step <= 48; step++) + { + /* compute the step value */ + int stepval = (int)floor(16.0 * pow(11.0 / 10.0, (double)step)); + + /* loop over all nibbles and compute the difference */ + for (nib = 0; nib < 16; nib++) + { + diff_lookup[step*16 + nib] = nbl2bit[nib][0] * + (stepval * nbl2bit[nib][1] + + stepval/2 * nbl2bit[nib][2] + + stepval/4 * nbl2bit[nib][3] + + stepval/8); + } + } + + tables_computed = 1; +} + + + +/********************************************************************************************** + + reset_adpcm -- reset the ADPCM stream + +***********************************************************************************************/ + +void reset_adpcm(struct adpcm_state *state) +{ + /* make sure we have our tables */ + if (!tables_computed) + compute_tables(); + + /* reset the signal/step */ + state->signal = -2; + state->step = 0; +} + + + +/********************************************************************************************** + + clock_adpcm -- clock the next ADPCM byte + +***********************************************************************************************/ + +INT16 clock_adpcm(struct adpcm_state *state, UINT8 nibble) +{ + state->signal += diff_lookup[state->step * 16 + (nibble & 15)]; + + /* clamp to the maximum */ + if (state->signal > 2047) + state->signal = 2047; + else if (state->signal < -2048) + state->signal = -2048; + + /* adjust the step size and clamp */ + state->step += index_shift[nibble & 7]; + if (state->step > 48) + state->step = 48; + else if (state->step < 0) + state->step = 0; + + /* return the signal */ + return state->signal; +} + + + +/********************************************************************************************** + + generate_adpcm -- general ADPCM decoding routine + +***********************************************************************************************/ + +static UINT8 memory_raw_read_byte(okim6295_state *info, offs_t offset) +{ + offs_t CurOfs; + + CurOfs = info->bank_offs | offset; + if (CurOfs < info->ROMSize) + return info->ROM[CurOfs]; + else + return 0x00; +} + +static void generate_adpcm(okim6295_state *chip, struct ADPCMVoice *voice, INT16 *buffer, int samples) +{ + /* if this voice is active */ + if (voice->playing) + { + offs_t base = voice->base_offset; + int sample = voice->sample; + int count = voice->count; + + /* loop while we still have samples to generate */ + while (samples) + { + /* compute the new amplitude and update the current step */ + //int nibble = memory_raw_read_byte(chip->device->space(), base + sample / 2) >> (((sample & 1) << 2) ^ 4); + UINT8 nibble = memory_raw_read_byte(chip, base + sample / 2) >> (((sample & 1) << 2) ^ 4); + + /* output to the buffer, scaling by the volume */ + /* signal in range -2048..2047, volume in range 2..32 => signal * volume / 2 in range -32768..32767 */ + *buffer++ = clock_adpcm(&voice->adpcm, nibble) * voice->volume / 2; + samples--; + + /* next! */ + if (++sample >= count) + { + voice->playing = 0; + break; + } + } + + /* update the parameters */ + voice->sample = sample; + } + + /* fill the rest with silence */ + while (samples--) + *buffer++ = 0; +} + + + +/********************************************************************************************** + * + * OKIM 6295 ADPCM chip: + * + * Command bytes are sent: + * + * 1xxx xxxx = start of 2-byte command sequence, xxxxxxx is the sample number to trigger + * abcd vvvv = second half of command; one of the abcd bits is set to indicate which voice + * the v bits seem to be volumed + * + * 0abc d000 = stop playing; one or more of the abcd bits is set to indicate which voice(s) + * + * Status is read: + * + * ???? abcd = one bit per voice, set to 0 if nothing is playing, or 1 if it is active + * +***********************************************************************************************/ + + +/********************************************************************************************** + + okim6295_update -- update the sound chip so that it is in sync with CPU execution + +***********************************************************************************************/ + +//static STREAM_UPDATE( okim6295_update ) +void okim6295_update(void *_chip, stream_sample_t **outputs, int samples) +{ + okim6295_state *chip = (okim6295_state *)_chip; + int i; + + memset(outputs[0], 0, samples * sizeof(*outputs[0])); + + for (i = 0; i < OKIM6295_VOICES; i++) + { + struct ADPCMVoice *voice = &chip->voice[i]; + if (! voice->Muted) + { + stream_sample_t *buffer = outputs[0]; + INT16 sample_data[MAX_SAMPLE_CHUNK]; + int remaining = samples; + + /* loop while we have samples remaining */ + while (remaining) + { + int samples = (remaining > MAX_SAMPLE_CHUNK) ? MAX_SAMPLE_CHUNK : remaining; + int samp; + + generate_adpcm(chip, voice, sample_data, samples); + for (samp = 0; samp < samples; samp++) + *buffer++ += sample_data[samp]; + + remaining -= samples; + } + } + } + + memcpy(outputs[1], outputs[0], samples * sizeof(*outputs[0])); +} + + + +/********************************************************************************************** + + state save support for MAME + +***********************************************************************************************/ + +/*static void adpcm_state_save_register(struct ADPCMVoice *voice, running_device *device, int index) +{ + state_save_register_device_item(device, index, voice->playing); + state_save_register_device_item(device, index, voice->sample); + state_save_register_device_item(device, index, voice->count); + state_save_register_device_item(device, index, voice->adpcm.signal); + state_save_register_device_item(device, index, voice->adpcm.step); + state_save_register_device_item(device, index, voice->volume); + state_save_register_device_item(device, index, voice->base_offset); +} + +static STATE_POSTLOAD( okim6295_postload ) +{ + running_device *device = (running_device *)param; + okim6295_state *info = get_safe_token(device); + okim6295_set_bank_base(device, info->bank_offs); +} + +static void okim6295_state_save_register(okim6295_state *info, running_device *device) +{ + int j; + + state_save_register_device_item(device, 0, info->command); + state_save_register_device_item(device, 0, info->bank_offs); + for (j = 0; j < OKIM6295_VOICES; j++) + adpcm_state_save_register(&info->voice[j], device, j); + + state_save_register_postload(device->machine, okim6295_postload, (void *)device); +}*/ + + + +/********************************************************************************************** + + DEVICE_START( okim6295 ) -- start emulation of an OKIM6295-compatible chip + +***********************************************************************************************/ + +//static DEVICE_START( okim6295 ) +void * device_start_okim6295(int clock) +{ + //const okim6295_interface *intf = (const okim6295_interface *)device->baseconfig().static_config; + //okim6295_state *info = get_safe_token(device); + okim6295_state *info; + //int divisor = intf->pin7 ? 132 : 165; + int divisor; + //int voice; + + info = (okim6295_state *) calloc(1, sizeof(okim6295_state)); + + compute_tables(); + + info->command = -1; + info->bank_installed = FALSE; + info->bank_offs = 0; + //info->device = device; + + //info->master_clock = device->clock; + info->master_clock = clock & 0x7FFFFFFF; + info->pin7_state = (clock & 0x80000000) >> 31; + + /* generate the name and create the stream */ + divisor = info->pin7_state ? 132 : 165; + //info->stream = stream_create(device, 0, 1, device->clock/divisor, info, okim6295_update); + + // moved to device_reset + /*// initialize the voices // + for (voice = 0; voice < OKIM6295_VOICES; voice++) + { + // initialize the rest of the structure // + info->voice[voice].volume = 0; + reset_adpcm(&info->voice[voice].adpcm); + }*/ + + //okim6295_state_save_register(info, device); + + return info; //return info->master_clock / divisor; +} + + + +void device_stop_okim6295(void *_chip) +{ + okim6295_state* chip = (okim6295_state *) _chip; + + free(chip->ROM); chip->ROM = NULL; + chip->ROMSize = 0x00; + + free(chip); +} + +/********************************************************************************************** + + DEVICE_RESET( okim6295 ) -- stop emulation of an OKIM6295-compatible chip + +***********************************************************************************************/ + +//static DEVICE_RESET( okim6295 ) +void device_reset_okim6295(void *chip) +{ + okim6295_state *info = (okim6295_state *) chip; + int voice; + + for (voice = 0; voice < OKIM6295_VOICES; voice++) + { + info->voice[voice].volume = 0; + reset_adpcm(&info->voice[voice].adpcm); + + info->voice[voice].playing = 0; + } +} + + + +/********************************************************************************************** + + okim6295_set_bank_base -- set the base of the bank for a given voice on a given chip + +***********************************************************************************************/ + +//void okim6295_set_bank_base(running_device *device, int base) +void okim6295_set_bank_base(okim6295_state *info, int base) +{ + //okim6295_state *info = get_safe_token(device); + //stream_update(info->stream); + + /* if we are setting a non-zero base, and we have no bank, allocate one */ + if (!info->bank_installed && base != 0) + { + /* override our memory map with a bank */ + //memory_install_read_bank(device->space(), 0x00000, 0x3ffff, 0, 0, device->tag()); + info->bank_installed = TRUE; + } + + /* if we have a bank number, set the base pointer */ + if (info->bank_installed) + { + info->bank_offs = base; + //memory_set_bankptr(device->machine, device->tag(), device->region->base.u8 + base); + } +} + + + +/********************************************************************************************** + + okim6295_set_pin7 -- adjust pin 7, which controls the internal clock division + +***********************************************************************************************/ + +static void okim6295_clock_changed(okim6295_state *info) +{ + int divisor; + divisor = info->pin7_state ? 132 : 165; + //stream_set_sample_rate(info->stream, info->master_clock/divisor); +} + +//void okim6295_set_pin7(running_device *device, int pin7) +static void okim6295_set_pin7(okim6295_state *info, int pin7) +{ + //okim6295_state *info = get_safe_token(device); + //int divisor = pin7 ? 132 : 165; + + info->pin7_state = pin7; + //stream_set_sample_rate(info->stream, info->master_clock/divisor); + okim6295_clock_changed(info); +} + + +/********************************************************************************************** + + okim6295_status_r -- read the status port of an OKIM6295-compatible chip + +***********************************************************************************************/ + +//READ8_DEVICE_HANDLER( okim6295_r ) +UINT8 okim6295_r(void *chip, offs_t offset) +{ + //okim6295_state *info = get_safe_token(device); + okim6295_state *info = (okim6295_state *) chip; + int i, result; + + result = 0xf0; /* naname expects bits 4-7 to be 1 */ + + /* set the bit to 1 if something is playing on a given channel */ + //stream_update(info->stream); + for (i = 0; i < OKIM6295_VOICES; i++) + { + struct ADPCMVoice *voice = &info->voice[i]; + + /* set the bit if it's playing */ + if (voice->playing) + result |= 1 << i; + } + + return result; +} + + + +/********************************************************************************************** + + okim6295_data_w -- write to the data port of an OKIM6295-compatible chip + +***********************************************************************************************/ + +//WRITE8_DEVICE_HANDLER( okim6295_w ) +void okim6295_write_command(okim6295_state *info, UINT8 data) +{ + //okim6295_state *info = get_safe_token(device); + + /* if a command is pending, process the second half */ + if (info->command != -1) + { + int temp = data >> 4, i, start, stop; + offs_t base; + + /* the manual explicitly says that it's not possible to start multiple voices at the same time */ + if (temp != 0 && temp != 1 && temp != 2 && temp != 4 && temp != 8) + printf("OKI6295 start %x contact MAMEDEV\n", temp); + + /* update the stream */ + //stream_update(info->stream); + + /* determine which voice(s) (voice is set by a 1 bit in the upper 4 bits of the second byte) */ + for (i = 0; i < OKIM6295_VOICES; i++, temp >>= 1) + { + if (temp & 1) + { + struct ADPCMVoice *voice = &info->voice[i]; + + /* determine the start/stop positions */ + base = info->command * 8; + + //start = memory_raw_read_byte(device->space(), base + 0) << 16; + start = memory_raw_read_byte(info, base + 0) << 16; + start |= memory_raw_read_byte(info, base + 1) << 8; + start |= memory_raw_read_byte(info, base + 2) << 0; + start &= 0x3ffff; + + stop = memory_raw_read_byte(info, base + 3) << 16; + stop |= memory_raw_read_byte(info, base + 4) << 8; + stop |= memory_raw_read_byte(info, base + 5) << 0; + stop &= 0x3ffff; + + /* set up the voice to play this sample */ + if (start < stop) + { + if (!voice->playing) /* fixes Got-cha and Steel Force */ + { + voice->playing = 1; + voice->base_offset = start; + voice->sample = 0; + voice->count = 2 * (stop - start + 1); + + /* also reset the ADPCM parameters */ + reset_adpcm(&voice->adpcm); + voice->volume = volume_table[data & 0x0f]; + } + else + { + //logerror("OKIM6295:'%s' requested to play sample %02x on non-stopped voice\n",device->tag(),info->command); + // just displays warnings when seeking + //logerror("OKIM6295: Voice %u requested to play sample %02x on non-stopped voice\n",i,info->command); + } + } + /* invalid samples go here */ + else + { + //logerror("OKIM6295:'%s' requested to play invalid sample %02x\n",device->tag(),info->command); + logerror("OKIM6295: Voice %u requested to play invalid sample %02x\n",i,info->command); + voice->playing = 0; + } + } + } + + /* reset the command */ + info->command = -1; + } + + /* if this is the start of a command, remember the sample number for next time */ + else if (data & 0x80) + { + info->command = data & 0x7f; + } + + /* otherwise, see if this is a silence command */ + else + { + int temp = data >> 3, i; + + /* update the stream, then turn it off */ + //stream_update(info->stream); + + /* determine which voice(s) (voice is set by a 1 bit in bits 3-6 of the command */ + for (i = 0; i < OKIM6295_VOICES; i++, temp >>= 1) + { + if (temp & 1) + { + struct ADPCMVoice *voice = &info->voice[i]; + + voice->playing = 0; + } + } + } +} + +void okim6295_w(void *_chip, offs_t offset, UINT8 data) +{ + okim6295_state* chip = (okim6295_state *) _chip; + + switch(offset) + { + case 0x00: + okim6295_write_command(chip, data); + break; + case 0x08: + chip->master_clock &= ~0x000000FF; + chip->master_clock |= data << 0; + break; + case 0x09: + chip->master_clock &= ~0x0000FF00; + chip->master_clock |= data << 8; + break; + case 0x0A: + chip->master_clock &= ~0x00FF0000; + chip->master_clock |= data << 16; + break; + case 0x0B: + chip->master_clock &= ~0xFF000000; + chip->master_clock |= data << 24; + okim6295_clock_changed(chip); + break; + case 0x0C: + okim6295_set_pin7(chip, data); + break; + case 0x0F: + okim6295_set_bank_base(chip, data << 18); + break; + } + + return; +} + +void okim6295_write_rom(void *_chip, offs_t ROMSize, offs_t DataStart, offs_t DataLength, + const UINT8* ROMData) +{ + okim6295_state *chip = (okim6295_state *) _chip; + + if (chip->ROMSize != ROMSize) + { + chip->ROM = (UINT8*)realloc(chip->ROM, ROMSize); + chip->ROMSize = ROMSize; + memset(chip->ROM, 0xFF, ROMSize); + } + if (DataStart > ROMSize) + return; + if (DataStart + DataLength > ROMSize) + DataLength = ROMSize - DataStart; + + memcpy(chip->ROM + DataStart, ROMData, DataLength); +} + + +void okim6295_set_mute_mask(void *_chip, UINT32 MuteMask) +{ + okim6295_state *chip = (okim6295_state *) _chip; + UINT8 CurChn; + + for (CurChn = 0; CurChn < OKIM6295_VOICES; CurChn ++) + chip->voice[CurChn].Muted = (MuteMask >> CurChn) & 0x01; +} diff --git a/Frameworks/GME/gme/okim6295.h b/Frameworks/GME/gme/okim6295.h new file mode 100644 index 000000000..0ca8fc510 --- /dev/null +++ b/Frameworks/GME/gme/okim6295.h @@ -0,0 +1,60 @@ +#pragma once + +#include "mamedef.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* an interface for the OKIM6295 and similar chips */ + +/* + Note about the playback frequency: the external clock is internally divided, + depending on pin 7, by 132 (high) or 165 (low). +*/ +/*typedef struct _okim6295_interface okim6295_interface; +struct _okim6295_interface +{ + int pin7; +}; + +extern const okim6295_interface okim6295_interface_pin7high; +extern const okim6295_interface okim6295_interface_pin7low;*/ + + + +//void okim6295_set_bank_base(running_device *device, int base); +//void okim6295_set_pin7(running_device *device, int pin7); + +void okim6295_update(void *, stream_sample_t **outputs, int samples); +void * device_start_okim6295(int clock); +void device_stop_okim6295(void *); +void device_reset_okim6295(void *); + +//READ8_DEVICE_HANDLER( okim6295_r ); +//WRITE8_DEVICE_HANDLER( okim6295_w ); +void okim6295_w(void *, offs_t offset, UINT8 data); + +void okim6295_write_rom(void *, offs_t ROMSize, offs_t DataStart, offs_t DataLength, + const UINT8* ROMData); +void okim6295_set_mute_mask(void *, UINT32 MuteMask); + + +/* + To help the various custom ADPCM generators out there, + the following routines may be used. +*/ +struct adpcm_state +{ + INT32 signal; + INT32 step; +}; +void reset_adpcm(struct adpcm_state *state); +INT16 clock_adpcm(struct adpcm_state *state, UINT8 nibble); + +//DEVICE_GET_INFO( okim6295 ); +//#define SOUND_OKIM6295 DEVICE_GET_INFO_NAME( okim6295 ) + +#ifdef __cplusplus +} +#endif diff --git a/Frameworks/GME/gme/pwm.c b/Frameworks/GME/gme/pwm.c new file mode 100644 index 000000000..b1c78129f --- /dev/null +++ b/Frameworks/GME/gme/pwm.c @@ -0,0 +1,444 @@ +/*************************************************************************** + * Gens: PWM audio emulator. * + * * + * Copyright (c) 1999-2002 by Stéphane Dallongeville * + * Copyright (c) 2003-2004 by Stéphane Akhoun * + * Copyright (c) 2008-2009 by David Korth * + * * + * This program is free software; you can redistribute it and/or modify it * + * under the terms of the GNU General Public License as published by the * + * Free Software Foundation; either version 2 of the License, or (at your * + * option) any later version. * + * * + * This program 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 General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License along * + * with this program; if not, write to the Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#include "pwm.h" + +#include +#include + +#ifndef INLINE +#define INLINE static __inline +#endif + +//#include "gens_core/mem/mem_sh2.h" +//#include "gens_core/cpu/sh2/sh2.h" + +#define CHILLY_WILLY_SCALE 1 + +#if PWM_BUF_SIZE == 8 +unsigned char PWM_FULL_TAB[PWM_BUF_SIZE * PWM_BUF_SIZE] = +{ + 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, + 0x80, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x80, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x80, 0x40, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x80, 0x40, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x80, 0x40, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x40, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x40, +}; +#elif PWM_BUF_SIZE == 4 +unsigned char PWM_FULL_TAB[PWM_BUF_SIZE * PWM_BUF_SIZE] = +{ + 0x40, 0x00, 0x00, 0x80, + 0x80, 0x40, 0x00, 0x00, + 0x00, 0x80, 0x40, 0x00, + 0x00, 0x00, 0x80, 0x40, +}; +#else +#error PWM_BUF_SIZE must equal 4 or 8. +#endif /* PWM_BUF_SIZE */ + +typedef struct _pwm_chip +{ + unsigned short PWM_FIFO_R[8]; + unsigned short PWM_FIFO_L[8]; + unsigned int PWM_RP_R; + unsigned int PWM_WP_R; + unsigned int PWM_RP_L; + unsigned int PWM_WP_L; + unsigned int PWM_Cycles; + unsigned int PWM_Cycle; + unsigned int PWM_Cycle_Cnt; + unsigned int PWM_Int; + unsigned int PWM_Int_Cnt; + unsigned int PWM_Mode; + //unsigned int PWM_Enable; + unsigned int PWM_Out_R; + unsigned int PWM_Out_L; + + unsigned int PWM_Cycle_Tmp; + unsigned int PWM_Cycles_Tmp; + unsigned int PWM_Int_Tmp; + unsigned int PWM_FIFO_L_Tmp; + unsigned int PWM_FIFO_R_Tmp; + +#if CHILLY_WILLY_SCALE +// TODO: Fix Chilly Willy's new scaling algorithm. + /* PWM scaling variables. */ + int PWM_Offset; + int PWM_Scale; + //int PWM_Loudness; +#endif + + int clock; +} pwm_chip; +#if CHILLY_WILLY_SCALE +// TODO: Fix Chilly Willy's new scaling algorithm. +#define PWM_Loudness 0 +#endif + +void PWM_Init(pwm_chip* chip); +void PWM_Recalc_Scale(pwm_chip* chip); + +void PWM_Set_Cycle(pwm_chip* chip, unsigned int cycle); +void PWM_Set_Int(pwm_chip* chip, unsigned int int_time); + +void PWM_Update(pwm_chip* chip, int **buf, int length); + + +/** + * PWM_Init(): Initialize the PWM audio emulator. + */ +void PWM_Init(pwm_chip* chip) +{ + chip->PWM_Mode = 0; + chip->PWM_Out_R = 0; + chip->PWM_Out_L = 0; + + memset(chip->PWM_FIFO_R, 0x00, sizeof(chip->PWM_FIFO_R)); + memset(chip->PWM_FIFO_L, 0x00, sizeof(chip->PWM_FIFO_L)); + + chip->PWM_RP_R = 0; + chip->PWM_WP_R = 0; + chip->PWM_RP_L = 0; + chip->PWM_WP_L = 0; + chip->PWM_Cycle_Tmp = 0; + chip->PWM_Int_Tmp = 0; + chip->PWM_FIFO_L_Tmp = 0; + chip->PWM_FIFO_R_Tmp = 0; + + //PWM_Loudness = 0; + PWM_Set_Cycle(chip, 0); + PWM_Set_Int(chip, 0); +} + + +#if CHILLY_WILLY_SCALE +// TODO: Fix Chilly Willy's new scaling algorithm. +void PWM_Recalc_Scale(pwm_chip* chip) +{ + chip->PWM_Offset = (chip->PWM_Cycle / 2) + 1; + chip->PWM_Scale = 0x7FFF00 / chip->PWM_Offset; +} +#endif + + +void PWM_Set_Cycle(pwm_chip* chip, unsigned int cycle) +{ + cycle--; + chip->PWM_Cycle = (cycle & 0xFFF); + chip->PWM_Cycle_Cnt = chip->PWM_Cycles; + +#if CHILLY_WILLY_SCALE + // TODO: Fix Chilly Willy's new scaling algorithm. + PWM_Recalc_Scale(chip); +#endif +} + + +void PWM_Set_Int(pwm_chip* chip, unsigned int int_time) +{ + int_time &= 0x0F; + if (int_time) + chip->PWM_Int = chip->PWM_Int_Cnt = int_time; + else + chip->PWM_Int = chip->PWM_Int_Cnt = 16; +} + + +void PWM_Clear_Timer(pwm_chip* chip) +{ + chip->PWM_Cycle_Cnt = 0; +} + + +/** + * PWM_SHIFT(): Shift PWM data. + * @param src: Channel (L or R) with the source data. + * @param dest Channel (L or R) for the destination. + */ +#define PWM_SHIFT(src, dest) \ +{ \ + /* Make sure the source FIFO isn't empty. */ \ + if (PWM_RP_##src != PWM_WP_##src) \ + { \ + /* Get destination channel output from the source channel FIFO. */ \ + PWM_Out_##dest = PWM_FIFO_##src[PWM_RP_##src]; \ + \ + /* Increment the source channel read pointer, resetting to 0 if it overflows. */ \ + PWM_RP_##src = (PWM_RP_##src + 1) & (PWM_BUF_SIZE - 1); \ + } \ +} + + +/*static void PWM_Shift_Data(void) +{ + switch (PWM_Mode & 0x0F) + { + case 0x01: + case 0x0D: + // Rx_LL: Right -> Ignore, Left -> Left + PWM_SHIFT(L, L); + break; + + case 0x02: + case 0x0E: + // Rx_LR: Right -> Ignore, Left -> Right + PWM_SHIFT(L, R); + break; + + case 0x04: + case 0x07: + // RL_Lx: Right -> Left, Left -> Ignore + PWM_SHIFT(R, L); + break; + + case 0x05: + case 0x09: + // RR_LL: Right -> Right, Left -> Left + PWM_SHIFT(L, L); + PWM_SHIFT(R, R); + break; + + case 0x06: + case 0x0A: + // RL_LR: Right -> Left, Left -> Right + PWM_SHIFT(L, R); + PWM_SHIFT(R, L); + break; + + case 0x08: + case 0x0B: + // RR_Lx: Right -> Right, Left -> Ignore + PWM_SHIFT(R, R); + break; + + case 0x00: + case 0x03: + case 0x0C: + case 0x0F: + default: + // Rx_Lx: Right -> Ignore, Left -> Ignore + break; + } +} + + +void PWM_Update_Timer(unsigned int cycle) +{ + // Don't do anything if PWM is disabled in the Sound menu. + + // Don't do anything if PWM isn't active. + if ((PWM_Mode & 0x0F) == 0x00) + return; + + if (PWM_Cycle == 0x00 || (PWM_Cycle_Cnt > cycle)) + return; + + PWM_Shift_Data(); + + PWM_Cycle_Cnt += PWM_Cycle; + + PWM_Int_Cnt--; + if (PWM_Int_Cnt == 0) + { + PWM_Int_Cnt = PWM_Int; + + if (PWM_Mode & 0x0080) + { + // RPT => generate DREQ1 as well as INT + SH2_DMA1_Request(&M_SH2, 1); + SH2_DMA1_Request(&S_SH2, 1); + } + + if (_32X_MINT & 1) + SH2_Interrupt(&M_SH2, 6); + if (_32X_SINT & 1) + SH2_Interrupt(&S_SH2, 6); + } +}*/ + + +INLINE int PWM_Update_Scale(pwm_chip* chip, int PWM_In) +{ + if (PWM_In == 0) + return 0; + + // TODO: Chilly Willy's new scaling algorithm breaks drx's Sonic 1 32X (with PWM drums). +#ifdef CHILLY_WILLY_SCALE + //return (((PWM_In & 0xFFF) - chip->PWM_Offset) * chip->PWM_Scale) >> (8 - PWM_Loudness); + // Knuckles' Chaotix: Tachy Touch uses the values 0xF?? for negative values + // This small modification fixes the terrible pops. + PWM_In &= 0xFFF; + if (PWM_In & 0x800) + PWM_In |= ~0xFFF; + return ((PWM_In - chip->PWM_Offset) * chip->PWM_Scale) >> (8 - PWM_Loudness); +#else + const int PWM_adjust = ((chip->PWM_Cycle >> 1) + 1); + int PWM_Ret = ((chip->PWM_In & 0xFFF) - PWM_adjust); + + // Increase PWM volume so it's audible. + PWM_Ret <<= (5+2); + + // Make sure the PWM isn't oversaturated. + if (PWM_Ret > 32767) + PWM_Ret = 32767; + else if (PWM_Ret < -32768) + PWM_Ret = -32768; + + return PWM_Ret; +#endif +} + + +void PWM_Update(pwm_chip* chip, int **buf, int length) +{ + int tmpOutL; + int tmpOutR; + int i; + + //if (!PWM_Enable) + // return; + + if (chip->PWM_Out_L == 0 && chip->PWM_Out_R == 0) + { + memset(buf[0], 0x00, length * sizeof(int)); + memset(buf[1], 0x00, length * sizeof(int)); + return; + } + + // New PWM scaling algorithm provided by Chilly Willy on the Sonic Retro forums. + tmpOutL = PWM_Update_Scale(chip, (int)chip->PWM_Out_L); + tmpOutR = PWM_Update_Scale(chip, (int)chip->PWM_Out_R); + + for (i = 0; i < length; i ++) + { + buf[0][i] = tmpOutL; + buf[1][i] = tmpOutR; + } +} + + +void pwm_update(void *_chip, stream_sample_t **outputs, int samples) +{ + pwm_chip *chip = (pwm_chip *) _chip; + + PWM_Update(chip, outputs, samples); +} + +void * device_start_pwm(int clock) +{ + /* allocate memory for the chip */ + //pwm_state *chip = get_safe_token(device); + pwm_chip *chip; + int rate; + + chip = (pwm_chip *) malloc(sizeof(pwm_chip)); + if (!chip) return chip; + + rate = 22020; // that's the rate the PWM is mostly used + chip->clock = clock; + + PWM_Init(chip); + /* allocate the stream */ + //chip->stream = stream_create(device, 0, 2, device->clock / 384, chip, rf5c68_update); + + return chip; +} + +void device_stop_pwm(void *chip) +{ + //pwm_chip *chip = &PWM_Chip[ChipID]; + //free(chip->ram); + free(chip); +} + +void device_reset_pwm(void *_chip) +{ + pwm_chip *chip = (pwm_chip *) _chip; + PWM_Init(chip); +} + +void pwm_chn_w(void *_chip, UINT8 Channel, UINT16 data) +{ + pwm_chip *chip = (pwm_chip *) _chip; + + if (chip->clock == 1) + { // old-style commands + switch(Channel) + { + case 0x00: + chip->PWM_Out_L = data; + break; + case 0x01: + chip->PWM_Out_R = data; + break; + case 0x02: + PWM_Set_Cycle(chip, data); + break; + case 0x03: + chip->PWM_Out_L = data; + chip->PWM_Out_R = data; + break; + } + } + else + { + switch(Channel) + { + case 0x00/2: // control register + PWM_Set_Int(chip, data >> 8); + break; + case 0x02/2: // cycle register + PWM_Set_Cycle(chip, data); + break; + case 0x04/2: // l ch + chip->PWM_Out_L = data; + break; + case 0x06/2: // r ch + chip->PWM_Out_R = data; + if (! chip->PWM_Mode) + { + if (chip->PWM_Out_L == chip->PWM_Out_R) + { + // fixes these terrible pops when + // starting/stopping/pausing the song + chip->PWM_Offset = data; + chip->PWM_Mode = 0x01; + } + } + break; + case 0x08/2: // mono ch + chip->PWM_Out_L = data; + chip->PWM_Out_R = data; + if (! chip->PWM_Mode) + { + chip->PWM_Offset = data; + chip->PWM_Mode = 0x01; + } + break; + } + } + + return; +} diff --git a/Frameworks/GME/gme/pwm.h b/Frameworks/GME/gme/pwm.h new file mode 100644 index 000000000..98ccdbc7d --- /dev/null +++ b/Frameworks/GME/gme/pwm.h @@ -0,0 +1,71 @@ +/*************************************************************************** + * Gens: PWM audio emulator. * + * * + * Copyright (c) 1999-2002 by Stéphane Dallongeville * + * Copyright (c) 2003-2004 by Stéphane Akhoun * + * Copyright (c) 2008 by David Korth * + * * + * This program is free software; you can redistribute it and/or modify it * + * under the terms of the GNU General Public License as published by the * + * Free Software Foundation; either version 2 of the License, or (at your * + * option) any later version. * + * * + * This program 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 General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License along * + * with this program; if not, write to the Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#define PWM_BUF_SIZE 4 + +#include "mamedef.h" + +/*extern unsigned char PWM_FULL_TAB[PWM_BUF_SIZE * PWM_BUF_SIZE]; + +extern unsigned short PWM_FIFO_R[8]; +extern unsigned short PWM_FIFO_L[8]; +extern unsigned int PWM_RP_R; +extern unsigned int PWM_WP_R; +extern unsigned int PWM_RP_L; +extern unsigned int PWM_WP_L; +extern unsigned int PWM_Cycles; +extern unsigned int PWM_Cycle; +extern unsigned int PWM_Cycle_Cnt; +extern unsigned int PWM_Int; +extern unsigned int PWM_Int_Cnt; +extern unsigned int PWM_Mode; +extern unsigned int PWM_Enable; +extern unsigned int PWM_Out_R; +extern unsigned int PWM_Out_L;*/ + +//void PWM_Init(void); +//void PWM_Recalc_Scale(void); + +/* Functions called by x86 asm. */ +//void PWM_Set_Cycle(unsigned int cycle); +//void PWM_Set_Int(unsigned int int_time); + +/* Functions called by C/C++ code only. */ +//void PWM_Clear_Timer(void); +//void PWM_Update_Timer(unsigned int cycle); +//void PWM_Update(int **buf, int length); + +#ifdef __cplusplus +extern "C" { +#endif + +void pwm_update(void *chip, stream_sample_t **outputs, int samples); + +void * device_start_pwm(int clock); +void device_stop_pwm(void *chip); +void device_reset_pwm(void *chip); + +void pwm_chn_w(void *chip, UINT8 Channel, UINT16 data); + +#ifdef __cplusplus +} +#endif diff --git a/Frameworks/GME/gme/qmix.c b/Frameworks/GME/gme/qmix.c new file mode 100644 index 000000000..7a855eec3 --- /dev/null +++ b/Frameworks/GME/gme/qmix.c @@ -0,0 +1,462 @@ +///////////////////////////////////////////////////////////////////////////// +// +// qmix - QSound mixer +// +///////////////////////////////////////////////////////////////////////////// + +#include "qmix.h" + +///////////////////////////////////////////////////////////////////////////// + +#define ANTICLICK_TIME (64) +#define ANTICLICK_THRESHHOLD (32) + +///////////////////////////////////////////////////////////////////////////// + +#define RENDERMAX (200) + +///////////////////////////////////////////////////////////////////////////// + +static const sint32 gauss_shuffled_reverse_table[1024] = { + 366,1305, 374, 0, 362,1304, 378, 0, 358,1304, 381, 0, 354,1304, 385, 0, 351,1304, 389, 0, 347,1304, 393, 0, 343,1303, 397, 0, 339,1303, 401, 0, + 336,1303, 405, 0, 332,1302, 410, 0, 328,1302, 414, 0, 325,1301, 418, 0, 321,1300, 422, 0, 318,1300, 426, 0, 314,1299, 430, 0, 311,1298, 434, 0, + 307,1297, 439, 1, 304,1297, 443, 1, 300,1296, 447, 1, 297,1295, 451, 1, 293,1294, 456, 1, 290,1293, 460, 1, 286,1292, 464, 1, 283,1291, 469, 1, + 280,1290, 473, 1, 276,1288, 477, 1, 273,1287, 482, 1, 270,1286, 486, 2, 267,1284, 491, 2, 263,1283, 495, 2, 260,1282, 499, 2, 257,1280, 504, 2, + 254,1279, 508, 2, 251,1277, 513, 2, 248,1275, 517, 3, 245,1274, 522, 3, 242,1272, 527, 3, 239,1270, 531, 3, 236,1269, 536, 3, 233,1267, 540, 4, + 230,1265, 545, 4, 227,1263, 550, 4, 224,1261, 554, 4, 221,1259, 559, 4, 218,1257, 563, 5, 215,1255, 568, 5, 212,1253, 573, 5, 210,1251, 577, 5, + 207,1248, 582, 6, 204,1246, 587, 6, 201,1244, 592, 6, 199,1241, 596, 6, 196,1239, 601, 7, 193,1237, 606, 7, 191,1234, 611, 7, 188,1232, 615, 8, + 186,1229, 620, 8, 183,1227, 625, 8, 180,1224, 630, 9, 178,1221, 635, 9, 175,1219, 640, 9, 173,1216, 644, 10, 171,1213, 649, 10, 168,1210, 654, 10, + 166,1207, 659, 11, 163,1205, 664, 11, 161,1202, 669, 11, 159,1199, 674, 12, 156,1196, 678, 12, 154,1193, 683, 13, 152,1190, 688, 13, 150,1186, 693, 14, + 147,1183, 698, 14, 145,1180, 703, 15, 143,1177, 708, 15, 141,1174, 713, 15, 139,1170, 718, 16, 137,1167, 723, 16, 134,1164, 728, 17, 132,1160, 732, 17, + 130,1157, 737, 18, 128,1153, 742, 19, 126,1150, 747, 19, 124,1146, 752, 20, 122,1143, 757, 20, 120,1139, 762, 21, 118,1136, 767, 21, 117,1132, 772, 22, + 115,1128, 777, 23, 113,1125, 782, 23, 111,1121, 787, 24, 109,1117, 792, 24, 107,1113, 797, 25, 106,1109, 802, 26, 104,1106, 806, 27, 102,1102, 811, 27, + 100,1098, 816, 28, 99,1094, 821, 29, 97,1090, 826, 29, 95,1086, 831, 30, 94,1082, 836, 31, 92,1078, 841, 32, 90,1074, 846, 32, 89,1070, 851, 33, + 87,1066, 855, 34, 86,1061, 860, 35, 84,1057, 865, 36, 83,1053, 870, 36, 81,1049, 875, 37, 80,1045, 880, 38, 78,1040, 884, 39, 77,1036, 889, 40, + 76,1032, 894, 41, 74,1027, 899, 42, 73,1023, 904, 43, 71,1019, 908, 44, 70,1014, 913, 45, 69,1010, 918, 46, 67,1005, 923, 47, 66,1001, 927, 48, + 65, 997, 932, 49, 64, 992, 937, 50, 62, 988, 941, 51, 61, 983, 946, 52, 60, 978, 951, 53, 59, 974, 955, 54, 58, 969, 960, 55, 56, 965, 965, 56, + 55, 960, 969, 58, 54, 955, 974, 59, 53, 951, 978, 60, 52, 946, 983, 61, 51, 941, 988, 62, 50, 937, 992, 64, 49, 932, 997, 65, 48, 927,1001, 66, + 47, 923,1005, 67, 46, 918,1010, 69, 45, 913,1014, 70, 44, 908,1019, 71, 43, 904,1023, 73, 42, 899,1027, 74, 41, 894,1032, 76, 40, 889,1036, 77, + 39, 884,1040, 78, 38, 880,1045, 80, 37, 875,1049, 81, 36, 870,1053, 83, 36, 865,1057, 84, 35, 860,1061, 86, 34, 855,1066, 87, 33, 851,1070, 89, + 32, 846,1074, 90, 32, 841,1078, 92, 31, 836,1082, 94, 30, 831,1086, 95, 29, 826,1090, 97, 29, 821,1094, 99, 28, 816,1098, 100, 27, 811,1102, 102, + 27, 806,1106, 104, 26, 802,1109, 106, 25, 797,1113, 107, 24, 792,1117, 109, 24, 787,1121, 111, 23, 782,1125, 113, 23, 777,1128, 115, 22, 772,1132, 117, + 21, 767,1136, 118, 21, 762,1139, 120, 20, 757,1143, 122, 20, 752,1146, 124, 19, 747,1150, 126, 19, 742,1153, 128, 18, 737,1157, 130, 17, 732,1160, 132, + 17, 728,1164, 134, 16, 723,1167, 137, 16, 718,1170, 139, 15, 713,1174, 141, 15, 708,1177, 143, 15, 703,1180, 145, 14, 698,1183, 147, 14, 693,1186, 150, + 13, 688,1190, 152, 13, 683,1193, 154, 12, 678,1196, 156, 12, 674,1199, 159, 11, 669,1202, 161, 11, 664,1205, 163, 11, 659,1207, 166, 10, 654,1210, 168, + 10, 649,1213, 171, 10, 644,1216, 173, 9, 640,1219, 175, 9, 635,1221, 178, 9, 630,1224, 180, 8, 625,1227, 183, 8, 620,1229, 186, 8, 615,1232, 188, + 7, 611,1234, 191, 7, 606,1237, 193, 7, 601,1239, 196, 6, 596,1241, 199, 6, 592,1244, 201, 6, 587,1246, 204, 6, 582,1248, 207, 5, 577,1251, 210, + 5, 573,1253, 212, 5, 568,1255, 215, 5, 563,1257, 218, 4, 559,1259, 221, 4, 554,1261, 224, 4, 550,1263, 227, 4, 545,1265, 230, 4, 540,1267, 233, + 3, 536,1269, 236, 3, 531,1270, 239, 3, 527,1272, 242, 3, 522,1274, 245, 3, 517,1275, 248, 2, 513,1277, 251, 2, 508,1279, 254, 2, 504,1280, 257, + 2, 499,1282, 260, 2, 495,1283, 263, 2, 491,1284, 267, 2, 486,1286, 270, 1, 482,1287, 273, 1, 477,1288, 276, 1, 473,1290, 280, 1, 469,1291, 283, + 1, 464,1292, 286, 1, 460,1293, 290, 1, 456,1294, 293, 1, 451,1295, 297, 1, 447,1296, 300, 1, 443,1297, 304, 1, 439,1297, 307, 0, 434,1298, 311, + 0, 430,1299, 314, 0, 426,1300, 318, 0, 422,1300, 321, 0, 418,1301, 325, 0, 414,1302, 328, 0, 410,1302, 332, 0, 405,1303, 336, 0, 401,1303, 339, + 0, 397,1303, 343, 0, 393,1304, 347, 0, 389,1304, 351, 0, 385,1304, 354, 0, 381,1304, 358, 0, 378,1304, 362, 0, 374,1305, 366, 0, 370,1305, 370, +}; + +static const sint32 pan_table[33] = { + 0, 724,1024,1254,1448,1619,1774,1916, +2048,2172,2290,2401,2508,2611,2709,2804, +2896,2985,3072,3156,3238,3318,3396,3473, +3547,3620,3692,3762,3831,3899,3966,4031, +4096}; + +///////////////////////////////////////////////////////////////////////////// +// +// Static information +// +sint32 EMU_CALL _qmix_init(void) { return 0; } + +///////////////////////////////////////////////////////////////////////////// +// +// State information +// +#define QMIXSTATE ((struct QMIX_STATE*)(state)) + +struct QMIX_CHAN { + uint32 on; + uint32 startbank; + uint32 startaddr; + uint32 curbank; + uint32 curaddr; + uint32 startloop; + uint32 startend; + uint32 curloop; + uint32 curend; + uint32 phase; + uint32 pitch; + uint32 vol; + uint32 pan; + sint32 current_mix_l; + sint32 current_mix_r; + sint32 sample[4]; + sint32 sample_last_l; + sint32 sample_last_r; + sint32 sample_anticlick_l; + sint32 sample_anticlick_r; + sint32 sample_anticlick_remaining_l; + sint32 sample_anticlick_remaining_r; +}; + +///////////////////////////////////////////////////////////////////////////// + +static EMU_INLINE void get_anticlicked_samples( + struct QMIX_CHAN *chan, sint32 *l, sint32 *r +) { + sint32 out, remain; + remain = chan->sample_anticlick_remaining_l; + if(remain) { + sint32 diff = chan->sample_last_l - chan->sample_anticlick_l; + if(diff < 0) { diff = -diff; } + if(diff < ANTICLICK_THRESHHOLD) { + out = chan->sample_last_l; + chan->sample_anticlick_remaining_l = 0; + } else { + out = ( + chan->sample_last_l * (ANTICLICK_TIME-remain) + + chan->sample_anticlick_l * (remain) + ) / ANTICLICK_TIME; + chan->sample_anticlick_remaining_l--; + } + } else { + out = chan->sample_last_l; + } + *l = out; + + remain = chan->sample_anticlick_remaining_r; + if(remain) { + sint32 diff = chan->sample_last_r - chan->sample_anticlick_r; + if(diff < 0) { diff = -diff; } + if(diff < ANTICLICK_THRESHHOLD) { + out = chan->sample_last_r; + chan->sample_anticlick_remaining_r = 0; + } else { + out = ( + chan->sample_last_r * (ANTICLICK_TIME-remain) + + chan->sample_anticlick_r * (remain) + ) / ANTICLICK_TIME; + chan->sample_anticlick_remaining_r--; + } + } else { + out = chan->sample_last_r; + } + *r = out; + +} + +///////////////////////////////////////////////////////////////////////////// + +static EMU_INLINE void anticlick(struct QMIX_CHAN *chan) { + sint32 l, r; + get_anticlicked_samples(chan, &l, &r); + chan->sample_anticlick_l = l; + chan->sample_anticlick_r = r; + chan->sample_anticlick_remaining_l = ANTICLICK_TIME; + chan->sample_anticlick_remaining_r = ANTICLICK_TIME; +} + +///////////////////////////////////////////////////////////////////////////// + +struct QMIX_STATE { + uint8 *sample_rom; + uint32 sample_rom_size; + uint32 pitchscaler; + struct QMIX_CHAN chan[16]; + sint32 last_in_l; + sint32 last_in_r; + sint32 last_out_l; + sint32 last_out_r; + sint32 acc_l; + sint32 acc_r; +}; + +uint32 EMU_CALL _qmix_get_state_size(void) { + return sizeof(struct QMIX_STATE); +} + +void EMU_CALL _qmix_clear_state(void *state) { + memset(state, 0, sizeof(struct QMIX_STATE)); + + +} + +void EMU_CALL _qmix_set_sample_rom(void *state, void *rom, uint32 size) { + QMIXSTATE->sample_rom = rom; + QMIXSTATE->sample_rom_size = size; +} + +///////////////////////////////////////////////////////////////////////////// + +static EMU_INLINE void chan_advance( + struct QMIX_STATE *state, + struct QMIX_CHAN *chan +) { + uint32 rom_addr = chan->curbank + chan->curaddr; + if(rom_addr >= state->sample_rom_size) rom_addr = 0; + chan->sample[0] = chan->sample[1]; + chan->sample[1] = chan->sample[2]; + chan->sample[2] = chan->sample[3]; + chan->sample[3] = (sint32)((sint8)(state->sample_rom[rom_addr])); + chan->curaddr++; +// FIXME: MAME thinks this is >=, but is it > ? + if(chan->curaddr >= chan->curend) { +// if(!chan->curloop) { +// chan->on = 0; +// chan->curaddr--; +// } else { + chan->curaddr = chan->curend - chan->curloop; +// chan->curaddr -= 1 + chan->curloop; +// } + } + chan->curaddr &= 0xFFFF; +} + +///////////////////////////////////////////////////////////////////////////// + +static EMU_INLINE sint32 chan_get_resampled( + struct QMIX_STATE *state, + struct QMIX_CHAN *chan +) { + sint32 sum; + sint32 phase = chan->phase & 0xFFF; + +// sum = chan->sample[2]; +// sum <<= 8; + + const sint32 *gauss = (sint32*) + (((sint8*)gauss_shuffled_reverse_table) + (phase & 0x0FF0)); + sum = chan->sample[0] * gauss[0]; + sum += chan->sample[1] * gauss[1]; + sum += chan->sample[2] * gauss[2]; + sum += chan->sample[3] * gauss[3]; + sum /= 8; + +// sum = chan->sample[1] * (0x1000-phase); +// sum += chan->sample[2] * ( phase); +// sum >>= 4; + + chan->phase += chan->pitch; + while(chan->phase >= 0x1000) { + chan_advance(state, chan); + chan->phase -= 0x1000; + } + + return sum; +} + +static EMU_INLINE void chan_get_stereo_anticlicked( + struct QMIX_STATE *state, + struct QMIX_CHAN *chan, + sint32 *l, + sint32 *r +) { + if(!chan->on) { + chan->sample_last_l = 0; + chan->sample_last_r = 0; + } else { + sint32 out = chan_get_resampled(state, chan); + chan->sample_last_l = (out * chan->current_mix_l) / 0x8000; + chan->sample_last_r = (out * chan->current_mix_r) / 0x8000; + // if we suddenly keyed off, perform an anticlick here + if(!chan->on) { anticlick(chan); } + } + get_anticlicked_samples(chan, l, r); +} + +///////////////////////////////////////////////////////////////////////////// + +static void recalc_mix(struct QMIX_CHAN *chan) { + sint32 realpan = (chan->pan & 0x3F) - 0x10; + sint32 realvol = chan->vol & 0xFFFF; + if(realpan < 0x00) realpan = 0x00; + if(realpan > 0x20) realpan = 0x20; + +// chan->current_mix_l = realvol << 3; +// chan->current_mix_r = realvol << 3; +// if(realpan < 0x10) { +// chan->current_mix_r *= realpan; +// chan->current_mix_r >>= 4; +// } +// if(realpan > 0x10) { +// chan->current_mix_l *= (0x20-realpan); +// chan->current_mix_l >>= 4; +// } + +// chan->current_mix_l = ((0x20-realpan) * realvol) >> 1; +// chan->current_mix_r = (( realpan) * realvol) >> 1; + + chan->current_mix_l = (realvol * pan_table[0x20-realpan]) / 0x2000; + chan->current_mix_r = (realvol * pan_table[ realpan]) / 0x2000; + + // perform anticlick + //anticlick(chan); +} + +///////////////////////////////////////////////////////////////////////////// +// +// Command handling +// +//#include + +void EMU_CALL _qmix_command(void *state, uint8 cmd, uint16 data) { + struct QMIX_CHAN *chan; + uint32 ch = 0; + uint32 reg = 99; +//printf("qmix command 0x%02X:0x%04X\n",cmd,data); + if(cmd < 0x80) { + reg = cmd & 7; + ch = cmd >> 3; + } else if(cmd < 0x90) { + reg = 8; + ch = cmd - 0x80; + } else if(cmd >= 0xBA && cmd < 0xCA) { + reg = 9; + ch = cmd - 0xBA; + } else { + reg = 99; + ch = 0; + } + chan = QMIXSTATE->chan + ch; + switch(reg) { + case 0: // bank + ch = (ch+1) & 0xF; chan = QMIXSTATE->chan + ch; + //printf("qmix: bank ch%X = %04X\n",ch,data); + chan->startbank = (((uint32)data) & 0x7F) << 16; + break; + case 1: // start + //printf("qmix: start ch%X = %04X\n",ch,data); + chan->startaddr = ((uint32)data) & 0xFFFF; + break; + case 2: // pitch + //printf("qmix: pitch ch%X = %04X\n",ch,data); + chan->pitch = (((uint32)(data & 0xFFFF)) * QMIXSTATE->pitchscaler) / 0x10000; + if (chan->pitch == 0) { + chan->on = 0; + anticlick(chan); + } + break; + case 3: // unknown + break; + case 4: // loop start + //printf("qmix: loop ch%X = %04X\n",ch,data); + chan->startloop = data; + break; + case 5: // end + //printf("qmix: end ch%X = %04X\n",ch,data); + chan->startend = data; + break; + case 6: // volume + //printf("qmix: vol ch%X = %04X\n",ch,data); +//printf("volume=%04X\n",data); +// if(!data) { +// chan->on = 0; +// } else { +// chan->on = 1; +// chan->address = chan->start; +// chan->phase = 0; +// } + //printf("qmix: unknown reg3 ch%X = %04X\n",ch,data); + if(data == 0) { + chan->on = 0; + anticlick(chan); + } else if (chan->on == 0) { + chan->on = 1; + chan->curbank = chan->startbank; + chan->curaddr = chan->startaddr; + chan->curloop = chan->startloop; + chan->curend = chan->startend; + chan->phase = 0; + chan->sample[0] = 0; + chan->sample[1] = 0; + chan->sample[2] = 0; + chan->sample[3] = 0; + anticlick(chan); + } + + chan->vol = data; + recalc_mix(chan); + break; + case 7: // unknown + //printf("qmix: unknown reg7 ch%X = %04X\n",ch,data); + break; + case 8: // pan (0x110-0x130) + //printf("qmix: pan ch%X = %04X\n",ch,data); +//printf("pan=%04X\n",data); + chan->pan = data; + recalc_mix(chan); + break; + case 9: // ADSR? + //printf("qmix: unknown reg9 ch%X = %04X\n",ch,data); + break; + default: + //printf("qmix: unknown reg %02X = %04X\n",cmd,data); + break; + } +} + +///////////////////////////////////////////////////////////////////////////// +// +// Rendering +// +static void render( + struct QMIX_STATE *state, + sint16 *buf, + uint32 samples +) { + sint32 buf_l[RENDERMAX]; + sint32 buf_r[RENDERMAX]; + sint32 l, r; + uint32 s; + int ch; + memset(buf_l, 0, 4 * samples); + memset(buf_r, 0, 4 * samples); + for(ch = 0; ch < 16; ch++) { + struct QMIX_CHAN *chan = state->chan + ch; + for(s = 0; s < samples; s++) { + chan_get_stereo_anticlicked(state, chan, &l, &r); + buf_l[s] += l; + buf_r[s] += r; + } + } + if(!buf) return; + for(s = 0; s < samples; s++) { + sint32 diff_l = buf_l[s] - state->last_in_l; + sint32 diff_r = buf_r[s] - state->last_in_r; + state->last_in_l = buf_l[s]; + state->last_in_r = buf_r[s]; + l = ((state->last_out_l * 255) / 256) + diff_l; + r = ((state->last_out_r * 255) / 256) + diff_r; + state->last_out_l = l; + state->last_out_r = r; +// l /= 2; +// r /= 2; + l *= 8; + r *= 8; + if(l > ( 32767)) l = ( 32767); + if(l < (-32768)) l = (-32768); + if(r > ( 32767)) r = ( 32767); + if(r < (-32768)) r = (-32768); + *buf++ = l; + *buf++ = r; + } +} + +///////////////////////////////////////////////////////////////////////////// + +void EMU_CALL _qmix_render(void *state, sint16 *buf, uint32 samples) { +//printf("qmix render %u samples\n",samples); + for(; samples >= RENDERMAX; samples -= RENDERMAX) { + render(QMIXSTATE, buf, RENDERMAX); + if(buf) buf += 2 * RENDERMAX; + } + if(samples) { + render(QMIXSTATE, buf, samples); + } +} + +///////////////////////////////////////////////////////////////////////////// + +void EMU_CALL _qmix_set_sample_rate(void *state, uint32 rate) { + if(rate < 1) rate = 1; + QMIXSTATE->pitchscaler = (65536 * 24000) / rate; +} + +///////////////////////////////////////////////////////////////////////////// diff --git a/Frameworks/GME/gme/qmix.h b/Frameworks/GME/gme/qmix.h new file mode 100644 index 000000000..2ce3ebf8d --- /dev/null +++ b/Frameworks/GME/gme/qmix.h @@ -0,0 +1,29 @@ +///////////////////////////////////////////////////////////////////////////// +// +// qmix - QSound mixer +// +///////////////////////////////////////////////////////////////////////////// + +#ifndef __Q_QMIX_H__ +#define __Q_QMIX_H__ + +#include "emuconfig.h" + +#ifdef __cplusplus +extern "C" { +#endif + +sint32 EMU_CALL _qmix_init(void); +uint32 EMU_CALL _qmix_get_state_size(void); +void EMU_CALL _qmix_clear_state(void *state); + +void EMU_CALL _qmix_set_sample_rate(void *state, uint32 rate); +void EMU_CALL _qmix_set_sample_rom(void *state, void *rom, uint32 size); +void EMU_CALL _qmix_command(void *state, uint8 cmd, uint16 data); +void EMU_CALL _qmix_render(void *state, sint16 *buf, uint32 samples); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/Frameworks/GME/gme/rf5c68.c b/Frameworks/GME/gme/rf5c68.c new file mode 100644 index 000000000..69286ee70 --- /dev/null +++ b/Frameworks/GME/gme/rf5c68.c @@ -0,0 +1,447 @@ +/*********************************************************/ +/* ricoh RF5C68(or clone) PCM controller */ +/*********************************************************/ + +#include +#include +//#include "sndintrf.h" +//#include "streams.h" +#include "rf5c68.h" +#include + +#define NULL ((void *)0) + + +#define NUM_CHANNELS (8) +#define WRITES_PER_SAMPLE 12 + + + +typedef struct _pcm_channel pcm_channel; +struct _pcm_channel +{ + UINT8 enable; + UINT8 env; + UINT8 pan; + UINT8 start; + UINT32 addr; + UINT16 step; + UINT16 loopst; + UINT8 Muted; +}; + +typedef struct _mem_stream mem_stream; +struct _mem_stream +{ + UINT32 BaseAddr; + UINT32 EndAddr; + UINT32 CurAddr; + const UINT8* MemPnt; +}; + + +typedef struct _rf5c68_state rf5c68_state; +struct _rf5c68_state +{ + //sound_stream * stream; + pcm_channel chan[NUM_CHANNELS]; + UINT8 cbank; + UINT8 wbank; + UINT8 enable; + UINT32 datasize; + UINT8* data; + //void (*sample_callback)(running_device* device,int channel); + mem_stream memstrm; +}; + + +static void rf5c68_mem_stream_flush(rf5c68_state *chip); + +/*INLINE rf5c68_state *get_safe_token(const device_config *device) +{ + assert(device != NULL); + assert(device->token != NULL); + assert(device->type == SOUND); + assert(sound_get_type(device) == SOUND_RF5C68); + return (rf5c68_state *)device->token; +}*/ + +/************************************************/ +/* RF5C68 stream update */ +/************************************************/ + +static void memstream_sample_check(rf5c68_state *chip, UINT32 addr) +{ + mem_stream* ms = &chip->memstrm; + + if (addr >= ms->CurAddr) + { + if (addr - ms->CurAddr <= WRITES_PER_SAMPLE * 5) + { + ms->CurAddr -= WRITES_PER_SAMPLE * 2; + if (ms->CurAddr < ms->BaseAddr) + ms->CurAddr = ms->BaseAddr; + } + } + else + { + if (ms->CurAddr - addr <= WRITES_PER_SAMPLE * 4) + { + rf5c68_mem_stream_flush(chip); + } + } + + return; +} + +//static STREAM_UPDATE( rf5c68_update ) +void rf5c68_update(void *_chip, stream_sample_t **outputs, int samples) +{ + //rf5c68_state *chip = (rf5c68_state *)param; + rf5c68_state *chip = (rf5c68_state *) _chip; + mem_stream* ms = &chip->memstrm; + stream_sample_t *left = outputs[0]; + stream_sample_t *right = outputs[1]; + int i, j; + + /* start with clean buffers */ + memset(left, 0, samples * sizeof(*left)); + memset(right, 0, samples * sizeof(*right)); + + /* bail if not enabled */ + if (!chip->enable) + return; + + /* loop over channels */ + for (i = 0; i < NUM_CHANNELS; i++) + { + pcm_channel *chan = &chip->chan[i]; + + /* if this channel is active, accumulate samples */ + if (chan->enable && ! chan->Muted) + { + int lv = (chan->pan & 0x0f) * chan->env; + int rv = ((chan->pan >> 4) & 0x0f) * chan->env; + + /* loop over the sample buffer */ + for (j = 0; j < samples; j++) + { + int sample; + + /* trigger sample callback */ + /*if(chip->sample_callback) + { + if(((chan->addr >> 11) & 0xfff) == 0xfff) + chip->sample_callback(chip->device,((chan->addr >> 11)/0x2000)); + }*/ + + memstream_sample_check(chip, (chan->addr >> 11) & 0xffff); + /* fetch the sample and handle looping */ + sample = chip->data[(chan->addr >> 11) & 0xffff]; + if (sample == 0xff) + { + chan->addr = chan->loopst << 11; + sample = chip->data[(chan->addr >> 11) & 0xffff]; + + /* if we loop to a loop point, we're effectively dead */ + if (sample == 0xff) + break; + } + chan->addr += chan->step; + + /* add to the buffer */ + if (sample & 0x80) + { + sample &= 0x7f; + left[j] += (sample * lv) >> 5; + right[j] += (sample * rv) >> 5; + } + else + { + left[j] -= (sample * lv) >> 5; + right[j] -= (sample * rv) >> 5; + } + } + } + } + + if (samples && ms->CurAddr < ms->EndAddr) + { + i = WRITES_PER_SAMPLE * samples; + if (ms->CurAddr + i > ms->EndAddr) + i = ms->EndAddr - ms->CurAddr; + + memcpy(chip->data + ms->CurAddr, ms->MemPnt + (ms->CurAddr - ms->BaseAddr), i); + ms->CurAddr += i; + } + + // I think, this is completely useless + /* now clamp and shift the result (output is only 10 bits) */ + /*for (j = 0; j < samples; j++) + { + stream_sample_t temp; + + temp = left[j]; + if (temp > 32767) temp = 32767; + else if (temp < -32768) temp = -32768; + left[j] = temp & ~0x3f; + + temp = right[j]; + if (temp > 32767) temp = 32767; + else if (temp < -32768) temp = -32768; + right[j] = temp & ~0x3f; + }*/ +} + + +/************************************************/ +/* RF5C68 start */ +/************************************************/ + +//static DEVICE_START( rf5c68 ) +void * device_start_rf5c68() +{ + //const rf5c68_interface* intf = (const rf5c68_interface*)device->baseconfig().static_config(); + + /* allocate memory for the chip */ + //rf5c68_state *chip = get_safe_token(device); + rf5c68_state *chip; + int chn; + + chip = (rf5c68_state *) malloc(sizeof(rf5c68_state)); + if (!chip) return chip; + + chip->datasize = 0x10000; + chip->data = (UINT8*)malloc(chip->datasize); + + /* allocate the stream */ + //chip->stream = stream_create(device, 0, 2, device->clock / 384, chip, rf5c68_update); + + /* set up callback */ + /*if(intf != NULL) + chip->sample_callback = intf->sample_end_callback; + else + chip->sample_callback = NULL;*/ + for (chn = 0; chn < NUM_CHANNELS; chn ++) + chip->chan[chn].Muted = 0x00; + + return chip; +} + +void device_stop_rf5c68(void *_chip) +{ + rf5c68_state *chip = (rf5c68_state *) _chip; + free(chip->data); chip->data = NULL; + free(chip); +} + +void device_reset_rf5c68(void *_chip) +{ + rf5c68_state *chip = (rf5c68_state *) _chip; + int i; + pcm_channel* chan; + mem_stream* ms = &chip->memstrm; + + // Clear the PCM memory. + memset(chip->data, 0x00, chip->datasize); + + chip->enable = 0; + chip->cbank = 0; + chip->wbank = 0; + + /* clear channel registers */ + for (i = 0; i < NUM_CHANNELS; i ++) + { + chan = &chip->chan[i]; + chan->enable = 0; + chan->env = 0; + chan->pan = 0; + chan->start = 0; + chan->addr = 0; + chan->step = 0; + chan->loopst = 0; + } + + ms->BaseAddr = 0x0000; + ms->CurAddr = 0x0000; + ms->EndAddr = 0x0000; + ms->MemPnt = NULL; +} + +/************************************************/ +/* RF5C68 write register */ +/************************************************/ + +//WRITE8_DEVICE_HANDLER( rf5c68_w ) +void rf5c68_w(void *_chip, offs_t offset, UINT8 data) +{ + //rf5c68_state *chip = get_safe_token(device); + rf5c68_state *chip = (rf5c68_state *) _chip; + pcm_channel *chan = &chip->chan[chip->cbank]; + int i; + + /* force the stream to update first */ + //stream_update(chip->stream); + + /* switch off the address */ + switch (offset) + { + case 0x00: /* envelope */ + chan->env = data; + break; + + case 0x01: /* pan */ + chan->pan = data; + break; + + case 0x02: /* FDL */ + chan->step = (chan->step & 0xff00) | (data & 0x00ff); + break; + + case 0x03: /* FDH */ + chan->step = (chan->step & 0x00ff) | ((data << 8) & 0xff00); + break; + + case 0x04: /* LSL */ + chan->loopst = (chan->loopst & 0xff00) | (data & 0x00ff); + break; + + case 0x05: /* LSH */ + chan->loopst = (chan->loopst & 0x00ff) | ((data << 8) & 0xff00); + break; + + case 0x06: /* ST */ + chan->start = data; + if (!chan->enable) + chan->addr = chan->start << (8 + 11); + break; + + case 0x07: /* control reg */ + chip->enable = (data >> 7) & 1; + if (data & 0x40) + chip->cbank = data & 7; + else + chip->wbank = data & 15; + break; + + case 0x08: /* channel on/off reg */ + for (i = 0; i < 8; i++) + { + chip->chan[i].enable = (~data >> i) & 1; + if (!chip->chan[i].enable) + chip->chan[i].addr = chip->chan[i].start << (8 + 11); + } + break; + } +} + + +/************************************************/ +/* RF5C68 read memory */ +/************************************************/ + +//READ8_DEVICE_HANDLER( rf5c68_mem_r ) +UINT8 rf5c68_mem_r(void *_chip, offs_t offset) +{ + //rf5c68_state *chip = get_safe_token(device); + rf5c68_state *chip = (rf5c68_state *) _chip; + return chip->data[chip->wbank * 0x1000 + offset]; +} + + +/************************************************/ +/* RF5C68 write memory */ +/************************************************/ + +//WRITE8_DEVICE_HANDLER( rf5c68_mem_w ) +void rf5c68_mem_w(void *_chip, offs_t offset, UINT8 data) +{ + //rf5c68_state *chip = get_safe_token(device); + rf5c68_state *chip = (rf5c68_state *) _chip; + rf5c68_mem_stream_flush(chip); + chip->data[chip->wbank * 0x1000 | offset] = data; +} + +static void rf5c68_mem_stream_flush(rf5c68_state *chip) +{ + mem_stream* ms = &chip->memstrm; + + if (ms->CurAddr >= ms->EndAddr) + return; + + memcpy(chip->data + ms->CurAddr, ms->MemPnt + (ms->CurAddr - ms->BaseAddr), ms->EndAddr - ms->CurAddr); + ms->CurAddr = ms->EndAddr; + + return; +} + +void rf5c68_write_ram(void *_chip, offs_t DataStart, offs_t DataLength, const UINT8* RAMData) +{ + rf5c68_state *chip = (rf5c68_state *) _chip; + mem_stream* ms = &chip->memstrm; + UINT16 BytCnt; + + if (DataStart >= chip->datasize) + return; + if (DataStart + DataLength > chip->datasize) + DataLength = chip->datasize - DataStart; + + //memcpy(chip->data + (chip->wbank * 0x1000 | DataStart), RAMData, DataLength); + + rf5c68_mem_stream_flush(chip); + + ms->BaseAddr = chip->wbank * 0x1000 | DataStart; + ms->CurAddr = ms->BaseAddr; + ms->EndAddr = ms->BaseAddr + DataLength; + ms->MemPnt = RAMData; + + BytCnt = WRITES_PER_SAMPLE; + if (ms->CurAddr + BytCnt > ms->EndAddr) + BytCnt = ms->EndAddr - ms->CurAddr; + + memcpy(chip->data + ms->CurAddr, ms->MemPnt + (ms->CurAddr - ms->BaseAddr), BytCnt); + ms->CurAddr += BytCnt; + + return; +} + + +void rf5c68_set_mute_mask(void *_chip, UINT32 MuteMask) +{ + rf5c68_state *chip = (rf5c68_state *) _chip; + unsigned char CurChn; + + for (CurChn = 0; CurChn < NUM_CHANNELS; CurChn ++) + chip->chan[CurChn].Muted = (MuteMask >> CurChn) & 0x01; + + return; +} + + + +/************************************************************************** + * Generic get_info + **************************************************************************/ + +/*DEVICE_GET_INFO( rf5c68 ) +{ + switch (state) + { + // --- the following bits of info are returned as 64-bit signed integers --- + case DEVINFO_INT_TOKEN_BYTES: info->i = sizeof(rf5c68_state); break; + + // --- the following bits of info are returned as pointers to data or functions --- + case DEVINFO_FCT_START: info->start = DEVICE_START_NAME( rf5c68 ); break; + case DEVINFO_FCT_STOP: // Nothing break; + case DEVINFO_FCT_RESET: // Nothing break; + + // --- the following bits of info are returned as NULL-terminated strings --- + case DEVINFO_STR_NAME: strcpy(info->s, "RF5C68"); break; + case DEVINFO_STR_FAMILY: strcpy(info->s, "Ricoh PCM"); break; + case DEVINFO_STR_VERSION: strcpy(info->s, "1.0"); break; + case DEVINFO_STR_SOURCE_FILE: strcpy(info->s, __FILE__); break; + case DEVINFO_STR_CREDITS: strcpy(info->s, "Copyright Nicola Salmoria and the MAME Team"); break; + } +}*/ + +/**************** end of file ****************/ diff --git a/Frameworks/GME/gme/rf5c68.h b/Frameworks/GME/gme/rf5c68.h new file mode 100644 index 000000000..c2f3d07dc --- /dev/null +++ b/Frameworks/GME/gme/rf5c68.h @@ -0,0 +1,38 @@ +/*********************************************************/ +/* ricoh RF5C68(or clone) PCM controller */ +/*********************************************************/ + +#pragma once + +#include "mamedef.h" + +/******************************************/ +/*WRITE8_DEVICE_HANDLER( rf5c68_w ); + +READ8_DEVICE_HANDLER( rf5c68_mem_r ); +WRITE8_DEVICE_HANDLER( rf5c68_mem_w ); + +DEVICE_GET_INFO( rf5c68 ); +#define SOUND_RF5C68 DEVICE_GET_INFO_NAME( rf5c68 )*/ + +#ifdef __cplusplus +extern "C" { +#endif + +void rf5c68_update(void *chip, stream_sample_t **outputs, int samples); + +void * device_start_rf5c68(); +void device_stop_rf5c68(void *chip); +void device_reset_rf5c68(void *chip); + +void rf5c68_w(void *chip, offs_t offset, UINT8 data); + +UINT8 rf5c68_mem_r(void *chip, offs_t offset); +void rf5c68_mem_w(void *chip, offs_t offset, UINT8 data); +void rf5c68_write_ram(void *chip, offs_t DataStart, offs_t DataLength, const UINT8* RAMData); + +void rf5c68_set_mute_mask(void *chip, UINT32 MuteMask); + +#ifdef __cplusplus +} +#endif diff --git a/Frameworks/GME/gme/s_deltat.c b/Frameworks/GME/gme/s_deltat.c new file mode 100644 index 000000000..8684005b3 --- /dev/null +++ b/Frameworks/GME/gme/s_deltat.c @@ -0,0 +1,281 @@ +#include "kmsnddev.h" +#include "divfix.h" +#include "s_logtbl.h" +#include "s_deltat.h" +#include + +#define CPS_SHIFT 16 +#define PHASE_SHIFT 16 /* 16(fix) */ + +typedef struct { + KMIF_SOUND_DEVICE kmif; + KMIF_LOGTABLE *logtbl; + struct YMDELTATPCMSOUND_COMMON_TAG { + Int32 mastervolume; + Int32 step; + Int32 output; + Uint32 cnt; + Uint32 cps; + Uint32 phase; + Uint32 deltan; + Uint32 scale; + Uint32 mem; + Uint32 play; + Uint32 start; + Uint32 stop; + Int32 level32; + Uint8 key; + Uint8 level; + Uint8 granuality; + Uint8 pad4_3; + Uint8 regs[0x10]; + } common; + Uint8 *romrambuf; + Uint32 romrammask; + Uint8 *rambuf; + Uint32 rammask; + Uint8 *rombuf; + Uint32 rommask; + Uint8 ymdeltatpcm_type; + Uint8 memshift; +} YMDELTATPCMSOUND; + +static Uint8 const table_step[8] = +{ + 1, 3, 5, 7, 9, 11, 13, 15, +}; +static Uint8 const table_scale[16] = +{ + 57, 57, 57, 57, 77, 102, 128, 153, + 57, 57, 57, 57, 77, 102, 128, 153, +}; + +__inline static void writeram(YMDELTATPCMSOUND *sndp, Uint32 v) +{ + sndp->rambuf[(sndp->common.mem >> 1) & sndp->rammask] = v; + sndp->common.mem += 1 << 1; +} + +__inline static Uint32 readram(YMDELTATPCMSOUND *sndp) +{ + Uint32 v; + v = sndp->romrambuf[(sndp->common.play >> 1) & sndp->romrammask]; + if (sndp->common.play & 1) + v &= 0x0F; + else + v >>= 4; + sndp->common.play += 1; + if (sndp->common.play >= sndp->common.stop) + { + if (sndp->common.regs[0] & 0x10) + { + sndp->common.play = sndp->common.start; + sndp->common.step = 0; + sndp->common.scale = 127; + } + else + { + sndp->common.key = 0; + } + } + return v; +} + +__inline static void DelrtatStep(YMDELTATPCMSOUND *sndp, Uint32 data) +{ + if (data & 8) + sndp->common.step -= (table_step[data & 7] * sndp->common.scale) >> 3; + else + sndp->common.step += (table_step[data & 7] * sndp->common.scale) >> 3; + if (sndp->common.step > ((1 << 15) - 1)) sndp->common.step = ((1 << 15) - 1); + if (sndp->common.step < -(1 << 15)) sndp->common.step = -(1 << 15); + sndp->common.scale = (sndp->common.scale * table_scale[data]) >> 6; + if (sndp->common.scale > 24576) sndp->common.scale = 24576; + if (sndp->common.scale < 127) sndp->common.scale = 127; +} + +#if (((-1) >> 1) == -1) +#define SSR(x, y) (((Int32)x) >> (y)) +#else +#define SSR(x, y) (((x) >= 0) ? ((x) >> (y)) : (-((-(x) - 1) >> (y)) - 1)) +#endif + +static int sndsynth(YMDELTATPCMSOUND *sndp ) +{ + if (!sndp->common.key) + return 0; + { + Uint32 step; + sndp->common.cnt += sndp->common.cps; + step = sndp->common.cnt >> CPS_SHIFT; + sndp->common.cnt &= (1 << CPS_SHIFT) - 1; + sndp->common.phase += step * sndp->common.deltan; + step = sndp->common.phase >> PHASE_SHIFT; + sndp->common.phase &= (1 << PHASE_SHIFT) - 1; + if (step) + { + do + { + DelrtatStep(sndp, readram(sndp)); + } while (--step); + sndp->common.output = sndp->common.step * sndp->common.level32; + sndp->common.output = SSR(sndp->common.output, 8 + 2); + } + } + return sndp->common.output; +} + + + +static void sndwrite(YMDELTATPCMSOUND *sndp, Uint32 a, Uint32 v) +{ + sndp->common.regs[a] = v; + switch (a) + { + /* START,REC,MEMDATA,REPEAT,SPOFF,--,--,RESET */ + case 0x00: /* Control Register 1 */ + if ((v & 0x80) && !sndp->common.key) + { + sndp->common.key = 1; + sndp->common.play = sndp->common.start; + sndp->common.step = 0; + sndp->common.scale = 127; + } + if (v & 1) sndp->common.key = 0; + break; + /* L,R,-,-,SAMPLE,DA/AD,RAMTYPE,ROM */ + case 0x01: /* Control Register 2 */ + sndp->romrambuf = (sndp->common.regs[1] & 1) ? sndp->rombuf : sndp->rambuf; + sndp->romrammask = (sndp->common.regs[1] & 1) ? sndp->rommask : sndp->rammask; + break; + case 0x02: /* Start Address L */ + case 0x03: /* Start Address H */ + sndp->common.granuality = (v & 2) ? 1 : 4; + sndp->common.start = ((sndp->common.regs[3] << 8) + sndp->common.regs[2]) << (sndp->memshift + 1); + sndp->common.mem = sndp->common.start; + break; + case 0x04: /* Stop Address L */ + case 0x05: /* Stop Address H */ + sndp->common.stop = ((sndp->common.regs[5] << 8) + sndp->common.regs[4]) << (sndp->memshift + 1); + break; + case 0x06: /* Prescale L */ + case 0x07: /* Prescale H */ + break; + case 0x08: /* Data */ + if ((sndp->common.regs[0] & 0x60) == 0x60) writeram(sndp, v); + break; + case 0x09: /* Delta-N L */ + case 0x0A: /* Delta-N H */ + sndp->common.deltan = (sndp->common.regs[0xA] << 8) + sndp->common.regs[0x9]; + if (sndp->common.deltan < 0x100) sndp->common.deltan = 0x100; + break; + case 0x0B: /* Level Control */ + sndp->common.level = v; + sndp->common.level32 = ((Int32)(sndp->common.level * LogToLin(sndp->logtbl, sndp->common.mastervolume, LOG_LIN_BITS - 15))) >> 7; + sndp->common.output = sndp->common.step * sndp->common.level32; + sndp->common.output = SSR(sndp->common.output, 8 + 2); + break; + } +} + +static Uint32 sndread(YMDELTATPCMSOUND *sndp, Uint32 a) +{ + return 0; +} + +static void sndreset(YMDELTATPCMSOUND *sndp, Uint32 clock, Uint32 freq) +{ + XMEMSET(&sndp->common, 0, sizeof(sndp->common)); + sndp->common.cps = DivFix(clock, 72 * freq, CPS_SHIFT); + sndp->romrambuf = (sndp->common.regs[1] & 1) ? sndp->rombuf : sndp->rambuf; + sndp->romrammask = (sndp->common.regs[1] & 1) ? sndp->rommask : sndp->rammask; + sndp->common.granuality = 4; +} + +static void sndvolume(YMDELTATPCMSOUND *sndp, Int32 volume) +{ + volume = (volume << (LOG_BITS - 8)) << 1; + sndp->common.mastervolume = volume; + sndp->common.level32 = ((Int32)(sndp->common.level * LogToLin(sndp->logtbl, sndp->common.mastervolume, LOG_LIN_BITS - 15))) >> 7; + sndp->common.output = sndp->common.step * sndp->common.level32; + sndp->common.output = SSR(sndp->common.output, 8 + 2); +} + +static void sndrelease(YMDELTATPCMSOUND *sndp) +{ + if (sndp->logtbl) sndp->logtbl->release(sndp->logtbl->ctx); + XFREE(sndp); +} + +static void setinst(YMDELTATPCMSOUND *sndp, Uint32 n, void *p, Uint32 l) +{ + if (n) return; + if (p) + { + sndp->rombuf = (Uint8*) p; + sndp->rommask = l - 1; + sndp->romrambuf = (sndp->common.regs[1] & 1) ? sndp->rombuf : sndp->rambuf; + sndp->romrammask = (sndp->common.regs[1] & 1) ? sndp->rommask : sndp->rammask; + } + else + { + sndp->rombuf = 0; + sndp->rommask = 0; + } + +} + +KMIF_SOUND_DEVICE *YMDELTATPCMSoundAlloc(Uint32 ymdeltatpcm_type) +{ + Uint32 ram_size; + YMDELTATPCMSOUND *sndp; + switch (ymdeltatpcm_type) + { + case YMDELTATPCM_TYPE_Y8950: + ram_size = 32 * 1024; + break; + case YMDELTATPCM_TYPE_YM2608: + ram_size = 256 * 1024; + break; + default: + ram_size = 0; + break; + } + sndp = (YMDELTATPCMSOUND*) XMALLOC(sizeof(YMDELTATPCMSOUND) + ram_size); + if (!sndp) return 0; + sndp->ymdeltatpcm_type = ymdeltatpcm_type; + switch (ymdeltatpcm_type) + { + case YMDELTATPCM_TYPE_Y8950: + sndp->memshift = 2; + break; + case YMDELTATPCM_TYPE_YM2608: + /* OPNA */ + sndp->memshift = 6; + break; + case YMDELTATPCM_TYPE_YM2610: + sndp->memshift = 9; + break; + } + sndp->kmif.ctx = sndp; + sndp->kmif.release = (void (*)( void* )) sndrelease; + sndp->kmif.synth = (int (*)( void* )) sndsynth; + sndp->kmif.volume = (void (*)( void*, int )) sndvolume; + sndp->kmif.reset = (void (*)( void*, Uint32, Uint32 )) sndreset; + sndp->kmif.write = (void (*)( void*, Uint32, Uint32 )) sndwrite; + sndp->kmif.read = (Uint32 (*)( void*, Uint32 )) sndread; + sndp->kmif.setinst = (void (*)( void*, Uint32, void*, Uint32 )) setinst; + /* RAM */ + sndp->rambuf = ram_size ? (Uint8 *)(sndp + 1) : 0; + sndp->rammask = ram_size ? (ram_size - 1) : 0; + /* ROM */ + sndp->rombuf = 0; + sndp->rommask = 0; + sndp->logtbl = LogTableAddRef(); + if (!sndp->logtbl) + { + sndrelease(sndp); + return 0; + } + return &sndp->kmif; +} diff --git a/Frameworks/GME/gme/s_deltat.h b/Frameworks/GME/gme/s_deltat.h new file mode 100644 index 000000000..89c9242c8 --- /dev/null +++ b/Frameworks/GME/gme/s_deltat.h @@ -0,0 +1,23 @@ +#ifndef S_DELTAT_H__ +#define S_DELTAT_H__ + +#include "kmsnddev.h" + +#ifdef __cplusplus +extern "C" { +#endif + +enum { + /* MSX-AUDIO */ YMDELTATPCM_TYPE_Y8950, + /* OPNA ADPCM */ YMDELTATPCM_TYPE_YM2608, + /* OPNB ADPCMB */ YMDELTATPCM_TYPE_YM2610 +}; + +KMIF_SOUND_DEVICE *YMDELTATPCMSoundAlloc(Uint32 ymdeltatpcm_type); + +#ifdef __cplusplus +} +#endif + +#endif /* S_DELTAT_H__ */ + diff --git a/Frameworks/GME/gme/s_logtbl.c b/Frameworks/GME/gme/s_logtbl.c new file mode 100644 index 000000000..0f455f2b7 --- /dev/null +++ b/Frameworks/GME/gme/s_logtbl.c @@ -0,0 +1,88 @@ +#include "nestypes.h" +#include "s_logtbl.h" + +#if STATIC_TABLES + +static void LogTableRelease(void *ctx) +{ +} + +static KMIF_LOGTABLE log_static_tables = { + &log_static_tables; + LogTableRelease, +#include "s_logt.h" +}; + + +KMIF_LOGTABLE *LogTableAddRef(void) +{ + log_static_tables.release = LogTableRelease; + return &log_static_tables; +} + +#else + +#include + +static volatile Uint32 log_tables_mutex = 0; +static Uint32 log_tables_refcount = 0; +static KMIF_LOGTABLE *log_tables = 0; + +static void LogTableRelease(void *ctx) +{ + ++log_tables_mutex; + while (log_tables_mutex != 1) + { + XSLEEP(0); + } + log_tables_refcount--; + if (!log_tables_refcount) + { + XFREE(ctx); + log_tables = 0; + } + --log_tables_mutex; +} + +static void LogTableCalc(KMIF_LOGTABLE *kmif_lt) +{ + Uint32 i; + double a; + for (i = 0; i < (1 << LOG_BITS); i++) + { + a = (1 << LOG_LIN_BITS) / pow(2, i / (double)(1 << LOG_BITS)); + kmif_lt->logtbl[i] = (Uint32)a; + } + kmif_lt->lineartbl[0] = LOG_LIN_BITS << LOG_BITS; + for (i = 1; i < (1 << LIN_BITS) + 1; i++) + { + Uint32 ua; + a = i << (LOG_LIN_BITS - LIN_BITS); + ua = (Uint32)((LOG_LIN_BITS - (log(a) / log(2))) * (1 << LOG_BITS)); + kmif_lt->lineartbl[i] = ua << 1; + } +} + +KMIF_LOGTABLE *LogTableAddRef(void) +{ + ++log_tables_mutex; + while (log_tables_mutex != 1) + { + XSLEEP(0); + } + if (!log_tables_refcount) + { + log_tables = (KMIF_LOGTABLE*) XMALLOC(sizeof(KMIF_LOGTABLE)); + if (log_tables) + { + log_tables->ctx = log_tables; + log_tables->release = LogTableRelease; + LogTableCalc(log_tables); + } + } + if (log_tables) log_tables_refcount++; + --log_tables_mutex; + return log_tables; +} + +#endif diff --git a/Frameworks/GME/gme/s_logtbl.h b/Frameworks/GME/gme/s_logtbl.h new file mode 100644 index 000000000..3abc79c11 --- /dev/null +++ b/Frameworks/GME/gme/s_logtbl.h @@ -0,0 +1,43 @@ +#ifndef S_LOGTBL_H__ +#define S_LOGTBL_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#define LOG_BITS 12 +#define LIN_BITS 7 +#define LOG_LIN_BITS 30 + +typedef struct +{ + void *ctx; + void (*release)(void *ctx); + Uint32 lineartbl[(1 << LIN_BITS) + 1]; + Uint32 logtbl[1 << LOG_BITS]; +} KMIF_LOGTABLE; + +KMIF_LOGTABLE *LogTableAddRef(void); + +__inline static Uint32 LinToLog(KMIF_LOGTABLE *kmif_lt, Int32 l) +{ + return (l < 0) ? (kmif_lt->lineartbl[-l] + 1) : kmif_lt->lineartbl[l]; +} + +__inline static Int32 LogToLin(KMIF_LOGTABLE *kmif_lt, Int32 l, Uint32 sft) +{ + Int32 ret; + Uint32 ofs; + ofs = l + (sft << (LOG_BITS + 1)); + sft = ofs >> (LOG_BITS + 1); + if (sft >= LOG_LIN_BITS) return 0; + ofs = (ofs >> 1) & ((1 << LOG_BITS) - 1); + ret = kmif_lt->logtbl[ofs] >> sft; + return (l & 1) ? -ret : ret; +} + +#ifdef __cplusplus +} +#endif + +#endif /* S_LOGTBL_H__ */ diff --git a/Frameworks/GME/gme/s_opl.c b/Frameworks/GME/gme/s_opl.c new file mode 100644 index 000000000..83b33e6e8 --- /dev/null +++ b/Frameworks/GME/gme/s_opl.c @@ -0,0 +1,1244 @@ +/* + s_opl.c -- YM2413/Y8950/YM3526/YM3812 emulator by Mamiya, 2001. + + References: + fmopl.c -- 1999,2000 written by Tatsuyuki Satoh (MAME development). + fmopl.c(fixed) -- 2000 modified by mamiya (NEZplug development). + fmgen.cpp -- 1999-2001 written by cisc. + emu2413.c -- a YM2413 emulator : written by Mitsutaka Okazaki 2001 + fmpac.ill -- 2000 created by sama. + fmpac.ill -- 2000 created by NARUTO. + fmpac.ill -- 2001 created by Okazaki. + YM2413 application manual +*/ + +#include "kmsnddev.h" +#include "divfix.h" +#include "s_logtbl.h" +#include "s_opltbl.h" +#include "s_opl.h" +#include "s_deltat.h" +#include + +#define PG_SHIFT 10 /* fix */ +#define CPS_SHIFTE 20 +#define CPS_SHIFTP 14 +#define LFO_SHIFT 16 + +#define OPLL_INST_WORK 0x40 +#define OPLL_INST_WORK2 (OPLL_INST_WORK + 8 * 0x13) + +#define AR_BITS 6 /* fix */ +#define AR_SHIFT 14 /* fix */ +#define EG_SHIFT 15 /* fix */ + +#define AR_PHASEMAX (((1 << AR_BITS) - 1) << AR_SHIFT) +#define EG_PHASEMAX (127 << EG_SHIFT) +#define EG_KEYOFF (128 << EG_SHIFT) +#define LOG_KEYOFF (31 << (LOG_BITS + 1)) + +#if 0 + 0-48dB + AR_BITS=6 + AR_SHIFT=14 + x = FC000h(20.7618(sec) * 3579545 / 72)cycles 63 * 4000h + x / 3 * 4 1730.15(ms) + x / 3 * 256 27.03(ms) + + EG_BITS=7 + EG_SHIFT=15 + x = 3F8000h(83.7064(sec) * 3579545 / 72)cycles 127 * 8000h + x / 4 = 20926.6(ms) +#endif + + +#define TESTING_OPTIMIZE_AME 1 +#define USE_FBBUF 1 + +typedef struct +{ + Uint32 phase; + Uint32 spd; + Uint32 rng; +} OPL_PG; + +enum { + EG_MODE_ATTACK, + EG_MODE_DECAY, + EG_MODE_SUSTINE, + EG_MODE_RELEASE, + EG_MODE_NUM, + EG_MODE_SUSHOLD, + EG_MODE_OFF = EG_MODE_NUM +}; + +typedef struct +{ + Uint32 phasear; + Uint32 phase; + Uint32 spd [EG_MODE_NUM]; + Uint32 dr_phasemax; + Uint8 mode; + Uint8 pad4_1; + Uint8 pad4_2; + Uint8 pad4_3; +} OPL_EG; + +enum +{ + FLAG_AME = (1 << 0), + FLAG_PME = (1 << 1), + FLAG_EGT = (1 << 2), + FLAG_KSR = (1 << 3) +}; + +typedef struct +{ + OPL_PG pg; + OPL_EG eg; + Int32 input; +#if USE_FBBUF + Int32 fbbuf; +#endif +#if TESTING_OPTIMIZE_AME + Uint32* amin; +#endif + Uint32 tl_ofs; + Uint32* sintable; + + Uint8 modcar; /* 1:m 0:c */ + Uint8 fb; + Uint8 lvl; + Uint8 nst; + + Uint8 tll; + Uint8 key; + Uint8 rkey; + Uint8 prevkey; + + Uint8 enable; + Uint8 __enablehold__; + Uint8 flag; + Uint8 ksr; + + Uint8 mul; + Uint8 ksl; + Uint8 ar; + Uint8 dr; + Uint8 sl; + Uint8 rr; + Uint8 tl; + Uint8 wf; +} OPL_OP; + +typedef struct +{ + OPL_OP op [2]; + Uint8 con; + Uint8 freql; + Uint8 freqh; + Uint8 blk; + Uint8 kcode; + Uint8 sus; + Uint8 ksb; + Uint8 pad4_3; +} OPL_CH; + +enum +{ + LFO_UNIT_AM, + LFO_UNIT_PM, + LFO_UNIT_NUM +}; + +typedef struct +{ + Uint32 output; + Uint32 cnt; + Uint32 sps; /* step per sample */ + Uint32 adr; + Uint32 adrmask; + Uint32* table; +} OPL_LFO; + +typedef struct +{ + KMIF_SOUND_DEVICE kmif; + KMIF_SOUND_DEVICE* deltatpcm; + KMIF_LOGTABLE* logtbl; + KMIF_OPLTABLE* opltbl; + OPL_CH ch [9]; + OPL_LFO lfo [LFO_UNIT_NUM]; + struct OPLSOUND_COMMON_TAG + { + Uint32 cpsp; + Uint32 cnt; + Uint32* ar_table; + Uint32* tll2logtbl; +#if TESTING_OPTIMIZE_AME + Uint32 amzero; +#endif + Int32 mastervolume; + Uint32 sintablemask; + Uint32 ratetbla [4]; + Uint32 ratetbl [4]; + Uint8 adr; + Uint8 wfe; + Uint8 rc; + Uint8 rmode; + Uint8 enable; + } common; + Uint8 regs [0x100]; + Uint8 opl_type; +} OPLSOUND; + +static Uint8 romtone [3] [16 * 19] = +{ + { +#include "i_fmpac.h" + }, + { +#include "i_fmunit.h" + }, + { +#include "i_vrc7.h" + }, +}; + +static void SetOpOff(OPL_OP* opp ) +{ + opp->eg.mode = EG_MODE_OFF; + opp->eg.phase = EG_KEYOFF; + opp->enable = 0; +} + +inline static void EgStep( OPLSOUND* sndp, OPL_OP* opp ) +{ + switch ( opp->eg.mode ) + { + default: + NEVER_REACH + + case EG_MODE_ATTACK: + opp->eg.phase = sndp->common.ar_table [opp->eg.phasear >> (AR_SHIFT + AR_BITS - ARTBL_BITS)] >> (ARTBL_SHIFT - EG_SHIFT); + opp->eg.phasear += opp->eg.spd [EG_MODE_ATTACK]; + if ( opp->eg.phasear >= AR_PHASEMAX ) + { + opp->eg.mode = EG_MODE_DECAY; + opp->eg.phase = 0; + } + break; + + case EG_MODE_DECAY: + opp->eg.phase += opp->eg.spd [EG_MODE_DECAY]; + if ( opp->eg.phase >= opp->eg.dr_phasemax ) + { + opp->eg.phase = opp->eg.dr_phasemax; + opp->eg.mode = (opp->flag & FLAG_EGT) ? EG_MODE_SUSHOLD : EG_MODE_SUSTINE; + } + break; + + case EG_MODE_SUSTINE: + case EG_MODE_RELEASE: + opp->eg.phase += opp->eg.spd [opp->eg.mode]; + if ( opp->eg.phase >= EG_PHASEMAX ) + SetOpOff(opp); + break; + + case EG_MODE_SUSHOLD: + case EG_MODE_OFF: + break; + } +} + +static void OpStep( OPLSOUND* sndp, OPL_OP* opp ) +{ + int step; + EgStep(sndp, opp); + step = opp->pg.spd; + if ( opp->flag & FLAG_PME ) + step = (step * sndp->lfo [LFO_UNIT_PM].output) >> PM_SHIFT; + opp->pg.phase += step; +} + +__inline static void OpStepNG( OPLSOUND* sndp, OPL_OP* opp ) +{ + Uint32 step; + EgStep(sndp, opp); + opp->pg.phase += opp->pg.spd; + step = opp->pg.phase >> opp->nst/*(PG_SHIFT + 5)*/; + opp->pg.phase &= (1 << opp->nst/*(PG_SHIFT + 5)*/) - 1; + while ( step-- ) + { + opp->pg.rng ^= ((opp->pg.rng & 1) << 16) + ((opp->pg.rng & 1) << 13); + opp->pg.rng >>= 1; + } +} + +#if -1 >> 1 == -1 +/* RIGHT SHIFT IS SIGNED */ +#define SSR(x, y) ((Int32)(x) >> (y)) +#else +/* RIGHT SHIFT IS UNSIGNED */ +#define SSR(x, y) (((x) >= 0) ? ((x) >> (y)) : (-((-(x) - 1) >> (y)) - 1)) +#endif + + +inline static void OpSynthMod( OPLSOUND* sndp, OPL_OP* opp ) +{ + if ( opp->enable ) + { + Uint32 tll; + Int32 output; + OpStep(sndp, opp); + tll = opp->tll + (opp->eg.phase >> EG_SHIFT); + tll = (tll >= (1 << TLLTBL_BITS)) ? LOG_KEYOFF : sndp->common.tll2logtbl [tll]; + tll += opp->tl_ofs; +#if TESTING_OPTIMIZE_AME + tll += *opp->amin; +#else + if ( opp->flag & FLAG_AME ) + tll += sndp->lfo [LFO_UNIT_AM].output; +#endif + tll += opp->sintable [sndp->common.sintablemask & (opp->input + (opp->pg.phase >> PG_SHIFT))]; + output = LogToLin(sndp->logtbl, tll, -8 + ((LOG_LIN_BITS + 1) - (SINTBL_BITS + 2))); + if ( opp->fb ) + { +#if USE_FBBUF + Int32 fbtmp; + fbtmp = opp->fbbuf + output; + opp->fbbuf = output; + opp->input = SSR(fbtmp, (9 - opp->fb)); +#else + opp->input = SSR(output, (8 - opp->fb)); +#endif + } + opp [1].input = output; + } +} + +inline static Int32 OpSynthCarFb( OPLSOUND* sndp, OPL_OP* opp ) +{ + if ( opp->enable ) + { + Uint32 tll; + OpStep(sndp, opp); + tll = opp->tll + (opp->eg.phase >> EG_SHIFT); + tll = (tll >= (1 << TLLTBL_BITS)) ? LOG_KEYOFF : sndp->common.tll2logtbl [tll]; + tll += opp->tl_ofs; +#if TESTING_OPTIMIZE_AME + tll +=* opp->amin; +#else + if ( opp->flag & FLAG_AME) tll += sndp->lfo [LFO_UNIT_AM].output; +#endif + tll += opp->sintable [sndp->common.sintablemask & (opp->input + (opp->pg.phase >> PG_SHIFT))]; + if ( opp->fb ) + { +#if USE_FBBUF + Int32 output, fbtmp; + output = LogToLin(sndp->logtbl, tll, -8 + ((LOG_LIN_BITS + 1) - (SINTBL_BITS + 2))); + fbtmp = opp->fbbuf + output; + opp->fbbuf = output; + opp->input = SSR(fbtmp, (9 - opp->fb)); +#else + Int32 output; + output = LogToLin(sndp->logtbl, tll, -8 + ((LOG_LIN_BITS + 1) - (SINTBL_BITS + 2))); + opp->input = SSR(output, (8 - opp->fb)); +#endif + } + return LogToLin(sndp->logtbl, tll + sndp->common.mastervolume, -8 + LOG_LIN_BITS - 19 - opp->lvl); + } + return 0; +} + +inline static Int32 OpSynthCar( OPLSOUND* sndp, OPL_OP* opp ) +{ + if ( opp->enable ) + { + Uint32 tll; + OpStep(sndp, opp); + tll = opp->tll + (opp->eg.phase >> EG_SHIFT); + tll = (tll >= (1 << TLLTBL_BITS)) ? LOG_KEYOFF : sndp->common.tll2logtbl [tll]; + tll += opp->tl_ofs; +#if TESTING_OPTIMIZE_AME + tll += *opp->amin; +#else + if ( opp->flag & FLAG_AME ) + tll += sndp->lfo [LFO_UNIT_AM].output; +#endif + tll += opp->sintable [sndp->common.sintablemask & (opp->input + (opp->pg.phase >> PG_SHIFT))]; + return LogToLin(sndp->logtbl, tll + sndp->common.mastervolume, -8 + LOG_LIN_BITS - 19 - opp->lvl); + } + return 0; +} + +inline static Int32 OpSynthTom( OPLSOUND* sndp, OPL_OP* opp ) +{ + if ( opp->enable ) + { + Uint32 tll; + OpStep(sndp, opp); + tll = opp->tll + (opp->eg.phase >> EG_SHIFT); + tll = (tll >= 128 - 16) ? LOG_KEYOFF : sndp->common.tll2logtbl [tll]; + tll += opp->tl_ofs; + tll += opp->sintable [sndp->common.sintablemask & (opp->pg.phase >> PG_SHIFT)]; + return LogToLin(sndp->logtbl, tll + sndp->common.mastervolume, -8 + LOG_LIN_BITS - 19 - opp->lvl); + } + return 0; +} + + +static Int32 OpSynthRym( OPLSOUND* sndp, OPL_OP* opp ) +{ + if ( opp->enable ) + { + Uint32 tll; + OpStepNG(sndp, opp); + tll = opp->tll + (opp->eg.phase >> EG_SHIFT) + 0x10/* +6dB */; + tll = (tll >= (1 << TLLTBL_BITS)) ? LOG_KEYOFF : sndp->common.tll2logtbl [tll]; + tll += opp->tl_ofs; + tll += (opp->pg.rng & 1); + return LogToLin(sndp->logtbl, tll + sndp->common.mastervolume, -8 + LOG_LIN_BITS - 19 - opp->lvl); + } + return 0; +} + +inline static void LfoStep(OPL_LFO* lfop ) +{ + lfop->cnt += lfop->sps; + lfop->adr += lfop->cnt >> LFO_SHIFT; + lfop->cnt &= (1 << LFO_SHIFT) - 1; + lfop->output = lfop->table [lfop->adr & lfop->adrmask]; +} + +static int sndsynth( OPLSOUND* sndp ) +{ + Int32 accum = 0; + if ( sndp->common.enable ) + { + Uint32 i, rch; + for ( i = 0; i < LFO_UNIT_NUM; i++ ) + LfoStep(&sndp->lfo [i]); + + rch = sndp->common.rmode ? 7 : 9; + for ( i = 0; i < rch; i++ ) + { + if ( sndp->ch [i].op [0].modcar ) + OpSynthMod(sndp, &sndp->ch [i].op [0]); + else + accum += OpSynthCarFb(sndp, &sndp->ch [i].op [0]); + accum += OpSynthCar(sndp, &sndp->ch [i].op [1]); + } + if ( sndp->common.rmode ) + { + accum += OpSynthRym(sndp, &sndp->ch [7].op [0]); + accum += OpSynthRym(sndp, &sndp->ch [7].op [1]); + accum += OpSynthTom(sndp, &sndp->ch [8].op [0]); + accum += OpSynthRym(sndp, &sndp->ch [8].op [1]); + } + } + if ( sndp->deltatpcm ) + { + accum += sndp->deltatpcm->synth(sndp->deltatpcm->ctx); + } +#if 0 + /* NISE DAC */ + if ( accum >= 0 ) + accum = (Int32)(((Uint32) accum) & (((1 << 8) - 1) << (23 - 8))); + else + accum = -(Int32)(((Uint32)-accum) & (((1 << 8) - 1) << (23 - 8))); +#endif + return accum; +} + +static void sndvolume( OPLSOUND* sndp, Int32 volume ) +{ + if ( sndp->deltatpcm) sndp->deltatpcm->volume(sndp->deltatpcm->ctx, volume); + volume = (volume << (LOG_BITS - 8)) << 1; + sndp->common.mastervolume = volume; +} + +static Uint8 const op_table [0x20]= +{ + 0, 2, 4, 1, 3, 5,0xFF,0xFF, + 6, 8, 10, 7, 9, 11,0xFF,0xFF, + 12, 14, 16, 13, 15, 17,0xFF,0xFF, + 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, +}; + +static Uint8 const mul_table [0x10]= +{ + 1+0, 2+0, 4+0, 2+4, 8+0, 8+2, 8+4,16-2, + 16+0,16+2,16+4,16+4,16+8,16+8,32-2,32-2, +}; + +#define DB2TLL(x) (x * 2 / 375 ) +static Uint8 const ksl_table [8] [16]= +{ + { + DB2TLL( 0), DB2TLL( 0), DB2TLL( 0), DB2TLL( 0), + DB2TLL( 0), DB2TLL( 0), DB2TLL( 0), DB2TLL( 0), + DB2TLL( 0), DB2TLL( 0), DB2TLL( 0), DB2TLL( 0), + DB2TLL( 0), DB2TLL( 0), DB2TLL( 0), DB2TLL( 0), + },{ + DB2TLL( 0), DB2TLL( 0), DB2TLL( 0), DB2TLL( 0), + DB2TLL( 0), DB2TLL( 0), DB2TLL( 0), DB2TLL( 0), + DB2TLL( 0), DB2TLL( 750), DB2TLL( 1125), DB2TLL( 1500), + DB2TLL( 1875), DB2TLL( 2250), DB2TLL( 2625), DB2TLL( 3000), + },{ + DB2TLL( 0), DB2TLL( 0), DB2TLL( 0), DB2TLL( 0), + DB2TLL( 0), DB2TLL( 1125), DB2TLL( 1875), DB2TLL( 2625), + DB2TLL( 3000), DB2TLL( 3750), DB2TLL( 4125), DB2TLL( 4500), + DB2TLL( 4875), DB2TLL( 5250), DB2TLL( 5625), DB2TLL( 6000), + },{ + DB2TLL( 0), DB2TLL( 0), DB2TLL( 0), DB2TLL( 1875), + DB2TLL( 3000), DB2TLL( 4125), DB2TLL( 4875), DB2TLL( 5625), + DB2TLL( 6000), DB2TLL( 6750), DB2TLL( 7125), DB2TLL( 7500), + DB2TLL( 7875), DB2TLL( 8250), DB2TLL( 8625), DB2TLL( 9000), + },{ + DB2TLL( 0), DB2TLL( 0), DB2TLL( 3000), DB2TLL( 4875), + DB2TLL( 6000), DB2TLL( 7125), DB2TLL( 7875), DB2TLL( 8625), + DB2TLL( 9000), DB2TLL( 9750), DB2TLL(10125), DB2TLL(10500), + DB2TLL(10875), DB2TLL(11250), DB2TLL(11625), DB2TLL(12000), + },{ + DB2TLL( 0), DB2TLL( 3000), DB2TLL( 6000), DB2TLL( 7875), + DB2TLL( 9000), DB2TLL(10125), DB2TLL(10875), DB2TLL(11625), + DB2TLL(12000), DB2TLL(12750), DB2TLL(13125), DB2TLL(13500), + DB2TLL(13875), DB2TLL(14250), DB2TLL(14625), DB2TLL(15000), + },{ + DB2TLL( 0), DB2TLL( 6000), DB2TLL( 9000), DB2TLL(10875), + DB2TLL(12000), DB2TLL(13125), DB2TLL(13875), DB2TLL(14625), + DB2TLL(15000), DB2TLL(15750), DB2TLL(16125), DB2TLL(16500), + DB2TLL(16875), DB2TLL(17250), DB2TLL(17625), DB2TLL(18000), + },{ + DB2TLL( 0), DB2TLL( 9000), DB2TLL(12000), DB2TLL(13875), + DB2TLL(15000), DB2TLL(16125), DB2TLL(16875), DB2TLL(17625), + DB2TLL(18000), DB2TLL(18750), DB2TLL(19125), DB2TLL(19500), + DB2TLL(19875), DB2TLL(20250), DB2TLL(20625), DB2TLL(21000), + } +}; +#undef DB2TLL + +static Uint32 rateconvAR( OPLSOUND* sndp, Uint32 rrr, Uint32 ksr ) +{ + if ( !rrr ) + return 0; + rrr = rrr + (ksr >> 2); + if ( rrr >= 15) + return AR_PHASEMAX; + return sndp->common.ratetbla [ksr & 3] >> (CPS_SHIFTE + 1 - rrr); +} + +static Uint32 rateconv( OPLSOUND* sndp, Uint32 rrr, Uint32 ksr ) +{ + if ( !rrr ) + return 0; + rrr = rrr + (ksr >> 2); + if ( rrr > 15 ) + rrr = 15; + return sndp->common.ratetbl [ksr & 3] >> (CPS_SHIFTE + 1 - rrr); +} + +static void OpUpdateWF( OPLSOUND* sndp, OPL_OP* opp ) +{ + opp->sintable = sndp->opltbl->sin_table [opp->wf & sndp->common.wfe]; +} + +static void OpUpdatePG( OPLSOUND* sndp, OPL_CH* chp, OPL_OP* opp ) +{ + opp->pg.spd = (((chp->freqh << 8) + chp->freql) * opp->mul * sndp->common.cpsp) >> (CPS_SHIFTP - chp->blk); +} + +static void OpUpdateEG( OPLSOUND* sndp, OPL_CH* chp, OPL_OP* opp ) +{ + Uint32 sr, rr; + opp->ksr = chp->kcode >> ((opp->flag & FLAG_KSR) ? 0 : 2); + opp->eg.dr_phasemax = opp->sl << (1 + 2 + EG_SHIFT); /* 3dB->eg */ + opp->eg.spd [EG_MODE_ATTACK] = rateconvAR(sndp, opp->ar, opp->ksr); + opp->eg.spd [EG_MODE_DECAY] = rateconv(sndp, opp->dr, opp->ksr); + if ( opp->flag & FLAG_EGT ) + { + if ( opp->eg.mode == EG_MODE_SUSTINE ) + opp->eg.mode = EG_MODE_SUSHOLD; + sr = 0; + rr = opp->rr; + } + else + { + if ( opp->eg.mode == EG_MODE_SUSHOLD ) + opp->eg.mode = EG_MODE_SUSTINE; + sr = opp->rr; + rr = 7; + } + if ( chp->sus ) + { + rr = 5; + } + opp->eg.spd [EG_MODE_SUSTINE] = rateconv(sndp, sr, opp->ksr); + opp->eg.spd [EG_MODE_RELEASE] = rateconv(sndp, rr, opp->ksr); +} + +static void OpUpdateTLL( OPLSOUND* sndp, OPL_CH* chp, OPL_OP* opp ) +{ + opp->tll = (opp->tl + (chp->ksb >> opp->ksl)) << 1; +} + + + +static void oplsetopmul( OPLSOUND* sndp, OPL_CH* chp, OPL_OP* opp, Uint32 v ) +{ + opp->flag &= ~(FLAG_AME | FLAG_PME | FLAG_EGT | FLAG_KSR); +#if TESTING_OPTIMIZE_AME + if ( v & 0x80 ) + opp->amin = &sndp->lfo [LFO_UNIT_AM].output; else opp->amin = &sndp->common.amzero; +#else + if ( v & 0x80 ) + opp->flag |= FLAG_AME; +#endif + if ( v & 0x40 ) opp->flag |= FLAG_PME; + if ( v & 0x20 ) opp->flag |= FLAG_EGT; + if ( v & 0x10 ) opp->flag |= FLAG_KSR; + opp->mul = mul_table [v & 0x0F]; + OpUpdateEG(sndp, chp, opp); + OpUpdatePG(sndp, chp, opp); +} + +static void oplsetopkstl( OPLSOUND* sndp, OPL_CH* chp, OPL_OP* opp, Uint32 v ) +{ + opp->ksl = (v >> 6) ? (3 - (v >> 6)) : 15; /* 0 / 1.5 / 3 / 6 db/OCT */ + opp->tl = v & 0x3F; /* 0.75 db */ + OpUpdateTLL(sndp, chp, opp); +} + +static void oplsetopardr( OPLSOUND* sndp, OPL_CH* chp, OPL_OP* opp, Uint32 v ) +{ + opp->ar = v >> 4; + opp->dr = v & 0xF; + OpUpdateEG(sndp, chp, opp); +} + +static void oplsetopslrr( OPLSOUND* sndp, OPL_CH* chp, OPL_OP* opp, Uint32 v ) +{ + opp->sl = v >> 4; + opp->rr = v & 0xF; + OpUpdateEG(sndp, chp, opp); +} + +static void oplsetopwf( OPLSOUND* sndp, OPL_CH* chp, OPL_OP* opp, Uint32 v ) +{ + opp->wf = v & 0x3; + OpUpdateWF(sndp, opp); +} + +static void oplsetopkey( OPLSOUND* sndp, OPL_OP* opp ) +{ + Uint8 nextkey = ((sndp->common.rmode) && opp->rkey) || opp->key; + if ( opp->prevkey ^ nextkey ) + { + opp->prevkey = nextkey; + if ( nextkey ) + { + sndp->common.enable = 1; + opp->eg.mode = EG_MODE_ATTACK; + opp->eg.phase = EG_KEYOFF; + opp->enable = 1; + opp->eg.phasear = 0; + } + else if ( !opp->modcar && opp->eg.mode != EG_MODE_OFF ) + opp->eg.mode = EG_MODE_RELEASE; + } +} + +static void oplsetchfreql( OPLSOUND* sndp, OPL_CH* chp, Uint32 v ) +{ + chp->freql = v & 0xFF; + chp->ksb = ksl_table [chp->blk] [(chp->freqh << 2) + (chp->freql >> 6)]; + OpUpdatePG(sndp, chp, &chp->op [0]); + OpUpdatePG(sndp, chp, &chp->op [1]); + OpUpdateTLL(sndp, chp, &chp->op [0]); + OpUpdateTLL(sndp, chp, &chp->op [1]); +} + +static void oplsetchfreqh( OPLSOUND* sndp, OPL_CH* chp, Uint32 v ) +{ + Uint32 key = v & 0x20; + chp->kcode = (v >> 1) & 15; + chp->freqh = v & 3; + chp->blk = (v >> 2) & 7; + chp->op [0].key = chp->op [1].key = key; + oplsetopkey(sndp, &chp->op [0]); + oplsetopkey(sndp, &chp->op [1]); + chp->sus = 0; + chp->ksb = ksl_table [chp->blk] [(chp->freqh << 2) + (chp->freql >> 6)]; + OpUpdateEG(sndp, chp, &chp->op [0]); + OpUpdateEG(sndp, chp, &chp->op [1]); + OpUpdatePG(sndp, chp, &chp->op [0]); + OpUpdatePG(sndp, chp, &chp->op [1]); + OpUpdateTLL(sndp, chp, &chp->op [0]); + OpUpdateTLL(sndp, chp, &chp->op [1]); +} + +static void oplsetchfbcon( OPLSOUND* sndp, OPL_CH* chp, Uint32 v ) +{ + chp->op [0].fb = (v >> 1) & 7; +#if USE_FBBUF + chp->op [0].fbbuf = 0; +#endif + chp->con = v & 1; + chp->op [0].modcar = (chp->con) ? 0 : 1; + OpUpdateEG(sndp, chp, &chp->op [0]); + chp->op [1].input = 0; +} + +static void opllsetopvolume( OPLSOUND* sndp, OPL_CH* chp, OPL_OP* opp, Uint32 v ) +{ + opp->tl = v; + OpUpdateTLL(sndp, chp, opp); +} + +static void opllsetchfreql( OPLSOUND* sndp, OPL_CH* chp, Uint32 v ) +{ + chp->freql = v & 0xFF; + chp->ksb = ksl_table [chp->blk] [(chp->freqh << 3) + (chp->freql >> 5)]; + OpUpdatePG(sndp, chp, &chp->op [0]); + OpUpdatePG(sndp, chp, &chp->op [1]); + OpUpdateTLL(sndp, chp, &chp->op [0]); + OpUpdateTLL(sndp, chp, &chp->op [1]); +} + +static void opllsetchfreqh( OPLSOUND* sndp, OPL_CH* chp, Uint32 v ) +{ + Uint32 key = v & 0x10; + chp->kcode = v & 15; + chp->freqh = v & 1; + chp->blk = (v >> 1) & 7; + chp->op [0].key = chp->op [1].key = key; + oplsetopkey(sndp, &chp->op [0]); + oplsetopkey(sndp, &chp->op [1]); + chp->sus = v & 0x20; + chp->ksb = ksl_table [chp->blk] [(chp->freqh << 3) + (chp->freql >> 5)]; + OpUpdateEG(sndp, chp, &chp->op [0]); + OpUpdateEG(sndp, chp, &chp->op [1]); + OpUpdatePG(sndp, chp, &chp->op [0]); + OpUpdatePG(sndp, chp, &chp->op [1]); + OpUpdateTLL(sndp, chp, &chp->op [0]); + OpUpdateTLL(sndp, chp, &chp->op [1]); +} + +static void SetOpTone( OPLSOUND* sndp, OPL_OP* opp, Uint8* tonep ) +{ + opp->flag &= ~(FLAG_AME | FLAG_PME | FLAG_EGT | FLAG_KSR); +#if TESTING_OPTIMIZE_AME + if ( tonep [0] & 0x80 ) opp->amin = &sndp->lfo [LFO_UNIT_AM].output; else opp->amin = &sndp->common.amzero; +#else + if ( tonep [0] & 0x80 ) opp->flag |= FLAG_AME; +#endif + if ( tonep [0] & 0x40 ) opp->flag |= FLAG_PME; + if ( tonep [0] & 0x20 ) opp->flag |= FLAG_EGT; + if ( tonep [0] & 0x10 ) opp->flag |= FLAG_KSR; + opp->mul = mul_table [tonep [0] & 0x0F] << 1; + opp->ksl = (tonep [2] >> 6) ? (3 - (tonep [2] >> 6)) : 15; + opp->ar = tonep [4] >> 4; + opp->dr = tonep [4] & 0xF; + opp->sl = tonep [6] >> 4; + opp->rr = tonep [6] & 0xF; +} +static void SetChTone( OPLSOUND* sndp, OPL_CH* chp, Uint8* tonep, Uint8* tlofsp ) +{ + Uint32 op; + for ( op = 0; op < 2; op++ ) + SetOpTone(sndp, &chp->op [op], &tonep [op]); + chp->op [0].tl_ofs = (tlofsp [0] ^ 0x80) << (LOG_BITS - 4 + 1); + chp->op [1].tl_ofs = (tlofsp [1] ^ 0x80) << (LOG_BITS - 4 + 1); + chp->op [0].tl = tonep [2] & 0x3F; + chp->op [0].fb = tonep [3] & 0x7; + chp->op [0].wf = (tonep [3] >> 3) & 1; + chp->op [1].wf = (tonep [3] >> 4) & 1; +#if USE_FBBUF + chp->op [0].fbbuf = 0; +#endif + chp->op [1].input = 0; + OpUpdateWF(sndp, &chp->op [0]); + OpUpdateWF(sndp, &chp->op [1]); + OpUpdateEG(sndp, chp, &chp->op [0]); + OpUpdateEG(sndp, chp, &chp->op [1]); + OpUpdatePG(sndp, chp, &chp->op [0]); + OpUpdatePG(sndp, chp, &chp->op [1]); + OpUpdateTLL(sndp, chp, &chp->op [0]); + OpUpdateTLL(sndp, chp, &chp->op [1]); +} + +static void opllsetchtone( OPLSOUND* sndp, OPL_CH* chp, Uint32 tone ) +{ + SetChTone(sndp, chp, &sndp->regs [OPLL_INST_WORK + (tone << 3)], &sndp->regs [OPLL_INST_WORK2 + (tone << 1) + 0]); +} + +static void recovercon( OPLSOUND* sndp, OPL_CH* chp ) +{ + chp->op [0].modcar = (chp->con) ? 0 : 1; + chp->op [0].lvl = chp->con ? 1 : 0; + chp->op [1].lvl = 1; + OpUpdateEG(sndp, chp, &chp->op [0]); + chp->op [1].input = 0; +} + +static void initrc_common( OPLSOUND* sndp, Uint32 rmode ) +{ + if ( rmode ) + { + /* BD */ + sndp->ch [6].op [0].modcar = 1; + sndp->ch [6].op [0].lvl = 0; + OpUpdateEG(sndp, &sndp->ch [6], &sndp->ch [6].op [0]); + sndp->ch [6].op [1].input = 0; + sndp->ch [6].op [1].lvl = 2; + /* CYM */ + sndp->ch [7].op [0].modcar = 0; + sndp->ch [7].op [0].lvl = 1; + OpUpdateEG(sndp, &sndp->ch [7], &sndp->ch [7].op [0]); + /* SD */ + sndp->ch [7].op [1].input = 0; + sndp->ch [7].op [1].lvl = 2; + /* TOM */ + sndp->ch [8].op [0].modcar = 0; + sndp->ch [8].op [0].lvl = 2; + OpUpdateEG(sndp, &sndp->ch [8], &sndp->ch [8].op [0]); + /* HH */ + sndp->ch [8].op [1].input = 0; + sndp->ch [8].op [1].lvl = 1; + } + else + { + recovercon(sndp, &sndp->ch [6]); + if ( !sndp->ch [6].op [0].key ) SetOpOff(&sndp->ch [6].op [0]); + if ( !sndp->ch [6].op [1].key ) SetOpOff(&sndp->ch [6].op [1]); + recovercon(sndp, &sndp->ch [7]); + if ( !sndp->ch [7].op [0].key ) SetOpOff(&sndp->ch [7].op [0]); + if ( !sndp->ch [7].op [1].key ) SetOpOff(&sndp->ch [7].op [1]); + recovercon(sndp, &sndp->ch [8]); + if ( !sndp->ch [8].op [0].key ) SetOpOff(&sndp->ch [8].op [0]); + if ( !sndp->ch [8].op [1].key ) SetOpOff(&sndp->ch [8].op [1]); + } +} + +static void oplsetrc( OPLSOUND* sndp, Uint32 rc ) +{ + sndp->lfo [LFO_UNIT_AM].table = (rc & 0x80) ? sndp->opltbl->am_table1 : sndp->opltbl->am_table2; + sndp->lfo [LFO_UNIT_PM].table = (rc & 0x40) ? sndp->opltbl->pm_table1 : sndp->opltbl->pm_table2; + if ( (sndp->common.rmode ^ rc) & 0x20 ) + { + if ( rc & 0x20 ) + { +#if 0 + static Uint8 volini [2] = { 0, 0 }; + static Uint8 bdtone [8] = { 0x04, 0x20, 0x28, 0x00, 0xDF, 0xF8, 0xFF, 0xF8 }; + SetChTone(sndp, &sndp->ch [6], bdtone, volini); + SetChTone(sndp, &sndp->ch [7], &romtone [0] [0x11 << 4], volini); + SetChTone(sndp, &sndp->ch [8], &romtone [0] [0x12 << 4], volini); +#endif + sndp->ch [7].op [0].nst = PG_SHIFT + 4; + sndp->ch [7].op [1].nst = PG_SHIFT + 6; + sndp->ch [8].op [1].nst = PG_SHIFT + 5; + } + initrc_common(sndp, rc & 0x20); + } + sndp->common.rmode = rc & 0x20; + sndp->common.rc = rc & 0x1F; + /* BD */ + sndp->ch [6].op [0].rkey = sndp->ch [6].op [1].rkey = rc & 0x10; + oplsetopkey(sndp, &sndp->ch [6].op [0]); + oplsetopkey(sndp, &sndp->ch [6].op [1]); + /* CYM */ + sndp->ch [7].op [0].rkey = rc & 0x01; + oplsetopkey(sndp, &sndp->ch [7].op [0]); + /* SD */ + sndp->ch [7].op [1].rkey = rc & 0x08; + oplsetopkey(sndp, &sndp->ch [7].op [1]); + /* TOM */ + sndp->ch [8].op [0].rkey = rc & 0x04; + oplsetopkey(sndp, &sndp->ch [8].op [0]); + /* HH */ + sndp->ch [8].op [1].rkey = rc & 0x02; + oplsetopkey(sndp, &sndp->ch [8].op [1]); +} + +static void opllsetrc( OPLSOUND* sndp, Uint32 rc ) +{ + if ( (sndp->common.rmode ^ rc) & 0x20 ) + { + if ( rc & 0x20 ) + { + opllsetchtone(sndp, &sndp->ch [6], 0x10); + opllsetchtone(sndp, &sndp->ch [7], 0x11); + opllsetchtone(sndp, &sndp->ch [8], 0x12); + opllsetopvolume(sndp, &sndp->ch [7], &sndp->ch [7].op [0], (sndp->regs [0x37] & 0xF0) >> 2); + opllsetopvolume(sndp, &sndp->ch [8], &sndp->ch [8].op [0], (sndp->regs [0x38] & 0xF0) >> 2); + sndp->ch [7].op [0].nst = PG_SHIFT + 5; + sndp->ch [7].op [1].nst = PG_SHIFT + 5; + sndp->ch [8].op [1].nst = PG_SHIFT + 5; + } + else + { + opllsetchtone(sndp, &sndp->ch [6], sndp->regs [0x36]>>4); + opllsetchtone(sndp, &sndp->ch [7], sndp->regs [0x37]>>4); + opllsetchtone(sndp, &sndp->ch [8], sndp->regs [0x38]>>4); + } + initrc_common(sndp, rc & 0x20); + } + sndp->common.rmode = rc & 0x20; + sndp->common.rc = rc & 0x1F; + /* BD */ + sndp->ch [6].op [0].rkey = sndp->ch [6].op [1].rkey = rc & 0x10; + oplsetopkey(sndp, &sndp->ch [6].op [0]); + oplsetopkey(sndp, &sndp->ch [6].op [1]); + /* CYM */ + sndp->ch [7].op [0].rkey = rc & 0x01; + oplsetopkey(sndp, &sndp->ch [7].op [0]); + /* SD */ + sndp->ch [7].op [1].rkey = rc & 0x08; + oplsetopkey(sndp, &sndp->ch [7].op [1]); + /* TOM */ + sndp->ch [8].op [0].rkey = rc & 0x04; + oplsetopkey(sndp, &sndp->ch [8].op [0]); + /* HH */ + sndp->ch [8].op [1].rkey = rc & 0x02; + oplsetopkey(sndp, &sndp->ch [8].op [1]); +} + +#define OPLSETOP(func) { \ + Uint32 op = op_table [a & 0x1F]; \ + if ( op != 0xFF) func(sndp, &sndp->ch [op >> 1], &sndp->ch [op >> 1].op [op & 1], v); \ +} + +__inline static void oplwritereg( OPLSOUND* sndp, Uint32 a, Uint32 v ) +{ + switch ( a >> 5 ) + { + default: + NEVER_REACH + + case 0: + switch ( a & 0x1F ) + { + case 0x01: + if ( sndp->opl_type == OPL_TYPE_OPL2 ) + { + Uint32 i; + sndp->common.wfe = (v & 0x20) ? 3 : 0; + for ( i = 0; i < 9; i++ ) + { + OpUpdateWF(sndp, &sndp->ch [i].op [0]); + OpUpdateWF(sndp, &sndp->ch [i].op [1]); + } + } + break; + + case 0x08: + /* CSM mode */ + case 0x07: case 0x09: case 0x0A: case 0x0B: case 0x0C: + case 0x0D: case 0x0E: case 0x0F: case 0x10: case 0x11: case 0x12: + if ( sndp->deltatpcm ) + sndp->deltatpcm->write(sndp->deltatpcm->ctx, a - 0x07, v); + break; + } + break; + + case 1: OPLSETOP(oplsetopmul); break; + case 2: OPLSETOP(oplsetopkstl); break; + case 3: OPLSETOP(oplsetopardr); break; + case 4: OPLSETOP(oplsetopslrr); break; + case 7: OPLSETOP(oplsetopwf); break; + case 5: + if ( (a & 0x1F) == (0xBD & 0x1F) ) + oplsetrc(sndp, v); + else if ( (a & 0x1F) < 9 ) + oplsetchfreql(sndp, &sndp->ch [a & 0xF], v); + else if ( (a & 0xF) < 9 ) + oplsetchfreqh(sndp, &sndp->ch [a & 0xF], v); + break; + + case 6: + if ( (a & 0x1F) < 9) oplsetchfbcon(sndp, &sndp->ch [a & 0xF], v); + break; + } +} + +static void oplwrite( OPLSOUND* sndp, Uint32 a, Uint32 v ) +{ + if ( a & 1 ) + { + sndp->regs [sndp->common.adr] = v; + oplwritereg(sndp, sndp->common.adr, v); + } + else + sndp->common.adr = v; +} + +static Uint32 oplread( OPLSOUND* sndp, Uint32 a ) +{ + if ( a & 1 ) + return sndp->regs [sndp->common.adr]; + else + return 0x80; +} + +__inline static void opllwritereg( OPLSOUND* sndp, Uint32 a, Uint32 v ) +{ + switch ( a >> 3 ) + { + default: + NEVER_REACH + case 0: + sndp->regs [OPLL_INST_WORK + (a & 7)] = v; + break; + + case 1: + if ( a == 0xE) opllsetrc(sndp, v & 0x3F); + break; + + case 2: + case 3: + a &= 0xF; + if ( a < 9) opllsetchfreql(sndp, &sndp->ch [a], v); + break; + + case 4: + case 5: + a &= 0xF; + if ( a < 9) opllsetchfreqh(sndp, &sndp->ch [a], v); + break; + + case 6: + case 7: + a &= 0xF; + if ( a < 9 ) + { + if ( (sndp->common.rmode) && (a >= 6) ) + { + if ( a != 6) opllsetopvolume(sndp, &sndp->ch [a], &sndp->ch [a].op [0], (v & 0xF0) >> 2); + } + else + { + opllsetchtone(sndp, &sndp->ch [a], (v & 0xF0) >> 4); + } + opllsetopvolume(sndp, &sndp->ch [a], &sndp->ch [a].op [1], (v & 0xF) << 2); + } + break; + } +} + +static void opllwrite( OPLSOUND* sndp, Uint32 a, Uint32 v ) +{ + if ( a & 1 ) + { + if ( sndp->common.adr < 0x40 ) + { + sndp->regs [sndp->common.adr] = v; + opllwritereg(sndp, sndp->common.adr, v); + } + } + else + sndp->common.adr = v; +} + +static Uint32 opllread( OPLSOUND* sndp, Uint32 a ) +{ + return 0xFF; +} + +static void opreset( OPLSOUND* sndp, OPL_OP* opp ) +{ + /* XMEMSET(opp, 0, sizeof(OPL_OP)); */ + SetOpOff(opp); + opp->tl_ofs = 0x80 << (LOG_BITS - 4 + 1); +#if TESTING_OPTIMIZE_AME + opp->amin = &sndp->common.amzero; +#endif + opp->pg.rng = 0xFFFF; +} + +static void chreset( OPLSOUND* sndp, OPL_CH* chp, Uint32 clock, Uint32 freq ) +{ + Uint32 op; + XMEMSET(chp, 0, sizeof(OPL_CH)); + for ( op = 0; op < 2; op++ ) + { + opreset(sndp, &chp->op [op]); + } + recovercon(sndp, chp); +} + +static void sndreset( OPLSOUND* sndp, Uint32 clock, Uint32 freq ) +{ + Uint32 i, cpse; + XMEMSET(&sndp->common, 0, sizeof(sndp->common)); + XMEMSET(&sndp->lfo [LFO_UNIT_AM], 0, sizeof(OPL_LFO)); + sndp->lfo [LFO_UNIT_AM].sps = DivFix(37 * (1 << AMTBL_BITS), freq * 10, LFO_SHIFT); + sndp->lfo [LFO_UNIT_AM].adrmask = (1 << AMTBL_BITS) - 1; + sndp->lfo [LFO_UNIT_AM].table = sndp->opltbl->am_table1; + XMEMSET(&sndp->lfo [LFO_UNIT_PM], 0, sizeof(OPL_LFO)); + sndp->lfo [LFO_UNIT_PM].sps = DivFix(64 * (1 << PMTBL_BITS), freq * 10, LFO_SHIFT); + sndp->lfo [LFO_UNIT_PM].adrmask = (1 << PMTBL_BITS) - 1; + sndp->lfo [LFO_UNIT_PM].table = sndp->opltbl->pm_table1; + sndp->common.cpsp = DivFix(clock, 72 * freq, CPS_SHIFTP); + cpse = DivFix(clock, 72 * freq, CPS_SHIFTE); + for ( i = 0; i < 4; i++ ) + { + sndp->common.ratetbl [i] = (i + 4) * cpse; + sndp->common.ratetbla [i] = 3 * sndp->common.ratetbl [i]; + } + sndp->common.tll2logtbl = sndp->opltbl->tll2log_table; + sndp->common.sintablemask = (1 << SINTBL_BITS) - 1; + + for ( i = 0; i < 9; i++ ) + chreset(sndp, &sndp->ch [i], clock, freq); + + if ( sndp->deltatpcm ) + sndp->deltatpcm->reset(sndp->deltatpcm->ctx, clock, freq); + + if ( sndp->opl_type & OPL_TYPE_OPL ) + { + XMEMSET(&sndp->regs, 0, 0x100); + sndp->common.ar_table = sndp->opltbl->ar_tablepow; + sndp->common.sintablemask -= (1 << (SINTBL_BITS - 11)) - 1; + for ( i = 0x0; i < 0x100; i++ ) + { + oplwrite(sndp, 0, i); + oplwrite(sndp, 1, 0x00); + } + for ( i = 0xA0; i < 0xA9; i++ ) + { + oplwrite(sndp, 0, 0xA0 + i); + oplwrite(sndp, 1, 0x40); + oplwrite(sndp, 0, 0xB0 + i); + oplwrite(sndp, 1, 0x0E); + } + } + else + { + static Uint8 const fmbios_initdata [9] = "\x30\x10\x20\x20\xfb\xb2\xf3\xf3"; + XMEMSET(&sndp->regs, 0, 0x40); + sndp->common.ar_table = sndp->opltbl->ar_tablelog; + sndp->common.wfe = 1; + sndp->common.sintablemask -= (1 << (SINTBL_BITS - 8)) - 1; + for ( i = 0; i < sizeof(fmbios_initdata)-1; i++ ) + { + opllwrite(sndp, 0, i); + opllwrite(sndp, 1, fmbios_initdata [i]); + } + opllwrite(sndp, 0, 0x0E); + opllwrite(sndp, 1, 0x00); + opllwrite(sndp, 0, 0x0F); + opllwrite(sndp, 1, 0x00); + for ( i = 0; i < 9; i++ ) + { + opllwrite(sndp, 0, 0x10 + i); + opllwrite(sndp, 1, 0x20); + opllwrite(sndp, 0, 0x20 + i); + opllwrite(sndp, 1, 0x07); + opllwrite(sndp, 0, 0x30 + i); + opllwrite(sndp, 1, 0xB3); + } + } +} + +static void oplsetinst( OPLSOUND* sndp, Uint32 n, void* p, Uint32 l ) +{ + if ( sndp->deltatpcm) sndp->deltatpcm->setinst(sndp->deltatpcm->ctx, n, p, l); +} + +__inline static Uint32 GetDwordLE(Uint8* p ) +{ + return p [0] | (p [1] << 8) | (p [2] << 16) | (p [3] << 24); +} +#define GetDwordLEM(p) (Uint32)((((Uint8* )p) [0] | (((Uint8* )p) [1] << 8) | (((Uint8* )p) [2] << 16) | (((Uint8* )p) [3] << 24)) ) + +static void opllsetinst( OPLSOUND* sndp, Uint32 n, Uint8* p, Uint32 l ) +{ + Int32 i, j, sb = 9; + if ( n ) + return; + if ( (GetDwordLE(p) & 0xF0FFFFFF) == GetDwordLEM("ILL0") ) + { + if ( 0 < p [4] && p [4] <= SINTBL_BITS) sb = p [4]; + for ( j = 1; j < 16 + 3; j++ ) + for ( i = 0; i < 8; i++ ) + sndp->regs [OPLL_INST_WORK + (j << 3) + i] = p [(j << 4) + i]; + for ( j = 0; j < 16 + 3; j++ ) + { + sndp->regs [OPLL_INST_WORK2 + (j << 1) + 0] = p [(j << 4) + 8]; + sndp->regs [OPLL_INST_WORK2 + (j << 1) + 1] = p [(j << 4) + 9]; + } + } + else + { + for ( j = 1; j < 16; j++ ) + for ( i = 0; i < 8; i++ ) + sndp->regs [OPLL_INST_WORK + (j << 3) + i] = p [((j - 1) << 3) + i]; + } + sndp->common.sintablemask = (1 << SINTBL_BITS) - 1; + sndp->common.sintablemask -= (1 << (SINTBL_BITS - sb)) - 1; +} + +static void sndrelease( OPLSOUND* sndp ) +{ + if ( sndp->logtbl) sndp->logtbl->release(sndp->logtbl->ctx); + if ( sndp->opltbl) sndp->opltbl->release(sndp->opltbl->ctx); + if ( sndp->deltatpcm) sndp->deltatpcm->release(sndp->deltatpcm->ctx); + XFREE(sndp); +} + +KMIF_SOUND_DEVICE* OPLSoundAlloc(Uint32 opl_type ) +{ + OPLSOUND* sndp; + sndp = (OPLSOUND*) XMALLOC(sizeof(OPLSOUND)); + if ( !sndp) return 0; + sndp->opl_type = opl_type; + sndp->kmif.ctx = sndp; + sndp->kmif.release = (void (*)( void* )) sndrelease; + sndp->kmif.volume = (void (*)( void*, int )) sndvolume; + sndp->kmif.reset = (void (*)( void*, Uint32, Uint32 )) sndreset; + sndp->kmif.synth = (int (*)( void* )) sndsynth; + if ( sndp->opl_type == OPL_TYPE_MSXAUDIO ) + { + sndp->deltatpcm = YMDELTATPCMSoundAlloc(YMDELTATPCM_TYPE_Y8950); + } + else + sndp->deltatpcm = 0; + if ( sndp->opl_type & OPL_TYPE_OPL ) + { + sndp->kmif.write = (void (*)( void*, Uint32, Uint32 )) oplwrite; + sndp->kmif.read = (Uint32 (*)( void*, Uint32 )) oplread; + sndp->kmif.setinst = (void (*)( void*, Uint32, void*, Uint32 )) oplsetinst; + } + else + { + sndp->kmif.write = (void (*)( void*, Uint32, Uint32 )) opllwrite; + sndp->kmif.read = (Uint32 (*)( void*, Uint32 )) opllread; + sndp->kmif.setinst = (void (*)( void*, Uint32, void*, Uint32 )) opllsetinst; + switch ( sndp->opl_type ) + { + case OPL_TYPE_OPLL: + case OPL_TYPE_MSXMUSIC: + opllsetinst(sndp, 0, romtone [0], 16 * 19); + break; + + case OPL_TYPE_SMSFMUNIT: + opllsetinst(sndp, 0, romtone [1], 16 * 19); + break; + + case OPL_TYPE_VRC7: + opllsetinst(sndp, 0, romtone [2], 16 * 19); + break; + } + } + sndp->logtbl = LogTableAddRef(); + sndp->opltbl = OplTableAddRef(); + if ( !sndp->logtbl || !sndp->opltbl ) + { + sndrelease(sndp); + return 0; + } + + return &sndp->kmif; +} diff --git a/Frameworks/GME/gme/s_opl.h b/Frameworks/GME/gme/s_opl.h new file mode 100644 index 000000000..227c03c3c --- /dev/null +++ b/Frameworks/GME/gme/s_opl.h @@ -0,0 +1,26 @@ +#ifndef S_OPL_H__ +#define S_OPL_H__ + +#include "kmsnddev.h" + +#ifdef __cplusplus +extern "C" { +#endif + +enum { + OPL_TYPE_OPLL = 0x10, /* YAMAHA YM2413 */ + OPL_TYPE_MSXMUSIC = 0x11, /* YAMAHA YM2413 */ + OPL_TYPE_SMSFMUNIT = 0x12, /* YAMAHA YM2413? */ + OPL_TYPE_VRC7 = 0x13, /* KONAMI 053982 VRV VII */ + OPL_TYPE_OPL = 0x20, /* YAMAHA YM3526 */ + OPL_TYPE_MSXAUDIO = 0x21, /* YAMAHA Y8950 */ + OPL_TYPE_OPL2 = 0x22 /* YAMAHA YM3812 */ +}; + +KMIF_SOUND_DEVICE *OPLSoundAlloc(Uint32 opl_type); + +#ifdef __cplusplus +} +#endif + +#endif /* S_OPL_H__ */ diff --git a/Frameworks/GME/gme/s_opltbl.c b/Frameworks/GME/gme/s_opltbl.c new file mode 100644 index 000000000..35e54edbd --- /dev/null +++ b/Frameworks/GME/gme/s_opltbl.c @@ -0,0 +1,136 @@ +#include "nestypes.h" +#include "s_logtbl.h" +#include "s_opltbl.h" + +#if STATIC_TABLES + +static void OplTableRelease(void *ctx) +{ +} + +static KMIF_OPLTABLE opl_static_tables = { + &opl_static_tables; + OplTableRelease, +#include "s_oplt.h" +}; + +KMIF_OPLTABLE *OplTableAddRef(void) +{ + opl_static_tables.release = OplTableRelease; + return &opl_static_tables; +} + +#else + +#include +#ifndef M_PI +#ifdef PI +#define M_PI PI +#else +#define M_PI 3.14159265358979323846 +#endif +#endif + +#define AM_DEPTH1 4.8 /* dB */ +#define AM_DEPTH2 1.0 /* dB */ +#define PM_DEPTH1 14.0 /* cent */ +#define PM_DEPTH2 7.0 /* cent */ +#define LOG_KEYOFF (15 << (LOG_BITS + 1)) + +#define DB0375_TO_LOG(x) ((Uint32)(0.375 * (1 << (LOG_BITS + x)) / 10)) + +#define AR_OFF (128 << ARTBL_SHIFT) +#define AR_MAX (127 << ARTBL_SHIFT) + + +static volatile Uint32 opl_tables_mutex = 0; +static Uint32 opl_tables_refcount = 0; +static KMIF_OPLTABLE *opl_tables = 0; + +static void OplTableRelease(void *ctx) +{ + ++opl_tables_mutex; + while (opl_tables_mutex != 1) + { + XSLEEP(0); + } + opl_tables_refcount--; + if (!opl_tables_refcount) + { + XFREE(ctx); + opl_tables = 0; + } + --opl_tables_mutex; +} + +static void OplTableCalc(KMIF_OPLTABLE *tbl) +{ + Uint32 u, u2, i; + tbl->sin_table[0][0] = tbl->sin_table[0][1 << (SINTBL_BITS - 1)] = LOG_KEYOFF; + for (i = 1 ;i < (1 << (SINTBL_BITS - 1)); i++) + { + double d; + d = (1 << LOG_BITS) * -(log(sin(2.0 * M_PI * ((double)i) / (1 << SINTBL_BITS))) / log(2)); + if (d > (LOG_KEYOFF >> 1)) d = (LOG_KEYOFF >> 1); + tbl->sin_table[0][i] = ((Uint32)d) << 1; + tbl->sin_table[0][i + (1 << (SINTBL_BITS - 1))] = (((Uint32)d) << 1) + 1; + } + for (i = 0 ;i < (1 << SINTBL_BITS); i++) + { + tbl->sin_table[1][i] = (tbl->sin_table[0][i] & 1) ? tbl->sin_table[0][0] : tbl->sin_table[0][i]; + tbl->sin_table[2][i] = tbl->sin_table[0][i] & ~1; + tbl->sin_table[3][i] = (i & (1 << (SINTBL_BITS - 2))) ? LOG_KEYOFF : tbl->sin_table[2][i]; + } + for (i = 0; i < (1 << TLLTBL_BITS); i++) + { + tbl->tll2log_table[i] = (i * DB0375_TO_LOG(0)) << 1; + } + for (i = 0; i < (1 << AMTBL_BITS); i++) + { + u = (Uint32)((1 + sin(2 * M_PI * ((double)i) / (1 << AMTBL_BITS))) * ((1 << LOG_BITS) * AM_DEPTH1 / 20.0)); + u2 = (Uint32)((1 + sin(2 * M_PI * ((double)i) / (1 << AMTBL_BITS))) * ((1 << LOG_BITS) * AM_DEPTH2 / 20.0)); + tbl->am_table1[i] = u << 1; + tbl->am_table2[i] = u2 << 1; + } + for (i = 0; i < (1 << PMTBL_BITS); i++) + { + u = (Uint32)((1 << PM_SHIFT) * pow(2, sin(2 * M_PI * ((double)i) / (1 << PMTBL_BITS)) * PM_DEPTH1 / 1200.0)); + u2 = (Uint32)((1 << PM_SHIFT) * pow(2, sin(2 * M_PI * ((double)i) / (1 << PMTBL_BITS)) * PM_DEPTH2 / 1200.0)); + tbl->pm_table1[i] = u; + tbl->pm_table2[i] = u2; + } + + for (i = 0; i < (1 << ARTBL_BITS); i++) + { + u = (Uint32)(((double)AR_MAX) * (1 - log(1 + i) / log(1 << ARTBL_BITS))); + tbl->ar_tablelog[i] = u; +#if 1 + u = (Uint32)(((double)AR_MAX) * (pow(1 - i / (double)(1 << ARTBL_BITS), 8))); + tbl->ar_tablepow[i] = u; +#endif + } +} + +KMIF_OPLTABLE *OplTableAddRef(void) +{ + ++opl_tables_mutex; + while (opl_tables_mutex != 1) + { + XSLEEP(0); + } + if (!opl_tables_refcount) + { + opl_tables = (KMIF_OPLTABLE*) XMALLOC(sizeof(KMIF_OPLTABLE)); + if (opl_tables) + { + opl_tables->ctx = opl_tables; + opl_tables->release = OplTableRelease; + OplTableCalc(opl_tables); + } + } + if (opl_tables) opl_tables_refcount++; + --opl_tables_mutex; + return opl_tables; +} + +#endif diff --git a/Frameworks/GME/gme/s_opltbl.h b/Frameworks/GME/gme/s_opltbl.h new file mode 100644 index 000000000..7a3f805f7 --- /dev/null +++ b/Frameworks/GME/gme/s_opltbl.h @@ -0,0 +1,38 @@ +#ifndef S_OPLTBL_H__ +#define S_OPLTBL_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#define SINTBL_BITS 11 +#define AMTBL_BITS 8 +#define PMTBL_BITS 8 +#define PM_SHIFT 9 +#define ARTBL_BITS 7 +#define ARTBL_SHIFT 20 +#define TLLTBL_BITS 7 + +typedef struct +{ + void *ctx; + void (*release)(void *ctx); + Uint32 sin_table[4][1 << SINTBL_BITS]; + Uint32 tll2log_table[1 << TLLTBL_BITS]; + Uint32 ar_tablelog[1 << ARTBL_BITS]; + Uint32 am_table1[1 << AMTBL_BITS]; + Uint32 pm_table1[1 << PMTBL_BITS]; +#if 1 + Uint32 ar_tablepow[1 << ARTBL_BITS]; +#endif + Uint32 am_table2[1 << AMTBL_BITS]; + Uint32 pm_table2[1 << PMTBL_BITS]; +} KMIF_OPLTABLE; + +KMIF_OPLTABLE *OplTableAddRef(void); + +#ifdef __cplusplus +} +#endif + +#endif /* S_OPLTBL_H__ */ diff --git a/Frameworks/GME/gme/scd_pcm.c b/Frameworks/GME/gme/scd_pcm.c new file mode 100644 index 000000000..ee3d4701f --- /dev/null +++ b/Frameworks/GME/gme/scd_pcm.c @@ -0,0 +1,498 @@ +/***********************************************************/ +/* */ +/* PCM.C : PCM RF5C164 emulator */ +/* */ +/* This source is a part of Gens project */ +/* Written by Stéphane Dallongeville (gens@consolemul.com) */ +/* Copyright (c) 2002 by Stéphane Dallongeville */ +/* */ +/***********************************************************/ + +#include +#include +#include + +#include "scd_pcm.h" +int PCM_Init(void *chip, int Rate); +void PCM_Set_Rate(void *chip, int Rate); +void PCM_Reset(void *chip); +void PCM_Write_Reg(void *chip, unsigned int Reg, unsigned int Data); +int PCM_Update(void *chip, int **buf, int Length); + +#define PCM_STEP_SHIFT 11 + +static unsigned char VolTabIsInit = 0x00; +static int PCM_Volume_Tab[256 * 256]; + +//unsigned char Ram_PCM[64 * 1024]; +//int PCM_Enable; + + +/** + * PCM_Init(): Initialize the PCM chip. + * @param Rate Sample rate. + * @return 0 if successful. + */ +int PCM_Init(void *_chip, int Rate) +{ + struct pcm_chip_ *chip = (struct pcm_chip_ *) _chip; + int i, j, out; + + if (! VolTabIsInit) + { + for (i = 0; i < 0x100; i++) + { + for (j = 0; j < 0x100; j++) + { + if (i & 0x80) + { + out = -(i & 0x7F); + out *= j; + PCM_Volume_Tab[(j << 8) + i] = out; + } + else + { + out = i * j; + PCM_Volume_Tab[(j << 8) + i] = out; + } + } + } + VolTabIsInit = 0x01; + } + + for (i = 0; i < 8; i ++) + chip->Channel[i].Muted = 0x00; + + chip->RAMSize = 64 * 1024; + chip->RAM = (unsigned char*)malloc(chip->RAMSize); + PCM_Reset(chip); + PCM_Set_Rate(chip, Rate); + + return 0; +} + + +/** + * PCM_Reset(): Reset the PCM chip. + */ +void PCM_Reset(void *_chip) +{ + struct pcm_chip_ *chip = (struct pcm_chip_ *) _chip; + int i; + struct pcm_chan_* chan; + + // Clear the PCM memory. + memset(chip->RAM, 0x00, chip->RAMSize); + + chip->Enable = 0; + chip->Cur_Chan = 0; + chip->Bank = 0; + + /* clear channel registers */ + for (i = 0; i < 8; i++) + { + chan = &chip->Channel[i]; + chan->Enable = 0; + chan->ENV = 0; + chan->PAN = 0; + chan->St_Addr = 0; + chan->Addr = 0; + chan->Loop_Addr = 0; + chan->Step = 0; + chan->Step_B = 0; + chan->Data = 0; + } +} + + +/** + * PCM_Set_Rate(): Change the PCM sample rate. + * @param Rate New sample rate. + */ +void PCM_Set_Rate(void *_chip, int Rate) +{ + struct pcm_chip_ *chip = (struct pcm_chip_ *) _chip; + int i; + + if (Rate == 0) + return; + + //chip->Rate = (float) (32 * 1024) / (float) Rate; + chip->Rate = (float) (31.8 * 1024) / (float) Rate; + + for (i = 0; i < 8; i++) + { + chip->Channel[i].Step = + (int) ((float) chip->Channel[i].Step_B * chip->Rate); + } +} + + +/** + * PCM_Write_Reg(): Write to a PCM register. + * @param Reg Register ID. + * @param Data Data to write. + */ +void PCM_Write_Reg(void *_chip, unsigned int Reg, unsigned int Data) +{ + struct pcm_chip_ *chip = (struct pcm_chip_ *) _chip; + int i; + struct pcm_chan_* chan = &chip->Channel[chip->Cur_Chan]; + + Data &= 0xFF; + + switch (Reg) + { + case 0x00: + /* evelope register */ + chan->ENV = Data; + chan->MUL_L = (Data * (chan->PAN & 0x0F)) >> 5; + chan->MUL_R = (Data * (chan->PAN >> 4)) >> 5; + break; + + case 0x01: + /* pan register */ + chan->PAN = Data; + chan->MUL_L = ((Data & 0x0F) * chan->ENV) >> 5; + chan->MUL_R = ((Data >> 4) * chan->ENV) >> 5; + break; + + case 0x02: + /* frequency step (LB) registers */ + chan->Step_B &= 0xFF00; + chan->Step_B += Data; + chan->Step = (int)((float)chan->Step_B * chip->Rate); + + //LOG_MSG(pcm, LOG_MSG_LEVEL_DEBUG1, + // "Step low = %.2X Step calculated = %.8X", + // Data, chan->Step); + break; + + case 0x03: + /* frequency step (HB) registers */ + chan->Step_B &= 0x00FF; + chan->Step_B += Data << 8; + chan->Step = (int)((float)chan->Step_B * chip->Rate); + + //LOG_MSG(pcm, LOG_MSG_LEVEL_DEBUG1, + // "Step high = %.2X Step calculated = %.8X", + // Data, chan->Step); + break; + + case 0x04: + chan->Loop_Addr &= 0xFF00; + chan->Loop_Addr += Data; + + //LOG_MSG(pcm, LOG_MSG_LEVEL_DEBUG1, + // "Loop low = %.2X Loop = %.8X", + // Data, chan->Loop_Addr); + break; + + case 0x05: + /* loop address registers */ + chan->Loop_Addr &= 0x00FF; + chan->Loop_Addr += Data << 8; + + //LOG_MSG(pcm, LOG_MSG_LEVEL_DEBUG1, + // "Loop high = %.2X Loop = %.8X", + // Data, chan->Loop_Addr); + break; + + case 0x06: + /* start address registers */ + chan->St_Addr = Data << (PCM_STEP_SHIFT + 8); + //chan->Addr = chan->St_Addr; + + //LOG_MSG(pcm, LOG_MSG_LEVEL_DEBUG1, + // "Start addr = %.2X New Addr = %.8X", + // Data, chan->Addr); + break; + + case 0x07: + /* control register */ + /* mod is H */ + if (Data & 0x40) + { + /* select channel */ + chip->Cur_Chan = Data & 0x07; + } + /* mod is L */ + else + { + /* pcm ram bank select */ + chip->Bank = (Data & 0x0F) << 12; + } + + /* sounding bit */ + if (Data & 0x80) + chip->Enable = 0xFF; // Used as mask + else + chip->Enable = 0; + + //LOG_MSG(pcm, LOG_MSG_LEVEL_DEBUG1, + // "General Enable = %.2X", Data); + break; + + case 0x08: + /* sound on/off register */ + Data ^= 0xFF; + + //LOG_MSG(pcm, LOG_MSG_LEVEL_DEBUG1, + // "Channel Enable = %.2X", Data); + + for (i = 0; i < 8; i++) + { + chan = &chip->Channel[i]; + if (!chan->Enable) + chan->Addr = chan->St_Addr; + } + + for (i = 0; i < 8; i++) + { + chip->Channel[i].Enable = Data & (1 << i); + } + } +} + + +/** + * PCM_Update(): Update the PCM buffer. + * @param buf PCM buffer. + * @param Length Buffer length. + */ +int PCM_Update(void *_chip, int **buf, int Length) +{ + struct pcm_chip_ *chip = (struct pcm_chip_ *) _chip; + int i, j; + int *bufL, *bufR; //, *volL, *volR; + unsigned int Addr, k; + struct pcm_chan_ *CH; + + bufL = buf[0]; + bufR = buf[1]; + + // clear buffers + memset(bufL, 0, Length * sizeof(int)); + memset(bufR, 0, Length * sizeof(int)); + + // if PCM disable, no sound + if (!chip->Enable) + return 1; + +#if 0 + // faster for short update + for (j = 0; j < Length; j++) + { + for (i = 0; i < 8; i++) + { + CH = &(chip->Channel[i]); + + // only loop when sounding and on + if (CH->Enable && ! CH->Muted) + { + Addr = CH->Addr >> PCM_STEP_SHIFT; + + if (Addr & 0x10000) + { + for(k = CH->Old_Addr; k < 0x10000; k++) + { + if (chip->RAM[k] == 0xFF) + { + CH->Old_Addr = Addr = CH->Loop_Addr; + CH->Addr = Addr << PCM_STEP_SHIFT; + break; + } + } + + if (Addr & 0x10000) + { + //CH->Addr -= CH->Step; + CH->Enable = 0; + break; + } + } + else + { + for(k = CH->Old_Addr; k <= Addr; k++) + { + if (chip->RAM[k] == 0xFF) + { + CH->Old_Addr = Addr = CH->Loop_Addr; + CH->Addr = Addr << PCM_STEP_SHIFT; + break; + } + } + } + + // test for loop signal + if (chip->RAM[Addr] == 0xFF) + { + Addr = CH->Loop_Addr; + CH->Addr = Addr << PCM_STEP_SHIFT; + } + + if (chip->RAM[Addr] & 0x80) + { + CH->Data = chip->RAM[Addr] & 0x7F; + bufL[j] -= CH->Data * CH->MUL_L; + bufR[j] -= CH->Data * CH->MUL_R; + } + else + { + CH->Data = chip->RAM[Addr]; + bufL[j] += CH->Data * CH->MUL_L; + bufR[j] += CH->Data * CH->MUL_R; + } + + // update address register + //CH->Addr = (CH->Addr + CH->Step) & 0x7FFFFFF; + CH->Addr += CH->Step; + CH->Old_Addr = Addr + 1; + } + } + } +#endif + +#if 1 + // for long update + for (i = 0; i < 8; i++) + { + CH = &(chip->Channel[i]); + + // only loop when sounding and on + if (CH->Enable && ! CH->Muted) + { + Addr = CH->Addr >> PCM_STEP_SHIFT; + //volL = &(PCM_Volume_Tab[CH->MUL_L << 8]); + //volR = &(PCM_Volume_Tab[CH->MUL_R << 8]); + + for (j = 0; j < Length; j++) + { + // test for loop signal + if (chip->RAM[Addr] == 0xFF) + { + CH->Addr = (Addr = CH->Loop_Addr) << PCM_STEP_SHIFT; + if (chip->RAM[Addr] == 0xFF) + break; + else + j--; + } + else + { + if (chip->RAM[Addr] & 0x80) + { + CH->Data = chip->RAM[Addr] & 0x7F; + bufL[j] -= CH->Data * CH->MUL_L; + bufR[j] -= CH->Data * CH->MUL_R; + } + else + { + CH->Data = chip->RAM[Addr]; + bufL[j] += CH->Data * CH->MUL_L; + bufR[j] += CH->Data * CH->MUL_R; + } + + // update address register + k = Addr + 1; + CH->Addr = (CH->Addr + CH->Step) & 0x7FFFFFF; + Addr = CH->Addr >> PCM_STEP_SHIFT; + + for (; k < Addr; k++) + { + if (chip->RAM[k] == 0xFF) + { + CH->Addr = (Addr = CH->Loop_Addr) << PCM_STEP_SHIFT; + break; + } + } + } + } + + if (chip->RAM[Addr] == 0xFF) + { + CH->Addr = CH->Loop_Addr << PCM_STEP_SHIFT; + } + } + } +#endif + + return 0; +} + + +void rf5c164_update(void *_chip, stream_sample_t **outputs, int samples) +{ + struct pcm_chip_ *chip = (struct pcm_chip_ *) _chip; + + PCM_Update(chip, outputs, samples); +} + +void * device_start_rf5c164( int clock ) +{ + /* allocate memory for the chip */ + //rf5c164_state *chip = get_safe_token(device); + struct pcm_chip_ *chip; + int rate; + + chip = (struct pcm_chip_ *) malloc(sizeof(struct pcm_chip_)); + if (!chip) return chip; + + rate = clock / 384; + + PCM_Init(chip, rate); + /* allocate the stream */ + //chip->stream = stream_create(device, 0, 2, device->clock / 384, chip, rf5c68_update); + + return chip; +} + +void device_stop_rf5c164(void *_chip) +{ + struct pcm_chip_ *chip = (struct pcm_chip_ *) _chip; + free(chip->RAM); chip->RAM = NULL; + free(chip); +} + +void device_reset_rf5c164(void *chip) +{ + //struct pcm_chip_ *chip = &PCM_Chip[ChipID]; + PCM_Reset(chip); +} + +void rf5c164_w(void *chip, offs_t offset, UINT8 data) +{ + //struct pcm_chip_ *chip = &PCM_Chip[ChipID]; + PCM_Write_Reg(chip, offset, data); +} + +void rf5c164_mem_w(void *_chip, offs_t offset, UINT8 data) +{ + struct pcm_chip_ *chip = (struct pcm_chip_ *) _chip; + chip->RAM[chip->Bank | offset] = data; +} + +void rf5c164_write_ram(void *_chip, offs_t DataStart, offs_t DataLength, const UINT8* RAMData) +{ + struct pcm_chip_ *chip = (struct pcm_chip_ *) _chip; + + if (DataStart >= chip->RAMSize) + return; + if (DataStart + DataLength > chip->RAMSize) + DataLength = chip->RAMSize - DataStart; + + memcpy(chip->RAM + (chip->Bank | DataStart), RAMData, DataLength); + + return; +} + + +void rf5c164_set_mute_mask(void *_chip, UINT32 MuteMask) +{ + struct pcm_chip_ *chip = (struct pcm_chip_ *) _chip; + unsigned char CurChn; + + for (CurChn = 0; CurChn < 8; CurChn ++) + chip->Channel[CurChn].Muted = (MuteMask >> CurChn) & 0x01; + + return; +} diff --git a/Frameworks/GME/gme/scd_pcm.h b/Frameworks/GME/gme/scd_pcm.h new file mode 100644 index 000000000..aab658868 --- /dev/null +++ b/Frameworks/GME/gme/scd_pcm.h @@ -0,0 +1,57 @@ +#include "mamedef.h" + +struct pcm_chan_ { + unsigned int ENV; /* envelope register */ + unsigned int PAN; /* pan register */ + unsigned int MUL_L; /* envelope & pan product letf */ + unsigned int MUL_R; /* envelope & pan product right */ + unsigned int St_Addr; /* start address register */ + unsigned int Loop_Addr; /* loop address register */ + unsigned int Addr; /* current address register */ + unsigned int Step; /* frequency register */ + unsigned int Step_B; /* frequency register binaire */ + unsigned int Enable; /* channel on/off register */ + int Data; /* wave data */ + unsigned int Muted; +}; + +struct pcm_chip_ +{ + float Rate; + int Enable; + int Cur_Chan; + int Bank; + + struct pcm_chan_ Channel[8]; + + unsigned long int RAMSize; + unsigned char* RAM; +}; + +//extern struct pcm_chip_ PCM_Chip; +//extern unsigned char Ram_PCM[64 * 1024]; +//extern int PCM_Enable; + +//int PCM_Init(int Rate); +//void PCM_Set_Rate(int Rate); +//void PCM_Reset(void); +//void PCM_Write_Reg(unsigned int Reg, unsigned int Data); +//int PCM_Update(int **buf, int Length); + +#ifdef __cplusplus +extern "C" { +#endif + +void rf5c164_update(void *chip, stream_sample_t **outputs, int samples); +void * device_start_rf5c164(int clock); +void device_stop_rf5c164(void *chip); +void device_reset_rf5c164(void *chip); +void rf5c164_w(void *chip, offs_t offset, UINT8 data); +void rf5c164_mem_w(void *chip, offs_t offset, UINT8 data); +void rf5c164_write_ram(void *chip, offs_t DataStart, offs_t DataLength, const UINT8* RAMData); + +void rf5c164_set_mute_mask(void *chip, UINT32 MuteMask); + +#ifdef __cplusplus +} +#endif diff --git a/Frameworks/GME/gme/segapcm.c b/Frameworks/GME/gme/segapcm.c new file mode 100644 index 000000000..7370c369e --- /dev/null +++ b/Frameworks/GME/gme/segapcm.c @@ -0,0 +1,384 @@ +/*********************************************************/ +/* SEGA 16ch 8bit PCM */ +/*********************************************************/ + +#include +#include +#include +//#include "sndintrf.h" +//#include "streams.h" +#include "segapcm.h" + + +typedef struct _segapcm_state segapcm_state; +struct _segapcm_state +{ + UINT8 *ram; + UINT8 low[16]; + UINT32 ROMSize; + UINT8 *rom; +#ifdef _DEBUG + UINT8 *romusage; +#endif + int bankshift; + int bankmask; + int rgnmask; + sega_pcm_interface intf; + UINT8 Muted[16]; + //sound_stream * stream; +}; + +UINT8 SegaPCM_NewCore = 0x00; + +/*INLINE segapcm_state *get_safe_token(const device_config *device) +{ + assert(device != NULL); + assert(device->token != NULL); + assert(device->type == SOUND); + assert(sound_get_type(device) == SOUND_SEGAPCM); + return (segapcm_state *)device->token; +}*/ + +//static STREAM_UPDATE( SEGAPCM_update ) +void SEGAPCM_update(void *chip, stream_sample_t **outputs, int samples) +{ + //segapcm_state *spcm = (segapcm_state *)param; + segapcm_state *spcm = (segapcm_state *) chip; + int rgnmask = spcm->rgnmask; + int ch; + + /* clear the buffers */ + memset(outputs[0], 0, samples*sizeof(stream_sample_t)); + memset(outputs[1], 0, samples*sizeof(stream_sample_t)); + + // reg function + // ------------------------------------------------ + // 0x00 ? + // 0x01 ? + // 0x02 volume left + // 0x03 volume right + // 0x04 loop address (08-15) + // 0x05 loop address (16-23) + // 0x06 end address + // 0x07 address delta + // 0x80 ? + // 0x81 ? + // 0x82 ? + // 0x83 ? + // 0x84 current address (08-15), 00-07 is internal? + // 0x85 current address (16-23) + // 0x86 bit 0: channel disable? + // bit 1: loop disable + // other bits: bank + // 0x87 ? + + /* loop over channels */ + for (ch = 0; ch < 16; ch++) + { +if (! SegaPCM_NewCore) +{ + /* only process active channels */ + if (!(spcm->ram[0x86+8*ch] & 1) && ! spcm->Muted[ch]) + { + UINT8 *base = spcm->ram+8*ch; + UINT8 flags = base[0x86]; + const UINT8 *rom = spcm->rom + ((flags & spcm->bankmask) << spcm->bankshift); +#ifdef _DEBUG + const UINT8 *romusage = spcm->romusage + ((flags & spcm->bankmask) << spcm->bankshift); +#endif + UINT32 addr = (base[5] << 16) | (base[4] << 8) | spcm->low[ch]; + UINT16 loop = (base[0x85] << 8) | base[0x84]; + UINT8 end = base[6] + 1; + UINT8 delta = base[7]; + UINT8 voll = base[2]; + UINT8 volr = base[3]; + int i; + + /* loop over samples on this channel */ + for (i = 0; i < samples; i++) + { + INT8 v = 0; + + /* handle looping if we've hit the end */ + if ((addr >> 16) == end) + { + if (!(flags & 2)) + addr = loop << 8; + else + { + flags |= 1; + break; + } + } + + /* fetch the sample */ + v = rom[(addr >> 8) & rgnmask] - 0x80; +#ifdef _DEBUG + if (romusage[(addr >> 8) & rgnmask] == 0xFF && (voll || volr)) + printf("Access to empty ROM section! (0x%06lX)\n", + ((flags & spcm->bankmask) << spcm->bankshift) + (addr >> 8) & rgnmask); +#endif + + /* apply panning and advance */ + outputs[0][i] += v * voll; + outputs[1][i] += v * volr; + addr += delta; + } + + /* store back the updated address and info */ + base[0x86] = flags; + base[4] = addr >> 8; + base[5] = addr >> 16; + spcm->low[ch] = flags & 1 ? 0 : addr; + } +} +else +{ + UINT8 *regs = spcm->ram+8*ch; + + /* only process active channels */ + if (!(regs[0x86] & 1) && ! spcm->Muted[ch]) + { + const UINT8 *rom = spcm->rom + ((regs[0x86] & spcm->bankmask) << spcm->bankshift); +#ifdef _DEBUG + const UINT8 *romusage = spcm->romusage + ((regs[0x86] & spcm->bankmask) << spcm->bankshift); +#endif + UINT32 addr = (regs[0x85] << 16) | (regs[0x84] << 8) | spcm->low[ch]; + UINT32 loop = (regs[0x05] << 16) | (regs[0x04] << 8); + UINT8 end = regs[6] + 1; + int i; + + /* loop over samples on this channel */ + for (i = 0; i < samples; i++) + { + INT8 v = 0; + + /* handle looping if we've hit the end */ + if ((addr >> 16) == end) + { + if (regs[0x86] & 2) + { + regs[0x86] |= 1; + break; + } + else addr = loop; + } + + /* fetch the sample */ + v = rom[(addr >> 8) & rgnmask] - 0x80; +#ifdef _DEBUG + if (romusage[(addr >> 8) & rgnmask] == 0xFF && (regs[2] || regs[3])) + printf("Access to empty ROM section! (0x%06lX)\n", + ((regs[0x86] & spcm->bankmask) << spcm->bankshift) + (addr >> 8) & rgnmask); +#endif + + /* apply panning and advance */ + outputs[0][i] += v * regs[2]; + outputs[1][i] += v * regs[3]; + addr = (addr + regs[7]) & 0xffffff; + } + + /* store back the updated address */ + regs[0x84] = addr >> 8; + regs[0x85] = addr >> 16; + spcm->low[ch] = regs[0x86] & 1 ? 0 : addr; + } +} + } +} + +//static DEVICE_START( segapcm ) +void * device_start_segapcm(int intf_bank) +{ + const UINT32 STD_ROM_SIZE = 0x80000; + //const sega_pcm_interface *intf = (const sega_pcm_interface *)device->static_config; + sega_pcm_interface *intf; + int mask, rom_mask, len; + //segapcm_state *spcm = get_safe_token(device); + segapcm_state *spcm; + + spcm = (segapcm_state *) malloc(sizeof(segapcm_state)); + if (!spcm) return spcm; + + intf = &spcm->intf; + intf->bank = intf_bank; + + //spcm->rom = (const UINT8 *)device->region; + //spcm->ram = auto_alloc_array(device->machine, UINT8, 0x800); + spcm->ROMSize = STD_ROM_SIZE; + spcm->rom = (UINT8*) malloc(STD_ROM_SIZE); +#ifdef _DEBUG + spcm->romusage = (UINT8*) malloc(STD_ROM_SIZE); +#endif + spcm->ram = (UINT8*) malloc(0x800); + + memset(spcm->rom, 0xFF, STD_ROM_SIZE); +#ifdef _DEBUG + memset(spcm->romusage, 0xFF, STD_ROM_SIZE); +#endif + //memset(spcm->ram, 0xff, 0x800); // RAM Clear is done at device_reset + + spcm->bankshift = (UINT8)(intf->bank); + mask = intf->bank >> 16; + if(!mask) + mask = BANK_MASK7>>16; + + len = STD_ROM_SIZE; + spcm->rgnmask = len - 1; + for(rom_mask = 1; rom_mask < len; rom_mask *= 2); + rom_mask--; + + spcm->bankmask = mask & (rom_mask >> spcm->bankshift); + + //spcm->stream = stream_create(device, 0, 2, device->clock / 128, spcm, SEGAPCM_update); + + //state_save_register_device_item_array(device, 0, spcm->low); + //state_save_register_device_item_pointer(device, 0, spcm->ram, 0x800); + + for (mask = 0; mask < 16; mask ++) + spcm->Muted[mask] = 0x00; + + return spcm; +} + +//static DEVICE_STOP( segapcm ) +void device_stop_segapcm(void *chip) +{ + //segapcm_state *spcm = get_safe_token(device); + segapcm_state *spcm = (segapcm_state *) chip; + free(spcm->rom); spcm->rom = NULL; +#ifdef _DEBUG + free(spcm->romusage); +#endif + free(spcm->ram); + + free(spcm); +} + +//static DEVICE_RESET( segapcm ) +void device_reset_segapcm(void *chip) +{ + //segapcm_state *spcm = get_safe_token(device); + segapcm_state *spcm = (segapcm_state *) chip; + + memset(spcm->ram, 0xFF, 0x800); + + return; +} + + +//WRITE8_DEVICE_HANDLER( sega_pcm_w ) +void sega_pcm_w(void *chip, offs_t offset, UINT8 data) +{ + //segapcm_state *spcm = get_safe_token(device); + segapcm_state *spcm = (segapcm_state *) chip; + //stream_update(spcm->stream); + + spcm->ram[offset & 0x07ff] = data; +} + +//READ8_DEVICE_HANDLER( sega_pcm_r ) +UINT8 sega_pcm_r(void *chip, offs_t offset) +{ + //segapcm_state *spcm = get_safe_token(device); + segapcm_state *spcm = (segapcm_state *) chip; + //stream_update(spcm->stream); + return spcm->ram[offset & 0x07ff]; +} + +void sega_pcm_write_rom(void *chip, offs_t ROMSize, offs_t DataStart, offs_t DataLength, + const UINT8* ROMData) +{ + segapcm_state *spcm = (segapcm_state *) chip; + + if (spcm->ROMSize != ROMSize) + { + unsigned long int mask, rom_mask; + + spcm->rom = (UINT8*)realloc(spcm->rom, ROMSize); +#ifdef _DEBUG + spcm->romusage = (UINT8*)realloc(spcm->romusage, ROMSize); +#endif + spcm->ROMSize = ROMSize; + memset(spcm->rom, 0xFF, ROMSize); +#ifdef _DEBUG + memset(spcm->romusage, 0xFF, ROMSize); +#endif + + // recalculate bankmask + mask = spcm->intf.bank >> 16; + if (! mask) + mask = BANK_MASK7 >> 16; + + spcm->rgnmask = ROMSize - 1; + for (rom_mask = 1; rom_mask < ROMSize; rom_mask *= 2); + rom_mask --; + + spcm->bankmask = mask & (rom_mask >> spcm->bankshift); + } + if (DataStart > ROMSize) + return; + if (DataStart + DataLength > ROMSize) + DataLength = ROMSize - DataStart; + + memcpy(spcm->rom + DataStart, ROMData, DataLength); +#ifdef _DEBUG + memset(spcm->romusage + DataStart, 0x00, DataLength); +#endif + + return; +} + + +/*void sega_pcm_fwrite_romusage(UINT8 ChipID) +{ + segapcm_state *spcm = &SPCMData[ChipID]; + + FILE* hFile; + + hFile = fopen("SPCM_ROMUsage.bin", "wb"); + if (hFile == NULL) + return; + + fwrite(spcm->romusage, 0x01, spcm->ROMSize, hFile); + + fclose(hFile); + return; +}*/ + +void segapcm_set_mute_mask(void *chip, UINT32 MuteMask) +{ + segapcm_state *spcm = (segapcm_state *) chip; + unsigned char CurChn; + + for (CurChn = 0; CurChn < 16; CurChn ++) + spcm->Muted[CurChn] = (MuteMask >> CurChn) & 0x01; + + return; +} + + +/************************************************************************** + * Generic get_info + **************************************************************************/ + +/*DEVICE_GET_INFO( segapcm ) +{ + switch (state) + { + // --- the following bits of info are returned as 64-bit signed integers --- + case DEVINFO_INT_TOKEN_BYTES: info->i = sizeof(segapcm_state); break; + + // --- the following bits of info are returned as pointers to data or functions --- + case DEVINFO_FCT_START: info->start = DEVICE_START_NAME( segapcm ); break; + case DEVINFO_FCT_STOP: // Nothing break; + case DEVINFO_FCT_RESET: // Nothing break; + + // --- the following bits of info are returned as NULL-terminated strings --- + case DEVINFO_STR_NAME: strcpy(info->s, "Sega PCM"); break; + case DEVINFO_STR_FAMILY: strcpy(info->s, "Sega custom"); break; + case DEVINFO_STR_VERSION: strcpy(info->s, "1.0"); break; + case DEVINFO_STR_SOURCE_FILE: strcpy(info->s, __FILE__); break; + case DEVINFO_STR_CREDITS: strcpy(info->s, "Copyright Nicola Salmoria and the MAME Team"); break; + } +}*/ diff --git a/Frameworks/GME/gme/segapcm.h b/Frameworks/GME/gme/segapcm.h new file mode 100644 index 000000000..0053e1548 --- /dev/null +++ b/Frameworks/GME/gme/segapcm.h @@ -0,0 +1,48 @@ +/*********************************************************/ +/* SEGA 8bit PCM */ +/*********************************************************/ + +#pragma once + +#include "mamedef.h" + +#define BANK_256 (11) +#define BANK_512 (12) +#define BANK_12M (13) +#define BANK_MASK7 (0x70<<16) +#define BANK_MASKF (0xf0<<16) +#define BANK_MASKF8 (0xf8<<16) + +typedef struct _sega_pcm_interface sega_pcm_interface; +struct _sega_pcm_interface +{ + int bank; +}; + +/*WRITE8_DEVICE_HANDLER( sega_pcm_w ); +READ8_DEVICE_HANDLER( sega_pcm_r ); + +DEVICE_GET_INFO( segapcm ); +#define SOUND_SEGAPCM DEVICE_GET_INFO_NAME( segapcm )*/ + +#ifdef __cplusplus +extern "C" { +#endif + +void SEGAPCM_update(void *chip, stream_sample_t **outputs, int samples); + +void * device_start_segapcm(int intf_bank); +void device_stop_segapcm(void *chip); +void device_reset_segapcm(void *chip); + +void sega_pcm_w(void *chip, offs_t offset, UINT8 data); +UINT8 sega_pcm_r(void *chip, offs_t offset); +void sega_pcm_write_rom(void *chip, offs_t ROMSize, offs_t DataStart, offs_t DataLength, + const UINT8* ROMData); + +//void sega_pcm_fwrite_romusage(UINT8 ChipID); +void segapcm_set_mute_mask(void *chip, UINT32 MuteMask); + +#ifdef __cplusplus +} +#endif diff --git a/Frameworks/GME/gme/ym2151.c b/Frameworks/GME/gme/ym2151.c new file mode 100644 index 000000000..342d1be4c --- /dev/null +++ b/Frameworks/GME/gme/ym2151.c @@ -0,0 +1,2010 @@ +/***************************************************************************** +* +* Yamaha YM2151 driver (version 2.150 final beta) +* +******************************************************************************/ + +#include "mathdefs.h" +#include +#include +#include +#include "mamedef.h" +#include "ym2151.h" + +#ifndef logerror +#define logerror (void) +#endif + + +/* struct describing a single operator */ +typedef struct +{ + UINT32 phase; /* accumulated operator phase */ + UINT32 freq; /* operator frequency count */ + INT32 dt1; /* current DT1 (detune 1 phase inc/decrement) value */ + UINT32 mul; /* frequency count multiply */ + UINT32 dt1_i; /* DT1 index * 32 */ + UINT32 dt2; /* current DT2 (detune 2) value */ + + signed int *connect; /* operator output 'direction' */ + + /* only M1 (operator 0) is filled with this data: */ + signed int *mem_connect; /* where to put the delayed sample (MEM) */ + INT32 mem_value; /* delayed sample (MEM) value */ + + /* channel specific data; note: each operator number 0 contains channel specific data */ + UINT32 fb_shift; /* feedback shift value for operators 0 in each channel */ + INT32 fb_out_curr; /* operator feedback value (used only by operators 0) */ + INT32 fb_out_prev; /* previous feedback value (used only by operators 0) */ + UINT32 kc; /* channel KC (copied to all operators) */ + UINT32 kc_i; /* just for speedup */ + UINT32 pms; /* channel PMS */ + UINT32 ams; /* channel AMS */ + /* end of channel specific data */ + + UINT32 AMmask; /* LFO Amplitude Modulation enable mask */ + UINT32 state; /* Envelope state: 4-attack(AR) 3-decay(D1R) 2-sustain(D2R) 1-release(RR) 0-off */ + UINT8 eg_sh_ar; /* (attack state) */ + UINT8 eg_sel_ar; /* (attack state) */ + UINT32 tl; /* Total attenuation Level */ + INT32 volume; /* current envelope attenuation level */ + UINT8 eg_sh_d1r; /* (decay state) */ + UINT8 eg_sel_d1r; /* (decay state) */ + UINT32 d1l; /* envelope switches to sustain state after reaching this level */ + UINT8 eg_sh_d2r; /* (sustain state) */ + UINT8 eg_sel_d2r; /* (sustain state) */ + UINT8 eg_sh_rr; /* (release state) */ + UINT8 eg_sel_rr; /* (release state) */ + + UINT32 key; /* 0=last key was KEY OFF, 1=last key was KEY ON */ + + UINT32 ks; /* key scale */ + UINT32 ar; /* attack rate */ + UINT32 d1r; /* decay rate */ + UINT32 d2r; /* sustain rate */ + UINT32 rr; /* release rate */ + + UINT32 reserved0; /**/ + UINT32 reserved1; /**/ + +} YM2151Operator; + + +typedef struct +{ + signed int chanout[8]; + signed int m2,c1,c2; /* Phase Modulation input for operators 2,3,4 */ + signed int mem; /* one sample delay memory */ + + YM2151Operator oper[32]; /* the 32 operators */ + + UINT32 pan[16]; /* channels output masks (0xffffffff = enable) */ + + UINT32 eg_cnt; /* global envelope generator counter */ + UINT32 eg_timer; /* global envelope generator counter works at frequency = chipclock/64/3 */ + UINT32 eg_timer_add; /* step of eg_timer */ + UINT32 eg_timer_overflow; /* envelope generator timer overlfows every 3 samples (on real chip) */ + + UINT32 lfo_phase; /* accumulated LFO phase (0 to 255) */ + UINT32 lfo_timer; /* LFO timer */ + UINT32 lfo_timer_add; /* step of lfo_timer */ + UINT32 lfo_overflow; /* LFO generates new output when lfo_timer reaches this value */ + UINT32 lfo_counter; /* LFO phase increment counter */ + UINT32 lfo_counter_add; /* step of lfo_counter */ + UINT8 lfo_wsel; /* LFO waveform (0-saw, 1-square, 2-triangle, 3-random noise) */ + UINT8 amd; /* LFO Amplitude Modulation Depth */ + INT8 pmd; /* LFO Phase Modulation Depth */ + UINT32 lfa; /* LFO current AM output */ + INT32 lfp; /* LFO current PM output */ + + UINT8 test; /* TEST register */ + UINT8 ct; /* output control pins (bit1-CT2, bit0-CT1) */ + + UINT32 noise; /* noise enable/period register (bit 7 - noise enable, bits 4-0 - noise period */ + UINT32 noise_rng; /* 17 bit noise shift register */ + UINT32 noise_p; /* current noise 'phase'*/ + UINT32 noise_f; /* current noise period */ + + UINT32 csm_req; /* CSM KEY ON / KEY OFF sequence request */ + + UINT32 irq_enable; /* IRQ enable for timer B (bit 3) and timer A (bit 2); bit 7 - CSM mode (keyon to all slots, everytime timer A overflows) */ + UINT32 status; /* chip status (BUSY, IRQ Flags) */ + UINT8 connect[8]; /* channels connections */ + + /* Frequency-deltas to get the closest frequency possible. + * There are 11 octaves because of DT2 (max 950 cents over base frequency) + * and LFO phase modulation (max 800 cents below AND over base frequency) + * Summary: octave explanation + * 0 note code - LFO PM + * 1 note code + * 2 note code + * 3 note code + * 4 note code + * 5 note code + * 6 note code + * 7 note code + * 8 note code + * 9 note code + DT2 + LFO PM + * 10 note code + DT2 + LFO PM + */ + UINT32 freq[11*768]; /* 11 octaves, 768 'cents' per octave */ + + /* Frequency deltas for DT1. These deltas alter operator frequency + * after it has been taken from frequency-deltas table. + */ + INT32 dt1_freq[8*32]; /* 8 DT1 levels, 32 KC values */ + + UINT32 noise_tab[32]; /* 17bit Noise Generator periods */ + + unsigned int clock; /* chip clock in Hz (passed from 2151intf.c) */ + unsigned int sampfreq; /* sampling frequency in Hz (passed from 2151intf.c) */ + + UINT32 mask; +} YM2151; + + +#define FREQ_SH 16 /* 16.16 fixed point (frequency calculations) */ +#define EG_SH 16 /* 16.16 fixed point (envelope generator timing) */ +#define LFO_SH 10 /* 22.10 fixed point (LFO calculations) */ +#define TIMER_SH 16 /* 16.16 fixed point (timers calculations) */ + +#define FREQ_MASK ((1<>3) + +/* sin waveform table in 'decibel' scale */ +static unsigned int sin_tab[SIN_LEN]; + + +/* translate from D1L to volume index (16 D1L levels) */ +static UINT32 d1l_tab[16]; + + +#define RATE_STEPS (8) +static const UINT8 eg_inc[19*RATE_STEPS]={ + +/*cycle:0 1 2 3 4 5 6 7*/ + +/* 0 */ 0,1, 0,1, 0,1, 0,1, /* rates 00..11 0 (increment by 0 or 1) */ +/* 1 */ 0,1, 0,1, 1,1, 0,1, /* rates 00..11 1 */ +/* 2 */ 0,1, 1,1, 0,1, 1,1, /* rates 00..11 2 */ +/* 3 */ 0,1, 1,1, 1,1, 1,1, /* rates 00..11 3 */ + +/* 4 */ 1,1, 1,1, 1,1, 1,1, /* rate 12 0 (increment by 1) */ +/* 5 */ 1,1, 1,2, 1,1, 1,2, /* rate 12 1 */ +/* 6 */ 1,2, 1,2, 1,2, 1,2, /* rate 12 2 */ +/* 7 */ 1,2, 2,2, 1,2, 2,2, /* rate 12 3 */ + +/* 8 */ 2,2, 2,2, 2,2, 2,2, /* rate 13 0 (increment by 2) */ +/* 9 */ 2,2, 2,4, 2,2, 2,4, /* rate 13 1 */ +/*10 */ 2,4, 2,4, 2,4, 2,4, /* rate 13 2 */ +/*11 */ 2,4, 4,4, 2,4, 4,4, /* rate 13 3 */ + +/*12 */ 4,4, 4,4, 4,4, 4,4, /* rate 14 0 (increment by 4) */ +/*13 */ 4,4, 4,8, 4,4, 4,8, /* rate 14 1 */ +/*14 */ 4,8, 4,8, 4,8, 4,8, /* rate 14 2 */ +/*15 */ 4,8, 8,8, 4,8, 8,8, /* rate 14 3 */ + +/*16 */ 8,8, 8,8, 8,8, 8,8, /* rates 15 0, 15 1, 15 2, 15 3 (increment by 8) */ +/*17 */ 16,16,16,16,16,16,16,16, /* rates 15 2, 15 3 for attack */ +/*18 */ 0,0, 0,0, 0,0, 0,0, /* infinity rates for attack and decay(s) */ +}; + + +#define O(a) (a*RATE_STEPS) + +/*note that there is no O(17) in this table - it's directly in the code */ +static const UINT8 eg_rate_select[32+64+32]={ /* Envelope Generator rates (32 + 64 rates + 32 RKS) */ +/* 32 dummy (infinite time) rates */ +O(18),O(18),O(18),O(18),O(18),O(18),O(18),O(18), +O(18),O(18),O(18),O(18),O(18),O(18),O(18),O(18), +O(18),O(18),O(18),O(18),O(18),O(18),O(18),O(18), +O(18),O(18),O(18),O(18),O(18),O(18),O(18),O(18), + +/* rates 00-11 */ +O( 0),O( 1),O( 2),O( 3), +O( 0),O( 1),O( 2),O( 3), +O( 0),O( 1),O( 2),O( 3), +O( 0),O( 1),O( 2),O( 3), +O( 0),O( 1),O( 2),O( 3), +O( 0),O( 1),O( 2),O( 3), +O( 0),O( 1),O( 2),O( 3), +O( 0),O( 1),O( 2),O( 3), +O( 0),O( 1),O( 2),O( 3), +O( 0),O( 1),O( 2),O( 3), +O( 0),O( 1),O( 2),O( 3), +O( 0),O( 1),O( 2),O( 3), + +/* rate 12 */ +O( 4),O( 5),O( 6),O( 7), + +/* rate 13 */ +O( 8),O( 9),O(10),O(11), + +/* rate 14 */ +O(12),O(13),O(14),O(15), + +/* rate 15 */ +O(16),O(16),O(16),O(16), + +/* 32 dummy rates (same as 15 3) */ +O(16),O(16),O(16),O(16),O(16),O(16),O(16),O(16), +O(16),O(16),O(16),O(16),O(16),O(16),O(16),O(16), +O(16),O(16),O(16),O(16),O(16),O(16),O(16),O(16), +O(16),O(16),O(16),O(16),O(16),O(16),O(16),O(16) + +}; +#undef O + +/*rate 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15*/ +/*shift 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 0, 0, 0, 0 */ +/*mask 2047, 1023, 511, 255, 127, 63, 31, 15, 7, 3, 1, 0, 0, 0, 0, 0 */ + +#define O(a) (a*1) +static const UINT8 eg_rate_shift[32+64+32]={ /* Envelope Generator counter shifts (32 + 64 rates + 32 RKS) */ +/* 32 infinite time rates */ +O(0),O(0),O(0),O(0),O(0),O(0),O(0),O(0), +O(0),O(0),O(0),O(0),O(0),O(0),O(0),O(0), +O(0),O(0),O(0),O(0),O(0),O(0),O(0),O(0), +O(0),O(0),O(0),O(0),O(0),O(0),O(0),O(0), + + +/* rates 00-11 */ +O(11),O(11),O(11),O(11), +O(10),O(10),O(10),O(10), +O( 9),O( 9),O( 9),O( 9), +O( 8),O( 8),O( 8),O( 8), +O( 7),O( 7),O( 7),O( 7), +O( 6),O( 6),O( 6),O( 6), +O( 5),O( 5),O( 5),O( 5), +O( 4),O( 4),O( 4),O( 4), +O( 3),O( 3),O( 3),O( 3), +O( 2),O( 2),O( 2),O( 2), +O( 1),O( 1),O( 1),O( 1), +O( 0),O( 0),O( 0),O( 0), + +/* rate 12 */ +O( 0),O( 0),O( 0),O( 0), + +/* rate 13 */ +O( 0),O( 0),O( 0),O( 0), + +/* rate 14 */ +O( 0),O( 0),O( 0),O( 0), + +/* rate 15 */ +O( 0),O( 0),O( 0),O( 0), + +/* 32 dummy rates (same as 15 3) */ +O( 0),O( 0),O( 0),O( 0),O( 0),O( 0),O( 0),O( 0), +O( 0),O( 0),O( 0),O( 0),O( 0),O( 0),O( 0),O( 0), +O( 0),O( 0),O( 0),O( 0),O( 0),O( 0),O( 0),O( 0), +O( 0),O( 0),O( 0),O( 0),O( 0),O( 0),O( 0),O( 0) + +}; +#undef O + +/* DT2 defines offset in cents from base note +* +* This table defines offset in frequency-deltas table. +* User's Manual page 22 +* +* Values below were calculated using formula: value = orig.val / 1.5625 +* +* DT2=0 DT2=1 DT2=2 DT2=3 +* 0 600 781 950 +*/ +static const UINT32 dt2_tab[4] = { 0, 384, 500, 608 }; + +/* DT1 defines offset in Hertz from base note +* This table is converted while initialization... +* Detune table shown in YM2151 User's Manual is wrong (verified on the real chip) +*/ + +static const UINT8 dt1_tab[4*32] = { /* 4*32 DT1 values */ +/* DT1=0 */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + +/* DT1=1 */ + 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, + 2, 3, 3, 3, 4, 4, 4, 5, 5, 6, 6, 7, 8, 8, 8, 8, + +/* DT1=2 */ + 1, 1, 1, 1, 2, 2, 2, 2, 2, 3, 3, 3, 4, 4, 4, 5, + 5, 6, 6, 7, 8, 8, 9,10,11,12,13,14,16,16,16,16, + +/* DT1=3 */ + 2, 2, 2, 2, 2, 3, 3, 3, 4, 4, 4, 5, 5, 6, 6, 7, + 8, 8, 9,10,11,12,13,14,16,17,19,20,22,22,22,22 +}; + +static const UINT16 phaseinc_rom[768]={ +1299,1300,1301,1302,1303,1304,1305,1306,1308,1309,1310,1311,1313,1314,1315,1316, +1318,1319,1320,1321,1322,1323,1324,1325,1327,1328,1329,1330,1332,1333,1334,1335, +1337,1338,1339,1340,1341,1342,1343,1344,1346,1347,1348,1349,1351,1352,1353,1354, +1356,1357,1358,1359,1361,1362,1363,1364,1366,1367,1368,1369,1371,1372,1373,1374, +1376,1377,1378,1379,1381,1382,1383,1384,1386,1387,1388,1389,1391,1392,1393,1394, +1396,1397,1398,1399,1401,1402,1403,1404,1406,1407,1408,1409,1411,1412,1413,1414, +1416,1417,1418,1419,1421,1422,1423,1424,1426,1427,1429,1430,1431,1432,1434,1435, +1437,1438,1439,1440,1442,1443,1444,1445,1447,1448,1449,1450,1452,1453,1454,1455, +1458,1459,1460,1461,1463,1464,1465,1466,1468,1469,1471,1472,1473,1474,1476,1477, +1479,1480,1481,1482,1484,1485,1486,1487,1489,1490,1492,1493,1494,1495,1497,1498, +1501,1502,1503,1504,1506,1507,1509,1510,1512,1513,1514,1515,1517,1518,1520,1521, +1523,1524,1525,1526,1528,1529,1531,1532,1534,1535,1536,1537,1539,1540,1542,1543, +1545,1546,1547,1548,1550,1551,1553,1554,1556,1557,1558,1559,1561,1562,1564,1565, +1567,1568,1569,1570,1572,1573,1575,1576,1578,1579,1580,1581,1583,1584,1586,1587, +1590,1591,1592,1593,1595,1596,1598,1599,1601,1602,1604,1605,1607,1608,1609,1610, +1613,1614,1615,1616,1618,1619,1621,1622,1624,1625,1627,1628,1630,1631,1632,1633, +1637,1638,1639,1640,1642,1643,1645,1646,1648,1649,1651,1652,1654,1655,1656,1657, +1660,1661,1663,1664,1666,1667,1669,1670,1672,1673,1675,1676,1678,1679,1681,1682, +1685,1686,1688,1689,1691,1692,1694,1695,1697,1698,1700,1701,1703,1704,1706,1707, +1709,1710,1712,1713,1715,1716,1718,1719,1721,1722,1724,1725,1727,1728,1730,1731, +1734,1735,1737,1738,1740,1741,1743,1744,1746,1748,1749,1751,1752,1754,1755,1757, +1759,1760,1762,1763,1765,1766,1768,1769,1771,1773,1774,1776,1777,1779,1780,1782, +1785,1786,1788,1789,1791,1793,1794,1796,1798,1799,1801,1802,1804,1806,1807,1809, +1811,1812,1814,1815,1817,1819,1820,1822,1824,1825,1827,1828,1830,1832,1833,1835, +1837,1838,1840,1841,1843,1845,1846,1848,1850,1851,1853,1854,1856,1858,1859,1861, +1864,1865,1867,1868,1870,1872,1873,1875,1877,1879,1880,1882,1884,1885,1887,1888, +1891,1892,1894,1895,1897,1899,1900,1902,1904,1906,1907,1909,1911,1912,1914,1915, +1918,1919,1921,1923,1925,1926,1928,1930,1932,1933,1935,1937,1939,1940,1942,1944, +1946,1947,1949,1951,1953,1954,1956,1958,1960,1961,1963,1965,1967,1968,1970,1972, +1975,1976,1978,1980,1982,1983,1985,1987,1989,1990,1992,1994,1996,1997,1999,2001, +2003,2004,2006,2008,2010,2011,2013,2015,2017,2019,2021,2022,2024,2026,2028,2029, +2032,2033,2035,2037,2039,2041,2043,2044,2047,2048,2050,2052,2054,2056,2058,2059, +2062,2063,2065,2067,2069,2071,2073,2074,2077,2078,2080,2082,2084,2086,2088,2089, +2092,2093,2095,2097,2099,2101,2103,2104,2107,2108,2110,2112,2114,2116,2118,2119, +2122,2123,2125,2127,2129,2131,2133,2134,2137,2139,2141,2142,2145,2146,2148,2150, +2153,2154,2156,2158,2160,2162,2164,2165,2168,2170,2172,2173,2176,2177,2179,2181, +2185,2186,2188,2190,2192,2194,2196,2197,2200,2202,2204,2205,2208,2209,2211,2213, +2216,2218,2220,2222,2223,2226,2227,2230,2232,2234,2236,2238,2239,2242,2243,2246, +2249,2251,2253,2255,2256,2259,2260,2263,2265,2267,2269,2271,2272,2275,2276,2279, +2281,2283,2285,2287,2288,2291,2292,2295,2297,2299,2301,2303,2304,2307,2308,2311, +2315,2317,2319,2321,2322,2325,2326,2329,2331,2333,2335,2337,2338,2341,2342,2345, +2348,2350,2352,2354,2355,2358,2359,2362,2364,2366,2368,2370,2371,2374,2375,2378, +2382,2384,2386,2388,2389,2392,2393,2396,2398,2400,2402,2404,2407,2410,2411,2414, +2417,2419,2421,2423,2424,2427,2428,2431,2433,2435,2437,2439,2442,2445,2446,2449, +2452,2454,2456,2458,2459,2462,2463,2466,2468,2470,2472,2474,2477,2480,2481,2484, +2488,2490,2492,2494,2495,2498,2499,2502,2504,2506,2508,2510,2513,2516,2517,2520, +2524,2526,2528,2530,2531,2534,2535,2538,2540,2542,2544,2546,2549,2552,2553,2556, +2561,2563,2565,2567,2568,2571,2572,2575,2577,2579,2581,2583,2586,2589,2590,2593 +}; + + +/* + Noise LFO waveform. + + Here are just 256 samples out of much longer data. + + It does NOT repeat every 256 samples on real chip and I wasnt able to find + the point where it repeats (even in strings as long as 131072 samples). + + I only put it here because its better than nothing and perhaps + someone might be able to figure out the real algorithm. + + + Note that (due to the way the LFO output is calculated) it is quite + possible that two values: 0x80 and 0x00 might be wrong in this table. + To be exact: + some 0x80 could be 0x81 as well as some 0x00 could be 0x01. +*/ + +static const UINT8 lfo_noise_waveform[256] = { +0xFF,0xEE,0xD3,0x80,0x58,0xDA,0x7F,0x94,0x9E,0xE3,0xFA,0x00,0x4D,0xFA,0xFF,0x6A, +0x7A,0xDE,0x49,0xF6,0x00,0x33,0xBB,0x63,0x91,0x60,0x51,0xFF,0x00,0xD8,0x7F,0xDE, +0xDC,0x73,0x21,0x85,0xB2,0x9C,0x5D,0x24,0xCD,0x91,0x9E,0x76,0x7F,0x20,0xFB,0xF3, +0x00,0xA6,0x3E,0x42,0x27,0x69,0xAE,0x33,0x45,0x44,0x11,0x41,0x72,0x73,0xDF,0xA2, + +0x32,0xBD,0x7E,0xA8,0x13,0xEB,0xD3,0x15,0xDD,0xFB,0xC9,0x9D,0x61,0x2F,0xBE,0x9D, +0x23,0x65,0x51,0x6A,0x84,0xF9,0xC9,0xD7,0x23,0xBF,0x65,0x19,0xDC,0x03,0xF3,0x24, +0x33,0xB6,0x1E,0x57,0x5C,0xAC,0x25,0x89,0x4D,0xC5,0x9C,0x99,0x15,0x07,0xCF,0xBA, +0xC5,0x9B,0x15,0x4D,0x8D,0x2A,0x1E,0x1F,0xEA,0x2B,0x2F,0x64,0xA9,0x50,0x3D,0xAB, + +0x50,0x77,0xE9,0xC0,0xAC,0x6D,0x3F,0xCA,0xCF,0x71,0x7D,0x80,0xA6,0xFD,0xFF,0xB5, +0xBD,0x6F,0x24,0x7B,0x00,0x99,0x5D,0xB1,0x48,0xB0,0x28,0x7F,0x80,0xEC,0xBF,0x6F, +0x6E,0x39,0x90,0x42,0xD9,0x4E,0x2E,0x12,0x66,0xC8,0xCF,0x3B,0x3F,0x10,0x7D,0x79, +0x00,0xD3,0x1F,0x21,0x93,0x34,0xD7,0x19,0x22,0xA2,0x08,0x20,0xB9,0xB9,0xEF,0x51, + +0x99,0xDE,0xBF,0xD4,0x09,0x75,0xE9,0x8A,0xEE,0xFD,0xE4,0x4E,0x30,0x17,0xDF,0xCE, +0x11,0xB2,0x28,0x35,0xC2,0x7C,0x64,0xEB,0x91,0x5F,0x32,0x0C,0x6E,0x00,0xF9,0x92, +0x19,0xDB,0x8F,0xAB,0xAE,0xD6,0x12,0xC4,0x26,0x62,0xCE,0xCC,0x0A,0x03,0xE7,0xDD, +0xE2,0x4D,0x8A,0xA6,0x46,0x95,0x0F,0x8F,0xF5,0x15,0x97,0x32,0xD4,0x28,0x1E,0x55 +}; + + + + +/* save output as raw 16-bit sample */ +/* #define SAVE_SAMPLE */ +/* #define SAVE_SEPARATE_CHANNELS */ +#if defined SAVE_SAMPLE || defined SAVE_SEPARATE_CHANNELS +static FILE *sample[9]; +#endif + + + + +static void init_tables(void) +{ + signed int i,x,n; + double o,m; + + for (x=0; x>= 4; /* 12 bits here */ + if (n&1) /* round to closest */ + n = (n>>1)+1; + else + n = n>>1; + /* 11 bits here (rounded) */ + n <<= 2; /* 13 bits here (as in real chip) */ + tl_tab[ x*2 + 0 ] = n; + tl_tab[ x*2 + 1 ] = -tl_tab[ x*2 + 0 ]; + + for (i=1; i<13; i++) + { + tl_tab[ x*2+0 + i*2*TL_RES_LEN ] = tl_tab[ x*2+0 ]>>i; + tl_tab[ x*2+1 + i*2*TL_RES_LEN ] = -tl_tab[ x*2+0 + i*2*TL_RES_LEN ]; + } + #if 0 + logerror("tl %04i", x*2); + for (i=0; i<13; i++) + logerror(", [%02i] %4i", i*2, tl_tab[ x*2 /*+1*/ + i*2*TL_RES_LEN ]); + logerror("\n"); + #endif + } + /*logerror("TL_TAB_LEN = %i (%i bytes)\n",TL_TAB_LEN, (int)sizeof(tl_tab));*/ + /*logerror("ENV_QUIET= %i\n",ENV_QUIET );*/ + + + for (i=0; i0.0) + o = 8*log(1.0/m)/log(2.0); /* convert to 'decibels' */ + else + o = 8*log(-1.0/m)/log(2.0); /* convert to 'decibels' */ + + o = o / (ENV_STEP/4); + + n = (int)(2.0*o); + if (n&1) /* round to closest */ + n = (n>>1)+1; + else + n = n>>1; + + sin_tab[ i ] = n*2 + (m>=0.0? 0: 1 ); + /*logerror("sin [0x%4x]= %4i (tl_tab value=%8x)\n", i, sin_tab[i],tl_tab[sin_tab[i]]);*/ + } + + + /* calculate d1l_tab table */ + for (i=0; i<16; i++) + { + m = (i!=15 ? i : i+16) * (4.0/ENV_STEP); /* every 3 'dB' except for all bits = 1 = 45+48 'dB' */ + d1l_tab[i] = m; + /*logerror("d1l_tab[%02x]=%08x\n",i,d1l_tab[i] );*/ + } + +#ifdef SAVE_SAMPLE + sample[8]=fopen("sampsum.pcm","wb"); +#endif +#ifdef SAVE_SEPARATE_CHANNELS + sample[0]=fopen("samp0.pcm","wb"); + sample[1]=fopen("samp1.pcm","wb"); + sample[2]=fopen("samp2.pcm","wb"); + sample[3]=fopen("samp3.pcm","wb"); + sample[4]=fopen("samp4.pcm","wb"); + sample[5]=fopen("samp5.pcm","wb"); + sample[6]=fopen("samp6.pcm","wb"); + sample[7]=fopen("samp7.pcm","wb"); +#endif +} + + +static void init_chip_tables(YM2151 *chip) +{ + int i,j; + double mult,phaseinc,Hz; + double scaler; + + scaler = ( (double)chip->clock / 64.0 ) / ( (double)chip->sampfreq ); + if ( fabs( scaler - 1.0 ) < 0.0000001 ) scaler = 1.0; + /*logerror("scaler = %20.15f\n", scaler);*/ + + + /* this loop calculates Hertz values for notes from c-0 to b-7 */ + /* including 64 'cents' (100/64 that is 1.5625 of real cent) per note */ + /* i*100/64/1200 is equal to i/768 */ + + /* real chip works with 10 bits fixed point values (10.10) */ + mult = (1<<(FREQ_SH-10)); /* -10 because phaseinc_rom table values are already in 10.10 format */ + + for (i=0; i<768; i++) + { + /* 3.4375 Hz is note A; C# is 4 semitones higher */ + Hz = 1000; +#if 0 +/* Hz is close, but not perfect */ + //Hz = scaler * 3.4375 * pow (2, (i + 4 * 64 ) / 768.0 ); + /* calculate phase increment */ + phaseinc = (Hz*SIN_LEN) / (double)chip->sampfreq; +#endif + + phaseinc = phaseinc_rom[i]; /* real chip phase increment */ + phaseinc *= scaler; /* adjust */ + + + /* octave 2 - reference octave */ + chip->freq[ 768+2*768+i ] = ((int)(phaseinc*mult)) & 0xffffffc0; /* adjust to X.10 fixed point */ + /* octave 0 and octave 1 */ + for (j=0; j<2; j++) + { + chip->freq[768 + j*768 + i] = (chip->freq[ 768+2*768+i ] >> (2-j) ) & 0xffffffc0; /* adjust to X.10 fixed point */ + } + /* octave 3 to 7 */ + for (j=3; j<8; j++) + { + chip->freq[768 + j*768 + i] = chip->freq[ 768+2*768+i ] << (j-2); + } + } + + /* octave -1 (all equal to: oct 0, _KC_00_, _KF_00_) */ + for (i=0; i<768; i++) + { + chip->freq[ 0*768 + i ] = chip->freq[1*768+0]; + } + + /* octave 8 and 9 (all equal to: oct 7, _KC_14_, _KF_63_) */ + for (j=8; j<10; j++) + { + for (i=0; i<768; i++) + { + chip->freq[768+ j*768 + i ] = chip->freq[768 + 8*768 -1]; + } + } + + mult = (1<clock/64.0) ) / (double)(1<<20); + + /*calculate phase increment*/ + phaseinc = (Hz*SIN_LEN) / (double)chip->sampfreq; + + /*positive and negative values*/ + chip->dt1_freq[ (j+0)*32 + i ] = phaseinc * mult; + chip->dt1_freq[ (j+4)*32 + i ] = -chip->dt1_freq[ (j+0)*32 + i ]; + +#if 0 + { + int x = j*32 + i; + pom = (double)chip->dt1_freq[x] / mult; + pom = pom * (double)chip->sampfreq / (double)SIN_LEN; + logerror("DT1(%03i)[%02i %02i][%08x]= real %19.15f Hz emul %19.15f Hz\n", + x, j, i, chip->dt1_freq[x], Hz, pom); + } +#endif + } + } + + + /* calculate noise periods table */ + scaler = ( (double)chip->clock / 64.0 ) / ( (double)chip->sampfreq ); + if ( fabs( scaler - 1.0 ) < 0.0000001 ) scaler = 1.0; + for (i=0; i<32; i++) + { + j = (i!=31 ? i : 30); /* rate 30 and 31 are the same */ + j = 32-j; + j = (65536.0 / (double)(j*32.0)); /* number of samples per one shift of the shift register */ + /*chip->noise_tab[i] = j * 64;*/ /* number of chip clock cycles per one shift */ + chip->noise_tab[i] = j * 64 * scaler; + /*logerror("noise_tab[%02x]=%08x\n", i, chip->noise_tab[i]);*/ + } +} + +#define KEY_ON(op, key_set){ \ + if (!(op)->key) \ + { \ + (op)->phase = 0; /* clear phase */ \ + (op)->state = EG_ATT; /* KEY ON = attack */ \ + (op)->volume += (~(op)->volume * \ + (eg_inc[(op)->eg_sel_ar + ((PSG->eg_cnt>>(op)->eg_sh_ar)&7)]) \ + ) >>4; \ + if ((op)->volume <= MIN_ATT_INDEX) \ + { \ + (op)->volume = MIN_ATT_INDEX; \ + (op)->state = EG_DEC; \ + } \ + } \ + (op)->key |= key_set; \ +} + +#define KEY_OFF(op, key_clr){ \ + if ((op)->key) \ + { \ + (op)->key &= key_clr; \ + if (!(op)->key) \ + { \ + if ((op)->state>EG_REL) \ + (op)->state = EG_REL;/* KEY OFF = release */\ + } \ + } \ +} + +INLINE void envelope_KONKOFF(YM2151 *PSG, YM2151Operator * op, int v) +{ + if (v&0x08) /* M1 */ + KEY_ON (op+0, 1) + else + KEY_OFF(op+0,~1) + + if (v&0x20) /* M2 */ + KEY_ON (op+1, 1) + else + KEY_OFF(op+1,~1) + + if (v&0x10) /* C1 */ + KEY_ON (op+2, 1) + else + KEY_OFF(op+2,~1) + + if (v&0x40) /* C2 */ + KEY_ON (op+3, 1) + else + KEY_OFF(op+3,~1) +} + + +INLINE void set_connect(YM2151 *PSG, YM2151Operator *om1, int cha, int v) +{ + YM2151Operator *om2 = om1+1; + YM2151Operator *oc1 = om1+2; + + /* set connect algorithm */ + + /* MEM is simply one sample delay */ + + switch( v&7 ) + { + case 0: + /* M1---C1---MEM---M2---C2---OUT */ + om1->connect = &PSG->c1; + oc1->connect = &PSG->mem; + om2->connect = &PSG->c2; + om1->mem_connect = &PSG->m2; + break; + + case 1: + /* M1------+-MEM---M2---C2---OUT */ + /* C1-+ */ + om1->connect = &PSG->mem; + oc1->connect = &PSG->mem; + om2->connect = &PSG->c2; + om1->mem_connect = &PSG->m2; + break; + + case 2: + /* M1-----------------+-C2---OUT */ + /* C1---MEM---M2-+ */ + om1->connect = &PSG->c2; + oc1->connect = &PSG->mem; + om2->connect = &PSG->c2; + om1->mem_connect = &PSG->m2; + break; + + case 3: + /* M1---C1---MEM------+-C2---OUT */ + /* M2-+ */ + om1->connect = &PSG->c1; + oc1->connect = &PSG->mem; + om2->connect = &PSG->c2; + om1->mem_connect = &PSG->c2; + break; + + case 4: + /* M1---C1-+-OUT */ + /* M2---C2-+ */ + /* MEM: not used */ + om1->connect = &PSG->c1; + oc1->connect = &PSG->chanout[cha]; + om2->connect = &PSG->c2; + om1->mem_connect = &PSG->mem; /* store it anywhere where it will not be used */ + break; + + case 5: + /* +----C1----+ */ + /* M1-+-MEM---M2-+-OUT */ + /* +----C2----+ */ + om1->connect = 0; /* special mark */ + oc1->connect = &PSG->chanout[cha]; + om2->connect = &PSG->chanout[cha]; + om1->mem_connect = &PSG->m2; + break; + + case 6: + /* M1---C1-+ */ + /* M2-+-OUT */ + /* C2-+ */ + /* MEM: not used */ + om1->connect = &PSG->c1; + oc1->connect = &PSG->chanout[cha]; + om2->connect = &PSG->chanout[cha]; + om1->mem_connect = &PSG->mem; /* store it anywhere where it will not be used */ + break; + + case 7: + /* M1-+ */ + /* C1-+-OUT */ + /* M2-+ */ + /* C2-+ */ + /* MEM: not used*/ + om1->connect = &PSG->chanout[cha]; + oc1->connect = &PSG->chanout[cha]; + om2->connect = &PSG->chanout[cha]; + om1->mem_connect = &PSG->mem; /* store it anywhere where it will not be used */ + break; + } +} + + +INLINE void refresh_EG(YM2151Operator * op) +{ + UINT32 kc; + UINT32 v; + + kc = op->kc; + + /* v = 32 + 2*RATE + RKS = max 126 */ + + v = kc >> op->ks; + if ((op->ar+v) < 32+62) + { + op->eg_sh_ar = eg_rate_shift [op->ar + v ]; + op->eg_sel_ar = eg_rate_select[op->ar + v ]; + } + else + { + op->eg_sh_ar = 0; + op->eg_sel_ar = 17*RATE_STEPS; + } + op->eg_sh_d1r = eg_rate_shift [op->d1r + v]; + op->eg_sel_d1r= eg_rate_select[op->d1r + v]; + op->eg_sh_d2r = eg_rate_shift [op->d2r + v]; + op->eg_sel_d2r= eg_rate_select[op->d2r + v]; + op->eg_sh_rr = eg_rate_shift [op->rr + v]; + op->eg_sel_rr = eg_rate_select[op->rr + v]; + + + op+=1; + + v = kc >> op->ks; + if ((op->ar+v) < 32+62) + { + op->eg_sh_ar = eg_rate_shift [op->ar + v ]; + op->eg_sel_ar = eg_rate_select[op->ar + v ]; + } + else + { + op->eg_sh_ar = 0; + op->eg_sel_ar = 17*RATE_STEPS; + } + op->eg_sh_d1r = eg_rate_shift [op->d1r + v]; + op->eg_sel_d1r= eg_rate_select[op->d1r + v]; + op->eg_sh_d2r = eg_rate_shift [op->d2r + v]; + op->eg_sel_d2r= eg_rate_select[op->d2r + v]; + op->eg_sh_rr = eg_rate_shift [op->rr + v]; + op->eg_sel_rr = eg_rate_select[op->rr + v]; + + op+=1; + + v = kc >> op->ks; + if ((op->ar+v) < 32+62) + { + op->eg_sh_ar = eg_rate_shift [op->ar + v ]; + op->eg_sel_ar = eg_rate_select[op->ar + v ]; + } + else + { + op->eg_sh_ar = 0; + op->eg_sel_ar = 17*RATE_STEPS; + } + op->eg_sh_d1r = eg_rate_shift [op->d1r + v]; + op->eg_sel_d1r= eg_rate_select[op->d1r + v]; + op->eg_sh_d2r = eg_rate_shift [op->d2r + v]; + op->eg_sel_d2r= eg_rate_select[op->d2r + v]; + op->eg_sh_rr = eg_rate_shift [op->rr + v]; + op->eg_sel_rr = eg_rate_select[op->rr + v]; + + op+=1; + + v = kc >> op->ks; + if ((op->ar+v) < 32+62) + { + op->eg_sh_ar = eg_rate_shift [op->ar + v ]; + op->eg_sel_ar = eg_rate_select[op->ar + v ]; + } + else + { + op->eg_sh_ar = 0; + op->eg_sel_ar = 17*RATE_STEPS; + } + op->eg_sh_d1r = eg_rate_shift [op->d1r + v]; + op->eg_sel_d1r= eg_rate_select[op->d1r + v]; + op->eg_sh_d2r = eg_rate_shift [op->d2r + v]; + op->eg_sel_d2r= eg_rate_select[op->d2r + v]; + op->eg_sh_rr = eg_rate_shift [op->rr + v]; + op->eg_sel_rr = eg_rate_select[op->rr + v]; +} + + +/* write a register on YM2151 chip number 'n' */ +void ym2151_write_reg(void *_chip, int r, int v) +{ + YM2151 *chip = (YM2151 *)_chip; + YM2151Operator *op = &chip->oper[ (r&0x07)*4+((r&0x18)>>3) ]; + + /* adjust bus to 8 bits */ + r &= 0xff; + v &= 0xff; + +#if 0 + /* There is no info on what YM2151 really does when busy flag is set */ + if ( chip->status & 0x80 ) return; + timer_set ( attotime::from_hz(chip->clock) * 64, chip, 0, timer_callback_chip_busy); + chip->status |= 0x80; /* set busy flag for 64 chip clock cycles */ +#endif + + + switch(r & 0xe0) + { + case 0x00: + switch(r){ + case 0x01: /* LFO reset(bit 1), Test Register (other bits) */ + chip->test = v; + if (v&2) chip->lfo_phase = 0; + break; + + case 0x08: + envelope_KONKOFF(chip, &chip->oper[ (v&7)*4 ], v ); + break; + + case 0x0f: /* noise mode enable, noise period */ + chip->noise = v; + chip->noise_f = chip->noise_tab[ v & 0x1f ]; + break; + + case 0x10: /* timer A hi */ + break; + + case 0x11: /* timer A low */ + break; + + case 0x12: /* timer B */ + break; + + case 0x14: /* CSM, irq flag reset, irq enable, timer start/stop */ + + chip->irq_enable = v; /* bit 3-timer B, bit 2-timer A, bit 7 - CSM */ + + break; + + case 0x18: /* LFO frequency */ + { + chip->lfo_overflow = ( 1 << ((15-(v>>4))+3) ) * (1<lfo_counter_add = 0x10 + (v & 0x0f); + } + break; + + case 0x19: /* PMD (bit 7==1) or AMD (bit 7==0) */ + if (v&0x80) + chip->pmd = v & 0x7f; + else + chip->amd = v & 0x7f; + break; + + case 0x1b: /* CT2, CT1, LFO waveform */ + chip->ct = v >> 6; + chip->lfo_wsel = v & 3; + break; + + default: + logerror("YM2151 Write %02x to undocumented register #%02x\n",v,r); + break; + } + break; + + case 0x20: + op = &chip->oper[ (r&7) * 4 ]; + switch(r & 0x18) + { + case 0x00: /* RL enable, Feedback, Connection */ + op->fb_shift = ((v>>3)&7) ? ((v>>3)&7)+6:0; + chip->pan[ (r&7)*2 ] = (v & 0x40) ? ~0 : 0; + chip->pan[ (r&7)*2 +1 ] = (v & 0x80) ? ~0 : 0; + chip->connect[r&7] = v&7; + set_connect(chip, op, r&7, v&7); + break; + + case 0x08: /* Key Code */ + v &= 0x7f; + if (v != (int)(op->kc)) + { + UINT32 kc, kc_channel; + + kc_channel = (v - (v>>2))*64; + kc_channel += 768; + kc_channel |= (op->kc_i & 63); + + (op+0)->kc = v; + (op+0)->kc_i = kc_channel; + (op+1)->kc = v; + (op+1)->kc_i = kc_channel; + (op+2)->kc = v; + (op+2)->kc_i = kc_channel; + (op+3)->kc = v; + (op+3)->kc_i = kc_channel; + + kc = v>>2; + + (op+0)->dt1 = chip->dt1_freq[ (op+0)->dt1_i + kc ]; + (op+0)->freq = ( (chip->freq[ kc_channel + (op+0)->dt2 ] + (op+0)->dt1) * (op+0)->mul ) >> 1; + + (op+1)->dt1 = chip->dt1_freq[ (op+1)->dt1_i + kc ]; + (op+1)->freq = ( (chip->freq[ kc_channel + (op+1)->dt2 ] + (op+1)->dt1) * (op+1)->mul ) >> 1; + + (op+2)->dt1 = chip->dt1_freq[ (op+2)->dt1_i + kc ]; + (op+2)->freq = ( (chip->freq[ kc_channel + (op+2)->dt2 ] + (op+2)->dt1) * (op+2)->mul ) >> 1; + + (op+3)->dt1 = chip->dt1_freq[ (op+3)->dt1_i + kc ]; + (op+3)->freq = ( (chip->freq[ kc_channel + (op+3)->dt2 ] + (op+3)->dt1) * (op+3)->mul ) >> 1; + + refresh_EG( op ); + } + break; + + case 0x10: /* Key Fraction */ + v >>= 2; + if (v != (int)(op->kc_i & 63)) + { + UINT32 kc_channel; + + kc_channel = v; + kc_channel |= (op->kc_i & ~63); + + (op+0)->kc_i = kc_channel; + (op+1)->kc_i = kc_channel; + (op+2)->kc_i = kc_channel; + (op+3)->kc_i = kc_channel; + + (op+0)->freq = ( (chip->freq[ kc_channel + (op+0)->dt2 ] + (op+0)->dt1) * (op+0)->mul ) >> 1; + (op+1)->freq = ( (chip->freq[ kc_channel + (op+1)->dt2 ] + (op+1)->dt1) * (op+1)->mul ) >> 1; + (op+2)->freq = ( (chip->freq[ kc_channel + (op+2)->dt2 ] + (op+2)->dt1) * (op+2)->mul ) >> 1; + (op+3)->freq = ( (chip->freq[ kc_channel + (op+3)->dt2 ] + (op+3)->dt1) * (op+3)->mul ) >> 1; + } + break; + + case 0x18: /* PMS, AMS */ + op->pms = (v>>4) & 7; + op->ams = (v & 3); + break; + } + break; + + case 0x40: /* DT1, MUL */ + { + UINT32 olddt1_i = op->dt1_i; + UINT32 oldmul = op->mul; + + op->dt1_i = (v&0x70)<<1; + op->mul = (v&0x0f) ? (v&0x0f)<<1: 1; + + if (olddt1_i != op->dt1_i) + op->dt1 = chip->dt1_freq[ op->dt1_i + (op->kc>>2) ]; + + if ( (olddt1_i != op->dt1_i) || (oldmul != op->mul) ) + op->freq = ( (chip->freq[ op->kc_i + op->dt2 ] + op->dt1) * op->mul ) >> 1; + } + break; + + case 0x60: /* TL */ + op->tl = (v&0x7f)<<(ENV_BITS-7); /* 7bit TL */ + break; + + case 0x80: /* KS, AR */ + { + UINT32 oldks = op->ks; + UINT32 oldar = op->ar; + + op->ks = 5-(v>>6); + op->ar = (v&0x1f) ? 32 + ((v&0x1f)<<1) : 0; + + if ( (op->ar != oldar) || (op->ks != oldks) ) + { + if ((op->ar + (op->kc>>op->ks)) < 32+62) + { + op->eg_sh_ar = eg_rate_shift [op->ar + (op->kc>>op->ks) ]; + op->eg_sel_ar = eg_rate_select[op->ar + (op->kc>>op->ks) ]; + } + else + { + op->eg_sh_ar = 0; + op->eg_sel_ar = 17*RATE_STEPS; + } + } + + if (op->ks != oldks) + { + op->eg_sh_d1r = eg_rate_shift [op->d1r + (op->kc>>op->ks) ]; + op->eg_sel_d1r= eg_rate_select[op->d1r + (op->kc>>op->ks) ]; + op->eg_sh_d2r = eg_rate_shift [op->d2r + (op->kc>>op->ks) ]; + op->eg_sel_d2r= eg_rate_select[op->d2r + (op->kc>>op->ks) ]; + op->eg_sh_rr = eg_rate_shift [op->rr + (op->kc>>op->ks) ]; + op->eg_sel_rr = eg_rate_select[op->rr + (op->kc>>op->ks) ]; + } + } + break; + + case 0xa0: /* LFO AM enable, D1R */ + op->AMmask = (v&0x80) ? ~0 : 0; + op->d1r = (v&0x1f) ? 32 + ((v&0x1f)<<1) : 0; + op->eg_sh_d1r = eg_rate_shift [op->d1r + (op->kc>>op->ks) ]; + op->eg_sel_d1r= eg_rate_select[op->d1r + (op->kc>>op->ks) ]; + break; + + case 0xc0: /* DT2, D2R */ + { + UINT32 olddt2 = op->dt2; + op->dt2 = dt2_tab[ v>>6 ]; + if (op->dt2 != olddt2) + op->freq = ( (chip->freq[ op->kc_i + op->dt2 ] + op->dt1) * op->mul ) >> 1; + } + op->d2r = (v&0x1f) ? 32 + ((v&0x1f)<<1) : 0; + op->eg_sh_d2r = eg_rate_shift [op->d2r + (op->kc>>op->ks) ]; + op->eg_sel_d2r= eg_rate_select[op->d2r + (op->kc>>op->ks) ]; + break; + + case 0xe0: /* D1L, RR */ + op->d1l = d1l_tab[ v>>4 ]; + op->rr = 34 + ((v&0x0f)<<2); + op->eg_sh_rr = eg_rate_shift [op->rr + (op->kc>>op->ks) ]; + op->eg_sel_rr = eg_rate_select[op->rr + (op->kc>>op->ks) ]; + break; + } +} + + +/* +* Initialize YM2151 emulator(s). +* +* 'num' is the number of virtual YM2151's to allocate +* 'clock' is the chip clock in Hz +* 'rate' is sampling rate +*/ +void * ym2151_init(int clock, int rate) +{ + YM2151 *PSG; + + PSG = (YM2151 *) malloc(sizeof(YM2151)); + + memset(PSG, 0, sizeof(YM2151)); + + init_tables(); + + PSG->clock = clock; + /*rate = clock/64;*/ + PSG->sampfreq = rate ? rate : 44100; /* avoid division by 0 in init_chip_tables() */ + init_chip_tables( PSG ); + + PSG->lfo_timer_add = (1<sampfreq; + + PSG->eg_timer_add = (1<sampfreq; + PSG->eg_timer_overflow = ( 3 ) * (1<eg_timer_add, PSG->eg_timer_overflow);*/ + + ym2151_reset_chip(PSG); + /*logerror("YM2151[init] clock=%i sampfreq=%i\n", PSG->clock, PSG->sampfreq);*/ + + return PSG; +} + + + +void ym2151_shutdown(void *_chip) +{ + YM2151 *chip = (YM2151 *)_chip; + + free (chip); + +#ifdef SAVE_SAMPLE + fclose(sample[8]); +#endif +#ifdef SAVE_SEPARATE_CHANNELS + fclose(sample[0]); + fclose(sample[1]); + fclose(sample[2]); + fclose(sample[3]); + fclose(sample[4]); + fclose(sample[5]); + fclose(sample[6]); + fclose(sample[7]); +#endif +} + + + +/* +* Reset chip number 'n'. +*/ +void ym2151_reset_chip(void *_chip) +{ + int i; + YM2151 *chip = (YM2151 *)_chip; + + + /* initialize hardware registers */ + for (i=0; i<32; i++) + { + memset(&chip->oper[i],'\0',sizeof(YM2151Operator)); + chip->oper[i].volume = MAX_ATT_INDEX; + chip->oper[i].kc_i = 768; /* min kc_i value */ + } + + chip->eg_timer = 0; + chip->eg_cnt = 0; + + chip->lfo_timer = 0; + chip->lfo_counter= 0; + chip->lfo_phase = 0; + chip->lfo_wsel = 0; + chip->pmd = 0; + chip->amd = 0; + chip->lfa = 0; + chip->lfp = 0; + + chip->test= 0; + + chip->irq_enable = 0; + + chip->noise = 0; + chip->noise_rng = 0; + chip->noise_p = 0; + chip->noise_f = chip->noise_tab[0]; + + chip->csm_req = 0; + chip->status = 0; + + ym2151_write_reg(chip, 0x1b, 0); /* only because of CT1, CT2 output pins */ + ym2151_write_reg(chip, 0x18, 0); /* set LFO frequency */ + for (i=0x20; i<0x100; i++) /* set the operators */ + { + ym2151_write_reg(chip, i, 0); + } +} + + + +INLINE signed int op_calc(YM2151Operator * OP, unsigned int env, signed int pm) +{ + UINT32 p; + + + p = (env<<3) + sin_tab[ ( ((signed int)((OP->phase & ~FREQ_MASK) + (pm<<15))) >> FREQ_SH ) & SIN_MASK ]; + + if (p >= TL_TAB_LEN) + return 0; + + return tl_tab[p]; +} + +INLINE signed int op_calc1(YM2151Operator * OP, unsigned int env, signed int pm) +{ + UINT32 p; + INT32 i; + + + i = (OP->phase & ~FREQ_MASK) + pm; + +/*logerror("i=%08x (i>>16)&511=%8i phase=%i [pm=%08x] ",i, (i>>16)&511, OP->phase>>FREQ_SH, pm);*/ + + p = (env<<3) + sin_tab[ (i>>FREQ_SH) & SIN_MASK]; + +/*logerror("(p&255=%i p>>8=%i) out= %i\n", p&255,p>>8, tl_tab[p&255]>>(p>>8) );*/ + + if (p >= TL_TAB_LEN) + return 0; + + return tl_tab[p]; +} + + + +#define volume_calc(OP) ((OP)->tl + ((UINT32)(OP)->volume) + (AM & (OP)->AMmask)) + +INLINE void chan_calc(YM2151 *PSG, unsigned int chan) +{ + YM2151Operator *op; + unsigned int env; + UINT32 AM = 0; + + PSG->m2 = PSG->c1 = PSG->c2 = PSG->mem = 0; + op = &PSG->oper[chan*4]; /* M1 */ + + *op->mem_connect = op->mem_value; /* restore delayed sample (MEM) value to m2 or c2 */ + + if (op->ams) + AM = PSG->lfa << (op->ams-1); + env = volume_calc(op); + { + INT32 out = op->fb_out_prev + op->fb_out_curr; + op->fb_out_prev = op->fb_out_curr; + + if (!op->connect) + /* algorithm 5 */ + PSG->mem = PSG->c1 = PSG->c2 = op->fb_out_prev; + else + /* other algorithms */ + *op->connect = op->fb_out_prev; + + op->fb_out_curr = 0; + if (env < ENV_QUIET) + { + if (!op->fb_shift) + out=0; + op->fb_out_curr = op_calc1(op, env, (out<fb_shift) ); + } + } + + env = volume_calc(op+1); /* M2 */ + if (env < ENV_QUIET) + *(op+1)->connect += op_calc(op+1, env, PSG->m2); + + env = volume_calc(op+2); /* C1 */ + if (env < ENV_QUIET) + *(op+2)->connect += op_calc(op+2, env, PSG->c1); + + env = volume_calc(op+3); /* C2 */ + if (env < ENV_QUIET) + PSG->chanout[chan] += op_calc(op+3, env, PSG->c2); + + /* M1 */ + op->mem_value = PSG->mem; +} + +INLINE void chan7_calc(YM2151 *PSG) +{ + YM2151Operator *op; + unsigned int env; + UINT32 AM = 0; + + PSG->m2 = PSG->c1 = PSG->c2 = PSG->mem = 0; + op = &PSG->oper[7*4]; /* M1 */ + + *op->mem_connect = op->mem_value; /* restore delayed sample (MEM) value to m2 or c2 */ + + if (op->ams) + AM = PSG->lfa << (op->ams-1); + env = volume_calc(op); + { + INT32 out = op->fb_out_prev + op->fb_out_curr; + op->fb_out_prev = op->fb_out_curr; + + if (!op->connect) + /* algorithm 5 */ + PSG->mem = PSG->c1 = PSG->c2 = op->fb_out_prev; + else + /* other algorithms */ + *op->connect = op->fb_out_prev; + + op->fb_out_curr = 0; + if (env < ENV_QUIET) + { + if (!op->fb_shift) + out=0; + op->fb_out_curr = op_calc1(op, env, (out<fb_shift) ); + } + } + + env = volume_calc(op+1); /* M2 */ + if (env < ENV_QUIET) + *(op+1)->connect += op_calc(op+1, env, PSG->m2); + + env = volume_calc(op+2); /* C1 */ + if (env < ENV_QUIET) + *(op+2)->connect += op_calc(op+2, env, PSG->c1); + + env = volume_calc(op+3); /* C2 */ + if (PSG->noise & 0x80) + { + UINT32 noiseout; + + noiseout = 0; + if (env < 0x3ff) + noiseout = (env ^ 0x3ff) * 2; /* range of the YM2151 noise output is -2044 to 2040 */ + PSG->chanout[7] += ((PSG->noise_rng&0x10000) ? noiseout: -noiseout); /* bit 16 -> output */ + } + else + { + if (env < ENV_QUIET) + PSG->chanout[7] += op_calc(op+3, env, PSG->c2); + } + /* M1 */ + op->mem_value = PSG->mem; +} + + + + + + +/* +The 'rate' is calculated from following formula (example on decay rate): + rks = notecode after key scaling (a value from 0 to 31) + DR = value written to the chip register + rate = 2*DR + rks; (max rate = 2*31+31 = 93) +Four MSBs of the 'rate' above are the 'main' rate (from 00 to 15) +Two LSBs of the 'rate' above are the value 'x' (the shape type). +(eg. '11 2' means that 'rate' is 11*4+2=46) + +NOTE: A 'sample' in the description below is actually 3 output samples, +thats because the Envelope Generator clock is equal to internal_clock/3. + +Single '-' (minus) character in the diagrams below represents one sample +on the output; this is for rates 11 x (11 0, 11 1, 11 2 and 11 3) + +these 'main' rates: +00 x: single '-' = 2048 samples; (ie. level can change every 2048 samples) +01 x: single '-' = 1024 samples; +02 x: single '-' = 512 samples; +03 x: single '-' = 256 samples; +04 x: single '-' = 128 samples; +05 x: single '-' = 64 samples; +06 x: single '-' = 32 samples; +07 x: single '-' = 16 samples; +08 x: single '-' = 8 samples; +09 x: single '-' = 4 samples; +10 x: single '-' = 2 samples; +11 x: single '-' = 1 sample; (ie. level can change every 1 sample) + +Shapes for rates 11 x look like this: +rate: step: +11 0 01234567 + +level: +0 -- +1 -- +2 -- +3 -- + +rate: step: +11 1 01234567 + +level: +0 -- +1 -- +2 - +3 - +4 -- + +rate: step: +11 2 01234567 + +level: +0 -- +1 - +2 - +3 -- +4 - +5 - + +rate: step: +11 3 01234567 + +level: +0 -- +1 - +2 - +3 - +4 - +5 - +6 - + + +For rates 12 x, 13 x, 14 x and 15 x output level changes on every +sample - this means that the waveform looks like this: (but the level +changes by different values on different steps) +12 3 01234567 + +0 - +2 - +4 - +8 - +10 - +12 - +14 - +18 - +20 - + +Notes about the timing: +---------------------- + +1. Synchronism + +Output level of each two (or more) voices running at the same 'main' rate +(eg 11 0 and 11 1 in the diagram below) will always be changing in sync, +even if there're started with some delay. + +Note that, in the diagram below, the decay phase in channel 0 starts at +sample #2, while in channel 1 it starts at sample #6. Anyway, both channels +will always change their levels at exactly the same (following) samples. + +(S - start point of this channel, A-attack phase, D-decay phase): + +step: +01234567012345670123456 + +channel 0: + -- + | -- + | - + | - + | -- + | -- +| -- +| - +| - +| -- +AADDDDDDDDDDDDDDDD +S + +01234567012345670123456 +channel 1: + - + | - + | -- + | -- + | -- + | - + | - + | -- + | -- + | -- + AADDDDDDDDDDDDDDDD + S +01234567012345670123456 + + +2. Shifted (delayed) synchronism + +Output of each two (or more) voices running at different 'main' rate +(9 1, 10 1 and 11 1 in the diagrams below) will always be changing +in 'delayed-sync' (even if there're started with some delay as in "1.") + +Note that the shapes are delayed by exactly one sample per one 'main' rate +increment. (Normally one would expect them to start at the same samples.) + +See diagram below (* - start point of the shape). + +cycle: +0123456701234567012345670123456701234567012345670123456701234567 + +rate 09 1 +*------- + -------- + ---- + ---- + -------- + *------- + | -------- + | ---- + | ---- + | -------- +rate 10 1 | +-- | + *--- | + ---- | + -- | + -- | + ---- | + *--- | + | ---- | + | -- | | <- one step (two samples) delay between 9 1 and 10 1 + | -- | | + | ----| + | *--- + | ---- + | -- + | -- + | ---- +rate 11 1 | +- | + -- | + *- | + -- | + - | + - | + -- | + *- | + -- | + - || <- one step (one sample) delay between 10 1 and 11 1 + - || + --| + *- + -- + - + - + -- + *- + -- + - + - + -- +*/ + +INLINE void advance_eg(YM2151 *PSG) +{ + YM2151Operator *op; + unsigned int i; + + + + PSG->eg_timer += PSG->eg_timer_add; + + while (PSG->eg_timer >= PSG->eg_timer_overflow) + { + PSG->eg_timer -= PSG->eg_timer_overflow; + + PSG->eg_cnt++; + + /* envelope generator */ + op = &PSG->oper[0]; /* CH 0 M1 */ + i = 32; + do + { + switch(op->state) + { + case EG_ATT: /* attack phase */ + if ( !(PSG->eg_cnt & ((1<eg_sh_ar)-1) ) ) + { + op->volume += (~op->volume * + (eg_inc[op->eg_sel_ar + ((PSG->eg_cnt>>op->eg_sh_ar)&7)]) + ) >>4; + + if (op->volume <= MIN_ATT_INDEX) + { + op->volume = MIN_ATT_INDEX; + op->state = EG_DEC; + } + + } + break; + + case EG_DEC: /* decay phase */ + if ( !(PSG->eg_cnt & ((1<eg_sh_d1r)-1) ) ) + { + op->volume += eg_inc[op->eg_sel_d1r + ((PSG->eg_cnt>>op->eg_sh_d1r)&7)]; + + if ( op->volume >= (INT32)(op->d1l) ) + op->state = EG_SUS; + + } + break; + + case EG_SUS: /* sustain phase */ + if ( !(PSG->eg_cnt & ((1<eg_sh_d2r)-1) ) ) + { + op->volume += eg_inc[op->eg_sel_d2r + ((PSG->eg_cnt>>op->eg_sh_d2r)&7)]; + + if ( op->volume >= MAX_ATT_INDEX ) + { + op->volume = MAX_ATT_INDEX; + op->state = EG_OFF; + } + + } + break; + + case EG_REL: /* release phase */ + if ( !(PSG->eg_cnt & ((1<eg_sh_rr)-1) ) ) + { + op->volume += eg_inc[op->eg_sel_rr + ((PSG->eg_cnt>>op->eg_sh_rr)&7)]; + + if ( op->volume >= MAX_ATT_INDEX ) + { + op->volume = MAX_ATT_INDEX; + op->state = EG_OFF; + } + + } + break; + } + op++; + i--; + }while (i); + } +} + + +INLINE void advance(YM2151 *PSG) +{ + YM2151Operator *op; + unsigned int i; + int a,p; + + /* LFO */ + if (PSG->test&2) + PSG->lfo_phase = 0; + else + { + PSG->lfo_timer += PSG->lfo_timer_add; + if (PSG->lfo_timer >= PSG->lfo_overflow) + { + PSG->lfo_timer -= PSG->lfo_overflow; + PSG->lfo_counter += PSG->lfo_counter_add; + PSG->lfo_phase += (PSG->lfo_counter>>4); + PSG->lfo_phase &= 255; + PSG->lfo_counter &= 15; + } + } + + i = PSG->lfo_phase; + /* calculate LFO AM and PM waveform value (all verified on real chip, except for noise algorithm which is impossible to analyse)*/ + switch (PSG->lfo_wsel) + { + case 0: + /* saw */ + /* AM: 255 down to 0 */ + /* PM: 0 to 127, -127 to 0 (at PMD=127: LFP = 0 to 126, -126 to 0) */ + a = 255 - i; + if (i<128) + p = i; + else + p = i - 255; + break; + case 1: + /* square */ + /* AM: 255, 0 */ + /* PM: 128,-128 (LFP = exactly +PMD, -PMD) */ + if (i<128) + { + a = 255; + p = 128; + } + else + { + a = 0; + p = -128; + } + break; + case 2: + /* triangle */ + /* AM: 255 down to 1 step -2; 0 up to 254 step +2 */ + /* PM: 0 to 126 step +2, 127 to 1 step -2, 0 to -126 step -2, -127 to -1 step +2*/ + if (i<128) + a = 255 - (i*2); + else + a = (i*2) - 256; + + if (i<64) /* i = 0..63 */ + p = i*2; /* 0 to 126 step +2 */ + else if (i<128) /* i = 64..127 */ + p = 255 - i*2; /* 127 to 1 step -2 */ + else if (i<192) /* i = 128..191 */ + p = 256 - i*2; /* 0 to -126 step -2*/ + else /* i = 192..255 */ + p = i*2 - 511; /*-127 to -1 step +2*/ + break; + case 3: + default: /*keep the compiler happy*/ + /* random */ + /* the real algorithm is unknown !!! + We just use a snapshot of data from real chip */ + + /* AM: range 0 to 255 */ + /* PM: range -128 to 127 */ + + a = lfo_noise_waveform[i]; + p = a-128; + break; + } + PSG->lfa = a * PSG->amd / 128; + PSG->lfp = p * PSG->pmd / 128; + + + /* The Noise Generator of the YM2151 is 17-bit shift register. + * Input to the bit16 is negated (bit0 XOR bit3) (EXNOR). + * Output of the register is negated (bit0 XOR bit3). + * Simply use bit16 as the noise output. + */ + PSG->noise_p += PSG->noise_f; + i = (PSG->noise_p>>16); /* number of events (shifts of the shift register) */ + PSG->noise_p &= 0xffff; + while (i) + { + UINT32 j; + j = ( (PSG->noise_rng ^ (PSG->noise_rng>>3) ) & 1) ^ 1; + PSG->noise_rng = (j<<16) | (PSG->noise_rng>>1); + i--; + } + + + /* phase generator */ + op = &PSG->oper[0]; /* CH 0 M1 */ + i = 8; + do + { + if (op->pms) /* only when phase modulation from LFO is enabled for this channel */ + { + INT32 mod_ind = PSG->lfp; /* -128..+127 (8bits signed) */ + if (op->pms < 6) + mod_ind >>= (6 - op->pms); + else + mod_ind <<= (op->pms - 5); + + if (mod_ind) + { + UINT32 kc_channel = op->kc_i + mod_ind; + (op+0)->phase += ( (PSG->freq[ kc_channel + (op+0)->dt2 ] + (op+0)->dt1) * (op+0)->mul ) >> 1; + (op+1)->phase += ( (PSG->freq[ kc_channel + (op+1)->dt2 ] + (op+1)->dt1) * (op+1)->mul ) >> 1; + (op+2)->phase += ( (PSG->freq[ kc_channel + (op+2)->dt2 ] + (op+2)->dt1) * (op+2)->mul ) >> 1; + (op+3)->phase += ( (PSG->freq[ kc_channel + (op+3)->dt2 ] + (op+3)->dt1) * (op+3)->mul ) >> 1; + } + else /* phase modulation from LFO is equal to zero */ + { + (op+0)->phase += (op+0)->freq; + (op+1)->phase += (op+1)->freq; + (op+2)->phase += (op+2)->freq; + (op+3)->phase += (op+3)->freq; + } + } + else /* phase modulation from LFO is disabled */ + { + (op+0)->phase += (op+0)->freq; + (op+1)->phase += (op+1)->freq; + (op+2)->phase += (op+2)->freq; + (op+3)->phase += (op+3)->freq; + } + + op+=4; + i--; + }while (i); + + + /* CSM is calculated *after* the phase generator calculations (verified on real chip) + * CSM keyon line seems to be ORed with the KO line inside of the chip. + * The result is that it only works when KO (register 0x08) is off, ie. 0 + * + * Interesting effect is that when timer A is set to 1023, the KEY ON happens + * on every sample, so there is no KEY OFF at all - the result is that + * the sound played is the same as after normal KEY ON. + */ + + if (PSG->csm_req) /* CSM KEYON/KEYOFF seqeunce request */ + { + if (PSG->csm_req==2) /* KEY ON */ + { + op = &PSG->oper[0]; /* CH 0 M1 */ + i = 32; + do + { + KEY_ON(op, 2); + op++; + i--; + }while (i); + PSG->csm_req = 1; + } + else /* KEY OFF */ + { + op = &PSG->oper[0]; /* CH 0 M1 */ + i = 32; + do + { + KEY_OFF(op,~2); + op++; + i--; + }while (i); + PSG->csm_req = 0; + } + } +} + +/* first macro saves left and right channels to mono file */ +/* second macro saves left and right channels to stereo file */ +#if 0 /*MONO*/ + #ifdef SAVE_SEPARATE_CHANNELS + #define SAVE_SINGLE_CHANNEL(j) \ + { signed int pom= -(chanout[j] & PSG->pan[j*2]); \ + if (pom > 32767) pom = 32767; else if (pom < -32768) pom = -32768; \ + fputc((unsigned short)pom&0xff,sample[j]); \ + fputc(((unsigned short)pom>>8)&0xff,sample[j]); \ + } + #else + #define SAVE_SINGLE_CHANNEL(j) + #endif +#else /*STEREO*/ + #ifdef SAVE_SEPARATE_CHANNELS + #define SAVE_SINGLE_CHANNEL(j) \ + { signed int pom = -(chanout[j] & PSG->pan[j*2]); \ + if (pom > 32767) pom = 32767; else if (pom < -32768) pom = -32768; \ + fputc((unsigned short)pom&0xff,sample[j]); \ + fputc(((unsigned short)pom>>8)&0xff,sample[j]); \ + pom = -(chanout[j] & PSG->pan[j*2+1]); \ + if (pom > 32767) pom = 32767; else if (pom < -32768) pom = -32768; \ + fputc((unsigned short)pom&0xff,sample[j]); \ + fputc(((unsigned short)pom>>8)&0xff,sample[j]); \ + } + #else + #define SAVE_SINGLE_CHANNEL(j) + #endif +#endif + +/* first macro saves left and right channels to mono file */ +/* second macro saves left and right channels to stereo file */ +#if 1 /*MONO*/ + #ifdef SAVE_SAMPLE + #define SAVE_ALL_CHANNELS \ + { signed int pom = outl; \ + /*pom = acc_calc(pom);*/ \ + /*fprintf(sample[8]," %i\n",pom);*/ \ + fputc((unsigned short)pom&0xff,sample[8]); \ + fputc(((unsigned short)pom>>8)&0xff,sample[8]); \ + } + #else + #define SAVE_ALL_CHANNELS + #endif +#else /*STEREO*/ + #ifdef SAVE_SAMPLE + #define SAVE_ALL_CHANNELS \ + { signed int pom = outl; \ + fputc((unsigned short)pom&0xff,sample[8]); \ + fputc(((unsigned short)pom>>8)&0xff,sample[8]); \ + pom = outr; \ + fputc((unsigned short)pom&0xff,sample[8]); \ + fputc(((unsigned short)pom>>8)&0xff,sample[8]); \ + } + #else + #define SAVE_ALL_CHANNELS + #endif +#endif + + +/* Generate samples for one of the YM2151's +* +* 'num' is the number of virtual YM2151 +* '**buffers' is table of pointers to the buffers: left and right +* 'length' is the number of samples that should be generated +*/ +void ym2151_update_one(void *chip, SAMP **buffers, int length) +{ + YM2151 *PSG = (YM2151 *)chip; + signed int *chanout = PSG->chanout; + int i; + UINT32 mask = ~PSG->mask; + signed int outl,outr; + SAMP *bufL, *bufR; + + bufL = buffers[0]; + bufR = buffers[1]; + + for (i=0; ipan[0]; + if (mask & 1) outr += chanout[0] & PSG->pan[1]; + if (mask & 2) outl += (chanout[1] & PSG->pan[2]); + if (mask & 2) outr += (chanout[1] & PSG->pan[3]); + if (mask & 4) outl += (chanout[2] & PSG->pan[4]); + if (mask & 4) outr += (chanout[2] & PSG->pan[5]); + if (mask & 8) outl += (chanout[3] & PSG->pan[6]); + if (mask & 8) outr += (chanout[3] & PSG->pan[7]); + if (mask & 16) outl += (chanout[4] & PSG->pan[8]); + if (mask & 16) outr += (chanout[4] & PSG->pan[9]); + if (mask & 32) outl += (chanout[5] & PSG->pan[10]); + if (mask & 32) outr += (chanout[5] & PSG->pan[11]); + if (mask & 64) outl += (chanout[6] & PSG->pan[12]); + if (mask & 64) outr += (chanout[6] & PSG->pan[13]); + if (mask & 128) outl += (chanout[7] & PSG->pan[14]); + if (mask & 128) outr += (chanout[7] & PSG->pan[15]); + + outl >>= FINAL_SH; + outr >>= FINAL_SH; + if (outl > MAXOUT) outl = MAXOUT; + else if (outl < MINOUT) outl = MINOUT; + if (outr > MAXOUT) outr = MAXOUT; + else if (outr < MINOUT) outr = MINOUT; + ((SAMP*)bufL)[i] = (SAMP)outl; + ((SAMP*)bufR)[i] = (SAMP)outr; + + SAVE_ALL_CHANNELS + + advance(PSG); + } +} + +void ym2151_set_mask(void *_chip, UINT32 mask) +{ + YM2151 *PSG = (YM2151 *)_chip; + + PSG->mask = mask; +} diff --git a/Frameworks/GME/gme/ym2151.h b/Frameworks/GME/gme/ym2151.h new file mode 100644 index 000000000..834dfe1bd --- /dev/null +++ b/Frameworks/GME/gme/ym2151.h @@ -0,0 +1,89 @@ +/* +** File: ym2151.h - header file for software implementation of YM2151 +** FM Operator Type-M(OPM) +** +** (c) 1997-2002 Jarek Burczynski (s0246@poczta.onet.pl, bujar@mame.net) +** Some of the optimizing ideas by Tatsuyuki Satoh +** +** Version 2.150 final beta May, 11th 2002 +** +** +** I would like to thank following people for making this project possible: +** +** Beauty Planets - for making a lot of real YM2151 samples and providing +** additional informations about the chip. Also for the time spent making +** the samples and the speed of replying to my endless requests. +** +** Shigeharu Isoda - for general help, for taking time to scan his YM2151 +** Japanese Manual first of all, and answering MANY of my questions. +** +** Nao - for giving me some info about YM2151 and pointing me to Shigeharu. +** Also for creating fmemu (which I still use to test the emulator). +** +** Aaron Giles and Chris Hardy - they made some samples of one of my favourite +** arcade games so I could compare it to my emulator. +** +** Bryan McPhail and Tim (powerjaw) - for making some samples. +** +** Ishmair - for the datasheet and motivation. +*/ + +#pragma once + +#ifndef __YM2151_H__ +#define __YM2151_H__ + + +/* 16- and 8-bit samples (signed) are supported*/ +#define SAMPLE_BITS 16 + +#include "mamedef.h" + +typedef stream_sample_t SAMP; +/* +#if (SAMPLE_BITS==16) + typedef INT16 SAMP; +#endif +#if (SAMPLE_BITS==8) + typedef signed char SAMP; +#endif +*/ + +#ifdef __cplusplus +extern "C" { +#endif + +/* +** Initialize YM2151 emulator(s). +** +** 'num' is the number of virtual YM2151's to allocate +** 'clock' is the chip clock in Hz +** 'rate' is sampling rate +*/ +void *ym2151_init(int clock, int rate); + +/* shutdown the YM2151 emulators*/ +void ym2151_shutdown(void *chip); + +/* reset all chip registers for YM2151 number 'num'*/ +void ym2151_reset_chip(void *chip); + +/* +** Generate samples for one of the YM2151's +** +** 'num' is the number of virtual YM2151 +** '**buffers' is table of pointers to the buffers: left and right +** 'length' is the number of samples that should be generated +*/ +void ym2151_update_one(void *chip, SAMP **buffers, int length); + +/* write 'v' to register 'r' on YM2151 chip number 'n'*/ +void ym2151_write_reg(void *chip, int r, int v); + +void ym2151_set_mask(void *chip, UINT32 mask); + +#ifdef __cplusplus +} +#endif + +#endif /*__YM2151_H__*/ diff --git a/Frameworks/GME/gme/ym2413.c b/Frameworks/GME/gme/ym2413.c new file mode 100644 index 000000000..af225d237 --- /dev/null +++ b/Frameworks/GME/gme/ym2413.c @@ -0,0 +1,2108 @@ +/* +** +** File: ym2413.c - software implementation of YM2413 +** FM sound generator type OPLL +** +** Copyright Jarek Burczynski +** +** Version 1.0 +** + + Features as listed in LSI-212413A2 data sheet: + 1. FM Sound Generator for real sound creation. + 2. Two Selectable modes: 9 simultaneous sounds or 6 melody sounds plus 5 rhythm sounds + (different tones can be used together in either case). + 3. Built-in Instruments data (15 melody tones, 5 rhythm tones, "CAPTAIN and TELETEXT applicalbe tones). + 4. Built-in DA Converter. + 5. Built-in Quartz Oscillator. + 6. Built-in Vibrato Oscillator/AM Oscillator + 7. TTL Compatible Input. + 8. Si-Gate NMOS LSI + 9. A single 5V power source. + +to do: + +- make sure of the sinus amplitude bits + +- make sure of the EG resolution bits (looks like the biggest + modulation index generated by the modulator is 123, 124 = no modulation) +- find proper algorithm for attack phase of EG + +- tune up instruments ROM + +- support sample replay in test mode (it is NOT as simple as setting bit 0 + in register 0x0f and using register 0x10 for sample data). + Which games use this feature ? + + +*/ + +#define _USE_MATH_DEFINES +#include +#include +#include +#include "mamedef.h" +#include "ym2413.h" + +#ifndef INLINE +#define INLINE static __inline +#endif +#ifndef logerror +#define logerror (void) +#endif + +#ifndef M_PI + #define M_PI 3.14159265358979323846 +#endif + +/* output final shift */ +#if (SAMPLE_BITS==16) + #define FINAL_SH (0) + #define MAXOUT (+32767) + #define MINOUT (-32768) +#else + #define FINAL_SH (8) + #define MAXOUT (+127) + #define MINOUT (-128) +#endif + + +#define FREQ_SH 16 /* 16.16 fixed point (frequency calculations) */ +#define EG_SH 16 /* 16.16 fixed point (EG timing) */ +#define LFO_SH 24 /* 8.24 fixed point (LFO calculations) */ + +#define FREQ_MASK ((1<>KSR */ + UINT8 mul; /* multiple: mul_tab[ML] */ + + /* Phase Generator */ + UINT32 phase; /* frequency counter */ + UINT32 freq; /* frequency counter step */ + UINT8 fb_shift; /* feedback shift value */ + INT32 op1_out[2]; /* slot1 output for feedback */ + + /* Envelope Generator */ + UINT8 eg_type; /* percussive/nonpercussive mode*/ + UINT8 state; /* phase type */ + UINT32 TL; /* total level: TL << 2 */ + INT32 TLL; /* adjusted now TL */ + INT32 volume; /* envelope counter */ + UINT32 sl; /* sustain level: sl_tab[SL] */ + + UINT8 eg_sh_dp; /* (dump state) */ + UINT8 eg_sel_dp; /* (dump state) */ + UINT8 eg_sh_ar; /* (attack state) */ + UINT8 eg_sel_ar; /* (attack state) */ + UINT8 eg_sh_dr; /* (decay state) */ + UINT8 eg_sel_dr; /* (decay state) */ + UINT8 eg_sh_rr; /* (release state for non-perc.)*/ + UINT8 eg_sel_rr; /* (release state for non-perc.)*/ + UINT8 eg_sh_rs; /* (release state for perc.mode)*/ + UINT8 eg_sel_rs; /* (release state for perc.mode)*/ + + UINT32 key; /* 0 = KEY OFF, >0 = KEY ON */ + + /* LFO */ + UINT32 AMmask; /* LFO Amplitude Modulation enable mask */ + UINT8 vib; /* LFO Phase Modulation enable flag (active high)*/ + + /* waveform select */ + unsigned int wavetable; +} OPLL_SLOT; + +#define OPLL_MASK_CH(x) (1<<(x)) +#define OPLL_MASK_HH (1<<(9)) +#define OPLL_MASK_CYM (1<<(10)) +#define OPLL_MASK_TOM (1<<(11)) +#define OPLL_MASK_SD (1<<(12)) +#define OPLL_MASK_BD (1<<(13)) +#define OPLL_MASK_RHYTHM ( OPLL_MASK_HH | OPLL_MASK_CYM | OPLL_MASK_TOM | OPLL_MASK_SD | OPLL_MASK_BD ) + +typedef struct{ + OPLL_SLOT SLOT[2]; + /* phase generator state */ + UINT32 block_fnum; /* block+fnum */ + UINT32 fc; /* Freq. freqement base */ + UINT32 ksl_base; /* KeyScaleLevel Base step */ + UINT8 kcode; /* key code (for key scaling) */ + UINT8 sus; /* sus on/off (release speed in percussive mode)*/ +} OPLL_CH; + +/* chip state */ +typedef struct { + OPLL_CH P_CH[9]; /* OPLL chips have 9 channels*/ + UINT8 instvol_r[9]; /* instrument/volume (or volume/volume in percussive mode)*/ + + UINT32 eg_cnt; /* global envelope generator counter */ + UINT32 eg_timer; /* global envelope generator counter works at frequency = chipclock/72 */ + UINT32 eg_timer_add; /* step of eg_timer */ + UINT32 eg_timer_overflow; /* envelope generator timer overlfows every 1 sample (on real chip) */ + + UINT8 rhythm; /* Rhythm mode */ + + /* LFO */ + UINT32 lfo_am_cnt; + UINT32 lfo_am_inc; + UINT32 lfo_pm_cnt; + UINT32 lfo_pm_inc; + + UINT32 noise_rng; /* 23 bit noise shift register */ + UINT32 noise_p; /* current noise 'phase' */ + UINT32 noise_f; /* current noise period */ + + +/* instrument settings */ +/* + 0-user instrument + 1-15 - fixed instruments + 16 -bass drum settings + 17,18 - other percussion instruments +*/ + UINT8 inst_tab[19][8]; + + /* external event callback handlers */ + OPLL_UPDATEHANDLER UpdateHandler; /* stream update handler */ + void * UpdateParam; /* stream update parameter */ + + UINT32 fn_tab[1024]; /* fnumber->increment counter */ + + UINT8 address; /* address register */ + UINT8 status; /* status flag */ + + int clock; /* master clock (Hz) */ + int rate; /* sampling rate (Hz) */ + double freqbase; /* frequency base */ + + /* work table */ + OPLL_SLOT *SLOT7_1,*SLOT7_2,*SLOT8_1,*SLOT8_2; + + signed int output[2]; + + UINT32 LFO_AM; + INT32 LFO_PM; + + int chip_type; + UINT32 mask; +} YM2413; + +/* key scale level */ +/* table is 3dB/octave, DV converts this into 6dB/octave */ +/* 0.1875 is bit 0 weight of the envelope counter (volume) expressed in the 'decibel' scale */ +#define DV (0.1875/1.0) +static const UINT32 ksl_tab[8*16]= +{ + /* OCT 0 */ + 0.000/DV, 0.000/DV, 0.000/DV, 0.000/DV, + 0.000/DV, 0.000/DV, 0.000/DV, 0.000/DV, + 0.000/DV, 0.000/DV, 0.000/DV, 0.000/DV, + 0.000/DV, 0.000/DV, 0.000/DV, 0.000/DV, + /* OCT 1 */ + 0.000/DV, 0.000/DV, 0.000/DV, 0.000/DV, + 0.000/DV, 0.000/DV, 0.000/DV, 0.000/DV, + 0.000/DV, 0.750/DV, 1.125/DV, 1.500/DV, + 1.875/DV, 2.250/DV, 2.625/DV, 3.000/DV, + /* OCT 2 */ + 0.000/DV, 0.000/DV, 0.000/DV, 0.000/DV, + 0.000/DV, 1.125/DV, 1.875/DV, 2.625/DV, + 3.000/DV, 3.750/DV, 4.125/DV, 4.500/DV, + 4.875/DV, 5.250/DV, 5.625/DV, 6.000/DV, + /* OCT 3 */ + 0.000/DV, 0.000/DV, 0.000/DV, 1.875/DV, + 3.000/DV, 4.125/DV, 4.875/DV, 5.625/DV, + 6.000/DV, 6.750/DV, 7.125/DV, 7.500/DV, + 7.875/DV, 8.250/DV, 8.625/DV, 9.000/DV, + /* OCT 4 */ + 0.000/DV, 0.000/DV, 3.000/DV, 4.875/DV, + 6.000/DV, 7.125/DV, 7.875/DV, 8.625/DV, + 9.000/DV, 9.750/DV,10.125/DV,10.500/DV, + 10.875/DV,11.250/DV,11.625/DV,12.000/DV, + /* OCT 5 */ + 0.000/DV, 3.000/DV, 6.000/DV, 7.875/DV, + 9.000/DV,10.125/DV,10.875/DV,11.625/DV, + 12.000/DV,12.750/DV,13.125/DV,13.500/DV, + 13.875/DV,14.250/DV,14.625/DV,15.000/DV, + /* OCT 6 */ + 0.000/DV, 6.000/DV, 9.000/DV,10.875/DV, + 12.000/DV,13.125/DV,13.875/DV,14.625/DV, + 15.000/DV,15.750/DV,16.125/DV,16.500/DV, + 16.875/DV,17.250/DV,17.625/DV,18.000/DV, + /* OCT 7 */ + 0.000/DV, 9.000/DV,12.000/DV,13.875/DV, + 15.000/DV,16.125/DV,16.875/DV,17.625/DV, + 18.000/DV,18.750/DV,19.125/DV,19.500/DV, + 19.875/DV,20.250/DV,20.625/DV,21.000/DV +}; +#undef DV + +/* sustain level table (3dB per step) */ +/* 0 - 15: 0, 3, 6, 9,12,15,18,21,24,27,30,33,36,39,42,45 (dB)*/ +#define SC(db) (UINT32) ( db * (1.0/ENV_STEP) ) +static const UINT32 sl_tab[16]={ + SC( 0),SC( 1),SC( 2),SC(3 ),SC(4 ),SC(5 ),SC(6 ),SC( 7), + SC( 8),SC( 9),SC(10),SC(11),SC(12),SC(13),SC(14),SC(15) +}; +#undef SC + + +#define RATE_STEPS (8) +static const unsigned char eg_inc[15*RATE_STEPS]={ + +/*cycle:0 1 2 3 4 5 6 7*/ + +/* 0 */ 0,1, 0,1, 0,1, 0,1, /* rates 00..12 0 (increment by 0 or 1) */ +/* 1 */ 0,1, 0,1, 1,1, 0,1, /* rates 00..12 1 */ +/* 2 */ 0,1, 1,1, 0,1, 1,1, /* rates 00..12 2 */ +/* 3 */ 0,1, 1,1, 1,1, 1,1, /* rates 00..12 3 */ + +/* 4 */ 1,1, 1,1, 1,1, 1,1, /* rate 13 0 (increment by 1) */ +/* 5 */ 1,1, 1,2, 1,1, 1,2, /* rate 13 1 */ +/* 6 */ 1,2, 1,2, 1,2, 1,2, /* rate 13 2 */ +/* 7 */ 1,2, 2,2, 1,2, 2,2, /* rate 13 3 */ + +/* 8 */ 2,2, 2,2, 2,2, 2,2, /* rate 14 0 (increment by 2) */ +/* 9 */ 2,2, 2,4, 2,2, 2,4, /* rate 14 1 */ +/*10 */ 2,4, 2,4, 2,4, 2,4, /* rate 14 2 */ +/*11 */ 2,4, 4,4, 2,4, 4,4, /* rate 14 3 */ + +/*12 */ 4,4, 4,4, 4,4, 4,4, /* rates 15 0, 15 1, 15 2, 15 3 (increment by 4) */ +/*13 */ 8,8, 8,8, 8,8, 8,8, /* rates 15 2, 15 3 for attack */ +/*14 */ 0,0, 0,0, 0,0, 0,0, /* infinity rates for attack and decay(s) */ +}; + + +#define O(a) (a*RATE_STEPS) + +/*note that there is no O(13) in this table - it's directly in the code */ +static const unsigned char eg_rate_select[16+64+16]={ /* Envelope Generator rates (16 + 64 rates + 16 RKS) */ +/* 16 infinite time rates */ +O(14),O(14),O(14),O(14),O(14),O(14),O(14),O(14), +O(14),O(14),O(14),O(14),O(14),O(14),O(14),O(14), + +/* rates 00-12 */ +O( 0),O( 1),O( 2),O( 3), +O( 0),O( 1),O( 2),O( 3), +O( 0),O( 1),O( 2),O( 3), +O( 0),O( 1),O( 2),O( 3), +O( 0),O( 1),O( 2),O( 3), +O( 0),O( 1),O( 2),O( 3), +O( 0),O( 1),O( 2),O( 3), +O( 0),O( 1),O( 2),O( 3), +O( 0),O( 1),O( 2),O( 3), +O( 0),O( 1),O( 2),O( 3), +O( 0),O( 1),O( 2),O( 3), +O( 0),O( 1),O( 2),O( 3), +O( 0),O( 1),O( 2),O( 3), + +/* rate 13 */ +O( 4),O( 5),O( 6),O( 7), + +/* rate 14 */ +O( 8),O( 9),O(10),O(11), + +/* rate 15 */ +O(12),O(12),O(12),O(12), + +/* 16 dummy rates (same as 15 3) */ +O(12),O(12),O(12),O(12),O(12),O(12),O(12),O(12), +O(12),O(12),O(12),O(12),O(12),O(12),O(12),O(12), + +}; +#undef O + +/*rate 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 */ +/*shift 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 0, 0 */ +/*mask 8191, 4095, 2047, 1023, 511, 255, 127, 63, 31, 15, 7, 3, 1, 0, 0, 0 */ + +#define O(a) (a*1) +static const unsigned char eg_rate_shift[16+64+16]={ /* Envelope Generator counter shifts (16 + 64 rates + 16 RKS) */ +/* 16 infinite time rates */ +O(0),O(0),O(0),O(0),O(0),O(0),O(0),O(0), +O(0),O(0),O(0),O(0),O(0),O(0),O(0),O(0), + +/* rates 00-12 */ +O(13),O(13),O(13),O(13), +O(12),O(12),O(12),O(12), +O(11),O(11),O(11),O(11), +O(10),O(10),O(10),O(10), +O( 9),O( 9),O( 9),O( 9), +O( 8),O( 8),O( 8),O( 8), +O( 7),O( 7),O( 7),O( 7), +O( 6),O( 6),O( 6),O( 6), +O( 5),O( 5),O( 5),O( 5), +O( 4),O( 4),O( 4),O( 4), +O( 3),O( 3),O( 3),O( 3), +O( 2),O( 2),O( 2),O( 2), +O( 1),O( 1),O( 1),O( 1), + +/* rate 13 */ +O( 0),O( 0),O( 0),O( 0), + +/* rate 14 */ +O( 0),O( 0),O( 0),O( 0), + +/* rate 15 */ +O( 0),O( 0),O( 0),O( 0), + +/* 16 dummy rates (same as 15 3) */ +O( 0),O( 0),O( 0),O( 0),O( 0),O( 0),O( 0),O( 0), +O( 0),O( 0),O( 0),O( 0),O( 0),O( 0),O( 0),O( 0), + +}; +#undef O + + +/* multiple table */ +#define ML 2 +static const UINT8 mul_tab[16]= { +/* 1/2, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,10,12,12,15,15 */ + 0.50*ML, 1.00*ML, 2.00*ML, 3.00*ML, 4.00*ML, 5.00*ML, 6.00*ML, 7.00*ML, + 8.00*ML, 9.00*ML,10.00*ML,10.00*ML,12.00*ML,12.00*ML,15.00*ML,15.00*ML +}; +#undef ML + +/* TL_TAB_LEN is calculated as: +* 11 - sinus amplitude bits (Y axis) +* 2 - sinus sign bit (Y axis) +* TL_RES_LEN - sinus resolution (X axis) +*/ +#define TL_TAB_LEN (11*2*TL_RES_LEN) +static signed int tl_tab[TL_TAB_LEN]; + +#define ENV_QUIET (TL_TAB_LEN>>5) + +/* sin waveform table in 'decibel' scale */ +/* two waveforms on OPLL type chips */ +static unsigned int sin_tab[SIN_LEN * 2]; + + +/* LFO Amplitude Modulation table (verified on real YM3812) + 27 output levels (triangle waveform); 1 level takes one of: 192, 256 or 448 samples + + Length: 210 elements. + + Each of the elements has to be repeated + exactly 64 times (on 64 consecutive samples). + The whole table takes: 64 * 210 = 13440 samples. + +We use data>>1, until we find what it really is on real chip... + +*/ + +#define LFO_AM_TAB_ELEMENTS 210 + +static const UINT8 lfo_am_table[LFO_AM_TAB_ELEMENTS] = { +0,0,0,0,0,0,0, +1,1,1,1, +2,2,2,2, +3,3,3,3, +4,4,4,4, +5,5,5,5, +6,6,6,6, +7,7,7,7, +8,8,8,8, +9,9,9,9, +10,10,10,10, +11,11,11,11, +12,12,12,12, +13,13,13,13, +14,14,14,14, +15,15,15,15, +16,16,16,16, +17,17,17,17, +18,18,18,18, +19,19,19,19, +20,20,20,20, +21,21,21,21, +22,22,22,22, +23,23,23,23, +24,24,24,24, +25,25,25,25, +26,26,26, +25,25,25,25, +24,24,24,24, +23,23,23,23, +22,22,22,22, +21,21,21,21, +20,20,20,20, +19,19,19,19, +18,18,18,18, +17,17,17,17, +16,16,16,16, +15,15,15,15, +14,14,14,14, +13,13,13,13, +12,12,12,12, +11,11,11,11, +10,10,10,10, +9,9,9,9, +8,8,8,8, +7,7,7,7, +6,6,6,6, +5,5,5,5, +4,4,4,4, +3,3,3,3, +2,2,2,2, +1,1,1,1 +}; + +/* LFO Phase Modulation table (verified on real YM2413) */ +static const INT8 lfo_pm_table[8*8] = { + +/* FNUM2/FNUM = 0 00xxxxxx (0x0000) */ +0, 0, 0, 0, 0, 0, 0, 0, + +/* FNUM2/FNUM = 0 01xxxxxx (0x0040) */ +1, 0, 0, 0,-1, 0, 0, 0, + +/* FNUM2/FNUM = 0 10xxxxxx (0x0080) */ +2, 1, 0,-1,-2,-1, 0, 1, + +/* FNUM2/FNUM = 0 11xxxxxx (0x00C0) */ +3, 1, 0,-1,-3,-1, 0, 1, + +/* FNUM2/FNUM = 1 00xxxxxx (0x0100) */ +4, 2, 0,-2,-4,-2, 0, 2, + +/* FNUM2/FNUM = 1 01xxxxxx (0x0140) */ +5, 2, 0,-2,-5,-2, 0, 2, + +/* FNUM2/FNUM = 1 10xxxxxx (0x0180) */ +6, 3, 0,-3,-6,-3, 0, 3, + +/* FNUM2/FNUM = 1 11xxxxxx (0x01C0) */ +7, 3, 0,-3,-7,-3, 0, 3, +}; + + + + + + +/* This is not 100% perfect yet but very close */ +/* + - multi parameters are 100% correct (instruments and drums) + - LFO PM and AM enable are 100% correct + - waveform DC and DM select are 100% correct +*/ + +static const unsigned char table[19][8] = { +/* MULT MULT modTL DcDmFb AR/DR AR/DR SL/RR SL/RR */ +/* 0 1 2 3 4 5 6 7 */ + {0x49, 0x4c, 0x4c, 0x12, 0x00, 0x00, 0x00, 0x00 }, /* 0 */ + + {0x61, 0x61, 0x1e, 0x17, 0xf0, 0x78, 0x00, 0x17 }, /* 1 */ + {0x13, 0x41, 0x1e, 0x0d, 0xd7, 0xf7, 0x13, 0x13 }, /* 2 */ + {0x13, 0x01, 0x99, 0x04, 0xf2, 0xf4, 0x11, 0x23 }, /* 3 */ + {0x21, 0x61, 0x1b, 0x07, 0xaf, 0x64, 0x40, 0x27 }, /* 4 */ + +/* {0x22, 0x21, 0x1e, 0x09, 0xf0, 0x76, 0x08, 0x28 }, //5 */ + {0x22, 0x21, 0x1e, 0x06, 0xf0, 0x75, 0x08, 0x18 }, /* 5 */ + +/* {0x31, 0x22, 0x16, 0x09, 0x90, 0x7f, 0x00, 0x08 }, //6 */ + {0x31, 0x22, 0x16, 0x05, 0x90, 0x71, 0x00, 0x13 }, /* 6 */ + + {0x21, 0x61, 0x1d, 0x07, 0x82, 0x80, 0x10, 0x17 }, /* 7 */ + {0x23, 0x21, 0x2d, 0x16, 0xc0, 0x70, 0x07, 0x07 }, /* 8 */ + {0x61, 0x61, 0x1b, 0x06, 0x64, 0x65, 0x10, 0x17 }, /* 9 */ + +/* {0x61, 0x61, 0x0c, 0x08, 0x85, 0xa0, 0x79, 0x07 }, //A */ + {0x61, 0x61, 0x0c, 0x18, 0x85, 0xf0, 0x70, 0x07 }, /* A */ + + {0x23, 0x01, 0x07, 0x11, 0xf0, 0xa4, 0x00, 0x22 }, /* B */ + {0x97, 0xc1, 0x24, 0x07, 0xff, 0xf8, 0x22, 0x12 }, /* C */ + +/* {0x61, 0x10, 0x0c, 0x08, 0xf2, 0xc4, 0x40, 0xc8 }, //D */ + {0x61, 0x10, 0x0c, 0x05, 0xf2, 0xf4, 0x40, 0x44 }, /* D */ + + {0x01, 0x01, 0x55, 0x03, 0xf3, 0x92, 0xf3, 0xf3 }, /* E */ + {0x61, 0x41, 0x89, 0x03, 0xf1, 0xf4, 0xf0, 0x13 }, /* F */ + +/* drum instruments definitions */ +/* MULTI MULTI modTL xxx AR/DR AR/DR SL/RR SL/RR */ +/* 0 1 2 3 4 5 6 7 */ + {0x01, 0x01, 0x16, 0x00, 0xfd, 0xf8, 0x2f, 0x6d },/* BD(multi verified, modTL verified, mod env - verified(close), carr. env verifed) */ + {0x01, 0x01, 0x00, 0x00, 0xd8, 0xd8, 0xf9, 0xf8 },/* HH(multi verified), SD(multi not used) */ + {0x05, 0x01, 0x00, 0x00, 0xf8, 0xba, 0x49, 0x55 },/* TOM(multi,env verified), TOP CYM(multi verified, env verified) */ +}; + +static const unsigned char table_vrc7[15][8] = +{ +/* VRC7 instruments, January 17, 2004 update -Xodnizel */ + {0x03, 0x21, 0x04, 0x06, 0x8D, 0xF2, 0x42, 0x17}, + {0x13, 0x41, 0x05, 0x0E, 0x99, 0x96, 0x63, 0x12}, + {0x31, 0x11, 0x10, 0x0A, 0xF0, 0x9C, 0x32, 0x02}, + {0x21, 0x61, 0x1D, 0x07, 0x9F, 0x64, 0x20, 0x27}, + {0x22, 0x21, 0x1E, 0x06, 0xF0, 0x76, 0x08, 0x28}, + {0x02, 0x01, 0x06, 0x00, 0xF0, 0xF2, 0x03, 0x95}, + {0x21, 0x61, 0x1C, 0x07, 0x82, 0x81, 0x16, 0x07}, + {0x23, 0x21, 0x1A, 0x17, 0xEF, 0x82, 0x25, 0x15}, + {0x25, 0x11, 0x1F, 0x00, 0x86, 0x41, 0x20, 0x11}, + {0x85, 0x01, 0x1F, 0x0F, 0xE4, 0xA2, 0x11, 0x12}, + {0x07, 0xC1, 0x2B, 0x45, 0xB4, 0xF1, 0x24, 0xF4}, + {0x61, 0x23, 0x11, 0x06, 0x96, 0x96, 0x13, 0x16}, + {0x01, 0x02, 0xD3, 0x05, 0x82, 0xA2, 0x31, 0x51}, + {0x61, 0x22, 0x0D, 0x02, 0xC3, 0x7F, 0x24, 0x05}, + {0x21, 0x62, 0x0E, 0x00, 0xA1, 0xA0, 0x44, 0x17}, + +}; + +INLINE int limit( int val, int max, int min ) { + if ( val > max ) + val = max; + else if ( val < min ) + val = min; + + return val; +} + + +/* advance LFO to next sample */ +INLINE void advance_lfo(YM2413 *chip) +{ + /* LFO */ + chip->lfo_am_cnt += chip->lfo_am_inc; + if (chip->lfo_am_cnt >= ((UINT32)LFO_AM_TAB_ELEMENTS<lfo_am_cnt -= ((UINT32)LFO_AM_TAB_ELEMENTS<LFO_AM = lfo_am_table[ chip->lfo_am_cnt >> LFO_SH ] >> 1; + + chip->lfo_pm_cnt += chip->lfo_pm_inc; + chip->LFO_PM = (chip->lfo_pm_cnt>>LFO_SH) & 7; +} + +/* advance to next sample */ +INLINE void advance(YM2413 *chip) +{ + OPLL_CH *CH; + OPLL_SLOT *op; + unsigned int i; + + /* Envelope Generator */ + chip->eg_timer += chip->eg_timer_add; + + while (chip->eg_timer >= chip->eg_timer_overflow) + { + chip->eg_timer -= chip->eg_timer_overflow; + + chip->eg_cnt++; + + for (i=0; i<9*2; i++) + { + CH = &chip->P_CH[i/2]; + + op = &CH->SLOT[i&1]; + + switch(op->state) + { + + case EG_DMP: /* dump phase */ + /*dump phase is performed by both operators in each channel*/ + /*when CARRIER envelope gets down to zero level, + ** phases in BOTH opearators are reset (at the same time ?) + */ + if ( !(chip->eg_cnt & ((1<eg_sh_dp)-1) ) ) + { + op->volume += eg_inc[op->eg_sel_dp + ((chip->eg_cnt>>op->eg_sh_dp)&7)]; + + if ( op->volume >= MAX_ATT_INDEX ) + { + op->volume = MAX_ATT_INDEX; + op->state = EG_ATT; + /* restart Phase Generator */ + op->phase = 0; + } + } + break; + + case EG_ATT: /* attack phase */ + if ( !(chip->eg_cnt & ((1<eg_sh_ar)-1) ) ) + { + op->volume += (~op->volume * + (eg_inc[op->eg_sel_ar + ((chip->eg_cnt>>op->eg_sh_ar)&7)]) + ) >>2; + + if (op->volume <= MIN_ATT_INDEX) + { + op->volume = MIN_ATT_INDEX; + op->state = EG_DEC; + } + } + break; + + case EG_DEC: /* decay phase */ + if ( !(chip->eg_cnt & ((1<eg_sh_dr)-1) ) ) + { + op->volume += eg_inc[op->eg_sel_dr + ((chip->eg_cnt>>op->eg_sh_dr)&7)]; + + if ( op->volume >= (INT32)(op->sl) ) + op->state = EG_SUS; + } + break; + + case EG_SUS: /* sustain phase */ + /* this is important behaviour: + one can change percusive/non-percussive modes on the fly and + the chip will remain in sustain phase - verified on real YM3812 */ + + if(op->eg_type) /* non-percussive mode (sustained tone) */ + { + /* do nothing */ + } + else /* percussive mode */ + { + /* during sustain phase chip adds Release Rate (in percussive mode) */ + if ( !(chip->eg_cnt & ((1<eg_sh_rr)-1) ) ) + { + op->volume += eg_inc[op->eg_sel_rr + ((chip->eg_cnt>>op->eg_sh_rr)&7)]; + + if ( op->volume >= MAX_ATT_INDEX ) + op->volume = MAX_ATT_INDEX; + } + /* else do nothing in sustain phase */ + } + break; + + case EG_REL: /* release phase */ + /* exclude modulators in melody channels from performing anything in this mode*/ + /* allowed are only carriers in melody mode and rhythm slots in rhythm mode */ + + /*This table shows which operators and on what conditions are allowed to perform EG_REL: + (a) - always perform EG_REL + (n) - never perform EG_REL + (r) - perform EG_REL in Rhythm mode ONLY + 0: 0 (n), 1 (a) + 1: 2 (n), 3 (a) + 2: 4 (n), 5 (a) + 3: 6 (n), 7 (a) + 4: 8 (n), 9 (a) + 5: 10(n), 11(a) + 6: 12(r), 13(a) + 7: 14(r), 15(a) + 8: 16(r), 17(a) + */ + if ( (i&1) || ((chip->rhythm&0x20) && (i>=12)) )/* exclude modulators */ + { + if(op->eg_type) /* non-percussive mode (sustained tone) */ + /*this is correct: use RR when SUS = OFF*/ + /*and use RS when SUS = ON*/ + { + if (CH->sus) + { + if ( !(chip->eg_cnt & ((1<eg_sh_rs)-1) ) ) + { + op->volume += eg_inc[op->eg_sel_rs + ((chip->eg_cnt>>op->eg_sh_rs)&7)]; + if ( op->volume >= MAX_ATT_INDEX ) + { + op->volume = MAX_ATT_INDEX; + op->state = EG_OFF; + } + } + } + else + { + if ( !(chip->eg_cnt & ((1<eg_sh_rr)-1) ) ) + { + op->volume += eg_inc[op->eg_sel_rr + ((chip->eg_cnt>>op->eg_sh_rr)&7)]; + if ( op->volume >= MAX_ATT_INDEX ) + { + op->volume = MAX_ATT_INDEX; + op->state = EG_OFF; + } + } + } + } + else /* percussive mode */ + { + if ( !(chip->eg_cnt & ((1<eg_sh_rs)-1) ) ) + { + op->volume += eg_inc[op->eg_sel_rs + ((chip->eg_cnt>>op->eg_sh_rs)&7)]; + if ( op->volume >= MAX_ATT_INDEX ) + { + op->volume = MAX_ATT_INDEX; + op->state = EG_OFF; + } + } + } + } + break; + + default: + break; + } + } + } + + for (i=0; i<9*2; i++) + { + CH = &chip->P_CH[i/2]; + op = &CH->SLOT[i&1]; + + /* Phase Generator */ + if(op->vib) + { + UINT8 block; + + unsigned int fnum_lfo = 8*((CH->block_fnum&0x01c0) >> 6); + unsigned int block_fnum = CH->block_fnum * 2; + signed int lfo_fn_table_index_offset = lfo_pm_table[chip->LFO_PM + fnum_lfo ]; + + if (lfo_fn_table_index_offset) /* LFO phase modulation active */ + { + block_fnum += lfo_fn_table_index_offset; + block = (block_fnum&0x1c00) >> 10; + op->phase += (chip->fn_tab[block_fnum&0x03ff] >> (7-block)) * op->mul; + } + else /* LFO phase modulation = zero */ + { + op->phase += op->freq; + } + } + else /* LFO phase modulation disabled for this operator */ + { + op->phase += op->freq; + } + } + + /* The Noise Generator of the YM3812 is 23-bit shift register. + * Period is equal to 2^23-2 samples. + * Register works at sampling frequency of the chip, so output + * can change on every sample. + * + * Output of the register and input to the bit 22 is: + * bit0 XOR bit14 XOR bit15 XOR bit22 + * + * Simply use bit 22 as the noise output. + */ + + chip->noise_p += chip->noise_f; + i = chip->noise_p >> FREQ_SH; /* number of events (shifts of the shift register) */ + chip->noise_p &= FREQ_MASK; + while (i) + { + /* + UINT32 j; + j = ( (chip->noise_rng) ^ (chip->noise_rng>>14) ^ (chip->noise_rng>>15) ^ (chip->noise_rng>>22) ) & 1; + chip->noise_rng = (j<<22) | (chip->noise_rng>>1); + */ + + /* + Instead of doing all the logic operations above, we + use a trick here (and use bit 0 as the noise output). + The difference is only that the noise bit changes one + step ahead. This doesn't matter since we don't know + what is real state of the noise_rng after the reset. + */ + + if (chip->noise_rng & 1) chip->noise_rng ^= 0x800302; + chip->noise_rng >>= 1; + + i--; + } +} + + +INLINE signed int op_calc(UINT32 phase, unsigned int env, signed int pm, unsigned int wave_tab) +{ + UINT32 p; + + p = (env<<5) + sin_tab[wave_tab + ((((signed int)((phase & ~FREQ_MASK) + (pm<<17))) >> FREQ_SH ) & SIN_MASK) ]; + + if (p >= TL_TAB_LEN) + return 0; + return tl_tab[p]; +} + +INLINE signed int op_calc1(UINT32 phase, unsigned int env, signed int pm, unsigned int wave_tab) +{ + UINT32 p; + INT32 i; + + i = (phase & ~FREQ_MASK) + pm; + +/*logerror("i=%08x (i>>16)&511=%8i phase=%i [pm=%08x] ",i, (i>>16)&511, phase>>FREQ_SH, pm);*/ + + p = (env<<5) + sin_tab[ wave_tab + ((i>>FREQ_SH) & SIN_MASK)]; + +/*logerror("(p&255=%i p>>8=%i) out= %i\n", p&255,p>>8, tl_tab[p&255]>>(p>>8) );*/ + + if (p >= TL_TAB_LEN) + return 0; + return tl_tab[p]; +} + + +#define volume_calc(OP) ((OP)->TLL + ((UINT32)(OP)->volume) + (chip->LFO_AM & (OP)->AMmask)) + +/* calculate output */ +INLINE void chan_calc( YM2413 *chip, OPLL_CH *CH ) +{ + OPLL_SLOT *SLOT; + unsigned int env; + signed int out; + signed int phase_modulation; /* phase modulation input (SLOT 2) */ + + + /* SLOT 1 */ + SLOT = &CH->SLOT[SLOT1]; + env = volume_calc(SLOT); + out = SLOT->op1_out[0] + SLOT->op1_out[1]; + + SLOT->op1_out[0] = SLOT->op1_out[1]; + phase_modulation = SLOT->op1_out[0]; + + SLOT->op1_out[1] = 0; + + if( env < ENV_QUIET ) + { + if (!SLOT->fb_shift) + out = 0; + SLOT->op1_out[1] = op_calc1(SLOT->phase, env, (out<fb_shift), SLOT->wavetable ); + } + + /* SLOT 2 */ + + SLOT++; + env = volume_calc(SLOT); + if( env < ENV_QUIET ) + { + signed int outp = op_calc(SLOT->phase, env, phase_modulation, SLOT->wavetable); + chip->output[0] += outp; + /* output[0] += op_calc(SLOT->phase, env, phase_modulation, SLOT->wavetable); */ + } +} + +/* + operators used in the rhythm sounds generation process: + + Envelope Generator: + +channel operator register number Bass High Snare Tom Top +/ slot number TL ARDR SLRR Wave Drum Hat Drum Tom Cymbal + 6 / 0 12 50 70 90 f0 + + 6 / 1 15 53 73 93 f3 + + 7 / 0 13 51 71 91 f1 + + 7 / 1 16 54 74 94 f4 + + 8 / 0 14 52 72 92 f2 + + 8 / 1 17 55 75 95 f5 + + + Phase Generator: + +channel operator register number Bass High Snare Tom Top +/ slot number MULTIPLE Drum Hat Drum Tom Cymbal + 6 / 0 12 30 + + 6 / 1 15 33 + + 7 / 0 13 31 + + + + 7 / 1 16 34 ----- n o t u s e d ----- + 8 / 0 14 32 + + 8 / 1 17 35 + + + +channel operator register number Bass High Snare Tom Top +number number BLK/FNUM2 FNUM Drum Hat Drum Tom Cymbal + 6 12,15 B6 A6 + + + 7 13,16 B7 A7 + + + + + 8 14,17 B8 A8 + + + + +*/ + +/* calculate rhythm */ + +INLINE void rhythm_calc( YM2413 *chip, OPLL_CH *CH, unsigned int noise ) +{ + OPLL_SLOT *SLOT; + signed int out; + unsigned int env; + signed int phase_modulation; /* phase modulation input (SLOT 2) */ + + + /* Bass Drum (verified on real YM3812): + - depends on the channel 6 'connect' register: + when connect = 0 it works the same as in normal (non-rhythm) mode (op1->op2->out) + when connect = 1 _only_ operator 2 is present on output (op2->out), operator 1 is ignored + - output sample always is multiplied by 2 + */ + + + /* SLOT 1 */ + if ( !(chip->mask & OPLL_MASK_BD) ) + { + SLOT = &CH[6].SLOT[SLOT1]; + env = volume_calc(SLOT); + + out = SLOT->op1_out[0] + SLOT->op1_out[1]; + SLOT->op1_out[0] = SLOT->op1_out[1]; + + phase_modulation = SLOT->op1_out[0]; + + SLOT->op1_out[1] = 0; + if( env < ENV_QUIET ) + { + if (!SLOT->fb_shift) + out = 0; + SLOT->op1_out[1] = op_calc1(SLOT->phase, env, (out<fb_shift), SLOT->wavetable ); + } + + /* SLOT 2 */ + SLOT++; + env = volume_calc(SLOT); + if( env < ENV_QUIET ) + chip->output[1] += op_calc(SLOT->phase, env, phase_modulation, SLOT->wavetable) * 2; + } + + + /* Phase generation is based on: + HH (13) channel 7->slot 1 combined with channel 8->slot 2 (same combination as TOP CYMBAL but different output phases) + SD (16) channel 7->slot 1 + TOM (14) channel 8->slot 1 + TOP (17) channel 7->slot 1 combined with channel 8->slot 2 (same combination as HIGH HAT but different output phases) */ + + /* Envelope generation based on: + HH channel 7->slot1 + SD channel 7->slot2 + TOM channel 8->slot1 + TOP channel 8->slot2 */ + + + /* The following formulas can be well optimized. + I leave them in direct form for now (in case I've missed something). + */ + + /* High Hat (verified on real YM3812) */ + if ( !(chip->mask & OPLL_MASK_HH) ) + { + env = volume_calc(chip->SLOT7_1); + if( env < ENV_QUIET ) + { + + /* high hat phase generation: + phase = d0 or 234 (based on frequency only) + phase = 34 or 2d0 (based on noise) + */ + + /* base frequency derived from operator 1 in channel 7 */ + unsigned char bit7 = ((chip->SLOT7_1->phase>>FREQ_SH)>>7)&1; + unsigned char bit3 = ((chip->SLOT7_1->phase>>FREQ_SH)>>3)&1; + unsigned char bit2 = ((chip->SLOT7_1->phase>>FREQ_SH)>>2)&1; + + unsigned char res1 = (bit2 ^ bit7) | bit3; + + /* when res1 = 0 phase = 0x000 | 0xd0; */ + /* when res1 = 1 phase = 0x200 | (0xd0>>2); */ + UINT32 phase = res1 ? (0x200|(0xd0>>2)) : 0xd0; + + /* enable gate based on frequency of operator 2 in channel 8 */ + unsigned char bit5e= ((chip->SLOT8_2->phase>>FREQ_SH)>>5)&1; + unsigned char bit3e= ((chip->SLOT8_2->phase>>FREQ_SH)>>3)&1; + + unsigned char res2 = (bit3e | bit5e); + + /* when res2 = 0 pass the phase from calculation above (res1); */ + /* when res2 = 1 phase = 0x200 | (0xd0>>2); */ + if (res2) + phase = (0x200|(0xd0>>2)); + + + /* when phase & 0x200 is set and noise=1 then phase = 0x200|0xd0 */ + /* when phase & 0x200 is set and noise=0 then phase = 0x200|(0xd0>>2), ie no change */ + if (phase&0x200) + { + if (noise) + phase = 0x200|0xd0; + } + else + /* when phase & 0x200 is clear and noise=1 then phase = 0xd0>>2 */ + /* when phase & 0x200 is clear and noise=0 then phase = 0xd0, ie no change */ + { + if (noise) + phase = 0xd0>>2; + } + + chip->output[1] += op_calc(phase<SLOT7_1->wavetable) * 2; + } + } + + /* Snare Drum (verified on real YM3812) */ + if ( !(chip->mask & OPLL_MASK_SD) ) + { + env = volume_calc(chip->SLOT7_2); + if( env < ENV_QUIET ) + { + /* base frequency derived from operator 1 in channel 7 */ + unsigned char bit8 = ((chip->SLOT7_1->phase>>FREQ_SH)>>8)&1; + + /* when bit8 = 0 phase = 0x100; */ + /* when bit8 = 1 phase = 0x200; */ + UINT32 phase = bit8 ? 0x200 : 0x100; + + /* Noise bit XOR'es phase by 0x100 */ + /* when noisebit = 0 pass the phase from calculation above */ + /* when noisebit = 1 phase ^= 0x100; */ + /* in other words: phase ^= (noisebit<<8); */ + if (noise) + phase ^= 0x100; + + chip->output[1] += op_calc(phase<SLOT7_2->wavetable) * 2; + } + } + + /* Tom Tom (verified on real YM3812) */ + if ( !(chip->mask & OPLL_MASK_TOM) ) + { + env = volume_calc(chip->SLOT8_1); + if( env < ENV_QUIET ) + chip->output[1] += op_calc(chip->SLOT8_1->phase, env, 0, chip->SLOT8_1->wavetable) * 2; + } + + /* Top Cymbal (verified on real YM2413) */ + if ( !(chip->mask & OPLL_MASK_CYM) ) + { + env = volume_calc(chip->SLOT8_2); + if( env < ENV_QUIET ) + { + /* base frequency derived from operator 1 in channel 7 */ + unsigned char bit7 = ((chip->SLOT7_1->phase>>FREQ_SH)>>7)&1; + unsigned char bit3 = ((chip->SLOT7_1->phase>>FREQ_SH)>>3)&1; + unsigned char bit2 = ((chip->SLOT7_1->phase>>FREQ_SH)>>2)&1; + + unsigned char res1 = (bit2 ^ bit7) | bit3; + + /* when res1 = 0 phase = 0x000 | 0x100; */ + /* when res1 = 1 phase = 0x200 | 0x100; */ + UINT32 phase = res1 ? 0x300 : 0x100; + + /* enable gate based on frequency of operator 2 in channel 8 */ + unsigned char bit5e= ((chip->SLOT8_2->phase>>FREQ_SH)>>5)&1; + unsigned char bit3e= ((chip->SLOT8_2->phase>>FREQ_SH)>>3)&1; + + unsigned char res2 = (bit3e | bit5e); + /* when res2 = 0 pass the phase from calculation above (res1); */ + /* when res2 = 1 phase = 0x200 | 0x100; */ + if (res2) + phase = 0x300; + + chip->output[1] += op_calc(phase<SLOT8_2->wavetable) * 2; + } + } +} + + +/* generic table initialize */ +static int init_tables(void) +{ + signed int i,x; + signed int n; + double o,m; + + + for (x=0; x>= 4; /* 12 bits here */ + if (n&1) /* round to nearest */ + n = (n>>1)+1; + else + n = n>>1; + /* 11 bits here (rounded) */ + tl_tab[ x*2 + 0 ] = n; + tl_tab[ x*2 + 1 ] = -tl_tab[ x*2 + 0 ]; + + for (i=1; i<11; i++) + { + tl_tab[ x*2+0 + i*2*TL_RES_LEN ] = tl_tab[ x*2+0 ]>>i; + tl_tab[ x*2+1 + i*2*TL_RES_LEN ] = -tl_tab[ x*2+0 + i*2*TL_RES_LEN ]; + } + #if 0 + logerror("tl %04i", x*2); + for (i=0; i<11; i++) + logerror(", [%02i] %5i", i*2, tl_tab[ x*2 /*+1*/ + i*2*TL_RES_LEN ] ); + logerror("\n"); + #endif + } + /*logerror("ym2413.c: TL_TAB_LEN = %i elements (%i bytes)\n",TL_TAB_LEN, (int)sizeof(tl_tab));*/ + + + for (i=0; i0.0) + o = 8*log(1.0/m)/log(2.0); /* convert to 'decibels' */ + else + o = 8*log(-1.0/m)/log(2.0); /* convert to 'decibels' */ + + o = o / (ENV_STEP/4); + + n = (int)(2.0*o); + if (n&1) /* round to nearest */ + n = (n>>1)+1; + else + n = n>>1; + + /* waveform 0: standard sinus */ + sin_tab[ i ] = n*2 + (m>=0.0? 0: 1 ); + + /*logerror("ym2413.c: sin [%4i (hex=%03x)]= %4i (tl_tab value=%5i)\n", i, i, sin_tab[i], tl_tab[sin_tab[i]] );*/ + + + /* waveform 1: __ __ */ + /* / \____/ \____*/ + /* output only first half of the sinus waveform (positive one) */ + if (i & (1<<(SIN_BITS-1)) ) + sin_tab[1*SIN_LEN+i] = TL_TAB_LEN; + else + sin_tab[1*SIN_LEN+i] = sin_tab[i]; + + /*logerror("ym2413.c: sin1[%4i]= %4i (tl_tab value=%5i)\n", i, sin_tab[1*SIN_LEN+i], tl_tab[sin_tab[1*SIN_LEN+i]] );*/ + } +#if 0 + logerror("YM2413.C: ENV_QUIET= %08x (*32=%08x)\n", ENV_QUIET, ENV_QUIET*32 ); + for (i=0; ifreqbase = (chip->rate) ? ((double)chip->clock / 72.0) / chip->rate : 0; + if ( fabs( chip->freqbase - 1.0 ) < 0.0000001 ) + chip->freqbase = 1.0; +#if 0 + chip->rate = (double)chip->clock / 72.0; + chip->freqbase = 1.0; + logerror("freqbase=%f\n", chip->freqbase); +#endif + + + + /* make fnumber -> increment counter table */ + for( i = 0 ; i < 1024; i++ ) + { + /* OPLL (YM2413) phase increment counter = 18bit */ + + chip->fn_tab[i] = (UINT32)( (double)i * 64 * chip->freqbase * (1<<(FREQ_SH-10)) ); /* -10 because chip works with 10.10 fixed point, while we use 16.16 */ +#if 0 + logerror("ym2413.c: fn_tab[%4i] = %08x (dec=%8i)\n", + i, chip->fn_tab[i]>>6, chip->fn_tab[i]>>6 ); +#endif + } + +#if 0 + for( i=0 ; i < 16 ; i++ ) + { + logerror("ym2413.c: sl_tab[%i] = %08x\n", i, sl_tab[i] ); + } + for( i=0 ; i < 8 ; i++ ) + { + int j; + logerror("ym2413.c: ksl_tab[oct=%2i] =",i); + for (j=0; j<16; j++) + { + logerror("%08x ", ksl_tab[i*16+j] ); + } + logerror("\n"); + } +#endif + + + /* Amplitude modulation: 27 output levels (triangle waveform); 1 level takes one of: 192, 256 or 448 samples */ + /* One entry from LFO_AM_TABLE lasts for 64 samples */ + chip->lfo_am_inc = (1.0 / 64.0 ) * (1<freqbase; + + /* Vibrato: 8 output levels (triangle waveform); 1 level takes 1024 samples */ + chip->lfo_pm_inc = (1.0 / 1024.0) * (1<freqbase; + + /*logerror ("chip->lfo_am_inc = %8x ; chip->lfo_pm_inc = %8x\n", chip->lfo_am_inc, chip->lfo_pm_inc);*/ + + /* Noise generator: a step takes 1 sample */ + chip->noise_f = (1.0 / 1.0) * (1<freqbase; + /*logerror("YM2413init noise_f=%8x\n", chip->noise_f);*/ + + chip->eg_timer_add = (1<freqbase; + chip->eg_timer_overflow = ( 1 ) * (1<eg_timer_add, chip->eg_timer_overflow);*/ +} + +INLINE void KEY_ON(OPLL_SLOT *SLOT, UINT32 key_set) +{ + if( !SLOT->key ) + { + /* do NOT restart Phase Generator (verified on real YM2413)*/ + /* phase -> Dump */ + SLOT->state = EG_DMP; + } + SLOT->key |= key_set; +} + +INLINE void KEY_OFF(OPLL_SLOT *SLOT, UINT32 key_clr) +{ + if( SLOT->key ) + { + SLOT->key &= key_clr; + + if( !SLOT->key ) + { + /* phase -> Release */ + if (SLOT->state>EG_REL) + SLOT->state = EG_REL; + } + } +} + +/* update phase increment counter of operator (also update the EG rates if necessary) */ +INLINE void CALC_FCSLOT(OPLL_CH *CH,OPLL_SLOT *SLOT) +{ + int ksr; + UINT32 SLOT_rs; + UINT32 SLOT_dp; + + /* (frequency) phase increment counter */ + SLOT->freq = CH->fc * SLOT->mul; + ksr = CH->kcode >> SLOT->KSR; + + if( SLOT->ksr != ksr ) + { + SLOT->ksr = ksr; + + /* calculate envelope generator rates */ + if ((SLOT->ar + SLOT->ksr) < 16+62) + { + SLOT->eg_sh_ar = eg_rate_shift [SLOT->ar + SLOT->ksr ]; + SLOT->eg_sel_ar = eg_rate_select[SLOT->ar + SLOT->ksr ]; + } + else + { + SLOT->eg_sh_ar = 0; + SLOT->eg_sel_ar = 13*RATE_STEPS; + } + SLOT->eg_sh_dr = eg_rate_shift [SLOT->dr + SLOT->ksr ]; + SLOT->eg_sel_dr = eg_rate_select[SLOT->dr + SLOT->ksr ]; + SLOT->eg_sh_rr = eg_rate_shift [SLOT->rr + SLOT->ksr ]; + SLOT->eg_sel_rr = eg_rate_select[SLOT->rr + SLOT->ksr ]; + + } + + if (CH->sus) + SLOT_rs = 16 + (5<<2); + else + SLOT_rs = 16 + (7<<2); + + SLOT->eg_sh_rs = eg_rate_shift [SLOT_rs + SLOT->ksr ]; + SLOT->eg_sel_rs = eg_rate_select[SLOT_rs + SLOT->ksr ]; + + SLOT_dp = 16 + (13<<2); + SLOT->eg_sh_dp = eg_rate_shift [SLOT_dp + SLOT->ksr ]; + SLOT->eg_sel_dp = eg_rate_select[SLOT_dp + SLOT->ksr ]; +} + +/* set multi,am,vib,EG-TYP,KSR,mul */ +INLINE void set_mul(YM2413 *chip,int slot,int v) +{ + OPLL_CH *CH = &chip->P_CH[slot/2]; + OPLL_SLOT *SLOT = &CH->SLOT[slot&1]; + + SLOT->mul = mul_tab[v&0x0f]; + SLOT->KSR = (v&0x10) ? 0 : 2; + SLOT->eg_type = (v&0x20); + SLOT->vib = (v&0x40); + SLOT->AMmask = (v&0x80) ? ~0 : 0; + CALC_FCSLOT(CH,SLOT); +} + +/* set ksl, tl */ +INLINE void set_ksl_tl(YM2413 *chip,int chan,int v) +{ + int ksl; + OPLL_CH *CH = &chip->P_CH[chan]; +/* modulator */ + OPLL_SLOT *SLOT = &CH->SLOT[SLOT1]; + + ksl = v>>6; /* 0 / 1.5 / 3.0 / 6.0 dB/OCT */ + + SLOT->ksl = ksl ? 3-ksl : 31; + SLOT->TL = (v&0x3f)<<(ENV_BITS-2-7); /* 7 bits TL (bit 6 = always 0) */ + SLOT->TLL = SLOT->TL + (CH->ksl_base>>SLOT->ksl); +} + +/* set ksl , waveforms, feedback */ +INLINE void set_ksl_wave_fb(YM2413 *chip,int chan,int v) +{ + int ksl; + OPLL_CH *CH = &chip->P_CH[chan]; +/* modulator */ + OPLL_SLOT *SLOT = &CH->SLOT[SLOT1]; + SLOT->wavetable = ((v&0x08)>>3)*SIN_LEN; + SLOT->fb_shift = (v&7) ? (v&7) + 8 : 0; + +/*carrier*/ + SLOT = &CH->SLOT[SLOT2]; + ksl = v>>6; /* 0 / 1.5 / 3.0 / 6.0 dB/OCT */ + + SLOT->ksl = ksl ? 3-ksl : 31; + SLOT->TLL = SLOT->TL + (CH->ksl_base>>SLOT->ksl); + + SLOT->wavetable = ((v&0x10)>>4)*SIN_LEN; +} + +/* set attack rate & decay rate */ +INLINE void set_ar_dr(YM2413 *chip,int slot,int v) +{ + OPLL_CH *CH = &chip->P_CH[slot/2]; + OPLL_SLOT *SLOT = &CH->SLOT[slot&1]; + + SLOT->ar = (v>>4) ? 16 + ((v>>4) <<2) : 0; + + if ((SLOT->ar + SLOT->ksr) < 16+62) + { + SLOT->eg_sh_ar = eg_rate_shift [SLOT->ar + SLOT->ksr ]; + SLOT->eg_sel_ar = eg_rate_select[SLOT->ar + SLOT->ksr ]; + } + else + { + SLOT->eg_sh_ar = 0; + SLOT->eg_sel_ar = 13*RATE_STEPS; + } + + SLOT->dr = (v&0x0f)? 16 + ((v&0x0f)<<2) : 0; + SLOT->eg_sh_dr = eg_rate_shift [SLOT->dr + SLOT->ksr ]; + SLOT->eg_sel_dr = eg_rate_select[SLOT->dr + SLOT->ksr ]; +} + +/* set sustain level & release rate */ +INLINE void set_sl_rr(YM2413 *chip,int slot,int v) +{ + OPLL_CH *CH = &chip->P_CH[slot/2]; + OPLL_SLOT *SLOT = &CH->SLOT[slot&1]; + + SLOT->sl = sl_tab[ v>>4 ]; + + SLOT->rr = (v&0x0f)? 16 + ((v&0x0f)<<2) : 0; + SLOT->eg_sh_rr = eg_rate_shift [SLOT->rr + SLOT->ksr ]; + SLOT->eg_sel_rr = eg_rate_select[SLOT->rr + SLOT->ksr ]; +} + +static void load_instrument(YM2413 *chip, UINT32 chan, UINT32 slot, UINT8* inst ) +{ + set_mul (chip, slot, inst[0]); + set_mul (chip, slot+1, inst[1]); + set_ksl_tl (chip, chan, inst[2]); + set_ksl_wave_fb (chip, chan, inst[3]); + set_ar_dr (chip, slot, inst[4]); + set_ar_dr (chip, slot+1, inst[5]); + set_sl_rr (chip, slot, inst[6]); + set_sl_rr (chip, slot+1, inst[7]); +} +static void update_instrument_zero(YM2413 *chip, UINT8 r ) +{ + UINT8* inst = &chip->inst_tab[0][0]; /* point to user instrument */ + UINT32 chan; + UINT32 chan_max; + + chan_max = 9; + if (chip->rhythm & 0x20) + chan_max=6; + + switch(r) + { + case 0: + for (chan=0; chaninstvol_r[chan]&0xf0)==0) + { + set_mul (chip, chan*2, inst[0]); + } + } + break; + case 1: + for (chan=0; chaninstvol_r[chan]&0xf0)==0) + { + set_mul (chip, chan*2+1,inst[1]); + } + } + break; + case 2: + for (chan=0; chaninstvol_r[chan]&0xf0)==0) + { + set_ksl_tl (chip, chan, inst[2]); + } + } + break; + case 3: + for (chan=0; chaninstvol_r[chan]&0xf0)==0) + { + set_ksl_wave_fb (chip, chan, inst[3]); + } + } + break; + case 4: + for (chan=0; chaninstvol_r[chan]&0xf0)==0) + { + set_ar_dr (chip, chan*2, inst[4]); + } + } + break; + case 5: + for (chan=0; chaninstvol_r[chan]&0xf0)==0) + { + set_ar_dr (chip, chan*2+1,inst[5]); + } + } + break; + case 6: + for (chan=0; chaninstvol_r[chan]&0xf0)==0) + { + set_sl_rr (chip, chan*2, inst[6]); + } + } + break; + case 7: + for (chan=0; chaninstvol_r[chan]&0xf0)==0) + { + set_sl_rr (chip, chan*2+1,inst[7]); + } + } + break; + } +} + +/* write a value v to register r on chip chip */ +static void OPLLWriteReg(YM2413 *chip, int r, int v) +{ + OPLL_CH *CH; + OPLL_SLOT *SLOT; + UINT8 *inst; + int chan; + int slot; + + /* adjust bus to 8 bits */ + r &= 0xff; + v &= 0xff; + + + switch(r&0xf0) + { + case 0x00: /* 00-0f:control */ + { + switch(r&0x0f) + { + case 0x00: /* AM/VIB/EGTYP/KSR/MULTI (modulator) */ + case 0x01: /* AM/VIB/EGTYP/KSR/MULTI (carrier) */ + case 0x02: /* Key Scale Level, Total Level (modulator) */ + case 0x03: /* Key Scale Level, carrier waveform, modulator waveform, Feedback */ + case 0x04: /* Attack, Decay (modulator) */ + case 0x05: /* Attack, Decay (carrier) */ + case 0x06: /* Sustain, Release (modulator) */ + case 0x07: /* Sustain, Release (carrier) */ + chip->inst_tab[0][r & 0x07] = v; + update_instrument_zero(chip,r&7); + break; + + case 0x0e: /* x, x, r,bd,sd,tom,tc,hh */ + { + if(v&0x20) + { + if ((chip->rhythm&0x20)==0) + /*rhythm off to on*/ + { + logerror("YM2413: Rhythm mode enable\n"); + + /* Load instrument settings for channel seven(chan=6 since we're zero based). (Bass drum) */ + chan = 6; + inst = &chip->inst_tab[16][0]; + slot = chan*2; + + load_instrument(chip, chan, slot, inst); + + /* Load instrument settings for channel eight. (High hat and snare drum) */ + chan = 7; + inst = &chip->inst_tab[17][0]; + slot = chan*2; + + load_instrument(chip, chan, slot, inst); + + CH = &chip->P_CH[chan]; + SLOT = &CH->SLOT[SLOT1]; /* modulator envelope is HH */ + SLOT->TL = ((chip->instvol_r[chan]>>4)<<2)<<(ENV_BITS-2-7); /* 7 bits TL (bit 6 = always 0) */ + SLOT->TLL = SLOT->TL + (CH->ksl_base>>SLOT->ksl); + + /* Load instrument settings for channel nine. (Tom-tom and top cymbal) */ + chan = 8; + inst = &chip->inst_tab[18][0]; + slot = chan*2; + + load_instrument(chip, chan, slot, inst); + + CH = &chip->P_CH[chan]; + SLOT = &CH->SLOT[SLOT1]; /* modulator envelope is TOM */ + SLOT->TL = ((chip->instvol_r[chan]>>4)<<2)<<(ENV_BITS-2-7); /* 7 bits TL (bit 6 = always 0) */ + SLOT->TLL = SLOT->TL + (CH->ksl_base>>SLOT->ksl); + } + /* BD key on/off */ + if(v&0x10) + { + KEY_ON (&chip->P_CH[6].SLOT[SLOT1], 2); + KEY_ON (&chip->P_CH[6].SLOT[SLOT2], 2); + } + else + { + KEY_OFF(&chip->P_CH[6].SLOT[SLOT1],~2); + KEY_OFF(&chip->P_CH[6].SLOT[SLOT2],~2); + } + /* HH key on/off */ + if(v&0x01) KEY_ON (&chip->P_CH[7].SLOT[SLOT1], 2); + else KEY_OFF(&chip->P_CH[7].SLOT[SLOT1],~2); + /* SD key on/off */ + if(v&0x08) KEY_ON (&chip->P_CH[7].SLOT[SLOT2], 2); + else KEY_OFF(&chip->P_CH[7].SLOT[SLOT2],~2); + /* TOM key on/off */ + if(v&0x04) KEY_ON (&chip->P_CH[8].SLOT[SLOT1], 2); + else KEY_OFF(&chip->P_CH[8].SLOT[SLOT1],~2); + /* TOP-CY key on/off */ + if(v&0x02) KEY_ON (&chip->P_CH[8].SLOT[SLOT2], 2); + else KEY_OFF(&chip->P_CH[8].SLOT[SLOT2],~2); + } + else + { + if ((chip->rhythm&0x20)==1) + /*rhythm on to off*/ + { + logerror("YM2413: Rhythm mode disable\n"); + /* Load instrument settings for channel seven(chan=6 since we're zero based).*/ + chan = 6; + inst = &chip->inst_tab[chip->instvol_r[chan]>>4][0]; + slot = chan*2; + + load_instrument(chip, chan, slot, inst); + + /* Load instrument settings for channel eight.*/ + chan = 7; + inst = &chip->inst_tab[chip->instvol_r[chan]>>4][0]; + slot = chan*2; + + load_instrument(chip, chan, slot, inst); + + /* Load instrument settings for channel nine.*/ + chan = 8; + inst = &chip->inst_tab[chip->instvol_r[chan]>>4][0]; + slot = chan*2; + + load_instrument(chip, chan, slot, inst); + } + /* BD key off */ + KEY_OFF(&chip->P_CH[6].SLOT[SLOT1],~2); + KEY_OFF(&chip->P_CH[6].SLOT[SLOT2],~2); + /* HH key off */ + KEY_OFF(&chip->P_CH[7].SLOT[SLOT1],~2); + /* SD key off */ + KEY_OFF(&chip->P_CH[7].SLOT[SLOT2],~2); + /* TOM key off */ + KEY_OFF(&chip->P_CH[8].SLOT[SLOT1],~2); + /* TOP-CY off */ + KEY_OFF(&chip->P_CH[8].SLOT[SLOT2],~2); + } + chip->rhythm = v&0x3f; + } + break; + } + } + break; + + case 0x10: + case 0x20: + { + UINT32 block_fnum; + + chan = r&0x0f; + + if (chan >= 9) + chan -= 9; /* verified on real YM2413 */ + + CH = &chip->P_CH[chan]; + + if(r&0x10) + { /* 10-18: FNUM 0-7 */ + block_fnum = (CH->block_fnum&0x0f00) | v; + } + else + { /* 20-28: suson, keyon, block, FNUM 8 */ + block_fnum = ((v&0x0f)<<8) | (CH->block_fnum&0xff); + + if(v&0x10) + { + KEY_ON (&CH->SLOT[SLOT1], 1); + KEY_ON (&CH->SLOT[SLOT2], 1); + } + else + { + KEY_OFF(&CH->SLOT[SLOT1],~1); + KEY_OFF(&CH->SLOT[SLOT2],~1); + } + + + if (CH->sus!=(v&0x20)) + logerror("chan=%i sus=%2x\n",chan,v&0x20); + + CH->sus = v & 0x20; + } + /* update */ + if(CH->block_fnum != block_fnum) + { + UINT8 block; + + CH->block_fnum = block_fnum; + + /* BLK 2,1,0 bits -> bits 3,2,1 of kcode, FNUM MSB -> kcode LSB */ + CH->kcode = (block_fnum&0x0f00)>>8; + + CH->ksl_base = ksl_tab[block_fnum>>5]; + + block_fnum = block_fnum * 2; + block = (block_fnum&0x1c00) >> 10; + CH->fc = chip->fn_tab[block_fnum&0x03ff] >> (7-block); + + /* refresh Total Level in both SLOTs of this channel */ + CH->SLOT[SLOT1].TLL = CH->SLOT[SLOT1].TL + (CH->ksl_base>>CH->SLOT[SLOT1].ksl); + CH->SLOT[SLOT2].TLL = CH->SLOT[SLOT2].TL + (CH->ksl_base>>CH->SLOT[SLOT2].ksl); + + /* refresh frequency counter in both SLOTs of this channel */ + CALC_FCSLOT(CH,&CH->SLOT[SLOT1]); + CALC_FCSLOT(CH,&CH->SLOT[SLOT2]); + } + } + break; + + case 0x30: /* inst 4 MSBs, VOL 4 LSBs */ + { + UINT8 old_instvol; + + chan = r&0x0f; + + if (chan >= 9) + chan -= 9; /* verified on real YM2413 */ + + old_instvol = chip->instvol_r[chan]; + chip->instvol_r[chan] = v; /* store for later use */ + + CH = &chip->P_CH[chan]; + SLOT = &CH->SLOT[SLOT2]; /* carrier */ + SLOT->TL = ((v&0x0f)<<2)<<(ENV_BITS-2-7); /* 7 bits TL (bit 6 = always 0) */ + SLOT->TLL = SLOT->TL + (CH->ksl_base>>SLOT->ksl); + + + /*check wether we are in rhythm mode and handle instrument/volume register accordingly*/ + if ((chan>=6) && (chip->rhythm&0x20)) + { + /* we're in rhythm mode*/ + + if (chan>=7) /* only for channel 7 and 8 (channel 6 is handled in usual way)*/ + { + SLOT = &CH->SLOT[SLOT1]; /* modulator envelope is HH(chan=7) or TOM(chan=8) */ + SLOT->TL = ((chip->instvol_r[chan]>>4)<<2)<<(ENV_BITS-2-7); /* 7 bits TL (bit 6 = always 0) */ + SLOT->TLL = SLOT->TL + (CH->ksl_base>>SLOT->ksl); + } + } + else + { + if ( (old_instvol&0xf0) == (v&0xf0) ) + return; + + inst = &chip->inst_tab[chip->instvol_r[chan]>>4][0]; + slot = chan*2; + + load_instrument(chip, chan, slot, inst); + + #if 0 + logerror("YM2413: chan#%02i inst=%02i: (r=%2x, v=%2x)\n",chan,v>>4,r,v); + logerror(" 0:%2x 1:%2x\n",inst[0],inst[1]); logerror(" 2:%2x 3:%2x\n",inst[2],inst[3]); + logerror(" 4:%2x 5:%2x\n",inst[4],inst[5]); logerror(" 6:%2x 7:%2x\n",inst[6],inst[7]); + #endif + } + } + break; + + default: + break; + } +} + +/* lock/unlock for common table */ +static void OPLLResetChip(YM2413 *chip) +{ + int c,s; + int i; + + chip->eg_timer = 0; + chip->eg_cnt = 0; + + chip->noise_rng = 1; /* noise shift register */ + + chip->mask = 0; + + /* setup instruments table */ + if (!chip->chip_type) + { + for (i=0; i<19; i++) + { + for (c=0; c<8; c++) + { + chip->inst_tab[i][c] = table[i][c]; + } + } + } + else + { + memset( &chip->inst_tab, 0, sizeof(chip->inst_tab) ); + + for (i=0; i<15; i++) + { + for (c=0; c<8; c++) + { + chip->inst_tab[i+1][c] = table_vrc7[i][c]; + } + } + } + + + /* reset with register write */ + OPLLWriteReg(chip,0x0f,0); /*test reg*/ + for(i = 0x3f ; i >= 0x10 ; i-- ) OPLLWriteReg(chip,i,0x00); + + /* reset operator parameters */ + for( c = 0 ; c < 9 ; c++ ) + { + OPLL_CH *CH = &chip->P_CH[c]; + for(s = 0 ; s < 2 ; s++ ) + { + /* wave table */ + CH->SLOT[s].wavetable = 0; + CH->SLOT[s].state = EG_OFF; + CH->SLOT[s].volume = MAX_ATT_INDEX; + } + } +} + +/* Create one of virtual YM2413 */ +/* 'clock' is chip clock in Hz */ +/* 'rate' is sampling rate */ +static YM2413 *OPLLCreate(int clock, int rate, int type) +{ + char *ptr; + YM2413 *chip; + int state_size; + + init_tables(); + + /* calculate chip state size */ + state_size = sizeof(YM2413); + + /* allocate memory block */ + ptr = (char *)malloc(state_size); + + if (ptr==NULL) + return NULL; + + /* clear */ + memset(ptr,0,state_size); + + chip = (YM2413 *)ptr; + + chip->clock = clock; + chip->rate = rate; + + chip->chip_type = type; + + chip->mask = 0; + + /* init global tables */ + OPLL_initalize(chip); + + /* reset chip */ + OPLLResetChip(chip); + return chip; +} + +/* Destroy one of virtual YM3812 */ +static void OPLLDestroy(YM2413 *chip) +{ + free(chip); +} + +/* Option handlers */ + +static void OPLLSetUpdateHandler(YM2413 *chip,OPLL_UPDATEHANDLER UpdateHandler,void * param) +{ + chip->UpdateHandler = UpdateHandler; + chip->UpdateParam = param; +} + +/* YM3812 I/O interface */ +static void OPLLWrite(YM2413 *chip,int a,int v) +{ + if( !(a&1) ) + { /* address port */ + chip->address = v & 0xff; + } + else + { /* data port */ + if(chip->UpdateHandler) chip->UpdateHandler(chip->UpdateParam,0); + OPLLWriteReg(chip,chip->address,v); + } +} + +static unsigned char OPLLRead(YM2413 *chip,int a) +{ + if( !(a&1) ) + { + /* status port */ + return chip->status; + } + return 0xff; +} + + + + + +void * ym2413_init(int clock, int rate, int type) +{ + /* emulator create */ + return OPLLCreate(clock, rate, type); +} + +void ym2413_shutdown(void *chip) +{ + YM2413 *OPLL = (YM2413 *)chip; + + /* emulator shutdown */ + OPLLDestroy(OPLL); +} + +void ym2413_reset_chip(void *chip) +{ + YM2413 *OPLL = (YM2413 *)chip; + OPLLResetChip(OPLL); +} + +void ym2413_write(void *chip, int a, int v) +{ + YM2413 *OPLL = (YM2413 *)chip; + OPLLWrite(OPLL, a, v); +} + +unsigned char ym2413_read(void *chip, int a) +{ + YM2413 *OPLL = (YM2413 *)chip; + return OPLLRead(OPLL, a) & 0x03 ; +} + +void ym2413_set_update_handler(void *chip,OPLL_UPDATEHANDLER UpdateHandler,void *param) +{ + YM2413 *OPLL = (YM2413 *)chip; + OPLLSetUpdateHandler(OPLL, UpdateHandler, param); +} + + +/* +** Generate samples for one of the YM2413's +** +** 'which' is the virtual YM2413 number +** '*buffer' is the output buffer pointer +** 'length' is the number of samples that should be generated +*/ +void ym2413_update_one(void *_chip, SAMP **buffers, int length) +{ + YM2413 *chip = (YM2413 *)_chip; + UINT8 rhythm = chip->rhythm&0x20; + SAMP *bufMO = buffers[0]; + SAMP *bufRO = buffers[1]; + + int i,j; + + chip->SLOT7_1 = &chip->P_CH[7].SLOT[SLOT1]; + chip->SLOT7_2 = &chip->P_CH[7].SLOT[SLOT2]; + chip->SLOT8_1 = &chip->P_CH[8].SLOT[SLOT1]; + chip->SLOT8_2 = &chip->P_CH[8].SLOT[SLOT2]; + + + for( i=0; i < length ; i++ ) + { + int mo,ro; + + chip->output[0] = 0; + chip->output[1] = 0; + + advance_lfo(chip); + +#if 0 + /* FM part */ + chan_calc(chip,&chip->P_CH[0]); +/* SAVE_SEPARATE_CHANNEL(0); */ + chan_calc(chip,&chip->P_CH[1]); + chan_calc(chip,&chip->P_CH[2]); + chan_calc(chip,&chip->P_CH[3]); + chan_calc(chip,&chip->P_CH[4]); + chan_calc(chip,&chip->P_CH[5]); +#else + for ( j=0; j < 6; j++ ) + { + if (!(chip->mask & OPLL_MASK_CH(j))) chan_calc(chip, &chip->P_CH[j]); + } +#endif + + if(!rhythm) + { +#if 0 + chan_calc(chip,&chip->P_CH[6]); + chan_calc(chip,&chip->P_CH[7]); + chan_calc(chip,&chip->P_CH[8]); +#else + for ( j=6; j < 9; j++ ) + { + if (!(chip->mask & OPLL_MASK_CH(j))) chan_calc(chip, &chip->P_CH[j]); + } +#endif + } + else /* Rhythm part */ + { + if ( ( chip->mask & OPLL_MASK_RHYTHM ) != OPLL_MASK_RHYTHM ) + rhythm_calc(chip,&chip->P_CH[0], (chip->noise_rng>>0)&1 ); + } + + mo = chip->output[0]; + ro = chip->output[1]; + + mo >>= FINAL_SH; + ro >>= FINAL_SH; + + /* limit check */ + mo = limit( mo , MAXOUT, MINOUT ); + ro = limit( ro , MAXOUT, MINOUT ); + + /* store to sound buffer */ + bufMO[i] = mo; + bufRO[i] = ro; + + advance(chip); + } + +} + +void ym2413_advance_lfo(void *_chip) +{ + YM2413 *chip = (YM2413 *)_chip; + advance_lfo(chip); +} + +void ym2413_advance(void *_chip) +{ + YM2413 *chip = (YM2413 *)_chip; + advance(chip); +} + +SAMP ym2413_calcch(void *_chip, int ch) +{ + YM2413 *chip = (YM2413 *)_chip; + + int output; + + chip->output[0] = 0; + chip->output[1] = 0; + + if (ch >= 0 && ch < 6) chan_calc( chip, &chip->P_CH[ch] ); + else if (ch >= 6 && ch < 9) + { + UINT8 rhythm = chip->rhythm&0x20; + if (!rhythm) chan_calc( chip, &chip->P_CH[ch] ); + else if (ch == 6) rhythm_calc(chip,&chip->P_CH[0], (chip->noise_rng>>0)&1 ); + } + + output = chip->output[0]; + output += chip->output[1]; + + return output; +} + +void * ym2413_get_inst0(void *_chip) +{ + YM2413 *chip = (YM2413 *)_chip; + + return &chip->inst_tab; +} + +void ym2413_set_mask(void *_chip, UINT32 mask) +{ + YM2413 *chip = (YM2413 *)_chip; + + chip->mask = mask; +} diff --git a/Frameworks/GME/gme/ym2413.h b/Frameworks/GME/gme/ym2413.h new file mode 100644 index 000000000..ebd51495e --- /dev/null +++ b/Frameworks/GME/gme/ym2413.h @@ -0,0 +1,50 @@ +#pragma once + +#ifndef __YM2413_H__ +#define __YM2413_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +/* select output bits size of output : 8 or 16 */ +#define SAMPLE_BITS 16 + +#include "mamedef.h" + +typedef stream_sample_t SAMP; +/* +#if (SAMPLE_BITS==16) +typedef INT16 SAMP; +#endif +#if (SAMPLE_BITS==8) +typedef INT8 SAMP; +#endif +*/ + + + +void *ym2413_init(int clock, int rate, int type); +void ym2413_shutdown(void *chip); +void ym2413_reset_chip(void *chip); +void ym2413_write(void *chip, int a, int v); +unsigned char ym2413_read(void *chip, int a); +void ym2413_update_one(void *chip, SAMP **buffers, int length); + +void ym2413_advance_lfo(void *chip); /* call this once */ +SAMP ym2413_calcch(void *chip, int ch); /* then call this for each channel */ +void ym2413_advance(void *chip); /* then call this */ + +void * ym2413_get_inst0(void *chip); + +void ym2413_set_mask(void *chip, UINT32 mask); + +typedef void (*OPLL_UPDATEHANDLER)(void *param,int min_interval_us); + +void ym2413_set_update_handler(void *chip, OPLL_UPDATEHANDLER UpdateHandler, void *param); + +#ifdef __cplusplus +} +#endif + +#endif /*__YM2413_H__*/ diff --git a/Frameworks/GME/gme/ymdeltat.cpp b/Frameworks/GME/gme/ymdeltat.cpp new file mode 100644 index 000000000..71681624c --- /dev/null +++ b/Frameworks/GME/gme/ymdeltat.cpp @@ -0,0 +1,659 @@ +/* +** +** File: ymdeltat.c +** +** YAMAHA DELTA-T adpcm sound emulation subroutine +** used by fmopl.c (Y8950) and fm.c (YM2608 and YM2610/B) +** +** Base program is YM2610 emulator by Hiromitsu Shioya. +** Written by Tatsuyuki Satoh +** Improvements by Jarek Burczynski (bujar at mame dot net) +** +** +** History: +** +** 03-08-2003 Jarek Burczynski: +** - fixed BRDY flag implementation. +** +** 24-07-2003 Jarek Burczynski, Frits Hilderink: +** - fixed delault value for control2 in YM_DELTAT_ADPCM_Reset +** +** 22-07-2003 Jarek Burczynski, Frits Hilderink: +** - fixed external memory support +** +** 15-06-2003 Jarek Burczynski: +** - implemented CPU -> AUDIO ADPCM synthesis (via writes to the ADPCM data reg $08) +** - implemented support for the Limit address register +** - supported two bits from the control register 2 ($01): RAM TYPE (x1 bit/x8 bit), ROM/RAM +** - implemented external memory access (read/write) via the ADPCM data reg reads/writes +** Thanks go to Frits Hilderink for the example code. +** +** 14-06-2003 Jarek Burczynski: +** - various fixes to enable proper support for status register flags: BSRDY, PCM BSY, ZERO +** - modified EOS handling +** +** 05-04-2003 Jarek Burczynski: +** - implemented partial support for external/processor memory on sample replay +** +** 01-12-2002 Jarek Burczynski: +** - fixed first missing sound in gigandes thanks to previous fix (interpolator) by ElSemi +** - renamed/removed some YM_DELTAT struct fields +** +** 28-12-2001 Acho A. Tang +** - added EOS status report on ADPCM playback. +** +** 05-08-2001 Jarek Burczynski: +** - now_step is initialized with 0 at the start of play. +** +** 12-06-2001 Jarek Burczynski: +** - corrected end of sample bug in YM_DELTAT_ADPCM_CALC. +** Checked on real YM2610 chip - address register is 24 bits wide. +** Thanks go to Stefan Jokisch (stefan.jokisch@gmx.de) for tracking down the problem. +** +** TO DO: +** Check size of the address register on the other chips.... +** +** Version 0.72 +** +** sound chips that have this unit: +** YM2608 OPNA +** YM2610/B OPNB +** Y8950 MSX AUDIO +** +*/ + +#include "ymdeltat.h" +#ifndef INLINE +#define INLINE __inline +#endif +#ifndef logerror +#define logerror (void) +#endif + +#define YM_DELTAT_DELTA_MAX (24576) +#define YM_DELTAT_DELTA_MIN (127) +#define YM_DELTAT_DELTA_DEF (127) + +#define YM_DELTAT_DECODE_RANGE 32768 +#define YM_DELTAT_DECODE_MIN (-(YM_DELTAT_DECODE_RANGE)) +#define YM_DELTAT_DECODE_MAX ((YM_DELTAT_DECODE_RANGE)-1) + + +/* Forecast to next Forecast (rate = *8) */ +/* 1/8 , 3/8 , 5/8 , 7/8 , 9/8 , 11/8 , 13/8 , 15/8 */ +static const INT32 ym_deltat_decode_tableB1[16] = { + 1, 3, 5, 7, 9, 11, 13, 15, + -1, -3, -5, -7, -9, -11, -13, -15, +}; +/* delta to next delta (rate= *64) */ +/* 0.9 , 0.9 , 0.9 , 0.9 , 1.2 , 1.6 , 2.0 , 2.4 */ +static const INT32 ym_deltat_decode_tableB2[16] = { + 57, 57, 57, 57, 77, 102, 128, 153, + 57, 57, 57, 57, 77, 102, 128, 153 +}; + +#if 0 +void YM_DELTAT_BRDY_callback(YM_DELTAT *DELTAT) +{ + logerror("BRDY_callback reached (flag set) !\n"); + + /* set BRDY bit in status register */ + if(DELTAT->status_set_handler) + if(DELTAT->status_change_BRDY_bit) + (DELTAT->status_set_handler)(DELTAT->status_change_which_chip, DELTAT->status_change_BRDY_bit); +} +#endif + +UINT8 YM_DELTAT_ADPCM_Read(YM_DELTAT *DELTAT) +{ + UINT8 v = 0; + + /* external memory read */ + if ( (DELTAT->portstate & 0xe0)==0x20 ) + { + /* two dummy reads */ + if (DELTAT->memread) + { + DELTAT->now_addr = DELTAT->start << 1; + DELTAT->memread--; + return 0; + } + + + if ( DELTAT->now_addr != (DELTAT->end<<1) ) + { + v = DELTAT->memory[DELTAT->now_addr>>1]; + + /*logerror("YM Delta-T memory read $%08x, v=$%02x\n", DELTAT->now_addr >> 1, v);*/ + + DELTAT->now_addr+=2; /* two nibbles at a time */ + + /* reset BRDY bit in status register, which means we are reading the memory now */ + if(DELTAT->status_reset_handler) + if(DELTAT->status_change_BRDY_bit) + (DELTAT->status_reset_handler)(DELTAT->status_change_which_chip, DELTAT->status_change_BRDY_bit); + + /* setup a timer that will callback us in 10 master clock cycles for Y8950 + * in the callback set the BRDY flag to 1 , which means we have another data ready. + * For now, we don't really do this; we simply reset and set the flag in zero time, so that the IRQ will work. + */ + /* set BRDY bit in status register */ + if(DELTAT->status_set_handler) + if(DELTAT->status_change_BRDY_bit) + (DELTAT->status_set_handler)(DELTAT->status_change_which_chip, DELTAT->status_change_BRDY_bit); + } + else + { + /* set EOS bit in status register */ + if(DELTAT->status_set_handler) + if(DELTAT->status_change_EOS_bit) + (DELTAT->status_set_handler)(DELTAT->status_change_which_chip, DELTAT->status_change_EOS_bit); + } + } + + return v; +} + + +/* 0-DRAM x1, 1-ROM, 2-DRAM x8, 3-ROM (3 is bad setting - not allowed by the manual) */ +static const UINT8 dram_rightshift[4]={3,0,0,0}; + +/* DELTA-T ADPCM write register */ +void YM_DELTAT_ADPCM_Write(YM_DELTAT *DELTAT,int r,int v) +{ + if(r>=0x10) return; + DELTAT->reg[r] = v; /* stock data */ + + switch( r ) + { + case 0x00: +/* +START: + Accessing *external* memory is started when START bit (D7) is set to "1", so + you must set all conditions needed for recording/playback before starting. + If you access *CPU-managed* memory, recording/playback starts after + read/write of ADPCM data register $08. + +REC: + 0 = ADPCM synthesis (playback) + 1 = ADPCM analysis (record) + +MEMDATA: + 0 = processor (*CPU-managed*) memory (means: using register $08) + 1 = external memory (using start/end/limit registers to access memory: RAM or ROM) + + +SPOFF: + controls output pin that should disable the speaker while ADPCM analysis + +RESET and REPEAT only work with external memory. + + +some examples: +value: START, REC, MEMDAT, REPEAT, SPOFF, x,x,RESET meaning: + C8 1 1 0 0 1 0 0 0 Analysis (recording) from AUDIO to CPU (to reg $08), sample rate in PRESCALER register + E8 1 1 1 0 1 0 0 0 Analysis (recording) from AUDIO to EXT.MEMORY, sample rate in PRESCALER register + 80 1 0 0 0 0 0 0 0 Synthesis (playing) from CPU (from reg $08) to AUDIO,sample rate in DELTA-N register + a0 1 0 1 0 0 0 0 0 Synthesis (playing) from EXT.MEMORY to AUDIO, sample rate in DELTA-N register + + 60 0 1 1 0 0 0 0 0 External memory write via ADPCM data register $08 + 20 0 0 1 0 0 0 0 0 External memory read via ADPCM data register $08 + +*/ + /* handle emulation mode */ + if(DELTAT->emulation_mode == YM_DELTAT_EMULATION_MODE_YM2610) + { + v |= 0x20; /* YM2610 always uses external memory and doesn't even have memory flag bit. */ + } + + DELTAT->portstate = v & (0x80|0x40|0x20|0x10|0x01); /* start, rec, memory mode, repeat flag copy, reset(bit0) */ + + if( DELTAT->portstate&0x80 )/* START,REC,MEMDATA,REPEAT,SPOFF,--,--,RESET */ + { + /* set PCM BUSY bit */ + DELTAT->PCM_BSY = 1; + + /* start ADPCM */ + DELTAT->now_step = 0; + DELTAT->acc = 0; + DELTAT->prev_acc = 0; + DELTAT->adpcml = 0; + DELTAT->adpcmd = YM_DELTAT_DELTA_DEF; + DELTAT->now_data = 0; + + } + + if( DELTAT->portstate&0x20 ) /* do we access external memory? */ + { + DELTAT->now_addr = DELTAT->start << 1; + DELTAT->memread = 2; /* two dummy reads needed before accesing external memory via register $08*/ + + /* if yes, then let's check if ADPCM memory is mapped and big enough */ + if(DELTAT->memory == 0) + { + logerror("YM Delta-T ADPCM rom not mapped\n"); + DELTAT->portstate = 0x00; + DELTAT->PCM_BSY = 0; + } + else + { + if( DELTAT->end >= DELTAT->memory_size ) /* Check End in Range */ + { + logerror("YM Delta-T ADPCM end out of range: $%08x\n", DELTAT->end); + DELTAT->end = DELTAT->memory_size - 1; + } + if( DELTAT->start >= DELTAT->memory_size ) /* Check Start in Range */ + { + logerror("YM Delta-T ADPCM start out of range: $%08x\n", DELTAT->start); + DELTAT->portstate = 0x00; + DELTAT->PCM_BSY = 0; + } + } + } + else /* we access CPU memory (ADPCM data register $08) so we only reset now_addr here */ + { + DELTAT->now_addr = 0; + } + + if( DELTAT->portstate&0x01 ) + { + DELTAT->portstate = 0x00; + + /* clear PCM BUSY bit (in status register) */ + DELTAT->PCM_BSY = 0; + + /* set BRDY flag */ + if(DELTAT->status_set_handler) + if(DELTAT->status_change_BRDY_bit) + (DELTAT->status_set_handler)(DELTAT->status_change_which_chip, DELTAT->status_change_BRDY_bit); + } + break; + case 0x01: /* L,R,-,-,SAMPLE,DA/AD,RAMTYPE,ROM */ + /* handle emulation mode */ + if(DELTAT->emulation_mode == YM_DELTAT_EMULATION_MODE_YM2610) + { + v |= 0x01; /* YM2610 always uses ROM as an external memory and doesn't tave ROM/RAM memory flag bit. */ + } + + DELTAT->pan = &DELTAT->output_pointer[(v>>6)&0x03]; + if ((DELTAT->control2 & 3) != (v & 3)) + { + /*0-DRAM x1, 1-ROM, 2-DRAM x8, 3-ROM (3 is bad setting - not allowed by the manual) */ + if (DELTAT->DRAMportshift != dram_rightshift[v&3]) + { + DELTAT->DRAMportshift = dram_rightshift[v&3]; + + /* final shift value depends on chip type and memory type selected: + 8 for YM2610 (ROM only), + 5 for ROM for Y8950 and YM2608, + 5 for x8bit DRAMs for Y8950 and YM2608, + 2 for x1bit DRAMs for Y8950 and YM2608. + */ + + /* refresh addresses */ + DELTAT->start = (DELTAT->reg[0x3]*0x0100 | DELTAT->reg[0x2]) << (DELTAT->portshift - DELTAT->DRAMportshift); + DELTAT->end = (DELTAT->reg[0x5]*0x0100 | DELTAT->reg[0x4]) << (DELTAT->portshift - DELTAT->DRAMportshift); + DELTAT->end += (1 << (DELTAT->portshift-DELTAT->DRAMportshift) ) - 1; + DELTAT->limit = (DELTAT->reg[0xd]*0x0100 | DELTAT->reg[0xc]) << (DELTAT->portshift - DELTAT->DRAMportshift); + } + } + DELTAT->control2 = v; + break; + case 0x02: /* Start Address L */ + case 0x03: /* Start Address H */ + DELTAT->start = (DELTAT->reg[0x3]*0x0100 | DELTAT->reg[0x2]) << (DELTAT->portshift - DELTAT->DRAMportshift); + /*logerror("DELTAT start: 02=%2x 03=%2x addr=%8x\n",DELTAT->reg[0x2], DELTAT->reg[0x3],DELTAT->start );*/ + break; + case 0x04: /* Stop Address L */ + case 0x05: /* Stop Address H */ + DELTAT->end = (DELTAT->reg[0x5]*0x0100 | DELTAT->reg[0x4]) << (DELTAT->portshift - DELTAT->DRAMportshift); + DELTAT->end += (1 << (DELTAT->portshift-DELTAT->DRAMportshift) ) - 1; + /*logerror("DELTAT end : 04=%2x 05=%2x addr=%8x\n",DELTAT->reg[0x4], DELTAT->reg[0x5],DELTAT->end );*/ + break; + case 0x06: /* Prescale L (ADPCM and Record frq) */ + case 0x07: /* Prescale H */ + break; + case 0x08: /* ADPCM data */ + +/* +some examples: +value: START, REC, MEMDAT, REPEAT, SPOFF, x,x,RESET meaning: + C8 1 1 0 0 1 0 0 0 Analysis (recording) from AUDIO to CPU (to reg $08), sample rate in PRESCALER register + E8 1 1 1 0 1 0 0 0 Analysis (recording) from AUDIO to EXT.MEMORY, sample rate in PRESCALER register + 80 1 0 0 0 0 0 0 0 Synthesis (playing) from CPU (from reg $08) to AUDIO,sample rate in DELTA-N register + a0 1 0 1 0 0 0 0 0 Synthesis (playing) from EXT.MEMORY to AUDIO, sample rate in DELTA-N register + + 60 0 1 1 0 0 0 0 0 External memory write via ADPCM data register $08 + 20 0 0 1 0 0 0 0 0 External memory read via ADPCM data register $08 + +*/ + + /* external memory write */ + if ( (DELTAT->portstate & 0xe0)==0x60 ) + { + if (DELTAT->memread) + { + DELTAT->now_addr = DELTAT->start << 1; + DELTAT->memread = 0; + } + + /*logerror("YM Delta-T memory write $%08x, v=$%02x\n", DELTAT->now_addr >> 1, v);*/ + + if ( DELTAT->now_addr != (DELTAT->end<<1) ) + { + DELTAT->memory[DELTAT->now_addr>>1] = v; + DELTAT->now_addr+=2; /* two nibbles at a time */ + + /* reset BRDY bit in status register, which means we are processing the write */ + if(DELTAT->status_reset_handler) + if(DELTAT->status_change_BRDY_bit) + (DELTAT->status_reset_handler)(DELTAT->status_change_which_chip, DELTAT->status_change_BRDY_bit); + + /* setup a timer that will callback us in 10 master clock cycles for Y8950 + * in the callback set the BRDY flag to 1 , which means we have written the data. + * For now, we don't really do this; we simply reset and set the flag in zero time, so that the IRQ will work. + */ + /* set BRDY bit in status register */ + if(DELTAT->status_set_handler) + if(DELTAT->status_change_BRDY_bit) + (DELTAT->status_set_handler)(DELTAT->status_change_which_chip, DELTAT->status_change_BRDY_bit); + + } + else + { + /* set EOS bit in status register */ + if(DELTAT->status_set_handler) + if(DELTAT->status_change_EOS_bit) + (DELTAT->status_set_handler)(DELTAT->status_change_which_chip, DELTAT->status_change_EOS_bit); + } + + return; + } + + /* ADPCM synthesis from CPU */ + if ( (DELTAT->portstate & 0xe0)==0x80 ) + { + DELTAT->CPU_data = v; + + /* Reset BRDY bit in status register, which means we are full of data */ + if(DELTAT->status_reset_handler) + if(DELTAT->status_change_BRDY_bit) + (DELTAT->status_reset_handler)(DELTAT->status_change_which_chip, DELTAT->status_change_BRDY_bit); + return; + } + + break; + case 0x09: /* DELTA-N L (ADPCM Playback Prescaler) */ + case 0x0a: /* DELTA-N H */ + DELTAT->delta = (DELTAT->reg[0xa]*0x0100 | DELTAT->reg[0x9]); + DELTAT->step = (UINT32)( (double)(DELTAT->delta /* *(1<<(YM_DELTAT_SHIFT-16)) */ ) * (DELTAT->freqbase) ); + /*logerror("DELTAT deltan:09=%2x 0a=%2x\n",DELTAT->reg[0x9], DELTAT->reg[0xa]);*/ + break; + case 0x0b: /* Output level control (volume, linear) */ + { + INT32 oldvol = DELTAT->volume; + DELTAT->volume = (v&0xff) * (DELTAT->output_range/256) / YM_DELTAT_DECODE_RANGE; +/* v * ((1<<16)>>8) >> 15; +* thus: v * (1<<8) >> 15; +* thus: output_range must be (1 << (15+8)) at least +* v * ((1<<23)>>8) >> 15; +* v * (1<<15) >> 15; +*/ + /*logerror("DELTAT vol = %2x\n",v&0xff);*/ + if( oldvol != 0 ) + { + DELTAT->adpcml = (int)((double)DELTAT->adpcml / (double)oldvol * (double)DELTAT->volume); + } + } + break; + case 0x0c: /* Limit Address L */ + case 0x0d: /* Limit Address H */ + DELTAT->limit = (DELTAT->reg[0xd]*0x0100 | DELTAT->reg[0xc]) << (DELTAT->portshift - DELTAT->DRAMportshift); + /*logerror("DELTAT limit: 0c=%2x 0d=%2x addr=%8x\n",DELTAT->reg[0xc], DELTAT->reg[0xd],DELTAT->limit );*/ + break; + } +} + +void YM_DELTAT_ADPCM_Reset(YM_DELTAT *DELTAT,int pan,int emulation_mode) +{ + DELTAT->now_addr = 0; + DELTAT->now_step = 0; + DELTAT->step = 0; + DELTAT->start = 0; + DELTAT->end = 0; + DELTAT->limit = ~0; /* this way YM2610 and Y8950 (both of which don't have limit address reg) will still work */ + DELTAT->volume = 0; + DELTAT->pan = &DELTAT->output_pointer[pan]; + DELTAT->acc = 0; + DELTAT->prev_acc = 0; + DELTAT->adpcmd = 127; + DELTAT->adpcml = 0; + DELTAT->emulation_mode = (UINT8)emulation_mode; + DELTAT->portstate = (emulation_mode == YM_DELTAT_EMULATION_MODE_YM2610) ? 0x20 : 0; + DELTAT->control2 = (emulation_mode == YM_DELTAT_EMULATION_MODE_YM2610) ? 0x01 : 0; /* default setting depends on the emulation mode. MSX demo called "facdemo_4" doesn't setup control2 register at all and still works */ + DELTAT->DRAMportshift = dram_rightshift[DELTAT->control2 & 3]; + + /* The flag mask register disables the BRDY after the reset, however + ** as soon as the mask is enabled the flag needs to be set. */ + + /* set BRDY bit in status register */ + if(DELTAT->status_set_handler) + if(DELTAT->status_change_BRDY_bit) + (DELTAT->status_set_handler)(DELTAT->status_change_which_chip, DELTAT->status_change_BRDY_bit); +} + +#if 0 +void YM_DELTAT_postload(YM_DELTAT *DELTAT,UINT8 *regs) +{ + int r; + + /* to keep adpcml */ + DELTAT->volume = 0; + /* update */ + for(r=1;r<16;r++) + YM_DELTAT_ADPCM_Write(DELTAT,r,regs[r]); + DELTAT->reg[0] = regs[0]; + + /* current rom data */ + if (DELTAT->memory) + DELTAT->now_data = *(DELTAT->memory + (DELTAT->now_addr>>1) ); + +} +void YM_DELTAT_savestate(const device_config *device,YM_DELTAT *DELTAT) +{ +#ifdef __STATE_H__ + state_save_register_device_item(device, 0, DELTAT->portstate); + state_save_register_device_item(device, 0, DELTAT->now_addr); + state_save_register_device_item(device, 0, DELTAT->now_step); + state_save_register_device_item(device, 0, DELTAT->acc); + state_save_register_device_item(device, 0, DELTAT->prev_acc); + state_save_register_device_item(device, 0, DELTAT->adpcmd); + state_save_register_device_item(device, 0, DELTAT->adpcml); +#endif +} +#endif + + +#define YM_DELTAT_Limit(val,max,min) \ +{ \ + if ( val > max ) val = max; \ + else if ( val < min ) val = min; \ +} + +INLINE void YM_DELTAT_synthesis_from_external_memory(YM_DELTAT *DELTAT) +{ + UINT32 step; + int data; + + DELTAT->now_step += DELTAT->step; + if ( DELTAT->now_step >= (1<now_step >> YM_DELTAT_SHIFT; + DELTAT->now_step &= (1<now_addr == (DELTAT->limit<<1) ) + DELTAT->now_addr = 0; + + if ( DELTAT->now_addr == (DELTAT->end<<1) ) { /* 12-06-2001 JB: corrected comparison. Was > instead of == */ + if( DELTAT->portstate&0x10 ){ + /* repeat start */ + DELTAT->now_addr = DELTAT->start<<1; + DELTAT->acc = 0; + DELTAT->adpcmd = YM_DELTAT_DELTA_DEF; + DELTAT->prev_acc = 0; + }else{ + /* set EOS bit in status register */ + if(DELTAT->status_set_handler) + if(DELTAT->status_change_EOS_bit) + (DELTAT->status_set_handler)(DELTAT->status_change_which_chip, DELTAT->status_change_EOS_bit); + + /* clear PCM BUSY bit (reflected in status register) */ + DELTAT->PCM_BSY = 0; + + DELTAT->portstate = 0; + DELTAT->adpcml = 0; + DELTAT->prev_acc = 0; + return; + } + } + + if( DELTAT->now_addr&1 ) data = DELTAT->now_data & 0x0f; + else + { + DELTAT->now_data = *(DELTAT->memory + (DELTAT->now_addr>>1)); + data = DELTAT->now_data >> 4; + } + + DELTAT->now_addr++; + /* 12-06-2001 JB: */ + /* YM2610 address register is 24 bits wide.*/ + /* The "+1" is there because we use 1 bit more for nibble calculations.*/ + /* WARNING: */ + /* Side effect: we should take the size of the mapped ROM into account */ + DELTAT->now_addr &= ( (1<<(24+1))-1); + + /* store accumulator value */ + DELTAT->prev_acc = DELTAT->acc; + + /* Forecast to next Forecast */ + DELTAT->acc += (ym_deltat_decode_tableB1[data] * DELTAT->adpcmd / 8); + YM_DELTAT_Limit(DELTAT->acc,YM_DELTAT_DECODE_MAX, YM_DELTAT_DECODE_MIN); + + /* delta to next delta */ + DELTAT->adpcmd = (DELTAT->adpcmd * ym_deltat_decode_tableB2[data] ) / 64; + YM_DELTAT_Limit(DELTAT->adpcmd,YM_DELTAT_DELTA_MAX, YM_DELTAT_DELTA_MIN ); + + /* ElSemi: Fix interpolator. */ + /*DELTAT->prev_acc = prev_acc + ((DELTAT->acc - prev_acc) / 2 );*/ + + }while(--step); + + } + + /* ElSemi: Fix interpolator. */ + DELTAT->adpcml = DELTAT->prev_acc * (int)((1<now_step); + DELTAT->adpcml += (DELTAT->acc * (int)DELTAT->now_step); + DELTAT->adpcml = (DELTAT->adpcml>>YM_DELTAT_SHIFT) * (int)DELTAT->volume; + + /* output for work of output channels (outd[OPNxxxx])*/ + *(DELTAT->pan) += DELTAT->adpcml; +} + + + +INLINE void YM_DELTAT_synthesis_from_CPU_memory(YM_DELTAT *DELTAT) +{ + UINT32 step; + int data; + + DELTAT->now_step += DELTAT->step; + if ( DELTAT->now_step >= (1<now_step >> YM_DELTAT_SHIFT; + DELTAT->now_step &= (1<now_addr&1 ) + { + data = DELTAT->now_data & 0x0f; + + DELTAT->now_data = DELTAT->CPU_data; + + /* after we used CPU_data, we set BRDY bit in status register, + * which means we are ready to accept another byte of data */ + if(DELTAT->status_set_handler) + if(DELTAT->status_change_BRDY_bit) + (DELTAT->status_set_handler)(DELTAT->status_change_which_chip, DELTAT->status_change_BRDY_bit); + } + else + { + data = DELTAT->now_data >> 4; + } + + DELTAT->now_addr++; + + /* store accumulator value */ + DELTAT->prev_acc = DELTAT->acc; + + /* Forecast to next Forecast */ + DELTAT->acc += (ym_deltat_decode_tableB1[data] * DELTAT->adpcmd / 8); + YM_DELTAT_Limit(DELTAT->acc,YM_DELTAT_DECODE_MAX, YM_DELTAT_DECODE_MIN); + + /* delta to next delta */ + DELTAT->adpcmd = (DELTAT->adpcmd * ym_deltat_decode_tableB2[data] ) / 64; + YM_DELTAT_Limit(DELTAT->adpcmd,YM_DELTAT_DELTA_MAX, YM_DELTAT_DELTA_MIN ); + + + }while(--step); + + } + + /* ElSemi: Fix interpolator. */ + DELTAT->adpcml = DELTAT->prev_acc * (int)((1<now_step); + DELTAT->adpcml += (DELTAT->acc * (int)DELTAT->now_step); + DELTAT->adpcml = (DELTAT->adpcml>>YM_DELTAT_SHIFT) * (int)DELTAT->volume; + + /* output for work of output channels (outd[OPNxxxx])*/ + *(DELTAT->pan) += DELTAT->adpcml; +} + + + +/* ADPCM B (Delta-T control type) */ +void YM_DELTAT_ADPCM_CALC(YM_DELTAT *DELTAT) +{ + +/* +some examples: +value: START, REC, MEMDAT, REPEAT, SPOFF, x,x,RESET meaning: + 80 1 0 0 0 0 0 0 0 Synthesis (playing) from CPU (from reg $08) to AUDIO,sample rate in DELTA-N register + a0 1 0 1 0 0 0 0 0 Synthesis (playing) from EXT.MEMORY to AUDIO, sample rate in DELTA-N register + C8 1 1 0 0 1 0 0 0 Analysis (recording) from AUDIO to CPU (to reg $08), sample rate in PRESCALER register + E8 1 1 1 0 1 0 0 0 Analysis (recording) from AUDIO to EXT.MEMORY, sample rate in PRESCALER register + + 60 0 1 1 0 0 0 0 0 External memory write via ADPCM data register $08 + 20 0 0 1 0 0 0 0 0 External memory read via ADPCM data register $08 + +*/ + + if ( (DELTAT->portstate & 0xe0)==0xa0 ) + { + YM_DELTAT_synthesis_from_external_memory(DELTAT); + return; + } + + if ( (DELTAT->portstate & 0xe0)==0x80 ) + { + /* ADPCM synthesis from CPU-managed memory (from reg $08) */ + YM_DELTAT_synthesis_from_CPU_memory(DELTAT); /* change output based on data in ADPCM data reg ($08) */ + return; + } + +//todo: ADPCM analysis +// if ( (DELTAT->portstate & 0xe0)==0xc0 ) +// if ( (DELTAT->portstate & 0xe0)==0xe0 ) + + return; +} + diff --git a/Frameworks/GME/gme/ymdeltat.h b/Frameworks/GME/gme/ymdeltat.h new file mode 100644 index 000000000..9ae628757 --- /dev/null +++ b/Frameworks/GME/gme/ymdeltat.h @@ -0,0 +1,94 @@ +#pragma once + +#ifndef __YMDELTAT_H__ +#define __YMDELTAT_H__ + +#include "mamedef.h" + +#define YM_DELTAT_SHIFT (16) + +#define YM_DELTAT_EMULATION_MODE_NORMAL 0 +#define YM_DELTAT_EMULATION_MODE_YM2610 1 + +#ifdef __cplusplus +extern "C" { +#endif + + +typedef void (*STATUS_CHANGE_HANDLER)(void *chip, UINT8 status_bits); + + +/* DELTA-T (adpcm type B) struct */ +typedef struct deltat_adpcm_state { /* AT: rearranged and tigntened structure */ + UINT8 *memory; + INT32 *output_pointer;/* pointer of output pointers */ + INT32 *pan; /* pan : &output_pointer[pan] */ + double freqbase; +#if 0 + double write_time; /* Y8950: 10 cycles of main clock; YM2608: 20 cycles of main clock */ + double read_time; /* Y8950: 8 cycles of main clock; YM2608: 18 cycles of main clock */ +#endif + UINT32 memory_size; + int output_range; + UINT32 now_addr; /* current address */ + UINT32 now_step; /* currect step */ + UINT32 step; /* step */ + UINT32 start; /* start address */ + UINT32 limit; /* limit address */ + UINT32 end; /* end address */ + UINT32 delta; /* delta scale */ + INT32 volume; /* current volume */ + INT32 acc; /* shift Measurement value*/ + INT32 adpcmd; /* next Forecast */ + INT32 adpcml; /* current value */ + INT32 prev_acc; /* leveling value */ + UINT8 now_data; /* current rom data */ + UINT8 CPU_data; /* current data from reg 08 */ + UINT8 portstate; /* port status */ + UINT8 control2; /* control reg: SAMPLE, DA/AD, RAM TYPE (x8bit / x1bit), ROM/RAM */ + UINT8 portshift; /* address bits shift-left: + ** 8 for YM2610, + ** 5 for Y8950 and YM2608 */ + + UINT8 DRAMportshift; /* address bits shift-right: + ** 0 for ROM and x8bit DRAMs, + ** 3 for x1 DRAMs */ + + UINT8 memread; /* needed for reading/writing external memory */ + + /* handlers and parameters for the status flags support */ + STATUS_CHANGE_HANDLER status_set_handler; + STATUS_CHANGE_HANDLER status_reset_handler; + + /* note that different chips have these flags on different + ** bits of the status register + */ + void * status_change_which_chip; /* this chip id */ + UINT8 status_change_EOS_bit; /* 1 on End Of Sample (record/playback/cycle time of AD/DA converting has passed)*/ + UINT8 status_change_BRDY_bit; /* 1 after recording 2 datas (2x4bits) or after reading/writing 1 data */ + UINT8 status_change_ZERO_bit; /* 1 if silence lasts for more than 290 miliseconds on ADPCM recording */ + + /* neither Y8950 nor YM2608 can generate IRQ when PCMBSY bit changes, so instead of above, + ** the statusflag gets ORed with PCM_BSY (below) (on each read of statusflag of Y8950 and YM2608) + */ + UINT8 PCM_BSY; /* 1 when ADPCM is playing; Y8950/YM2608 only */ + + UINT8 reg[16]; /* adpcm registers */ + UINT8 emulation_mode; /* which chip we're emulating */ +}YM_DELTAT; + +/*void YM_DELTAT_BRDY_callback(YM_DELTAT *DELTAT);*/ + +UINT8 YM_DELTAT_ADPCM_Read(YM_DELTAT *DELTAT); +void YM_DELTAT_ADPCM_Write(YM_DELTAT *DELTAT,int r,int v); +void YM_DELTAT_ADPCM_Reset(YM_DELTAT *DELTAT,int pan,int emulation_mode); +void YM_DELTAT_ADPCM_CALC(YM_DELTAT *DELTAT); + +/*void YM_DELTAT_postload(YM_DELTAT *DELTAT,UINT8 *regs); +void YM_DELTAT_savestate(const device_config *device,YM_DELTAT *DELTAT);*/ + +#ifdef __cplusplus +} +#endif + +#endif /* __YMDELTAT_H__ */ diff --git a/Frameworks/GME/gme/ymz280b.c b/Frameworks/GME/gme/ymz280b.c new file mode 100644 index 000000000..0fc4d5873 --- /dev/null +++ b/Frameworks/GME/gme/ymz280b.c @@ -0,0 +1,1303 @@ +/* + + Yamaha YMZ280B driver + by Aaron Giles + + YMZ280B 8-Channel PCMD8 PCM/ADPCM Decoder + + Features as listed in LSI-4MZ280B3 data sheet: + Voice data stored in external memory can be played back simultaneously for up to eight voices + Voice data format can be selected from 4-bit ADPCM, 8-bit PCM and 16-bit PCM + Control of voice data external memory + Up to 16M bytes of ROM or SRAM (x 8 bits, access time 150ms max) can be connected + Continuous access is possible + Loop playback between selective addresses is possible + Voice data playback frequency control + 4-bit ADPCM ................ 0.172 to 44.1kHz in 256 steps + 8-bit PCM, 16-bit PCM ...... 0.172 to 88.2kHz in 512 steps + 256 steps total level and 16 steps panpot can be set + Voice signal is output in stereo 16-bit 2's complement MSB-first format + +*/ + +#define _USE_MATH_DEFINES +#include + +#include "mamedef.h" +//#include "sndintrf.h" +//#include "streams.h" +#ifdef _DEBUG +#include +#endif +#include +#include +#include "ymz280b.h" + +static void update_irq_state_timer_common(void *param, int voicenum); + +//unsigned char DISABLE_YMZ_FIX = 0x00; + +#define MAX_SAMPLE_CHUNK 0x10000 +#define MAKE_WAVS 0 +#define MAKE_WAVS_CH 0 + +#define FRAC_BITS 14 +#define FRAC_ONE (1 << FRAC_BITS) +#define FRAC_MASK (FRAC_ONE - 1) + +#define INTERNAL_BUFFER_SIZE (1 << 15) +//#define INTERNAL_SAMPLE_RATE (chip->master_clock * 2.0) +#define INTERNAL_SAMPLE_RATE chip->rate + +#if MAKE_WAVS +#include "wavwrite.h" +#endif +#if MAKE_WAVS_CH +#include +FILE* hWavFile[8]; +signed short int* wavmem[8]; +#endif + + +/* struct describing a single playing ADPCM voice */ +struct YMZ280BVoice +{ + UINT8 playing; /* 1 if we are actively playing */ + + UINT8 keyon; /* 1 if the key is on */ + UINT8 looping; /* 1 if looping is enabled */ + UINT8 mode; /* current playback mode */ + UINT16 fnum; /* frequency */ + UINT8 level; /* output level */ + UINT8 pan; /* panning */ + + UINT32 start; /* start address, in nibbles */ + UINT32 stop; /* stop address, in nibbles */ + UINT32 loop_start; /* loop start address, in nibbles */ + UINT32 loop_end; /* loop end address, in nibbles */ + UINT32 position; /* current position, in nibbles */ + + INT32 signal; /* current ADPCM signal */ + INT32 step; /* current ADPCM step */ + + INT32 loop_signal; /* signal at loop start */ + INT32 loop_step; /* step at loop start */ + UINT32 loop_count; /* number of loops so far */ + + INT32 output_left; /* output volume (left) */ + INT32 output_right; /* output volume (right) */ + INT32 output_step; /* step value for frequency conversion */ + INT32 output_pos; /* current fractional position */ + INT16 last_sample; /* last sample output */ + INT16 curr_sample; /* current sample target */ + UINT8 irq_schedule; /* 1 if the IRQ state is updated by timer */ + UINT8 Muted; /* used for muting */ +}; + +typedef struct _ymz280b_state ymz280b_state; +struct _ymz280b_state +{ + //sound_stream * stream; /* which stream are we using */ + UINT8 *region_base; /* pointer to the base of the region */ + UINT32 region_size; + UINT8 current_register; /* currently accessible register */ + UINT8 status_register; /* current status register */ + UINT8 irq_state; /* current IRQ state */ + UINT8 irq_mask; /* current IRQ mask */ + UINT8 irq_enable; /* current IRQ enable */ + UINT8 keyon_enable; /* key on enable */ + double master_clock; /* master clock frequency */ + double rate; + //void (*irq_callback)(const device_config *, int); /* IRQ callback */ + void (*irq_callback)(int); /* IRQ callback */ + struct YMZ280BVoice voice[8]; /* the 8 voices */ + UINT32 rom_readback_addr; /* where the CPU can read the ROM */ + //devcb_resolved_read8 ext_ram_read; /* external RAM read handler */ + //devcb_resolved_write8 ext_ram_write; /* external RAM write handler */ + +#if MAKE_WAVS + void * wavresample; /* resampled waveform */ +#endif + + INT16 *scratch; + //const device_config *device; +}; + +static void write_to_register(ymz280b_state *chip, int data); + + +/* step size index shift table */ +static const int index_scale[8] = { 0x0e6, 0x0e6, 0x0e6, 0x0e6, 0x133, 0x199, 0x200, 0x266 }; + +/* lookup table for the precomputed difference */ +static int diff_lookup[16]; +static unsigned char lookup_init = 0x00; /* lookup-table is initialized */ + +/* timer callback */ +/*static TIMER_CALLBACK( update_irq_state_timer_0 ); +static TIMER_CALLBACK( update_irq_state_timer_1 ); +static TIMER_CALLBACK( update_irq_state_timer_2 ); +static TIMER_CALLBACK( update_irq_state_timer_3 ); +static TIMER_CALLBACK( update_irq_state_timer_4 ); +static TIMER_CALLBACK( update_irq_state_timer_5 ); +static TIMER_CALLBACK( update_irq_state_timer_6 ); +static TIMER_CALLBACK( update_irq_state_timer_7 ); + +static const timer_fired_func update_irq_state_cb[] = +{ + update_irq_state_timer_0, + update_irq_state_timer_1, + update_irq_state_timer_2, + update_irq_state_timer_3, + update_irq_state_timer_4, + update_irq_state_timer_5, + update_irq_state_timer_6, + update_irq_state_timer_7 +};*/ + + +/*INLINE ymz280b_state *get_safe_token(const device_config *device) +{ + assert(device != NULL); + assert(device->token != NULL); + assert(device->type == SOUND); + assert(sound_get_type(device) == SOUND_YMZ280B); + return (ymz280b_state *)device->token; +}*/ + +INLINE void update_irq_state(ymz280b_state *chip) +{ + int irq_bits = chip->status_register & chip->irq_mask; + + /* always off if the enable is off */ + if (!chip->irq_enable) + irq_bits = 0; + + /* update the state if changed */ + if (irq_bits && !chip->irq_state) + { + chip->irq_state = 1; + if (chip->irq_callback) + //(*chip->irq_callback)(chip->device, 1); + (*chip->irq_callback)(1); + //else logerror("YMZ280B: IRQ generated, but no callback specified!"); + } + else if (!irq_bits && chip->irq_state) + { + chip->irq_state = 0; + if (chip->irq_callback) + //(*chip->irq_callback)(chip->device, 0); + (*chip->irq_callback)(0); + //else logerror("YMZ280B: IRQ generated, but no callback specified!"); + } +} + + +INLINE void update_step(ymz280b_state *chip, struct YMZ280BVoice *voice) +{ + double frequency; + + /* compute the frequency */ + if (voice->mode == 1) + frequency = chip->master_clock * (double)((voice->fnum & 0x0ff) + 1) * (1.0 / 256.0); + else + frequency = chip->master_clock * (double)((voice->fnum & 0x1ff) + 1) * (1.0 / 256.0); + voice->output_step = (UINT32)(frequency * (double)FRAC_ONE / INTERNAL_SAMPLE_RATE); +} + + +INLINE void update_volumes(struct YMZ280BVoice *voice) +{ + if (voice->pan == 8) + { + voice->output_left = voice->level; + voice->output_right = voice->level; + } + else if (voice->pan < 8) + { + voice->output_left = voice->level; + + /* pan 1 is hard-left, what's pan 0? for now assume same as pan 1 */ + voice->output_right = (voice->pan == 0) ? 0 : voice->level * (voice->pan - 1) / 7; + } + else + { + voice->output_left = voice->level * (15 - voice->pan) / 7; + voice->output_right = voice->level; + } +} + + +INLINE UINT8 ymz280b_read_memory(UINT8 *base, UINT32 size, UINT32 offset) +{ + offset &= 0xFFFFFF; + if (offset < size) + return base[offset]; + + /* 16MB chip limit (shouldn't happen) */ + //else if (offset > 0xffffff) + // return base[offset & 0xffffff]; + + else + return 0; +} + + +static void update_irq_state_timer_common(void *param, int voicenum) +{ + ymz280b_state *chip = (ymz280b_state *)param; + struct YMZ280BVoice *voice = &chip->voice[voicenum]; + + if(!voice->irq_schedule) return; + + voice->playing = 0; + chip->status_register |= 1 << voicenum; + update_irq_state(chip); + voice->irq_schedule = 0; +} + +/*static TIMER_CALLBACK( update_irq_state_timer_0 ) { update_irq_state_timer_common(ptr, 0); } +static TIMER_CALLBACK( update_irq_state_timer_1 ) { update_irq_state_timer_common(ptr, 1); } +static TIMER_CALLBACK( update_irq_state_timer_2 ) { update_irq_state_timer_common(ptr, 2); } +static TIMER_CALLBACK( update_irq_state_timer_3 ) { update_irq_state_timer_common(ptr, 3); } +static TIMER_CALLBACK( update_irq_state_timer_4 ) { update_irq_state_timer_common(ptr, 4); } +static TIMER_CALLBACK( update_irq_state_timer_5 ) { update_irq_state_timer_common(ptr, 5); } +static TIMER_CALLBACK( update_irq_state_timer_6 ) { update_irq_state_timer_common(ptr, 6); } +static TIMER_CALLBACK( update_irq_state_timer_7 ) { update_irq_state_timer_common(ptr, 7); }*/ + + +/********************************************************************************************** + + compute_tables -- compute the difference tables + +***********************************************************************************************/ + +static void compute_tables(void) +{ + int nib; + + if (lookup_init) + return; + + /* loop over all nibbles and compute the difference */ + for (nib = 0; nib < 16; nib++) + { + int value = (nib & 0x07) * 2 + 1; + diff_lookup[nib] = (nib & 0x08) ? -value : value; + } + + lookup_init = 0x01; +} + + + +/********************************************************************************************** + + generate_adpcm -- general ADPCM decoding routine + +***********************************************************************************************/ + +static int generate_adpcm(struct YMZ280BVoice *voice, UINT8 *base, UINT32 size, INT16 *buffer, int samples) +{ + UINT32 position = voice->position; + int signal = voice->signal; + int step = voice->step; + int val; + + /*if (! DISABLE_YMZ_FIX) + { + if (position >= voice->stop) + { + voice->playing = 0; + return samples; + } + }*/ + + /* two cases: first cases is non-looping */ + if (!voice->looping) + { + /* loop while we still have samples to generate */ + while (samples) + { + /* compute the new amplitude and update the current step */ + //val = base[position / 2] >> ((~position & 1) << 2); + val = ymz280b_read_memory(base, size, position / 2) >> ((~position & 1) << 2); + signal += (step * diff_lookup[val & 15]) / 8; + + /* clamp to the maximum */ + if (signal > 32767) + signal = 32767; + else if (signal < -32768) + signal = -32768; + + /* adjust the step size and clamp */ + step = (step * index_scale[val & 7]) >> 8; + if (step > 0x6000) + step = 0x6000; + else if (step < 0x7f) + step = 0x7f; + + /* output to the buffer, scaling by the volume */ + *buffer++ = signal; + samples--; + + /* next! */ + position++; + if (position >= voice->stop) + { + if (!samples) + samples |= 0x10000; + + break; + } + } + } + + /* second case: looping */ + else + { + /* loop while we still have samples to generate */ + while (samples) + { + /* compute the new amplitude and update the current step */ + //val = base[position / 2] >> ((~position & 1) << 2); + val = ymz280b_read_memory(base, size, position / 2) >> ((~position & 1) << 2); + signal += (step * diff_lookup[val & 15]) / 8; + + /* clamp to the maximum */ + if (signal > 32767) + signal = 32767; + else if (signal < -32768) + signal = -32768; + + /* adjust the step size and clamp */ + step = (step * index_scale[val & 7]) >> 8; + if (step > 0x6000) + step = 0x6000; + else if (step < 0x7f) + step = 0x7f; + + /* output to the buffer, scaling by the volume */ + *buffer++ = signal; + samples--; + + /* next! */ + position++; + if (position == voice->loop_start && voice->loop_count == 0) + { + voice->loop_signal = signal; + voice->loop_step = step; + } + if (position >= voice->loop_end) + { + if (voice->keyon) + { + position = voice->loop_start; + signal = voice->loop_signal; + step = voice->loop_step; + voice->loop_count++; + } + } + if (position >= voice->stop) + { + if (!samples) + samples |= 0x10000; + + break; + } + } + } + + /* update the parameters */ + voice->position = position; + voice->signal = signal; + voice->step = step; + + return samples; +} + + + +/********************************************************************************************** + + generate_pcm8 -- general 8-bit PCM decoding routine + +***********************************************************************************************/ + +static int generate_pcm8(struct YMZ280BVoice *voice, UINT8 *base, UINT32 size, INT16 *buffer, int samples) +{ + int val; + UINT32 position = voice->position; + + /*if (! DISABLE_YMZ_FIX) + { + if (position >= voice->stop) + { + voice->playing = 0; + return samples; + } + }*/ + + /* two cases: first cases is non-looping */ + if (!voice->looping) + { + /* loop while we still have samples to generate */ + while (samples) + { + /* fetch the current value */ + //val = base[position / 2]; + val = ymz280b_read_memory(base, size, position / 2); + + /* output to the buffer, scaling by the volume */ + *buffer++ = (INT8)val * 256; + samples--; + + /* next! */ + position += 2; + if (position >= voice->stop) + { + if (!samples) + samples |= 0x10000; + + break; + } + } + } + + /* second case: looping */ + else + { + /* loop while we still have samples to generate */ + while (samples) + { + /* fetch the current value */ + //val = base[position / 2]; + val = ymz280b_read_memory(base, size, position / 2); + + /* output to the buffer, scaling by the volume */ + *buffer++ = (INT8)val * 256; + samples--; + + /* next! */ + position += 2; + if (position >= voice->loop_end) + { + if (voice->keyon) + position = voice->loop_start; + } + if (position >= voice->stop) + { + if (!samples) + samples |= 0x10000; + + break; + } + } + } + + /* update the parameters */ + voice->position = position; + + return samples; +} + + + +/********************************************************************************************** + + generate_pcm16 -- general 16-bit PCM decoding routine + +***********************************************************************************************/ + +static int generate_pcm16(struct YMZ280BVoice *voice, UINT8 *base, UINT32 size, INT16 *buffer, int samples) +{ + int val; + UINT32 position = voice->position; + + /*if (! DISABLE_YMZ_FIX) + { + if (position >= voice->stop) + { + voice->playing = 0; + return samples; + } + }*/ + + /* is it even used in any MAME game? */ + //popmessage("YMZ280B 16-bit PCM contact MAMEDEV"); + + /* two cases: first cases is non-looping */ + if (!voice->looping) + { + /* loop while we still have samples to generate */ + while (samples) + { + /* fetch the current value */ + //val = (INT16)((base[position / 2 + 1] << 8) + base[position / 2 + 0]); + val = (INT16)((ymz280b_read_memory(base, size, position / 2 + 0) << 8) + ymz280b_read_memory(base, size, position / 2 + 1)); + + /* output to the buffer, scaling by the volume */ + *buffer++ = val; + samples--; + + /* next! */ + position += 4; + if (position >= voice->stop) + { + if (!samples) + samples |= 0x10000; + + break; + } + } + } + + /* second case: looping */ + else + { + /* loop while we still have samples to generate */ + while (samples) + { + /* fetch the current value */ + //val = (INT16)((base[position / 2 + 1] << 8) + base[position / 2 + 0]); + val = (INT16)((ymz280b_read_memory(base, size, position / 2 + 0) << 8) + ymz280b_read_memory(base, size, position / 2 + 1)); + + /* output to the buffer, scaling by the volume */ + *buffer++ = val; + samples--; + + /* next! */ + position += 4; + if (position >= voice->loop_end) + { + if (voice->keyon) + position = voice->loop_start; + } + if (position >= voice->stop) + { + if (!samples) + samples |= 0x10000; + + break; + } + } + } + + /* update the parameters */ + voice->position = position; + + return samples; +} + + + +/********************************************************************************************** + + ymz280b_update -- update the sound chip so that it is in sync with CPU execution + +***********************************************************************************************/ + +//static STREAM_UPDATE( ymz280b_update ) +void ymz280b_update(void *_chip, stream_sample_t **outputs, int samples) +{ + //ymz280b_state *chip = (ymz280b_state *)param; + ymz280b_state *chip = (ymz280b_state *) _chip; + stream_sample_t *lacc = outputs[0]; + stream_sample_t *racc = outputs[1]; + int v; + + /* clear out the accumulator */ + memset(lacc, 0, samples * sizeof(lacc[0])); + memset(racc, 0, samples * sizeof(racc[0])); + + /* loop over voices */ + for (v = 0; v < 8; v++) + { + struct YMZ280BVoice *voice = &chip->voice[v]; + INT16 prev = voice->last_sample; + INT16 curr = voice->curr_sample; + INT16 *curr_data = chip->scratch; + INT32 *ldest = lacc; + INT32 *rdest = racc; + UINT32 new_samples, samples_left; + UINT32 final_pos; + int remaining = samples; + int lvol = voice->output_left; + int rvol = voice->output_right; +#if MAKE_WAVS_CH + signed short int* wavlog; + + memset(wavmem[v], 0x00, samples * 0x02); + wavlog = wavmem[v]; +#endif + + /* skip if muted */ + if (voice->Muted) + continue; + + /* quick out if we're not playing and we're at 0 */ + if (!voice->playing && curr == 0 && prev == 0) + { + /* make sure next sound plays immediately */ + voice->output_pos = FRAC_ONE; + + continue; + } + + /* finish off the current sample */ + /* interpolate */ + while (remaining > 0 && voice->output_pos < FRAC_ONE) + { + int interp_sample = (((INT32)prev * (FRAC_ONE - voice->output_pos)) + ((INT32)curr * voice->output_pos)) >> FRAC_BITS; + *ldest++ += interp_sample * lvol; + *rdest++ += interp_sample * rvol; +#if MAKE_WAVS_CH + *(wavlog ++) = (signed short int)interp_sample; +#endif + voice->output_pos += voice->output_step; + remaining--; + } + + /* if we're over, continue; otherwise, we're done */ + if (voice->output_pos >= FRAC_ONE) + voice->output_pos -= FRAC_ONE; + else + continue; + + /* compute how many new samples we need */ + final_pos = voice->output_pos + remaining * voice->output_step; + new_samples = (final_pos + FRAC_ONE) >> FRAC_BITS; + if (new_samples > MAX_SAMPLE_CHUNK) + new_samples = MAX_SAMPLE_CHUNK; + samples_left = new_samples; + + /* generate them into our buffer */ + switch (voice->playing << 7 | voice->mode) + { + case 0x81: samples_left = generate_adpcm(voice, chip->region_base, chip->region_size, chip->scratch, new_samples); break; + case 0x82: samples_left = generate_pcm8(voice, chip->region_base, chip->region_size, chip->scratch, new_samples); break; + case 0x83: samples_left = generate_pcm16(voice, chip->region_base, chip->region_size, chip->scratch, new_samples); break; + default: samples_left = 0; memset(chip->scratch, 0, new_samples * sizeof(chip->scratch[0])); break; + } + + /* if there are leftovers, ramp back to 0 */ + if (samples_left) + { + /* note: samples_left bit 16 is set if the voice was finished at the same time the function ended */ + int base; + UINT32 i; + int t; + + samples_left &= 0xffff; + base = new_samples - samples_left; + t = (base == 0) ? curr : chip->scratch[base - 1]; + + for (i = 0; i < samples_left; i++) + { + if (t < 0) t = -((-t * 15) >> 4); + else if (t > 0) t = (t * 15) >> 4; + chip->scratch[base + i] = t; + } + + /* if we hit the end and IRQs are enabled, signal it */ + if (base != 0) + { + voice->playing = 0; + + /* set update_irq_state_timer. IRQ is signaled on next CPU execution. */ + //timer_set(chip->device->machine, attotime_zero, chip, 0, update_irq_state_cb[v]); + voice->irq_schedule = 1; + } + } + + /* advance forward one sample */ + prev = curr; + curr = *curr_data++; + + /* then sample-rate convert with linear interpolation */ + while (remaining > 0) + { + /* interpolate */ + while (remaining > 0 && voice->output_pos < FRAC_ONE) + { + int interp_sample = (((INT32)prev * (FRAC_ONE - voice->output_pos)) + ((INT32)curr * voice->output_pos)) >> FRAC_BITS; + *ldest++ += interp_sample * lvol; + *rdest++ += interp_sample * rvol; +#if MAKE_WAVS_CH + *(wavlog ++) = (signed short int)interp_sample; +#endif + voice->output_pos += voice->output_step; + remaining--; + } + + /* if we're over, grab the next samples */ + if (voice->output_pos >= FRAC_ONE) + { + voice->output_pos -= FRAC_ONE; + prev = curr; + curr = *curr_data++; + } + } + + /* remember the last samples */ + voice->last_sample = prev; + voice->curr_sample = curr; + } + + for (v = 0; v < samples; v++) + { + outputs[0][v] /= 256; + outputs[1][v] /= 256; + } + + for (v = 0; v < 8; v++) + update_irq_state_timer_common(chip, v); + +#if MAKE_WAVS_CH + for (v = 0; v < 8; v++) + { + fwrite(wavmem[v], 0x02, samples, hWavFile[v]); + } +#endif +} + + + +/********************************************************************************************** + + DEVICE_START( ymz280b ) -- start emulation of the YMZ280B + +***********************************************************************************************/ + +//static DEVICE_START( ymz280b ) +void * device_start_ymz280b(int clock) +{ + static const ymz280b_interface defintrf = { 0 }; + //const ymz280b_interface *intf = (device->static_config != NULL) ? (const ymz280b_interface *)device->static_config : &defintrf; + const ymz280b_interface *intf = &defintrf; + //ymz280b_state *chip = get_safe_token(device); + ymz280b_state *chip; + int chn; + + chip = (ymz280b_state *) calloc(1, sizeof(ymz280b_state)); + //chip->device = device; + //devcb_resolve_read8(&chip->ext_ram_read, &intf->ext_read, device); + //devcb_resolve_write8(&chip->ext_ram_write, &intf->ext_write, device); + + /* compute ADPCM tables */ + compute_tables(); + + /* initialize the rest of the structure */ + chip->master_clock = (double)clock / 384.0; + + chip->rate = chip->master_clock * 2.0; + // disabled until the frequency calculation gets fixed + /*if ((CHIP_SAMPLING_MODE == 0x01 && chip->rate < CHIP_SAMPLE_RATE) || + CHIP_SAMPLING_MODE == 0x02) + chip->rate = (double)CHIP_SAMPLE_RATE;*/ + + //chip->region_base = device->region; + chip->region_size = 0x00; + chip->region_base = NULL; + chip->irq_callback = intf->irq_callback; + + /* create the stream */ + //chip->stream = stream_create(device, 0, 2, INTERNAL_SAMPLE_RATE, chip, ymz280b_update); + + /* allocate memory */ + //chip->scratch = auto_alloc_array(device->machine, INT16, MAX_SAMPLE_CHUNK); + chip->scratch = malloc(MAX_SAMPLE_CHUNK * sizeof(INT16)); + memset(chip->scratch, 0x00, MAX_SAMPLE_CHUNK * sizeof(INT16)); + + /* state save */ + /*{ + int j; + state_save_register_device_item(device, 0, chip->current_register); + state_save_register_device_item(device, 0, chip->status_register); + state_save_register_device_item(device, 0, chip->irq_state); + state_save_register_device_item(device, 0, chip->irq_mask); + state_save_register_device_item(device, 0, chip->irq_enable); + state_save_register_device_item(device, 0, chip->keyon_enable); + state_save_register_device_item(device, 0, chip->rom_readback_addr); + for (j = 0; j < 8; j++) + { + state_save_register_device_item(device, j, chip->voice[j].playing); + state_save_register_device_item(device, j, chip->voice[j].keyon); + state_save_register_device_item(device, j, chip->voice[j].looping); + state_save_register_device_item(device, j, chip->voice[j].mode); + state_save_register_device_item(device, j, chip->voice[j].fnum); + state_save_register_device_item(device, j, chip->voice[j].level); + state_save_register_device_item(device, j, chip->voice[j].pan); + state_save_register_device_item(device, j, chip->voice[j].start); + state_save_register_device_item(device, j, chip->voice[j].stop); + state_save_register_device_item(device, j, chip->voice[j].loop_start); + state_save_register_device_item(device, j, chip->voice[j].loop_end); + state_save_register_device_item(device, j, chip->voice[j].position); + state_save_register_device_item(device, j, chip->voice[j].signal); + state_save_register_device_item(device, j, chip->voice[j].step); + state_save_register_device_item(device, j, chip->voice[j].loop_signal); + state_save_register_device_item(device, j, chip->voice[j].loop_step); + state_save_register_device_item(device, j, chip->voice[j].loop_count); + state_save_register_device_item(device, j, chip->voice[j].output_left); + state_save_register_device_item(device, j, chip->voice[j].output_right); + state_save_register_device_item(device, j, chip->voice[j].output_pos); + state_save_register_device_item(device, j, chip->voice[j].last_sample); + state_save_register_device_item(device, j, chip->voice[j].curr_sample); + state_save_register_device_item(device, j, chip->voice[j].irq_schedule); + } + }*/ + + for (chn = 0; chn < 8; chn ++) + chip->voice[chn].Muted = 0x00; + +#if MAKE_WAVS + chip->wavresample = wav_open("resamp.wav", INTERNAL_SAMPLE_RATE, 2); +#endif +#if MAKE_WAVS_CH + hWavFile[0] = fopen("logwav0.raw", "wb"); + hWavFile[1] = fopen("logwav1.raw", "wb"); + hWavFile[2] = fopen("logwav2.raw", "wb"); + hWavFile[3] = fopen("logwav3.raw", "wb"); + hWavFile[4] = fopen("logwav4.raw", "wb"); + hWavFile[5] = fopen("logwav5.raw", "wb"); + hWavFile[6] = fopen("logwav6.raw", "wb"); + hWavFile[7] = fopen("logwav7.raw", "wb"); + { + char v; + for (v = 0; v < 8; v++) + { + wavmem[v] = (signed short int*)malloc(0x10 * 0x02); + } + } +#endif + + return chip; //return (int)INTERNAL_SAMPLE_RATE; +} + +//static DEVICE_STOP( ymz280b ) +void device_stop_ymz280b(void *_chip) +{ + //ymz280b_state *chip = get_safe_token(device); + ymz280b_state *chip = (ymz280b_state *) _chip; + free(chip->region_base); chip->region_base = NULL; + free(chip->scratch); + +#if MAKE_WAVS_CH + { + char v; + for (v = 0; v < 8; v++) + { + free(wavmem[v]); + fclose(hWavFile[v]); + } + } +#endif + + free(chip); +} + +//static DEVICE_RESET( ymz280b ) +void device_reset_ymz280b(void *_chip) +{ + ymz280b_state *chip = (ymz280b_state *) _chip; + /*struct YMZ280BVoice *voice; + unsigned char curvoc; + + chip->current_register = 0x00; + chip->status_register = 0x00; + chip->irq_state = 0x00; + chip->irq_mask = 0x00; + chip->irq_enable = 0x00; + chip->keyon_enable = 0x00; + chip->rom_readback_addr = 0x000000; + for (curvoc = 0; curvoc < 8; curvoc ++) + { + voice = &chip->voice[curvoc]; + + voice->playing = 0; + + voice->keyon = 0; + voice->looping = 0; + voice->mode = 0; + voice->fnum = 0; + voice->level = 0; + voice->pan = 8; + + voice->start = 0x000000; + voice->stop = 0x000000; + voice->loop_start = 0x000000; + voice->loop_end = 0x000000; + voice->position = 0x000000; + }*/ + + // new code from MAME 0.143u4 + int i; + + /* initial clear registers */ + for (i = 0xff; i >= 0; i--) + { + if (i == 0x83 || (i >= 88 && i <= 0xFD)) + continue; // avoid too many debug messages + chip->current_register = i; + write_to_register(chip, 0); + } + + chip->current_register = 0; + chip->status_register = 0; + + /* clear other voice parameters */ + for (i = 0; i < 8; i++) + { + struct YMZ280BVoice *voice = &chip->voice[i]; + + voice->curr_sample = 0; + voice->last_sample = 0; + voice->output_pos = FRAC_ONE; + voice->playing = 0; + } +} + + +/********************************************************************************************** + + write_to_register -- handle a write to the current register + +***********************************************************************************************/ + +static void write_to_register(ymz280b_state *chip, int data) +{ + struct YMZ280BVoice *voice; + int i; + //UINT8 mode_new; + + /* force an update */ + //stream_update(chip->stream); + + /* lower registers follow a pattern */ + if (chip->current_register < 0x80) + { + voice = &chip->voice[(chip->current_register >> 2) & 7]; + + switch (chip->current_register & 0xe3) + { + case 0x00: /* pitch low 8 bits */ + voice->fnum = (voice->fnum & 0x100) | (data & 0xff); + update_step(chip, voice); + break; + + case 0x01: /* pitch upper 1 bit, loop, key on, mode */ + voice->fnum = (voice->fnum & 0xff) | ((data & 0x01) << 8); + voice->looping = (data & 0x10) >> 4; + /*mode_new = (data & 0x60) >> 5; + if (! DISABLE_YMZ_FIX) + { + // that fixes the scratch-bug + if (voice->mode != mode_new) + { + // On-the-fly Mode-Change won't make sense, + // so I'm doing: KeyOff + Mode Change -> Instant Stop. + // (Deroon DeroDero uses this quite often) + // This is done by setting KeyOn to 0. + // Instant Stop/Restarting is done below. + voice->keyon = 0; + voice->irq_schedule = 0; + } + if (! mode_new) + data &= 0x7F; + } + voice->mode = mode_new;*/ + if ((data & 0x60) == 0) data &= 0x7f; /* ignore mode setting and set to same state as KON=0 */ + else voice->mode = (data & 0x60) >> 5; + + if (!voice->keyon && (data & 0x80) && chip->keyon_enable) + { + voice->playing = 1; + voice->position = voice->start; + voice->signal = voice->loop_signal = 0; + voice->step = voice->loop_step = 0x7f; + voice->loop_count = 0; + + /* if update_irq_state_timer is set, cancel it. */ + voice->irq_schedule = 0; + } + /*else if (voice->keyon && !(data & 0x80) && !voice->looping) + { + voice->playing = 0; + + // if update_irq_state_timer is set, cancel it. + voice->irq_schedule = 0; + } + else if (! DISABLE_YMZ_FIX && ! voice->keyon && !(data & 0x80) && voice->playing) + { + // 2x KeyOff -> Instant Stop, too (see Deroon DeroDero: Round Start-Tune) + voice->playing = 0; + voice->irq_schedule = 0; + }*/ + // new code from MAME 0.143u4 + else if (voice->keyon && !(data & 0x80)) + { + voice->playing = 0; + + // if update_irq_state_timer is set, cancel it. + voice->irq_schedule = 0; + } + voice->keyon = (data & 0x80) >> 7; + update_step(chip, voice); + break; + + case 0x02: /* total level */ + voice->level = data; + update_volumes(voice); + break; + + case 0x03: /* pan */ + voice->pan = data & 0x0f; + update_volumes(voice); + break; + + case 0x20: /* start address high */ + voice->start = (voice->start & (0x00ffff << 1)) | (data << 17); + break; + + case 0x21: /* loop start address high */ + voice->loop_start = (voice->loop_start & (0x00ffff << 1)) | (data << 17); + break; + + case 0x22: /* loop end address high */ + voice->loop_end = (voice->loop_end & (0x00ffff << 1)) | (data << 17); + break; + + case 0x23: /* stop address high */ + voice->stop = (voice->stop & (0x00ffff << 1)) | (data << 17); + break; + + case 0x40: /* start address middle */ + voice->start = (voice->start & (0xff00ff << 1)) | (data << 9); + break; + + case 0x41: /* loop start address middle */ + voice->loop_start = (voice->loop_start & (0xff00ff << 1)) | (data << 9); + break; + + case 0x42: /* loop end address middle */ + voice->loop_end = (voice->loop_end & (0xff00ff << 1)) | (data << 9); + break; + + case 0x43: /* stop address middle */ + voice->stop = (voice->stop & (0xff00ff << 1)) | (data << 9); + break; + + case 0x60: /* start address low */ + voice->start = (voice->start & (0xffff00 << 1)) | (data << 1); + break; + + case 0x61: /* loop start address low */ + voice->loop_start = (voice->loop_start & (0xffff00 << 1)) | (data << 1); + break; + + case 0x62: /* loop end address low */ + voice->loop_end = (voice->loop_end & (0xffff00 << 1)) | (data << 1); + break; + + case 0x63: /* stop address low */ + voice->stop = (voice->stop & (0xffff00 << 1)) | (data << 1); + break; + + default: +#ifdef _DEBUG + logerror("YMZ280B: unknown register write %02X = %02X\n", chip->current_register, data); +#endif + break; + } + } + + /* upper registers are special */ + else + { + switch (chip->current_register) + { + /* DSP related (not implemented yet) */ + case 0x80: // d0-2: DSP Rch, d3: enable Rch (0: yes, 1: no), d4-6: DSP Lch, d7: enable Lch (0: yes, 1: no) + case 0x81: // d0: enable control of $82 (0: yes, 1: no) + case 0x82: // DSP data + logerror("YMZ280B: DSP register write %02X = %02X\n", chip->current_register, data); + break; + + case 0x84: /* ROM readback / RAM write (high) */ + chip->rom_readback_addr &= 0xffff; + chip->rom_readback_addr |= (data<<16); + break; + + case 0x85: /* ROM readback / RAM write (med) */ + chip->rom_readback_addr &= 0xff00ff; + chip->rom_readback_addr |= (data<<8); + break; + + case 0x86: /* ROM readback / RAM write (low) */ + chip->rom_readback_addr &= 0xffff00; + chip->rom_readback_addr |= data; + break; + + case 0x87: /* RAM write */ + /*if (!chip->ext_ram_write.isnull()) + chip->ext_ram_write(chip->rom_readback_addr, data); + else + logerror("YMZ280B attempted RAM write to %X\n", chip->rom_readback_addr);*/ + chip->rom_readback_addr = (chip->rom_readback_addr + 1) & 0xffffff; + break; + + case 0xfe: /* IRQ mask */ + chip->irq_mask = data; + update_irq_state(chip); + break; + + case 0xff: /* IRQ enable, test, etc */ + chip->irq_enable = (data & 0x10) >> 4; + update_irq_state(chip); + + if (chip->keyon_enable && !(data & 0x80)) + { + for (i = 0; i < 8; i++) + { + chip->voice[i].playing = 0; + + /* if update_irq_state_timer is set, cancel it. */ + chip->voice[i].irq_schedule = 0; + } + } + else if (!chip->keyon_enable && (data & 0x80)) + { + for (i = 0; i < 8; i++) + { + if (chip->voice[i].keyon && chip->voice[i].looping) + chip->voice[i].playing = 1; + } + } + chip->keyon_enable = (data & 0x80) >> 7; + break; + + default: +#ifdef _DEBUG + logerror("YMZ280B: unknown register write %02X = %02X\n", chip->current_register, data); +#endif + break; + } + } +} + + + +/********************************************************************************************** + + compute_status -- determine the status bits + +***********************************************************************************************/ + +static int compute_status(ymz280b_state *chip) +{ + UINT8 result; + + /* ROM/RAM readback? */ + if (chip->current_register == 0x86) + { + //result = chip->region_base[chip->rom_readback_addr]; + result = ymz280b_read_memory(chip->region_base, chip->region_size, chip->rom_readback_addr); + chip->rom_readback_addr = (chip->rom_readback_addr + 1) & 0xffffff; + return result; + } + + /* force an update */ + //stream_update(chip->stream); + + result = chip->status_register; + + /* clear the IRQ state */ + chip->status_register = 0; + update_irq_state(chip); + + return result; +} + + + +/********************************************************************************************** + + ymz280b_r/ymz280b_w -- handle external accesses + +***********************************************************************************************/ + +//READ8_DEVICE_HANDLER( ymz280b_r ) +UINT8 ymz280b_r(void *_chip, offs_t offset) +{ + //ymz280b_state *chip = get_safe_token(device); + ymz280b_state *chip = (ymz280b_state *) _chip; + + if ((offset & 1) == 0) + { + /* read from external memory */ + UINT8 result; + /*if (chip->ext_ram_read.isnull()) + result = chip->ext_ram_read(chip->rom_readback_addr); + else + result = ymz280b_read_memory(chip->region_base, chip->region_size, chip->rom_readback_addr);*/ + result = 0x00; + + chip->rom_readback_addr = (chip->rom_readback_addr + 1) & 0xffffff; + return result; + } + else + { + return compute_status(chip); + } +} + + +//WRITE8_DEVICE_HANDLER( ymz280b_w ) +void ymz280b_w(void *_chip, offs_t offset, UINT8 data) +{ + //ymz280b_state *chip = get_safe_token(device); + ymz280b_state *chip = (ymz280b_state *) _chip; + + if ((offset & 1) == 0) + chip->current_register = data; + else + { + /* force an update */ + //chip->stream->update(); + + write_to_register(chip, data); + } +} + +void ymz280b_write_rom(void *_chip, offs_t ROMSize, offs_t DataStart, offs_t DataLength, + const UINT8* ROMData) +{ + ymz280b_state *chip = (ymz280b_state *) _chip; + + if (chip->region_size != ROMSize) + { + chip->region_base = (UINT8*)realloc(chip->region_base, ROMSize); + chip->region_size = ROMSize; + memset(chip->region_base, 0xFF, ROMSize); + } + if (DataStart > ROMSize) + return; + if (DataStart + DataLength > ROMSize) + DataLength = ROMSize - DataStart; + + memcpy(chip->region_base + DataStart, ROMData, DataLength); + + return; +} + + +void ymz280b_set_mute_mask(void *_chip, UINT32 MuteMask) +{ + ymz280b_state *chip = (ymz280b_state *) _chip; + UINT8 CurChn; + + for (CurChn = 0; CurChn < 8; CurChn ++) + chip->voice[CurChn].Muted = (MuteMask >> CurChn) & 0x01; + + return; +} diff --git a/Frameworks/GME/gme/ymz280b.h b/Frameworks/GME/gme/ymz280b.h new file mode 100644 index 000000000..d649dae2f --- /dev/null +++ b/Frameworks/GME/gme/ymz280b.h @@ -0,0 +1,47 @@ +/********************************************************************************************** + * + * Yamaha YMZ280B driver + * by Aaron Giles + * + **********************************************************************************************/ + +#pragma once + +#include "mamedef.h" + +//#include "devcb.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct _ymz280b_interface ymz280b_interface; +struct _ymz280b_interface +{ + //void (*irq_callback)(const device_config *device, int state); /* irq callback */ + void (*irq_callback)(int state); /* irq callback */ + //devcb_read8 ext_read; /* external RAM read */ + //devcb_write8 ext_write; /* external RAM write */ +}; + +/*READ8_DEVICE_HANDLER ( ymz280b_r ); +WRITE8_DEVICE_HANDLER( ymz280b_w ); + +DEVICE_GET_INFO( ymz280b ); +#define SOUND_YMZ280B DEVICE_GET_INFO_NAME( ymz280b )*/ + +void ymz280b_update(void *, stream_sample_t **outputs, int samples); +void * device_start_ymz280b(int clock); +void device_stop_ymz280b(void *); +void device_reset_ymz280b(void *); + +UINT8 ymz280b_r(void *, offs_t offset); +void ymz280b_w(void *, offs_t offset, UINT8 data); +void ymz280b_write_rom(void *, offs_t ROMSize, offs_t DataStart, offs_t DataLength, + const UINT8* ROMData); + +void ymz280b_set_mute_mask(void *, UINT32 MuteMask); + +#ifdef __cplusplus +} +#endif