Coverage Report

Created: 2026-08-13 07:43

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/vlc/contrib/contrib-build/game-music-emu/gme/Ay_Emu.cpp
Line
Count
Source
1
// Game_Music_Emu https://bitbucket.org/mpyne/game-music-emu/
2
3
#include "Ay_Emu.h"
4
5
#include "blargg_endian.h"
6
#include <string.h>
7
8
#include <algorithm> // min, max
9
10
/* Copyright (C) 2006 Shay Green. This module is free software; you
11
can redistribute it and/or modify it under the terms of the GNU Lesser
12
General Public License as published by the Free Software Foundation; either
13
version 2.1 of the License, or (at your option) any later version. This
14
module is distributed in the hope that it will be useful, but WITHOUT ANY
15
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
16
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
17
details. You should have received a copy of the GNU Lesser General Public
18
License along with this module; if not, write to the Free Software Foundation,
19
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
20
21
#include "blargg_source.h"
22
23
static long const spectrum_clock = 3546900;
24
static long const cpc_clock      = 2000000;
25
26
static unsigned const ram_start = 0x4000;
27
static int const osc_count = Ay_Apu::osc_count + 1;
28
29
using std::min;
30
using std::max;
31
32
Ay_Emu::Ay_Emu()
33
14
{
34
14
  beeper_output = 0;
35
14
  set_type( gme_ay_type );
36
37
14
  static const char* const names [osc_count] = {
38
14
    "Wave 1", "Wave 2", "Wave 3", "Beeper"
39
14
  };
40
14
  set_voice_names( names );
41
42
14
  static int const types [osc_count] = {
43
14
    wave_type | 0, wave_type | 1, wave_type | 2, mixed_type | 0
44
14
  };
45
14
  set_voice_types( types );
46
14
  set_silence_lookahead( 6 );
47
14
}
48
49
14
Ay_Emu::~Ay_Emu() { }
50
51
// Track info
52
53
static byte const* get_data( Ay_Emu::file_t const& file, byte const* ptr, int min_size )
54
104
{
55
104
  long pos = ptr - (byte const*) file.header;
56
104
  long file_size = file.end - (byte const*) file.header;
57
104
  assert( (unsigned long) pos <= (unsigned long) file_size - 2 );
58
104
  int offset = (int16_t) get_be16( ptr );
59
104
  if ( !offset || uint32_t (pos + offset) > uint32_t (file_size - min_size) )
60
63
    return 0;
61
41
  return ptr + offset;
62
104
}
63
64
static blargg_err_t parse_header( byte const* in, long size, Ay_Emu::file_t* out )
65
14
{
66
14
  typedef Ay_Emu::header_t header_t;
67
14
  out->header = (header_t const*) in;
68
14
  out->end    = in + size;
69
70
14
  if ( size < Ay_Emu::header_size )
71
1
    return gme_wrong_file_type;
72
73
13
  header_t const& h = *(header_t const*) in;
74
13
  if ( memcmp( h.tag, "ZXAYEMUL", 8 ) )
75
1
    return gme_wrong_file_type;
76
77
12
  out->tracks = get_data( *out, h.track_info, (h.max_track + 1) * 4 );
78
12
  if ( !out->tracks )
79
8
    return "Missing track data";
80
81
4
  return 0;
82
12
}
83
84
static void copy_ay_fields( Ay_Emu::file_t const& file, track_info_t* out, int track )
85
18
{
86
18
  Gme_File::copy_field_( out->song, (char const*) get_data( file, file.tracks + track * 4, 1 ) );
87
18
  byte const* track_info = get_data( file, file.tracks + track * 4 + 2, 6 );
88
18
  if ( track_info )
89
2
    out->length = get_be16( track_info + 4 ) * (1000L / 50); // frames to msec
90
91
18
  Gme_File::copy_field_( out->author,  (char const*) get_data( file, file.header->author, 1 ) );
92
18
  Gme_File::copy_field_( out->comment, (char const*) get_data( file, file.header->comment, 1 ) );
93
18
}
94
95
blargg_err_t Ay_Emu::track_info_( track_info_t* out, int track ) const
96
18
{
97
18
  copy_ay_fields( file, out, track );
98
18
  return 0;
99
18
}
100
101
struct Ay_File : Gme_Info_
102
{
103
  Ay_Emu::file_t file;
104
105
0
  Ay_File() { set_type( gme_ay_type ); }
106
107
  blargg_err_t load_mem_( byte const* begin, long size )
108
0
  {
109
0
    RETURN_ERR( parse_header( begin, size, &file ) );
110
0
    set_track_count( file.header->max_track + 1 );
111
0
    return 0;
112
0
  }
113
114
  blargg_err_t track_info_( track_info_t* out, int track ) const
115
0
  {
116
0
    copy_ay_fields( file, out, track );
117
0
    return 0;
118
0
  }
119
};
120
121
14
static Music_Emu* new_ay_emu () { return BLARGG_NEW Ay_Emu ; }
122
0
static Music_Emu* new_ay_file() { return BLARGG_NEW Ay_File; }
123
124
static gme_type_t_ const gme_ay_type_ = { "ZX Spectrum", 0, &new_ay_emu, &new_ay_file, "AY", 1 };
125
extern gme_type_t const gme_ay_type = &gme_ay_type_;
126
127
// Setup
128
129
blargg_err_t Ay_Emu::load_mem_( byte const* in, long size )
130
14
{
131
14
  blaarg_static_assert( offsetof (header_t,track_info [2]) == header_size, "AY Header layout incorrect!" );
132
133
14
  RETURN_ERR( parse_header( in, size, &file ) );
134
4
  set_track_count( file.header->max_track + 1 );
135
136
4
  if ( file.header->vers > 2 )
137
3
    set_warning( "Unknown file version" );
138
139
4
  set_voice_count( osc_count );
140
4
  apu.volume( gain() );
141
142
4
  return setup_buffer( spectrum_clock );
143
14
}
144
145
void Ay_Emu::update_eq( blip_eq_t const& eq )
146
4
{
147
4
  apu.treble_eq( eq );
148
4
}
149
150
void Ay_Emu::set_voice( int i, Blip_Buffer* center, Blip_Buffer*, Blip_Buffer* )
151
16
{
152
16
  if ( i >= Ay_Apu::osc_count )
153
4
    beeper_output = center;
154
12
  else
155
12
    apu.osc_output( i, center );
156
16
}
157
158
// Emulation
159
160
void Ay_Emu::set_tempo_( double t )
161
4
{
162
4
  play_period = blip_time_t (clock_rate() / 50 / t);
163
4
}
164
165
blargg_err_t Ay_Emu::start_track_( int track )
166
18
{
167
18
  RETURN_ERR( Classic_Emu::start_track_( track ) );
168
169
18
  memset( mem.ram + 0x0000, 0xC9, 0x100 ); // fill RST vectors with RET
170
18
  memset( mem.ram + 0x0100, 0xFF, 0x4000 - 0x100 );
171
18
  memset( mem.ram + ram_start, 0x00, sizeof mem.ram - ram_start );
172
18
  memset( mem.padding1, 0xFF, sizeof mem.padding1 );
173
18
  memset( mem.ram + 0x10000, 0xFF, sizeof mem.ram - 0x10000 );
174
175
  // locate data blocks
176
18
  byte const* const data = get_data( file, file.tracks + track * 4 + 2, 14 );
177
18
  if ( !data ) return "File data missing";
178
179
2
  byte const* const more_data = get_data( file, data + 10, 6 );
180
2
  if ( !more_data ) return "File data missing";
181
182
0
  byte const* blocks = get_data( file, data + 12, 8 );
183
0
  if ( !blocks ) return "File data missing";
184
185
  // initial addresses
186
0
  cpu::reset( mem.ram );
187
0
  r.sp = get_be16( more_data );
188
0
  r.b.a = r.b.b = r.b.d = r.b.h = data [8];
189
0
  r.b.flags = r.b.c = r.b.e = r.b.l = data [9];
190
0
  r.alt.w = r.w;
191
0
  r.ix = r.iy = r.w.hl;
192
193
0
  unsigned addr = get_be16( blocks );
194
0
  if ( !addr ) return "File data missing";
195
196
0
  unsigned init = get_be16( more_data + 2 );
197
0
  if ( !init )
198
0
    init = addr;
199
200
  // copy blocks into memory
201
0
  do
202
0
  {
203
0
    blocks += 2;
204
0
    unsigned len = get_be16( blocks ); blocks += 2;
205
0
    if ( addr + len > 0x10000 )
206
0
    {
207
0
      set_warning( "Bad data block size" );
208
0
      len = 0x10000 - addr;
209
0
    }
210
0
    check( len );
211
0
    byte const* in = get_data( file, blocks, 0 ); blocks += 2;
212
0
    if ( len > uint32_t (file.end - in) )
213
0
    {
214
0
      set_warning( "Missing file data" );
215
0
      len = file.end - in;
216
0
    }
217
    //debug_printf( "addr: $%04X, len: $%04X\n", addr, len );
218
0
    if ( addr < ram_start && addr >= 0x400 ) // several tracks use low data
219
0
      debug_printf( "Block addr in ROM\n" );
220
0
    memcpy( mem.ram + addr, in, len );
221
222
0
    if ( file.end - blocks < 8 )
223
0
    {
224
0
      set_warning( "Missing file data" );
225
0
      break;
226
0
    }
227
0
  }
228
0
  while ( (addr = get_be16( blocks )) != 0 );
229
230
  // copy and configure driver
231
0
  static byte const passive [] = {
232
0
    0xF3,       // DI
233
0
    0xCD, 0, 0, // CALL init
234
0
    0xED, 0x5E, // LOOP: IM 2
235
0
    0xFB,       // EI
236
0
    0x76,       // HALT
237
0
    0x18, 0xFA  // JR LOOP
238
0
  };
239
0
  static byte const active [] = {
240
0
    0xF3,       // DI
241
0
    0xCD, 0, 0, // CALL init
242
0
    0xED, 0x56, // LOOP: IM 1
243
0
    0xFB,       // EI
244
0
    0x76,       // HALT
245
0
    0xCD, 0, 0, // CALL play
246
0
    0x18, 0xF7  // JR LOOP
247
0
  };
248
0
  memcpy( mem.ram, passive, sizeof passive );
249
0
  unsigned play_addr = get_be16( more_data + 4 );
250
  //debug_printf( "Play: $%04X\n", play_addr );
251
0
  if ( play_addr )
252
0
  {
253
0
    memcpy( mem.ram, active, sizeof active );
254
0
    mem.ram [ 9] = play_addr;
255
0
    mem.ram [10] = play_addr >> 8;
256
0
  }
257
0
  mem.ram [2] = init;
258
0
  mem.ram [3] = init >> 8;
259
260
0
  mem.ram [0x38] = 0xFB; // Put EI at interrupt vector (followed by RET)
261
262
0
  memcpy( mem.ram + 0x10000, mem.ram, 0x80 ); // some code wraps around (ugh)
263
264
0
  beeper_delta = int (apu.amp_range * 0.65);
265
0
  last_beeper = 0;
266
0
  apu.reset();
267
0
  next_play = play_period;
268
269
  // start at spectrum speed
270
0
  change_clock_rate( spectrum_clock );
271
0
  set_tempo( tempo() );
272
273
0
  spectrum_mode = false;
274
0
  cpc_mode      = false;
275
0
  cpc_latch     = 0;
276
277
0
  return 0;
278
0
}
279
280
// Emulation
281
282
void Ay_Emu::cpu_out_misc( cpu_time_t time, unsigned addr, int data )
283
0
{
284
0
  if ( !cpc_mode )
285
0
  {
286
0
    switch ( addr & 0xFEFF )
287
0
    {
288
0
    case 0xFEFD:
289
0
      spectrum_mode = true;
290
0
      apu_addr = data & 0x0F;
291
0
      return;
292
293
0
    case 0xBEFD:
294
0
      spectrum_mode = true;
295
0
      apu.write( time, apu_addr, data );
296
0
      return;
297
0
    }
298
0
  }
299
300
0
  if ( !spectrum_mode )
301
0
  {
302
0
    switch ( addr >> 8 )
303
0
    {
304
0
    case 0xF6:
305
0
      switch ( data & 0xC0 )
306
0
      {
307
0
      case 0xC0:
308
0
        apu_addr = cpc_latch & 0x0F;
309
0
        goto enable_cpc;
310
311
0
      case 0x80:
312
0
        apu.write( time, apu_addr, cpc_latch );
313
0
        goto enable_cpc;
314
0
      }
315
0
      break;
316
317
0
    case 0xF4:
318
0
      cpc_latch = data;
319
0
      goto enable_cpc;
320
0
    }
321
0
  }
322
323
0
  debug_printf( "Unmapped OUT: $%04X <- $%02X\n", addr, data );
324
0
  return;
325
326
0
enable_cpc:
327
0
  if ( !cpc_mode )
328
0
  {
329
0
    cpc_mode = true;
330
0
    change_clock_rate( cpc_clock );
331
0
    set_tempo( tempo() );
332
0
  }
333
0
}
334
335
void ay_cpu_out( Ay_Cpu* cpu, cpu_time_t time, unsigned addr, int data )
336
0
{
337
0
  Ay_Emu& emu = STATIC_CAST(Ay_Emu&,*cpu);
338
339
0
  if ( (addr & 0xFF) == 0xFE && !emu.cpc_mode )
340
0
  {
341
0
    int delta = emu.beeper_delta;
342
0
    data &= 0x10;
343
0
    if ( emu.last_beeper != data )
344
0
    {
345
0
      emu.last_beeper = data;
346
0
      emu.beeper_delta = -delta;
347
0
      emu.spectrum_mode = true;
348
0
      if ( emu.beeper_output )
349
0
        emu.apu.synth_.offset( time, delta, emu.beeper_output );
350
0
    }
351
0
  }
352
0
  else
353
0
  {
354
0
    emu.cpu_out_misc( time, addr, data );
355
0
  }
356
0
}
357
358
int ay_cpu_in( Ay_Cpu*, unsigned addr )
359
0
{
360
  // keyboard read and other things
361
0
  if ( (addr & 0xFF) == 0xFE )
362
0
    return 0xFF; // other values break some beeper tunes
363
364
0
  debug_printf( "Unmapped IN : $%04X\n", addr );
365
0
  return 0xFF;
366
0
}
367
368
blargg_err_t Ay_Emu::run_clocks( blip_time_t& duration, int )
369
0
{
370
0
  set_time( 0 );
371
0
  if ( !(spectrum_mode | cpc_mode) )
372
0
    duration /= 2; // until mode is set, leave room for halved clock rate
373
374
0
  while ( time() < duration )
375
0
  {
376
0
    cpu::run( min( duration, (blip_time_t) next_play ) );
377
378
0
    if ( time() >= next_play )
379
0
    {
380
0
      next_play += play_period;
381
382
0
      if ( r.iff1 )
383
0
      {
384
0
        if ( mem.ram [r.pc] == 0x76 )
385
0
          r.pc++;
386
387
0
        r.iff1 = r.iff2 = 0;
388
389
0
        mem.ram [--r.sp] = uint8_t (r.pc >> 8);
390
0
        mem.ram [--r.sp] = uint8_t (r.pc);
391
0
        r.pc = 0x38;
392
0
        cpu::adjust_time( 12 );
393
0
        if ( r.im == 2 )
394
0
        {
395
0
          cpu::adjust_time( 6 );
396
0
          unsigned addr = r.i * 0x100u + 0xFF;
397
0
          r.pc = mem.ram [(addr + 1) & 0xFFFF] * 0x100u + mem.ram [addr];
398
0
        }
399
0
      }
400
0
    }
401
0
  }
402
0
  duration = time();
403
0
  next_play -= duration;
404
0
  check( next_play >= 0 );
405
0
  adjust_time( -duration );
406
407
0
  apu.end_frame( duration );
408
409
0
  return 0;
410
0
}