Coverage Report

Created: 2026-09-01 07:42

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/vlc/contrib/contrib-build/game-music-emu/gme/Gym_Emu.cpp
Line
Count
Source
1
// Game_Music_Emu https://bitbucket.org/mpyne/game-music-emu/
2
3
#include "Gym_Emu.h"
4
5
#include "blargg_endian.h"
6
#include <string.h>
7
8
/* Copyright (C) 2003-2006 Shay Green. This module is free software; you
9
can redistribute it and/or modify it under the terms of the GNU Lesser
10
General Public License as published by the Free Software Foundation; either
11
version 2.1 of the License, or (at your option) any later version. This
12
module is distributed in the hope that it will be useful, but WITHOUT ANY
13
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
14
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
15
details. You should have received a copy of the GNU Lesser General Public
16
License along with this module; if not, write to the Free Software Foundation,
17
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
18
19
#include "blargg_source.h"
20
21
static double const min_tempo = 0.25;
22
static double const oversample_factor = 5 / 3.0;
23
static double const fm_gain = 3.0;
24
25
static const long base_clock = 53700300;
26
static const long clock_rate = base_clock / 15;
27
28
Gym_Emu::Gym_Emu()
29
90
{
30
90
  data = 0;
31
90
  pos  = 0;
32
90
  set_type( gme_gym_type );
33
34
90
  static const char* const names [] = {
35
90
    "FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6", "PCM", "PSG"
36
90
  };
37
90
  set_voice_names( names );
38
90
  set_silence_lookahead( 1 ); // tracks should already be trimmed
39
90
}
40
41
90
Gym_Emu::~Gym_Emu() { }
42
43
// Track info
44
45
static void get_gym_info( Gym_Emu::header_t const& h, long length, track_info_t* out )
46
84
{
47
84
  if ( !memcmp( h.tag, "GYMX", 4 ) )
48
84
  {
49
84
    length = length * 50 / 3; // 1000 / 60
50
84
    long loop = get_le32( h.loop_start );
51
84
    if ( loop )
52
15
    {
53
15
      out->intro_length = loop * 50 / 3;
54
15
      out->loop_length  = length - out->intro_length;
55
15
    }
56
69
    else
57
69
    {
58
69
      out->length = length;
59
69
      out->intro_length = length; // make it clear that track is no longer than length
60
69
      out->loop_length = 0;
61
69
    }
62
63
    // more stupidity where the field should have been left
64
84
    if ( strcmp( h.song, "Unknown Song" ) )
65
84
      GME_COPY_FIELD( h, out, song );
66
67
84
    if ( strcmp( h.game, "Unknown Game" ) )
68
84
      GME_COPY_FIELD( h, out, game );
69
70
84
    if ( strcmp( h.copyright, "Unknown Publisher" ) )
71
84
      GME_COPY_FIELD( h, out, copyright );
72
73
84
    if ( strcmp( h.dumper, "Unknown Person" ) )
74
84
      GME_COPY_FIELD( h, out, dumper );
75
76
84
    if ( strcmp( h.comment, "Header added by YMAMP" ) )
77
84
      GME_COPY_FIELD( h, out, comment );
78
84
  }
79
84
}
80
81
blargg_err_t Gym_Emu::track_info_( track_info_t* out, int ) const
82
84
{
83
84
  get_gym_info( header_, track_length(), out );
84
84
  return 0;
85
84
}
86
87
static long gym_track_length( byte const* p, byte const* end )
88
84
{
89
84
  long time = 0;
90
9.97k
  while ( p < end )
91
9.88k
  {
92
9.88k
    switch ( *p++ )
93
9.88k
    {
94
9.00k
      case 0:
95
9.00k
        time++;
96
9.00k
        break;
97
98
20
      case 1:
99
20
      case 2:
100
20
        p += 2;
101
20
        break;
102
103
0
      case 3:
104
0
        p += 1;
105
0
        break;
106
9.88k
    }
107
9.88k
  }
108
84
  return time;
109
84
}
110
111
84
long Gym_Emu::track_length() const { return gym_track_length( data, data_end ); }
112
113
static blargg_err_t check_header( byte const* in, long size, int* data_offset = 0 )
114
174
{
115
174
  if ( size < 4 )
116
0
    return gme_wrong_file_type;
117
118
174
  if ( memcmp( in, "GYMX", 4 ) == 0 )
119
174
  {
120
174
    if ( size < Gym_Emu::header_size + 1 )
121
5
      return gme_wrong_file_type;
122
123
169
    if ( memcmp( ((Gym_Emu::header_t const*) in)->packed, "\0\0\0\0", 4 ) != 0 )
124
1
      return "Packed GYM file not supported";
125
126
168
    if ( data_offset )
127
168
      *data_offset = Gym_Emu::header_size;
128
168
  }
129
0
  else if ( *in > 3 )
130
0
  {
131
0
    return gme_wrong_file_type;
132
0
  }
133
134
168
  return 0;
135
174
}
136
137
struct Gym_File : Gme_Info_
138
{
139
  byte const* file_begin;
140
  byte const* file_end;
141
  int data_offset;
142
143
0
  Gym_File() { set_type( gme_gym_type ); }
144
145
  blargg_err_t load_mem_( byte const* in, long size )
146
0
  {
147
0
    file_begin = in;
148
0
    file_end   = in + size;
149
0
    data_offset = 0;
150
0
    return check_header( in, size, &data_offset );
151
0
  }
152
153
  blargg_err_t track_info_( track_info_t* out, int ) const
154
0
  {
155
0
    long length = gym_track_length( &file_begin [data_offset], file_end );
156
0
    get_gym_info( *(Gym_Emu::header_t const*) file_begin, length, out );
157
0
    return 0;
158
0
  }
159
};
160
161
90
static Music_Emu* new_gym_emu () { return BLARGG_NEW Gym_Emu ; }
162
0
static Music_Emu* new_gym_file() { return BLARGG_NEW Gym_File; }
163
164
static gme_type_t_ const gme_gym_type_ = { "Sega Genesis", 1, &new_gym_emu, &new_gym_file, "GYM", 0 };
165
extern gme_type_t const gme_gym_type = &gme_gym_type_;
166
167
// Setup
168
169
blargg_err_t Gym_Emu::set_sample_rate_( long sample_rate )
170
90
{
171
90
  blip_eq_t eq( -32, 8000, sample_rate );
172
90
  apu.treble_eq( eq );
173
90
  dac_synth.treble_eq( eq );
174
90
  apu.volume( 0.135 * fm_gain * gain() );
175
90
  dac_synth.volume( 0.125 / 256 * fm_gain * gain() );
176
90
  double factor = Dual_Resampler::setup( oversample_factor, 0.990, fm_gain * gain() );
177
90
  fm_sample_rate = sample_rate * factor;
178
179
90
  RETURN_ERR( blip_buf.set_sample_rate( sample_rate, int (1000 / 60.0 / min_tempo) ) );
180
90
  blip_buf.clock_rate( clock_rate );
181
182
90
  RETURN_ERR( fm.set_rate( fm_sample_rate, base_clock / 7.0 ) );
183
90
  RETURN_ERR( Dual_Resampler::reset( long (1.0 / 60 / min_tempo * sample_rate) ) );
184
185
90
  return 0;
186
90
}
187
188
void Gym_Emu::set_tempo_( double t )
189
84
{
190
84
  if ( t < min_tempo )
191
0
  {
192
0
    set_tempo( min_tempo );
193
0
    return;
194
0
  }
195
196
84
  if ( blip_buf.sample_rate() )
197
84
  {
198
84
    clocks_per_frame = long (clock_rate / 60 / tempo());
199
84
    Dual_Resampler::resize( long (sample_rate() / (60.0 * tempo())) );
200
84
  }
201
84
}
202
203
void Gym_Emu::mute_voices_( int mask )
204
84
{
205
84
  Music_Emu::mute_voices_( mask );
206
84
  fm.mute_voices( mask );
207
84
  dac_muted = (mask & 0x40) != 0;
208
84
  apu.output( (mask & 0x80) ? 0 : &blip_buf );
209
84
}
210
211
blargg_err_t Gym_Emu::load_mem_( byte const* in, long size )
212
174
{
213
174
  blaarg_static_assert( offsetof (header_t,packed [4]) == header_size, "GYM Header layout incorrect!" );
214
174
  int offset = 0;
215
174
  RETURN_ERR( check_header( in, size, &offset ) );
216
168
  set_voice_count( 8 );
217
218
168
  data     = in + offset;
219
168
  data_end = in + size;
220
168
  loop_begin = 0;
221
222
168
  if ( offset )
223
168
    header_ = *(header_t const*) in;
224
0
  else
225
0
    memset( &header_, 0, sizeof header_ );
226
227
168
  return 0;
228
174
}
229
230
// Emulation
231
232
blargg_err_t Gym_Emu::start_track_( int track )
233
84
{
234
84
  RETURN_ERR( Music_Emu::start_track_( track ) );
235
236
84
  pos         = data;
237
84
  loop_remain = get_le32( header_.loop_start );
238
239
84
  prev_dac_count = 0;
240
84
  dac_enabled    = false;
241
84
  dac_amp        = -1;
242
243
84
  fm.reset();
244
84
  apu.reset();
245
84
  blip_buf.clear();
246
84
  Dual_Resampler::clear();
247
84
  return 0;
248
84
}
249
250
void Gym_Emu::run_dac( int dac_count )
251
0
{
252
  // Guess beginning and end of sample and adjust rate and buffer position accordingly.
253
254
  // count dac samples in next frame
255
0
  int next_dac_count = 0;
256
0
  const byte* p = this->pos;
257
0
  int cmd;
258
0
  while ( (cmd = *p++) != 0 )
259
0
  {
260
0
    int data = *p++;
261
0
    if ( cmd <= 2 )
262
0
      ++p;
263
0
    if ( cmd == 1 && data == 0x2A )
264
0
      next_dac_count++;
265
0
  }
266
267
  // detect beginning and end of sample
268
0
  int rate_count = dac_count;
269
0
  int start = 0;
270
0
  if ( !prev_dac_count && next_dac_count && dac_count < next_dac_count )
271
0
  {
272
0
    rate_count = next_dac_count;
273
0
    start = next_dac_count - dac_count;
274
0
  }
275
0
  else if ( prev_dac_count && !next_dac_count && dac_count < prev_dac_count )
276
0
  {
277
0
    rate_count = prev_dac_count;
278
0
  }
279
280
  // Evenly space samples within buffer section being used
281
0
  blip_resampled_time_t period = blip_buf.resampled_duration( clocks_per_frame ) / rate_count;
282
283
0
  blip_resampled_time_t time = blip_buf.resampled_time( 0 ) +
284
0
      period * start + (period >> 1);
285
286
0
  int dac_amp = this->dac_amp;
287
0
  if ( dac_amp < 0 )
288
0
    dac_amp = dac_buf [0];
289
290
0
  for ( int i = 0; i < dac_count; i++ )
291
0
  {
292
0
    int delta = dac_buf [i] - dac_amp;
293
0
    dac_amp += delta;
294
0
    dac_synth.offset_resampled( time, delta, &blip_buf );
295
0
    time += period;
296
0
  }
297
0
  this->dac_amp = dac_amp;
298
0
}
299
300
void Gym_Emu::parse_frame()
301
9.00k
{
302
9.00k
  int dac_count = 0;
303
9.00k
  const byte* pos = this->pos;
304
305
9.00k
  if ( loop_remain && !--loop_remain )
306
0
    loop_begin = pos; // find loop on first time through sequence
307
308
9.00k
  int cmd;
309
9.88k
  while ( (cmd = *pos++) != 0 )
310
886
  {
311
886
    int data = *pos++;
312
886
    if ( cmd == 1 )
313
20
    {
314
20
      int data2 = *pos++;
315
20
      if ( data != 0x2A )
316
20
      {
317
20
        if ( data == 0x2B )
318
0
          dac_enabled = (data2 & 0x80) != 0;
319
320
20
        fm.write0( data, data2 );
321
20
      }
322
0
      else if ( dac_count < (int) sizeof dac_buf )
323
0
      {
324
0
        dac_buf [dac_count] = data2;
325
0
        dac_count += dac_enabled;
326
0
      }
327
20
    }
328
866
    else if ( cmd == 2 )
329
0
    {
330
0
      fm.write1( data, *pos++ );
331
0
    }
332
866
    else if ( cmd == 3 )
333
0
    {
334
0
      apu.write_data( 0, data );
335
0
    }
336
866
    else
337
866
    {
338
      // to do: many GYM streams are full of errors, and error count should
339
      // reflect cases where music is really having problems
340
      //log_error();
341
866
      --pos; // put data back
342
866
    }
343
886
  }
344
345
  // loop
346
9.00k
  if ( pos >= data_end )
347
84
  {
348
84
    check( pos == data_end );
349
350
84
    if ( loop_begin )
351
0
      pos = loop_begin;
352
84
    else
353
84
      set_track_ended();
354
84
  }
355
9.00k
  this->pos = pos;
356
357
  // dac
358
9.00k
  if ( dac_count && !dac_muted )
359
0
    run_dac( dac_count );
360
9.00k
  prev_dac_count = dac_count;
361
9.00k
}
362
363
int Gym_Emu::play_frame( blip_time_t blip_time, int sample_count, sample_t* buf )
364
9.00k
{
365
9.00k
  if ( !track_ended() )
366
9.00k
    parse_frame();
367
368
9.00k
  apu.end_frame( blip_time );
369
370
9.00k
  memset( buf, 0, sample_count * sizeof *buf );
371
9.00k
  fm.run( sample_count >> 1, buf );
372
373
9.00k
  return sample_count;
374
9.00k
}
375
376
blargg_err_t Gym_Emu::play_( long count, sample_t* out )
377
6.85k
{
378
6.85k
  Dual_Resampler::dual_play( count, out, blip_buf );
379
6.85k
  return 0;
380
6.85k
}