mirror of
https://github.com/DrBeef/Raze.git
synced 2024-12-15 23:21:21 +00:00
718112a8fe
Currently none of these is being used, but eventually they will, once more code gets ported over. So it's better to have them right away and avoid editing the project file too much, only to revert that later.
443 lines
9.9 KiB
C++
443 lines
9.9 KiB
C++
// Game_Music_Emu https://bitbucket.org/mpyne/game-music-emu/
|
|
|
|
#include "Sap_Emu.h"
|
|
|
|
#include "blargg_endian.h"
|
|
#include <string.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"
|
|
|
|
long const base_scanline_period = 114;
|
|
|
|
Sap_Emu::Sap_Emu()
|
|
{
|
|
set_type( gme_sap_type );
|
|
|
|
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 );
|
|
set_silence_lookahead( 6 );
|
|
}
|
|
|
|
Sap_Emu::~Sap_Emu() { }
|
|
|
|
// Track info
|
|
|
|
// Returns 16 or greater if not hex
|
|
inline int from_hex_char( int h )
|
|
{
|
|
h -= 0x30;
|
|
if ( (unsigned) h > 9 )
|
|
h = ((h - 0x11) & 0xDF) + 10;
|
|
return h;
|
|
}
|
|
|
|
static long from_hex( byte const* in )
|
|
{
|
|
unsigned 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 from_dec( byte const* in, byte const* end )
|
|
{
|
|
if ( in >= end )
|
|
return -1;
|
|
|
|
int n = 0;
|
|
while ( in < end )
|
|
{
|
|
int dig = *in++ - '0';
|
|
if ( (unsigned) dig > 9 )
|
|
return -1;
|
|
n = n * 10 + dig;
|
|
}
|
|
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 blargg_err_t parse_info( byte const* in, long size, Sap_Emu::info_t* out )
|
|
{
|
|
out->track_count = 1;
|
|
out->author [0] = 0;
|
|
out->name [0] = 0;
|
|
out->copyright [0] = 0;
|
|
|
|
if ( size < 16 || memcmp( in, "SAP\x0D\x0A", 5 ) )
|
|
return gme_wrong_file_type;
|
|
|
|
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( "INIT", tag, tag_len ) )
|
|
{
|
|
out->init_addr = from_hex( in );
|
|
if ( (unsigned long) out->init_addr > 0xFFFF )
|
|
return "Invalid init address";
|
|
}
|
|
else if ( !strncmp( "PLAYER", tag, tag_len ) )
|
|
{
|
|
out->play_addr = from_hex( in );
|
|
if ( (unsigned long) out->play_addr > 0xFFFF )
|
|
return "Invalid play address";
|
|
}
|
|
else if ( !strncmp( "MUSIC", tag, tag_len ) )
|
|
{
|
|
out->music_addr = from_hex( in );
|
|
if ( (unsigned long) out->music_addr > 0xFFFF )
|
|
return "Invalid music address";
|
|
}
|
|
else if ( !strncmp( "SONGS", tag, tag_len ) )
|
|
{
|
|
out->track_count = from_dec( in, line_end );
|
|
if ( out->track_count <= 0 )
|
|
return "Invalid track count";
|
|
}
|
|
else if ( !strncmp( "TYPE", tag, tag_len ) )
|
|
{
|
|
switch ( out->type = *in )
|
|
{
|
|
case 'C':
|
|
case 'B':
|
|
break;
|
|
|
|
case 'D':
|
|
return "Digimusic not supported";
|
|
|
|
default:
|
|
return "Unsupported 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 "Invalid 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 "ROM data missing";
|
|
out->rom_data = in + 2;
|
|
|
|
return 0;
|
|
}
|
|
|
|
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 );
|
|
}
|
|
|
|
blargg_err_t Sap_Emu::track_info_( track_info_t* out, int ) const
|
|
{
|
|
copy_sap_fields( info, out );
|
|
return 0;
|
|
}
|
|
|
|
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, long size )
|
|
{
|
|
RETURN_ERR( parse_info( begin, size, &info ) );
|
|
set_track_count( info.track_count );
|
|
return 0;
|
|
}
|
|
|
|
blargg_err_t track_info_( track_info_t* out, int ) const
|
|
{
|
|
copy_sap_fields( info, out );
|
|
return 0;
|
|
}
|
|
};
|
|
|
|
static Music_Emu* new_sap_emu () { return BLARGG_NEW Sap_Emu ; }
|
|
static Music_Emu* new_sap_file() { return BLARGG_NEW Sap_File; }
|
|
|
|
static gme_type_t_ const gme_sap_type_ = { "Atari XL", 0, &new_sap_emu, &new_sap_file, "SAP", 1 };
|
|
BLARGG_EXPORT extern gme_type_t const gme_sap_type = &gme_sap_type_;
|
|
|
|
// Setup
|
|
|
|
blargg_err_t Sap_Emu::load_mem_( byte const* in, long size )
|
|
{
|
|
file_end = in + size;
|
|
|
|
info.warning = 0;
|
|
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 );
|
|
apu_impl.volume( gain() );
|
|
|
|
return setup_buffer( 1773447 );
|
|
}
|
|
|
|
void Sap_Emu::update_eq( blip_eq_t const& eq )
|
|
{
|
|
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 )
|
|
apu2.osc_output( i2, right );
|
|
else
|
|
apu.osc_output( i, (info.stereo ? left : center) );
|
|
}
|
|
|
|
// Emulation
|
|
|
|
void Sap_Emu::set_tempo_( double t )
|
|
{
|
|
scanline_period = sap_time_t (base_scanline_period / t);
|
|
}
|
|
|
|
inline sap_time_t Sap_Emu::play_period() const { return info.fastplay * scanline_period; }
|
|
|
|
void Sap_Emu::cpu_jsr( sap_addr_t addr )
|
|
{
|
|
check( r.sp >= 0xFE ); // catch anything trying to leave data on stack
|
|
r.pc = addr;
|
|
int high_byte = (idle_addr - 1) >> 8;
|
|
if ( r.sp == 0xFE && mem.ram [0x1FF] == high_byte )
|
|
r.sp = 0xFF; // pop extra byte off
|
|
mem.ram [0x100 + r.sp--] = high_byte; // some routines use RTI to return
|
|
mem.ram [0x100 + r.sp--] = high_byte;
|
|
mem.ram [0x100 + r.sp--] = (idle_addr - 1) & 0xFF;
|
|
}
|
|
|
|
void Sap_Emu::run_routine( sap_addr_t addr )
|
|
{
|
|
cpu_jsr( addr );
|
|
cpu::run( 312 * base_scanline_period * 60 );
|
|
check( r.pc == idle_addr );
|
|
}
|
|
|
|
inline void Sap_Emu::call_init( int track )
|
|
{
|
|
switch ( info.type )
|
|
{
|
|
case 'B':
|
|
r.a = track;
|
|
run_routine( info.init_addr );
|
|
break;
|
|
|
|
case 'C':
|
|
r.a = 0x70;
|
|
r.x = info.music_addr&0xFF;
|
|
r.y = info.music_addr >> 8;
|
|
run_routine( info.play_addr + 3 );
|
|
r.a = 0;
|
|
r.x = track;
|
|
run_routine( info.play_addr + 3 );
|
|
break;
|
|
}
|
|
}
|
|
|
|
blargg_err_t Sap_Emu::start_track_( int track )
|
|
{
|
|
RETURN_ERR( Classic_Emu::start_track_( track ) );
|
|
|
|
memset( &mem, 0, sizeof mem );
|
|
|
|
byte const* in = info.rom_data;
|
|
while ( file_end - in >= 5 )
|
|
{
|
|
unsigned start = get_le16( in );
|
|
unsigned end = get_le16( in + 2 );
|
|
//debug_printf( "Block $%04X-$%04X\n", start, end );
|
|
in += 4;
|
|
if ( end < start )
|
|
{
|
|
set_warning( "Invalid file data block" );
|
|
break;
|
|
}
|
|
long len = end - start + 1;
|
|
if ( len > file_end - in )
|
|
{
|
|
set_warning( "Invalid file data block" );
|
|
break;
|
|
}
|
|
|
|
memcpy( mem.ram + start, in, len );
|
|
in += len;
|
|
if ( file_end - in >= 2 && in [0] == 0xFF && in [1] == 0xFF )
|
|
in += 2;
|
|
}
|
|
|
|
apu.reset( &apu_impl );
|
|
apu2.reset( &apu_impl );
|
|
cpu::reset( mem.ram );
|
|
time_mask = 0; // disables sound during init
|
|
call_init( track );
|
|
time_mask = -1;
|
|
|
|
next_play = play_period();
|
|
|
|
return 0;
|
|
}
|
|
|
|
// Emulation
|
|
|
|
// see sap_cpu_io.h for read/write functions
|
|
|
|
void Sap_Emu::cpu_write_( sap_addr_t addr, int data )
|
|
{
|
|
if ( (addr ^ Sap_Apu::start_addr) <= (Sap_Apu::end_addr - Sap_Apu::start_addr) )
|
|
{
|
|
GME_APU_HOOK( this, addr - Sap_Apu::start_addr, data );
|
|
apu.write_data( time() & time_mask, addr, data );
|
|
return;
|
|
}
|
|
|
|
if ( (addr ^ (Sap_Apu::start_addr + 0x10)) <= (Sap_Apu::end_addr - Sap_Apu::start_addr) &&
|
|
info.stereo )
|
|
{
|
|
GME_APU_HOOK( this, addr - 0x10 - Sap_Apu::start_addr + 10, data );
|
|
apu2.write_data( time() & time_mask, addr ^ 0x10, data );
|
|
return;
|
|
}
|
|
|
|
if ( (addr & ~0x0010) != 0xD20F || data != 0x03 )
|
|
debug_printf( "Unmapped write $%04X <- $%02X\n", addr, data );
|
|
}
|
|
|
|
inline void Sap_Emu::call_play()
|
|
{
|
|
switch ( info.type )
|
|
{
|
|
case 'B':
|
|
cpu_jsr( info.play_addr );
|
|
break;
|
|
|
|
case 'C':
|
|
cpu_jsr( info.play_addr + 6 );
|
|
break;
|
|
}
|
|
}
|
|
|
|
blargg_err_t Sap_Emu::run_clocks( blip_time_t& duration, int )
|
|
{
|
|
set_time( 0 );
|
|
while ( time() < duration )
|
|
{
|
|
if ( cpu::run( duration ) || r.pc > idle_addr )
|
|
return "Emulation error (illegal instruction)";
|
|
|
|
if ( r.pc == idle_addr )
|
|
{
|
|
if ( next_play <= duration )
|
|
{
|
|
set_time( next_play );
|
|
next_play += play_period();
|
|
call_play();
|
|
GME_FRAME_HOOK( this );
|
|
}
|
|
else
|
|
{
|
|
set_time( duration );
|
|
}
|
|
}
|
|
}
|
|
|
|
duration = time();
|
|
next_play -= duration;
|
|
check( next_play >= 0 );
|
|
if ( next_play < 0 )
|
|
next_play = 0;
|
|
apu.end_frame( duration );
|
|
if ( info.stereo )
|
|
apu2.end_frame( duration );
|
|
|
|
return 0;
|
|
}
|