cog/Frameworks/GME/gme/Ay_Emu.cpp

358 lines
9.6 KiB
C++

// Game_Music_Emu $vers. http://www.slack.net/~ant/
#include "Ay_Emu.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"
// TODO: probably don't need detailed errors as to why file is corrupt
int const spectrum_clock = 3546900; // 128K Spectrum
int const spectrum_period = 70908;
//int const spectrum_clock = 3500000; // 48K Spectrum
//int const spectrum_period = 69888;
int const cpc_clock = 2000000;
Ay_Emu::Ay_Emu()
{
core.set_cpc_callback( enable_cpc_, this );
set_type( gme_ay_type );
set_silence_lookahead( 6 );
}
Ay_Emu::~Ay_Emu() { }
// Track info
// Given pointer to 2-byte offset of data, returns pointer to data, or NULL if
// offset is 0 or there is less than min_size bytes of data available.
static byte const* get_data( Ay_Emu::file_t const& file, byte const ptr [], int min_size )
{
int offset = (BOOST::int16_t) get_be16( ptr );
int pos = ptr - (byte const*) file.header;
int size = file.end - (byte const*) file.header;
assert( (unsigned) pos <= (unsigned) size - 2 );
int limit = size - min_size;
if ( limit < 0 || !offset || (unsigned) (pos + offset) > (unsigned) limit )
return NULL;
return ptr + offset;
}
static blargg_err_t parse_header( byte const in [], int size, Ay_Emu::file_t* out )
{
typedef Ay_Emu::header_t header_t;
if ( size < header_t::size )
return blargg_err_file_type;
out->header = (header_t const*) in;
out->end = in + size;
header_t const& h = *(header_t const*) in;
if ( memcmp( h.tag, "ZXAYEMUL", 8 ) )
return blargg_err_file_type;
out->tracks = get_data( *out, h.track_info, (h.max_track + 1) * 4 );
if ( !out->tracks )
return BLARGG_ERR( BLARGG_ERR_FILE_CORRUPT, "missing track data" );
return blargg_ok;
}
static void copy_ay_fields( Ay_Emu::file_t const& file, track_info_t* out, int track )
{
Gme_File::copy_field_( out->song, (char const*) get_data( file, file.tracks + track * 4, 1 ) );
byte const* track_info = get_data( file, file.tracks + track * 4 + 2, 6 );
if ( track_info )
out->length = get_be16( track_info + 4 ) * (1000 / 50); // frames to msec
Gme_File::copy_field_( out->author, (char const*) get_data( file, file.header->author, 1 ) );
Gme_File::copy_field_( out->comment, (char const*) get_data( file, file.header->comment, 1 ) );
}
static void hash_ay_file( Ay_Emu::file_t const& file, Gme_Info_::Hash_Function& out )
{
out.hash_( &file.header->vers, sizeof(file.header->vers) );
out.hash_( &file.header->player, sizeof(file.header->player) );
out.hash_( &file.header->unused[0], sizeof(file.header->unused) );
out.hash_( &file.header->max_track, sizeof(file.header->max_track) );
out.hash_( &file.header->first_track, sizeof(file.header->first_track) );
for ( unsigned i = 0; i <= file.header->max_track; i++ )
{
byte const* track_info = get_data( file, file.tracks + i * 4 + 2, 14 );
if ( track_info )
{
out.hash_( track_info + 8, 2 );
byte const* points = get_data( file, track_info + 10, 6 );
if ( points ) out.hash_( points, 6 );
byte const* blocks = get_data( file, track_info + 12, 8 );
if ( blocks )
{
int addr = get_be16( blocks );
while ( addr )
{
out.hash_( blocks, 4 );
int len = get_be16( blocks + 2 );
byte const* block = get_data( file, blocks + 4, len );
if ( block ) out.hash_( block, len );
blocks += 6;
addr = get_be16( blocks );
}
}
}
}
}
blargg_err_t Ay_Emu::track_info_( track_info_t* out, int track ) const
{
copy_ay_fields( file, out, track );
return blargg_ok;
}
struct Ay_File : Gme_Info_
{
Ay_Emu::file_t file;
Ay_File() { set_type( gme_ay_type ); }
blargg_err_t load_mem_( byte const begin [], int size )
{
RETURN_ERR( parse_header( begin, size, &file ) );
set_track_count( file.header->max_track + 1 );
return blargg_ok;
}
blargg_err_t track_info_( track_info_t* out, int track ) const
{
copy_ay_fields( file, out, track );
return blargg_ok;
}
blargg_err_t hash_( Hash_Function& out ) const
{
hash_ay_file( file, out );
return blargg_ok;
}
};
static Music_Emu* new_ay_emu ()
{
return BLARGG_NEW Ay_Emu;
}
static Music_Emu* new_ay_file()
{
return BLARGG_NEW Ay_File;
}
gme_type_t_ const gme_ay_type [1] = {{
"ZX Spectrum",
0,
&new_ay_emu,
&new_ay_file,
"AY",
1
}};
// Setup
blargg_err_t Ay_Emu::load_mem_( byte const in [], int size )
{
assert( offsetof (header_t,track_info [2]) == header_t::size );
RETURN_ERR( parse_header( in, size, &file ) );
set_track_count( file.header->max_track + 1 );
if ( file.header->vers > 2 )
set_warning( "Unknown file version" );
int const osc_count = Ay_Apu::osc_count + 1; // +1 for beeper
set_voice_count( osc_count );
core.apu().volume( gain() );
static const char* const names [osc_count] = {
"Wave 1", "Wave 2", "Wave 3", "Beeper"
};
set_voice_names( names );
static int const types [osc_count] = {
wave_type+0, wave_type+1, wave_type+2, mixed_type+1
};
set_voice_types( types );
return setup_buffer( spectrum_clock );
}
void Ay_Emu::update_eq( blip_eq_t const& eq )
{
core.apu().treble_eq( eq );
}
void Ay_Emu::set_voice( int i, Blip_Buffer* center, Blip_Buffer*, Blip_Buffer* )
{
if ( i >= Ay_Apu::osc_count )
core.set_beeper_output( center );
else
core.apu().set_output( i, center );
}
void Ay_Emu::set_tempo_( double t )
{
int p = spectrum_period;
if ( clock_rate() != spectrum_clock )
p = clock_rate() / 50;
core.set_play_period( blip_time_t (p / t) );
}
blargg_err_t Ay_Emu::start_track_( int track )
{
RETURN_ERR( Classic_Emu::start_track_( track ) );
byte* const mem = core.mem();
memset( mem + 0x0000, 0xC9, 0x100 ); // fill RST vectors with RET
memset( mem + 0x0100, 0xFF, 0x4000 - 0x100 );
memset( mem + core.ram_addr, 0x00, core.mem_size - core.ram_addr );
// locate data blocks
byte const* const data = get_data( file, file.tracks + track * 4 + 2, 14 );
if ( !data )
return BLARGG_ERR( BLARGG_ERR_FILE_CORRUPT, "file data missing" );
byte const* const more_data = get_data( file, data + 10, 6 );
if ( !more_data )
return BLARGG_ERR( BLARGG_ERR_FILE_CORRUPT, "file data missing" );
byte const* blocks = get_data( file, data + 12, 8 );
if ( !blocks )
return BLARGG_ERR( BLARGG_ERR_FILE_CORRUPT, "file data missing" );
// initial addresses
unsigned addr = get_be16( blocks );
if ( !addr )
return BLARGG_ERR( BLARGG_ERR_FILE_CORRUPT, "file data missing" );
unsigned init = get_be16( more_data + 2 );
if ( !init )
init = addr;
// copy blocks into memory
do
{
blocks += 2;
unsigned len = get_be16( blocks ); blocks += 2;
if ( addr + len > core.mem_size )
{
set_warning( "Bad data block size" );
len = core.mem_size - addr;
}
check( len );
byte const* in = get_data( file, blocks, 0 ); blocks += 2;
if ( len > (unsigned) (file.end - in) )
{
set_warning( "File data missing" );
len = file.end - in;
}
//dprintf( "addr: $%04X, len: $%04X\n", addr, len );
if ( addr < core.ram_addr && addr >= 0x400 ) // several tracks use low data
dprintf( "Block addr in ROM\n" );
memcpy( mem + addr, in, len );
if ( file.end - blocks < 8 )
{
set_warning( "File data missing" );
break;
}
}
while ( (addr = get_be16( blocks )) != 0 );
// copy and configure driver
static byte const passive [] = {
0xF3, // DI
0xCD, 0, 0, // CALL init
0xED, 0x5E, // LOOP: IM 2
0xFB, // EI
0x76, // HALT
0x18, 0xFA // JR LOOP
};
static byte const active [] = {
0xF3, // DI
0xCD, 0, 0, // CALL init
0xED, 0x56, // LOOP: IM 1
0xFB, // EI
0x76, // HALT
0xCD, 0, 0, // CALL play
0x18, 0xF7 // JR LOOP
};
memcpy( mem, passive, sizeof passive );
int const play_addr = get_be16( more_data + 4 );
if ( play_addr )
{
memcpy( mem, active, sizeof active );
mem [ 9] = play_addr;
mem [10] = play_addr >> 8;
}
mem [2] = init;
mem [3] = init >> 8;
mem [0x38] = 0xFB; // Put EI at interrupt vector (followed by RET)
// start at spectrum speed
change_clock_rate( spectrum_clock );
set_tempo( tempo() );
Ay_Core::registers_t r = { };
r.sp = get_be16( more_data );
r.b.a = r.b.b = r.b.d = r.b.h = data [8];
r.b.flags = r.b.c = r.b.e = r.b.l = data [9];
r.alt.w = r.w;
r.ix = r.iy = r.w.hl;
core.start_track( r, play_addr );
return blargg_ok;
}
blargg_err_t Ay_Emu::run_clocks( blip_time_t& duration, int )
{
core.end_frame( &duration );
return blargg_ok;
}
inline void Ay_Emu::enable_cpc()
{
change_clock_rate( cpc_clock );
set_tempo( tempo() );
}
void Ay_Emu::enable_cpc_( void* data )
{
STATIC_CAST(Ay_Emu*,data)->enable_cpc();
}
blargg_err_t Ay_Emu::hash_( Hash_Function& out ) const
{
hash_ay_file( file, out );
return blargg_ok;
}