Coverage Report

Created: 2026-08-31 08:22

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/vlc/contrib/contrib-build/game-music-emu/gme/Sap_Emu.cpp
Line
Count
Source
1
// Game_Music_Emu https://bitbucket.org/mpyne/game-music-emu/
2
3
#include "Sap_Emu.h"
4
5
#include "blargg_endian.h"
6
#include <string.h>
7
#include <algorithm>
8
9
/* Copyright (C) 2006 Shay Green. This module is free software; you
10
can redistribute it and/or modify it under the terms of the GNU Lesser
11
General Public License as published by the Free Software Foundation; either
12
version 2.1 of the License, or (at your option) any later version. This
13
module is distributed in the hope that it will be useful, but WITHOUT ANY
14
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
15
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
16
details. You should have received a copy of the GNU Lesser General Public
17
License along with this module; if not, write to the Free Software Foundation,
18
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
19
20
#include "blargg_source.h"
21
22
static long const base_scanline_period = 114;
23
24
using std::min;
25
using std::max;
26
27
Sap_Emu::Sap_Emu()
28
0
{
29
0
  set_type( gme_sap_type );
30
31
0
  static const char* const names [Sap_Apu::osc_count * 2] = {
32
0
    "Wave 1", "Wave 2", "Wave 3", "Wave 4",
33
0
    "Wave 5", "Wave 6", "Wave 7", "Wave 8",
34
0
  };
35
0
  set_voice_names( names );
36
37
0
  static int const types [Sap_Apu::osc_count * 2] = {
38
0
    wave_type | 1, wave_type | 2, wave_type | 3, wave_type | 0,
39
0
    wave_type | 5, wave_type | 6, wave_type | 7, wave_type | 4,
40
0
  };
41
0
  set_voice_types( types );
42
0
  set_silence_lookahead( 6 );
43
0
}
44
45
0
Sap_Emu::~Sap_Emu() { }
46
47
// Track info
48
49
// Returns 16 or greater if not hex
50
inline int from_hex_char( int h )
51
0
{
52
0
  h -= 0x30;
53
0
  if ( (unsigned) h > 9 )
54
0
    h = ((h - 0x11) & 0xDF) + 10;
55
0
  return h;
56
0
}
57
58
static long from_hex( byte const* in )
59
0
{
60
0
  unsigned result = 0;
61
0
  for ( int n = 4; n--; )
62
0
  {
63
0
    int h = from_hex_char( *in++ );
64
0
    if ( h > 15 )
65
0
      return -1;
66
0
    result = result * 0x10 + h;
67
0
  }
68
0
  return result;
69
0
}
70
71
static int from_dec( byte const* in, byte const* end )
72
0
{
73
0
  if ( in >= end )
74
0
    return -1;
75
76
0
  int n = 0;
77
0
  while ( in < end )
78
0
  {
79
0
    int dig = *in++ - '0';
80
0
    if ( (unsigned) dig > 9 )
81
0
      return -1;
82
0
    n = n * 10 + dig;
83
0
  }
84
0
  return n;
85
0
}
86
87
static void parse_string( byte const* in, byte const* end, int len, char* out )
88
0
{
89
0
  byte const* start = in;
90
0
  if ( *in++ == '\"' )
91
0
  {
92
0
    start++;
93
0
    while ( in < end && *in != '\"' )
94
0
      in++;
95
0
  }
96
0
  else
97
0
  {
98
0
    in = end;
99
0
  }
100
0
  len = min( len - 1, int (in - start) );
101
0
  out [len] = 0;
102
0
  memcpy( out, start, len );
103
0
}
104
105
static blargg_err_t parse_info( byte const* in, long size, Sap_Emu::info_t* out )
106
0
{
107
0
  out->track_count   = 1;
108
0
  out->author    [0] = 0;
109
0
  out->name      [0] = 0;
110
0
  out->copyright [0] = 0;
111
112
0
  if ( size < 16 || memcmp( in, "SAP\x0D\x0A", 5 ) )
113
0
    return gme_wrong_file_type;
114
115
0
  byte const* file_end = in + size - 5;
116
0
  in += 5;
117
0
  while ( in < file_end && (in [0] != 0xFF || in [1] != 0xFF) )
118
0
  {
119
0
    byte const* line_end = in;
120
0
    while ( line_end < file_end && *line_end != 0x0D )
121
0
      line_end++;
122
123
0
    char const* tag = (char const*) in;
124
0
    while ( in < line_end && *in > ' ' )
125
0
      in++;
126
0
    int tag_len = (char const*) in - tag;
127
128
0
    while ( in < line_end && *in <= ' ' ) in++;
129
130
0
    if ( tag_len <= 0 )
131
0
    {
132
      // skip line
133
0
    }
134
0
    else if ( !strncmp( "INIT", tag, tag_len ) )
135
0
    {
136
0
      out->init_addr = from_hex( in );
137
0
      if ( (unsigned long) out->init_addr > 0xFFFF )
138
0
        return "Invalid init address";
139
0
    }
140
0
    else if ( !strncmp( "PLAYER", tag, tag_len ) )
141
0
    {
142
0
      out->play_addr = from_hex( in );
143
0
      if ( (unsigned long) out->play_addr > 0xFFFF )
144
0
        return "Invalid play address";
145
0
    }
146
0
    else if ( !strncmp( "MUSIC", tag, tag_len ) )
147
0
    {
148
0
      out->music_addr = from_hex( in );
149
0
      if ( (unsigned long) out->music_addr > 0xFFFF )
150
0
        return "Invalid music address";
151
0
    }
152
0
    else if ( !strncmp( "SONGS", tag, tag_len ) )
153
0
    {
154
0
      out->track_count = from_dec( in, line_end );
155
0
      if ( out->track_count <= 0 )
156
0
        return "Invalid track count";
157
0
    }
158
0
    else if ( !strncmp( "TYPE", tag, tag_len ) )
159
0
    {
160
0
      switch ( out->type = *in )
161
0
      {
162
0
      case 'C':
163
0
      case 'B':
164
0
        break;
165
166
0
      case 'D':
167
0
        return "Digimusic not supported";
168
169
0
      default:
170
0
        return "Unsupported player type";
171
0
      }
172
0
    }
173
0
    else if ( !strncmp( "STEREO", tag, tag_len ) )
174
0
    {
175
0
      out->stereo = true;
176
0
    }
177
0
    else if ( !strncmp( "FASTPLAY", tag, tag_len ) )
178
0
    {
179
0
      out->fastplay = from_dec( in, line_end );
180
0
      if ( out->fastplay <= 0 )
181
0
        return "Invalid fastplay value";
182
0
    }
183
0
    else if ( !strncmp( "AUTHOR", tag, tag_len ) )
184
0
    {
185
0
      parse_string( in, line_end, sizeof out->author, out->author );
186
0
    }
187
0
    else if ( !strncmp( "NAME", tag, tag_len ) )
188
0
    {
189
0
      parse_string( in, line_end, sizeof out->name, out->name );
190
0
    }
191
0
    else if ( !strncmp( "DATE", tag, tag_len ) )
192
0
    {
193
0
      parse_string( in, line_end, sizeof out->copyright, out->copyright );
194
0
    }
195
196
0
    in = line_end + 2;
197
0
  }
198
199
0
  if ( in [0] != 0xFF || in [1] != 0xFF )
200
0
    return "ROM data missing";
201
0
  out->rom_data = in + 2;
202
203
0
  return 0;
204
0
}
205
206
static void copy_sap_fields( Sap_Emu::info_t const& in, track_info_t* out )
207
0
{
208
0
  Gme_File::copy_field_( out->game,      in.name );
209
0
  Gme_File::copy_field_( out->author,    in.author );
210
0
  Gme_File::copy_field_( out->copyright, in.copyright );
211
0
}
212
213
blargg_err_t Sap_Emu::track_info_( track_info_t* out, int ) const
214
0
{
215
0
  copy_sap_fields( info, out );
216
0
  return 0;
217
0
}
218
219
struct Sap_File : Gme_Info_
220
{
221
  Sap_Emu::info_t info;
222
223
0
  Sap_File() { set_type( gme_sap_type ); }
224
225
  blargg_err_t load_mem_( byte const* begin, long size )
226
0
  {
227
0
    RETURN_ERR( parse_info( begin, size, &info ) );
228
0
    set_track_count( info.track_count );
229
0
    return 0;
230
0
  }
231
232
  blargg_err_t track_info_( track_info_t* out, int ) const
233
0
  {
234
0
    copy_sap_fields( info, out );
235
0
    return 0;
236
0
  }
237
};
238
239
0
static Music_Emu* new_sap_emu () { return BLARGG_NEW Sap_Emu ; }
240
0
static Music_Emu* new_sap_file() { return BLARGG_NEW Sap_File; }
241
242
static gme_type_t_ const gme_sap_type_ = { "Atari XL", 0, &new_sap_emu, &new_sap_file, "SAP", 1 };
243
extern gme_type_t const gme_sap_type = &gme_sap_type_;
244
245
// Setup
246
247
blargg_err_t Sap_Emu::load_mem_( byte const* in, long size )
248
0
{
249
0
  file_end = in + size;
250
251
0
  info.warning    = 0;
252
0
  info.type       = 'B';
253
0
  info.stereo     = false;
254
0
  info.init_addr  = -1;
255
0
  info.play_addr  = -1;
256
0
  info.music_addr = -1;
257
0
  info.fastplay   = 312;
258
0
  RETURN_ERR( parse_info( in, size, &info ) );
259
260
0
  set_warning( info.warning );
261
0
  set_track_count( info.track_count );
262
0
  set_voice_count( Sap_Apu::osc_count << static_cast<int>(info.stereo) );
263
0
  apu_impl.volume( gain() );
264
265
0
  return setup_buffer( 1773447 );
266
0
}
267
268
void Sap_Emu::update_eq( blip_eq_t const& eq )
269
0
{
270
0
  apu_impl.synth.treble_eq( eq );
271
0
}
272
273
void Sap_Emu::set_voice( int i, Blip_Buffer* center, Blip_Buffer* left, Blip_Buffer* right )
274
0
{
275
0
  int i2 = i - Sap_Apu::osc_count;
276
0
  if ( i2 >= 0 )
277
0
    apu2.osc_output( i2, right );
278
0
  else
279
0
    apu.osc_output( i, (info.stereo ? left : center) );
280
0
}
281
282
// Emulation
283
284
void Sap_Emu::set_tempo_( double t )
285
0
{
286
0
  scanline_period = sap_time_t (base_scanline_period / t);
287
0
}
288
289
0
inline sap_time_t Sap_Emu::play_period() const { return info.fastplay * scanline_period; }
290
291
void Sap_Emu::cpu_jsr( sap_addr_t addr )
292
0
{
293
0
  check( r.sp >= 0xFE ); // catch anything trying to leave data on stack
294
0
  r.pc = addr;
295
0
  int high_byte = (idle_addr - 1) >> 8;
296
0
  if ( r.sp == 0xFE && mem.ram [0x1FF] == high_byte )
297
0
    r.sp = 0xFF; // pop extra byte off
298
0
  mem.ram [0x100 + r.sp--] = high_byte; // some routines use RTI to return
299
0
  mem.ram [0x100 + r.sp--] = high_byte;
300
0
  mem.ram [0x100 + r.sp--] = (idle_addr - 1) & 0xFF;
301
0
}
302
303
void Sap_Emu::run_routine( sap_addr_t addr )
304
0
{
305
0
  cpu_jsr( addr );
306
0
  cpu::run( 312 * base_scanline_period * 60 );
307
0
  check( r.pc == idle_addr );
308
0
}
309
310
inline void Sap_Emu::call_init( int track )
311
0
{
312
0
  switch ( info.type )
313
0
  {
314
0
  case 'B':
315
0
    r.a = track;
316
0
    run_routine( info.init_addr );
317
0
    break;
318
319
0
  case 'C':
320
0
    r.a = 0x70;
321
0
    r.x = info.music_addr&0xFF;
322
0
    r.y = info.music_addr >> 8;
323
0
    run_routine( info.play_addr + 3 );
324
0
    r.a = 0;
325
0
    r.x = track;
326
0
    run_routine( info.play_addr + 3 );
327
0
    break;
328
0
  }
329
0
}
330
331
blargg_err_t Sap_Emu::start_track_( int track )
332
0
{
333
0
  RETURN_ERR( Classic_Emu::start_track_( track ) );
334
335
0
  memset( &mem, 0, sizeof mem );
336
337
0
  byte const* in = info.rom_data;
338
0
  while ( file_end - in >= 5 )
339
0
  {
340
0
    unsigned start = get_le16( in );
341
0
    unsigned end   = get_le16( in + 2 );
342
    //debug_printf( "Block $%04X-$%04X\n", start, end );
343
0
    in += 4;
344
0
    if ( end < start )
345
0
    {
346
0
      set_warning( "Invalid file data block" );
347
0
      break;
348
0
    }
349
0
    long len = end - start + 1;
350
0
    if ( len > file_end - in )
351
0
    {
352
0
      set_warning( "Invalid file data block" );
353
0
      break;
354
0
    }
355
356
0
    memcpy( mem.ram + start, in, len );
357
0
    in += len;
358
0
    if ( file_end - in >= 2 && in [0] == 0xFF && in [1] == 0xFF )
359
0
      in += 2;
360
0
  }
361
362
0
  apu.reset( &apu_impl );
363
0
  apu2.reset( &apu_impl );
364
0
  cpu::reset( mem.ram );
365
0
  time_mask = 0; // disables sound during init
366
0
  call_init( track );
367
0
  time_mask = -1;
368
369
0
  next_play = play_period();
370
371
0
  return 0;
372
0
}
373
374
// Emulation
375
376
// see sap_cpu_io.h for read/write functions
377
378
void Sap_Emu::cpu_write_( sap_addr_t addr, int data )
379
0
{
380
0
  if ( (addr ^ Sap_Apu::start_addr) <= (Sap_Apu::end_addr - Sap_Apu::start_addr) )
381
0
  {
382
0
    GME_APU_HOOK( this, addr - Sap_Apu::start_addr, data );
383
0
    apu.write_data( time() & time_mask, addr, data );
384
0
    return;
385
0
  }
386
387
0
  if ( (addr ^ (Sap_Apu::start_addr + 0x10)) <= (Sap_Apu::end_addr - Sap_Apu::start_addr) &&
388
0
      info.stereo )
389
0
  {
390
0
    GME_APU_HOOK( this, addr - 0x10 - Sap_Apu::start_addr + 10, data );
391
0
    apu2.write_data( time() & time_mask, addr ^ 0x10, data );
392
0
    return;
393
0
  }
394
395
0
  if ( (addr & ~0x0010) != 0xD20F || data != 0x03 )
396
0
    debug_printf( "Unmapped write $%04X <- $%02X\n", addr, data );
397
0
}
398
399
inline void Sap_Emu::call_play()
400
0
{
401
0
  switch ( info.type )
402
0
  {
403
0
  case 'B':
404
0
    cpu_jsr( info.play_addr );
405
0
    break;
406
407
0
  case 'C':
408
0
    cpu_jsr( info.play_addr + 6 );
409
0
    break;
410
0
  }
411
0
}
412
413
blargg_err_t Sap_Emu::run_clocks( blip_time_t& duration, int )
414
0
{
415
0
  set_time( 0 );
416
0
  while ( time() < duration )
417
0
  {
418
0
    if ( cpu::run( duration ) || r.pc > idle_addr )
419
0
      return "Emulation error (illegal instruction)";
420
421
0
    if ( r.pc == idle_addr )
422
0
    {
423
0
      if ( next_play <= duration )
424
0
      {
425
0
        set_time( next_play );
426
0
        next_play += play_period();
427
0
        call_play();
428
0
        GME_FRAME_HOOK( this );
429
0
      }
430
0
      else
431
0
      {
432
0
        set_time( duration );
433
0
      }
434
0
    }
435
0
  }
436
437
0
  duration = time();
438
0
  next_play -= duration;
439
0
  check( next_play >= 0 );
440
0
  if ( next_play < 0 )
441
0
    next_play = 0;
442
0
  apu.end_frame( duration );
443
0
  if ( info.stereo )
444
0
    apu2.end_frame( duration );
445
446
0
  return 0;
447
0
}