430 lines
10 KiB
C++
430 lines
10 KiB
C++
// Game_Music_Emu $vers. http://www.slack.net/~ant/
|
|
|
|
#include "Sap_Emu.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"
|
|
|
|
Sap_Emu::Sap_Emu()
|
|
{
|
|
set_type( gme_sap_type );
|
|
set_silence_lookahead( 6 );
|
|
}
|
|
|
|
Sap_Emu::~Sap_Emu() { }
|
|
|
|
// Track info
|
|
|
|
// Returns 16 or greater if not hex. Handles uppercase and lowercase.
|
|
// Thoroughly tested and rejects ALL non-hex characters.
|
|
inline int from_hex_char( int h )
|
|
{
|
|
h -= 0x30;
|
|
if ( (unsigned) h > 9 )
|
|
h = ((h - 0x11) & 0xDF) + 10;
|
|
return h;
|
|
}
|
|
|
|
static int from_hex( byte const in [] )
|
|
{
|
|
int result = 0;
|
|
for ( int n = 4; n--; )
|
|
{
|
|
int h = from_hex_char( *in++ );
|
|
if ( h > 15 )
|
|
return -1;
|
|
result = result * 0x10 + h;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
static int parse_int( byte const* io [], byte const* end )
|
|
{
|
|
byte const* in = *io;
|
|
int n = 0;
|
|
while ( in < end )
|
|
{
|
|
int dig = *in - '0';
|
|
if ( (unsigned) dig > 9 )
|
|
break;
|
|
++in;
|
|
n = n * 10 + dig;
|
|
}
|
|
if ( in == *io )
|
|
n = -1; // no numeric characters
|
|
*io = in;
|
|
return n;
|
|
}
|
|
|
|
static int from_dec( byte const in [], byte const* end )
|
|
{
|
|
int n = parse_int( &in, end );
|
|
if ( in < end )
|
|
n = -1;
|
|
return n;
|
|
}
|
|
|
|
static void parse_string( byte const in [], byte const* end, int len, char out [] )
|
|
{
|
|
byte const* start = in;
|
|
if ( *in++ == '\"' )
|
|
{
|
|
start++;
|
|
while ( in < end && *in != '\"' )
|
|
in++;
|
|
}
|
|
else
|
|
{
|
|
in = end;
|
|
}
|
|
len = min( len - 1, int (in - start) );
|
|
out [len] = 0;
|
|
memcpy( out, start, len );
|
|
}
|
|
|
|
static int parse_time( byte const in [], byte const* end )
|
|
{
|
|
int minutes = parse_int( &in, end );
|
|
if ( minutes < 0 || *in != ':' )
|
|
return 0;
|
|
|
|
++in;
|
|
int seconds = parse_int( &in, end );
|
|
if ( seconds < 0 )
|
|
return 0;
|
|
|
|
int time = minutes * 60000 + seconds * 1000;
|
|
if ( *in == '.' )
|
|
{
|
|
byte const* start = ++in;
|
|
int msec = parse_int( &in, end );
|
|
if ( msec >= 0 )
|
|
{
|
|
// allow 1-3 digits
|
|
for ( int n = in - start; n < 3; n++ )
|
|
msec *= 10;
|
|
time += msec;
|
|
}
|
|
}
|
|
|
|
while ( in < end && *in <= ' ' )
|
|
++in;
|
|
|
|
if ( end - in >= 4 && !memcmp( in, "LOOP", 4 ) )
|
|
time = -time;
|
|
|
|
return time;
|
|
}
|
|
|
|
static blargg_err_t parse_info( byte const in [], int size, Sap_Emu::info_t* out )
|
|
{
|
|
out->track_count = 1;
|
|
out->author [0] = 0;
|
|
out->name [0] = 0;
|
|
out->copyright [0] = 0;
|
|
|
|
for ( int i = 0; i < Sap_Emu::max_tracks; i++ )
|
|
out->track_times [i] = 0;
|
|
|
|
if ( size < 16 || memcmp( in, "SAP\x0D\x0A", 5 ) )
|
|
return blargg_err_file_type;
|
|
|
|
int time_count = 0;
|
|
byte const* file_end = in + size - 5;
|
|
in += 5;
|
|
while ( in < file_end && (in [0] != 0xFF || in [1] != 0xFF) )
|
|
{
|
|
byte const* line_end = in;
|
|
while ( line_end < file_end && *line_end != 0x0D )
|
|
line_end++;
|
|
|
|
char const* tag = (char const*) in;
|
|
while ( in < line_end && *in > ' ' )
|
|
in++;
|
|
int tag_len = (char const*) in - tag;
|
|
|
|
while ( in < line_end && *in <= ' ' ) in++;
|
|
|
|
if ( tag_len <= 0 )
|
|
{
|
|
// skip line
|
|
}
|
|
else if ( !strncmp( "TIME", tag, tag_len ) && time_count < Sap_Emu::max_tracks )
|
|
{
|
|
out->track_times [time_count++] = parse_time( in, line_end );
|
|
}
|
|
else if ( !strncmp( "INIT", tag, tag_len ) )
|
|
{
|
|
out->init_addr = from_hex( in );
|
|
if ( (unsigned) out->init_addr >= 0x10000 )
|
|
return BLARGG_ERR( BLARGG_ERR_FILE_CORRUPT, "init address" );
|
|
}
|
|
else if ( !strncmp( "PLAYER", tag, tag_len ) )
|
|
{
|
|
out->play_addr = from_hex( in );
|
|
if ( (unsigned) out->play_addr >= 0x10000 )
|
|
return BLARGG_ERR( BLARGG_ERR_FILE_CORRUPT, "play address" );
|
|
}
|
|
else if ( !strncmp( "MUSIC", tag, tag_len ) )
|
|
{
|
|
out->music_addr = from_hex( in );
|
|
if ( (unsigned) out->music_addr >= 0x10000 )
|
|
return BLARGG_ERR( BLARGG_ERR_FILE_CORRUPT, "music address" );
|
|
}
|
|
else if ( !strncmp( "SONGS", tag, tag_len ) )
|
|
{
|
|
out->track_count = from_dec( in, line_end );
|
|
if ( out->track_count <= 0 )
|
|
return BLARGG_ERR( BLARGG_ERR_FILE_CORRUPT, "track count" );
|
|
}
|
|
else if ( !strncmp( "TYPE", tag, tag_len ) )
|
|
{
|
|
switch ( out->type = *in )
|
|
{
|
|
case 'S':
|
|
out->type = 'C';
|
|
case 'B':
|
|
case 'C':
|
|
case 'D':
|
|
break;
|
|
|
|
default:
|
|
return BLARGG_ERR( BLARGG_ERR_FILE_FEATURE, "player type" );
|
|
}
|
|
}
|
|
else if ( !strncmp( "STEREO", tag, tag_len ) )
|
|
{
|
|
out->stereo = true;
|
|
}
|
|
else if ( !strncmp( "FASTPLAY", tag, tag_len ) )
|
|
{
|
|
out->fastplay = from_dec( in, line_end );
|
|
if ( out->fastplay <= 0 )
|
|
return BLARGG_ERR( BLARGG_ERR_FILE_CORRUPT, "fastplay value" );
|
|
}
|
|
else if ( !strncmp( "AUTHOR", tag, tag_len ) )
|
|
{
|
|
parse_string( in, line_end, sizeof out->author, out->author );
|
|
}
|
|
else if ( !strncmp( "NAME", tag, tag_len ) )
|
|
{
|
|
parse_string( in, line_end, sizeof out->name, out->name );
|
|
}
|
|
else if ( !strncmp( "DATE", tag, tag_len ) )
|
|
{
|
|
parse_string( in, line_end, sizeof out->copyright, out->copyright );
|
|
}
|
|
|
|
in = line_end + 2;
|
|
}
|
|
|
|
if ( in [0] != 0xFF || in [1] != 0xFF )
|
|
return BLARGG_ERR( BLARGG_ERR_FILE_CORRUPT, "ROM data missing" );
|
|
out->rom_data = in + 2;
|
|
|
|
return blargg_ok;
|
|
}
|
|
|
|
static void copy_sap_fields( Sap_Emu::info_t const& in, track_info_t* out )
|
|
{
|
|
Gme_File::copy_field_( out->game, in.name );
|
|
Gme_File::copy_field_( out->author, in.author );
|
|
Gme_File::copy_field_( out->copyright, in.copyright );
|
|
}
|
|
|
|
static void hash_sap_file( Sap_Emu::info_t const& i, byte const* data, int data_size, Music_Emu::Hash_Function& out )
|
|
{
|
|
unsigned char temp[4];
|
|
set_le32( &temp[0], i.init_addr ); out.hash_( &temp[0], sizeof(temp) );
|
|
set_le32( &temp[0], i.play_addr ); out.hash_( &temp[0], sizeof(temp) );
|
|
set_le32( &temp[0], i.music_addr ); out.hash_( &temp[0], sizeof(temp) );
|
|
set_le32( &temp[0], i.type ); out.hash_( &temp[0], sizeof(temp) );
|
|
set_le32( &temp[0], i.fastplay ); out.hash_( &temp[0], sizeof(temp) );
|
|
set_le32( &temp[0], i.stereo ); out.hash_( &temp[0], sizeof(temp) );
|
|
set_le32( &temp[0], i.track_count ); out.hash_( &temp[0], sizeof(temp) );
|
|
out.hash_( data, data_size );
|
|
}
|
|
|
|
blargg_err_t Sap_Emu::track_info_( track_info_t* out, int track ) const
|
|
{
|
|
copy_sap_fields( info_, out );
|
|
|
|
if ( track < max_tracks )
|
|
{
|
|
int time = info_.track_times [track];
|
|
if ( time )
|
|
{
|
|
if ( time > 0 )
|
|
{
|
|
out->loop_length = 0;
|
|
}
|
|
else
|
|
{
|
|
time = -time;
|
|
out->loop_length = time;
|
|
}
|
|
out->length = time;
|
|
}
|
|
}
|
|
return blargg_ok;
|
|
}
|
|
|
|
struct Sap_File : Gme_Info_
|
|
{
|
|
Sap_Emu::info_t info;
|
|
|
|
Sap_File() { set_type( gme_sap_type ); }
|
|
|
|
blargg_err_t load_mem_( byte const begin [], int size )
|
|
{
|
|
RETURN_ERR( parse_info( begin, size, &info ) );
|
|
set_track_count( info.track_count );
|
|
return blargg_ok;
|
|
}
|
|
|
|
blargg_err_t track_info_( track_info_t* out, int track ) const
|
|
{
|
|
copy_sap_fields( info, out );
|
|
|
|
if (track < Sap_Emu::max_tracks)
|
|
{
|
|
int time = info.track_times[track];
|
|
if (time)
|
|
{
|
|
if (time > 0)
|
|
{
|
|
out->loop_length = 0;
|
|
}
|
|
else
|
|
{
|
|
time = -time;
|
|
out->loop_length = time;
|
|
}
|
|
out->length = time;
|
|
}
|
|
}
|
|
|
|
return blargg_ok;
|
|
}
|
|
|
|
blargg_err_t hash_( Hash_Function& out ) const
|
|
{
|
|
hash_sap_file( info, info.rom_data, file_end() - info.rom_data, out );
|
|
return blargg_ok;
|
|
}
|
|
};
|
|
|
|
static Music_Emu* new_sap_emu () { return BLARGG_NEW Sap_Emu ; }
|
|
static Music_Emu* new_sap_file() { return BLARGG_NEW Sap_File; }
|
|
|
|
gme_type_t_ const gme_sap_type [1] = {{ "Atari XL", 0, &new_sap_emu, &new_sap_file, "SAP", 1 }};
|
|
|
|
// Setup
|
|
|
|
blargg_err_t Sap_Emu::load_mem_( byte const in [], int size )
|
|
{
|
|
file_end = in + size;
|
|
|
|
info_.warning = NULL;
|
|
info_.type = 'B';
|
|
info_.stereo = false;
|
|
info_.init_addr = -1;
|
|
info_.play_addr = -1;
|
|
info_.music_addr = -1;
|
|
info_.fastplay = 312;
|
|
RETURN_ERR( parse_info( in, size, &info_ ) );
|
|
|
|
set_warning( info_.warning );
|
|
set_track_count( info_.track_count );
|
|
set_voice_count( Sap_Apu::osc_count << info_.stereo );
|
|
core.apu_impl().volume( gain() );
|
|
|
|
static const char* const names [Sap_Apu::osc_count * 2] = {
|
|
"Wave 1", "Wave 2", "Wave 3", "Wave 4",
|
|
"Wave 5", "Wave 6", "Wave 7", "Wave 8",
|
|
};
|
|
set_voice_names( names );
|
|
|
|
static int const types [Sap_Apu::osc_count * 2] = {
|
|
wave_type+1, wave_type+2, wave_type+3, wave_type+0,
|
|
wave_type+5, wave_type+6, wave_type+7, wave_type+4,
|
|
};
|
|
set_voice_types( types );
|
|
|
|
return setup_buffer( 1773447 );
|
|
}
|
|
|
|
void Sap_Emu::update_eq( blip_eq_t const& eq )
|
|
{
|
|
core.apu_impl().synth.treble_eq( eq );
|
|
}
|
|
|
|
void Sap_Emu::set_voice( int i, Blip_Buffer* center, Blip_Buffer* left, Blip_Buffer* right )
|
|
{
|
|
int i2 = i - Sap_Apu::osc_count;
|
|
if ( i2 >= 0 )
|
|
core.apu2().set_output( i2, right );
|
|
else
|
|
core.apu().set_output( i, (info_.stereo ? left : center) );
|
|
}
|
|
|
|
// Emulation
|
|
|
|
void Sap_Emu::set_tempo_( double t )
|
|
{
|
|
core.set_tempo( t );
|
|
}
|
|
|
|
blargg_err_t Sap_Emu::start_track_( int track )
|
|
{
|
|
RETURN_ERR( Classic_Emu::start_track_( track ) );
|
|
|
|
core.setup_ram();
|
|
|
|
// Copy file data to RAM
|
|
byte const* in = info_.rom_data;
|
|
while ( file_end - in >= 5 )
|
|
{
|
|
int start = get_le16( in );
|
|
int end = get_le16( in + 2 );
|
|
//dprintf( "Block $%04X-$%04X\n", start, end );
|
|
in += 4;
|
|
int len = end - start + 1;
|
|
if ( (unsigned) len > (unsigned) (file_end - in) )
|
|
{
|
|
set_warning( "Invalid file data block" );
|
|
break;
|
|
}
|
|
|
|
memcpy( core.ram() + start, in, len );
|
|
in += len;
|
|
if ( file_end - in >= 2 && in [0] == 0xFF && in [1] == 0xFF )
|
|
in += 2;
|
|
}
|
|
|
|
return core.start_track( track, info_ );
|
|
}
|
|
|
|
blargg_err_t Sap_Emu::run_clocks( blip_time_t& duration, int )
|
|
{
|
|
return core.end_frame( duration );
|
|
}
|
|
|
|
blargg_err_t Sap_Emu::hash_( Hash_Function& out ) const
|
|
{
|
|
hash_sap_file( info(), info().rom_data, file_end - info().rom_data, out );
|
|
return blargg_ok;
|
|
}
|