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/Spc_Emu.cpp
Line
Count
Source
1
// Game_Music_Emu https://bitbucket.org/mpyne/game-music-emu/
2
3
#include "Spc_Emu.h"
4
5
#include "blargg_endian.h"
6
#include <stdlib.h>
7
#include <string.h>
8
#include <algorithm>
9
10
/* Copyright (C) 2004-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
using std::min;
24
using std::max;
25
26
// TODO: support Spc_Filter's bass
27
28
Spc_Emu::Spc_Emu()
29
169
{
30
169
  set_type( gme_spc_type );
31
32
169
  static const char* const names [Snes_Spc::voice_count] = {
33
169
    "DSP 1", "DSP 2", "DSP 3", "DSP 4", "DSP 5", "DSP 6", "DSP 7", "DSP 8"
34
169
  };
35
169
  set_voice_names( names );
36
37
169
  set_gain( 1.4 );
38
169
}
39
40
169
Spc_Emu::~Spc_Emu() { }
41
42
// Track info
43
44
static long const spc_size = Snes_Spc::spc_file_size;
45
static long const head_size = Spc_Emu::header_size;
46
47
314
byte const* Spc_Emu::trailer() const { return &file_data [min( file_size, spc_size )]; }
48
49
314
long Spc_Emu::trailer_size() const { return max( 0L, file_size - spc_size ); }
50
51
static void get_spc_xid6( byte const* begin, long size, track_info_t* out )
52
230
{
53
  // header
54
230
  byte const* end = begin + size;
55
230
  if ( size < 8 || memcmp( begin, "xid6", 4 ) )
56
230
  {
57
230
    check( false );
58
230
    return;
59
230
  }
60
0
  long info_size = get_le32( begin + 4 );
61
0
  byte const* in = begin + 8;
62
0
  if ( end - in > info_size )
63
0
  {
64
0
    debug_printf( "Extra data after SPC xid6 info\n" );
65
0
    end = in + info_size;
66
0
  }
67
68
0
  int year = 0;
69
0
  char copyright [256 + 5];
70
0
  int copyright_len = 0;
71
0
  int const year_len = 5;
72
73
0
  while ( end - in >= 4 )
74
0
  {
75
    // header
76
0
    int id   = in [0];
77
0
    int data = in [3] * 0x100 + in [2];
78
0
    int type = in [1];
79
0
    int len  = type ? data : 0;
80
0
    in += 4;
81
0
    if ( len > end - in )
82
0
    {
83
0
      check( false );
84
0
      break; // block goes past end of data
85
0
    }
86
87
    // handle specific block types
88
0
    char* field = 0;
89
0
    switch ( id )
90
0
    {
91
0
      case 0x01: field = out->song;    break;
92
0
      case 0x02: field = out->game;    break;
93
0
      case 0x03: field = out->author;  break;
94
0
      case 0x04: field = out->dumper;  break;
95
0
      case 0x07: field = out->comment; break;
96
0
      case 0x14: year = data;          break;
97
98
      //case 0x30: // intro length
99
      // Many SPCs have intro length set wrong for looped tracks, making it useless
100
      /*
101
      case 0x30:
102
        check( len == 4 );
103
        if ( len >= 4 )
104
        {
105
          out->intro_length = get_le32( in ) / 64;
106
          if ( out->length > 0 )
107
          {
108
            long loop = out->length - out->intro_length;
109
            if ( loop >= 2000 )
110
              out->loop_length = loop;
111
          }
112
        }
113
        break;
114
      */
115
116
0
      case 0x13:
117
0
        copyright_len = min( len, (int) sizeof copyright - year_len );
118
0
        memcpy( &copyright [year_len], in, copyright_len );
119
0
        break;
120
121
0
      default:
122
0
        if ( id < 0x01 || (id > 0x07 && id < 0x10) ||
123
0
            (id > 0x14 && id < 0x30) || id > 0x36 )
124
0
          debug_printf( "Unknown SPC xid6 block: %X\n", (int) id );
125
0
        break;
126
0
    }
127
0
    if ( field )
128
0
    {
129
0
      check( type == 1 );
130
0
      Gme_File::copy_field_( field, (char const*) in, len );
131
0
    }
132
133
    // skip to next block
134
0
    in += len;
135
136
    // blocks are supposed to be 4-byte aligned with zero-padding...
137
0
    byte const* unaligned = in;
138
0
    while ( (in - begin) & 3 && in < end )
139
0
    {
140
0
      if ( *in++ != 0 )
141
0
      {
142
        // ...but some files have no padding
143
0
        in = unaligned;
144
0
        debug_printf( "SPC info tag wasn't properly padded to align\n" );
145
0
        break;
146
0
      }
147
0
    }
148
0
  }
149
150
0
  char* p = &copyright [year_len];
151
0
  if ( year )
152
0
  {
153
0
    *--p = ' ';
154
0
    for ( int n = 4; n--; )
155
0
    {
156
0
      *--p = char (year % 10 + '0');
157
0
      year /= 10;
158
0
    }
159
0
    copyright_len += year_len;
160
0
  }
161
0
  if ( copyright_len )
162
0
    Gme_File::copy_field_( out->copyright, p, copyright_len );
163
164
0
  check( in == end );
165
0
}
166
167
static long decode_length( Spc_Emu::header_t const& h, byte const* tag, int size )
168
628
{
169
  // decode length (can be in text or binary format, sometimes ambiguous ugh)
170
628
  long len_secs = 0;
171
628
  for ( int i = 0; i < size; i++ )
172
628
  {
173
628
    unsigned n = tag [i] - '0';
174
628
    if ( n > 9 )
175
628
    {
176
      // ignore single-digit text lengths
177
      // (except if author field is present and begins at offset 1, ugh)
178
628
      if ( i == 1 && (h.author [0] || !h.author [1]) )
179
0
        len_secs = 0;
180
628
      break;
181
628
    }
182
0
    len_secs *= 10;
183
0
    len_secs += n;
184
0
  }
185
628
  return len_secs;
186
628
}
187
188
static void get_spc_info( Spc_Emu::header_t const& h, byte const* xid6, long xid6_size,
189
    track_info_t* out )
190
314
{
191
314
  long len_secs = decode_length( h, h.len_secs, 3 );
192
314
  if ( !len_secs || len_secs > 0x1FFF )
193
314
    len_secs = get_le16( h.len_secs );
194
314
  if ( len_secs < 0x1FFF )
195
306
    out->length = len_secs * 1000;
196
197
314
  out->fade_length = decode_length( h, h.fade_msec, 4 );
198
199
314
  int offset = (h.author [0] < ' ' || unsigned (h.author [0] - '0') <= 9);
200
314
  Gme_File::copy_field_( out->author, &h.author [offset], sizeof h.author - offset );
201
202
314
  GME_COPY_FIELD( h, out, song );
203
314
  GME_COPY_FIELD( h, out, game );
204
314
  GME_COPY_FIELD( h, out, dumper );
205
314
  GME_COPY_FIELD( h, out, comment );
206
207
314
  if ( xid6_size )
208
230
    get_spc_xid6( xid6, xid6_size, out );
209
314
}
210
211
blargg_err_t Spc_Emu::track_info_( track_info_t* out, int ) const
212
314
{
213
314
  get_spc_info( header(), trailer(), trailer_size(), out );
214
314
  return 0;
215
314
}
216
217
static blargg_err_t check_spc_header( void const* header )
218
315
{
219
315
  if ( memcmp( header, "SNES-SPC700 Sound File Data", 27 ) )
220
1
    return gme_wrong_file_type;
221
314
  return 0;
222
315
}
223
224
struct Spc_File : Gme_Info_
225
{
226
  Spc_Emu::header_t header;
227
  blargg_vector<byte> xid6;
228
229
0
  Spc_File() { set_type( gme_spc_type ); }
230
231
  blargg_err_t load_( Data_Reader& in )
232
0
  {
233
0
    long file_size = in.remain();
234
0
    if ( file_size < Snes_Spc::spc_min_file_size )
235
0
      return gme_wrong_file_type;
236
0
    RETURN_ERR( in.read( &header, head_size ) );
237
0
    RETURN_ERR( check_spc_header( header.tag ) );
238
0
    long xid6_size = file_size - spc_size;
239
0
    if ( xid6_size > 0 )
240
0
    {
241
0
      RETURN_ERR( xid6.resize( xid6_size ) );
242
0
      RETURN_ERR( in.skip( spc_size - head_size ) );
243
0
      RETURN_ERR( in.read( xid6.begin(), xid6.size() ) );
244
0
    }
245
0
    return 0;
246
0
  }
247
248
  blargg_err_t track_info_( track_info_t* out, int ) const
249
0
  {
250
0
    get_spc_info( header, xid6.begin(), xid6.size(), out );
251
0
    return 0;
252
0
  }
253
};
254
255
169
static Music_Emu* new_spc_emu () { return BLARGG_NEW Spc_Emu ; }
256
0
static Music_Emu* new_spc_file() { return BLARGG_NEW Spc_File; }
257
258
static gme_type_t_ const gme_spc_type_ = { "Super Nintendo", 1, &new_spc_emu, &new_spc_file, "SPC", 0 };
259
extern gme_type_t const gme_spc_type = &gme_spc_type_;
260
261
262
// Setup
263
264
blargg_err_t Spc_Emu::set_sample_rate_( long sample_rate )
265
169
{
266
169
  RETURN_ERR( apu.init() );
267
169
  enable_accuracy( false );
268
169
  if ( sample_rate != native_sample_rate )
269
169
  {
270
169
    RETURN_ERR( resampler.buffer_size( native_sample_rate / 20 * 2 ) );
271
169
    resampler.time_ratio( (double) native_sample_rate / sample_rate, 0.9965 );
272
169
  }
273
169
  return 0;
274
169
}
275
276
void Spc_Emu::enable_accuracy_( bool b )
277
169
{
278
169
  Music_Emu::enable_accuracy_( b );
279
169
  filter.enable( b );
280
169
}
281
282
void Spc_Emu::mute_voices_( int m )
283
157
{
284
157
  Music_Emu::mute_voices_( m );
285
157
  apu.mute_voices( m );
286
157
}
287
288
void Spc_Emu::disable_echo_( bool disable )
289
0
{
290
0
  apu.disable_echo( disable );
291
0
}
292
293
blargg_err_t Spc_Emu::load_mem_( byte const* in, long size )
294
326
{
295
326
  blaarg_static_assert( offsetof (header_t,unused2 [46]) == header_size, "SPC Header layout incorrect!" );
296
326
  file_data = in;
297
326
  file_size = size;
298
326
  set_voice_count( Snes_Spc::voice_count );
299
326
  if ( size < Snes_Spc::spc_min_file_size )
300
11
    return gme_wrong_file_type;
301
315
  return check_spc_header( in );
302
326
}
303
304
// Emulation
305
306
void Spc_Emu::set_tempo_( double t )
307
157
{
308
157
  apu.set_tempo( (int) (t * apu.tempo_unit) );
309
157
}
310
311
blargg_err_t Spc_Emu::start_track_( int track )
312
157
{
313
157
  RETURN_ERR( Music_Emu::start_track_( track ) );
314
157
  resampler.clear();
315
157
  filter.clear();
316
157
  RETURN_ERR( apu.load_spc( file_data, file_size ) );
317
157
  filter.set_gain( (int) (gain() * SPC_Filter::gain_unit) );
318
157
  apu.clear_echo();
319
157
  track_info_t spc_info;
320
157
  RETURN_ERR( track_info_( &spc_info, track ) );
321
322
  // Set a default track length, need a non-zero fadeout
323
157
  if ( autoload_playback_limit() && ( spc_info.length > 0 ) )
324
0
    set_fade ( spc_info.length, 50 );
325
157
  return 0;
326
157
}
327
328
blargg_err_t Spc_Emu::play_and_filter( long count, sample_t out [] )
329
17.3k
{
330
17.3k
  RETURN_ERR( apu.play( count, out ) );
331
17.2k
  filter.run( out, count );
332
17.2k
  return 0;
333
17.3k
}
334
335
blargg_err_t Spc_Emu::skip_( long count )
336
0
{
337
0
  if ( sample_rate() != native_sample_rate )
338
0
  {
339
0
    count = long (count * resampler.ratio()) & ~1;
340
0
    count -= resampler.skip_input( count );
341
0
  }
342
343
  // TODO: shouldn't skip be adjusted for the 64 samples read afterwards?
344
345
0
  if ( count > 0 )
346
0
  {
347
0
    RETURN_ERR( apu.skip( count ) );
348
0
    filter.clear();
349
0
  }
350
351
  // eliminate pop due to resampler
352
0
  const int resampler_latency = 64;
353
0
  sample_t buf [resampler_latency];
354
0
  return play_( resampler_latency, buf );
355
0
}
356
357
blargg_err_t Spc_Emu::play_( long count, sample_t* out )
358
39.9k
{
359
39.9k
  if ( sample_rate() == native_sample_rate )
360
0
    return play_and_filter( count, out );
361
362
39.9k
  long remain = count;
363
97.0k
  while ( remain > 0 )
364
57.1k
  {
365
57.1k
    remain -= resampler.read( &out [count - remain], remain );
366
57.1k
    if ( remain > 0 )
367
17.3k
    {
368
17.3k
      long n = resampler.max_write();
369
17.3k
      RETURN_ERR( play_and_filter( n, resampler.buffer() ) );
370
17.2k
      resampler.write( n );
371
17.2k
    }
372
57.1k
  }
373
39.8k
  check( remain == 0 );
374
39.8k
  return 0;
375
39.9k
}