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/Sap_Apu.cpp
Line
Count
Source
1
// Game_Music_Emu https://bitbucket.org/mpyne/game-music-emu/
2
3
#include "Sap_Apu.h"
4
5
#include <string.h>
6
7
/* Copyright (C) 2006 Shay Green. This module is free software; you
8
can redistribute it and/or modify it under the terms of the GNU Lesser
9
General Public License as published by the Free Software Foundation; either
10
version 2.1 of the License, or (at your option) any later version. This
11
module is distributed in the hope that it will be useful, but WITHOUT ANY
12
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
13
FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
14
details. You should have received a copy of the GNU Lesser General Public
15
License along with this module; if not, write to the Free Software Foundation,
16
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
17
18
#include "blargg_source.h"
19
20
static int const max_frequency = 12000; // pure waves above this frequency are silenced
21
22
static void gen_poly( uint32_t mask, int count, byte* out )
23
0
{
24
0
  uint32_t n = 1;
25
0
  do
26
0
  {
27
0
    int bits = 0;
28
0
    int b = 0;
29
0
    do
30
0
    {
31
      // implemented using "Galios configuration"
32
0
      bits |= (n & 1) << b;
33
0
      n = (n >> 1) ^ (mask & uMinus(n & 1));
34
0
    }
35
0
    while ( b++ < 7 );
36
0
    *out++ = bits;
37
0
  }
38
0
  while ( --count );
39
0
}
40
41
// poly5
42
static int const poly5_len = (1 <<  5) - 1;
43
static uint32_t const poly5_mask = (1UL << poly5_len) - 1;
44
static uint32_t const poly5 = 0x167C6EA1;
45
46
static inline uint32_t run_poly5( uint32_t in, int shift )
47
0
{
48
0
  return (in << shift & poly5_mask) | (in >> (poly5_len - shift));
49
0
}
50
51
#define POLY_MASK( width, tap1, tap2 ) \
52
0
  ((1UL << (width - 1 - tap1)) | (1UL << (width - 1 - tap2)))
53
54
Sap_Apu_Impl::Sap_Apu_Impl()
55
0
{
56
0
  gen_poly( POLY_MASK(  4, 1, 0 ), sizeof poly4,  poly4  );
57
0
  gen_poly( POLY_MASK(  9, 5, 0 ), sizeof poly9,  poly9  );
58
0
  gen_poly( POLY_MASK( 17, 5, 0 ), sizeof poly17, poly17 );
59
60
0
  if ( 0 ) // comment out to recauculate poly5 constant
61
0
  {
62
0
    byte poly5 [4];
63
0
    gen_poly( POLY_MASK(  5, 2, 0 ), sizeof poly5,  poly5  );
64
0
    uint32_t n = poly5 [3] * 0x1000000L + poly5 [2] * 0x10000L +
65
0
        poly5 [1] * 0x100L + poly5 [0];
66
0
    uint32_t rev = n & 1;
67
0
    for ( int i = 1; i < poly5_len; i++ )
68
0
      rev |= (n >> i & 1) << (poly5_len - i);
69
0
    debug_printf( "poly5: 0x%08lX\n", (long unsigned int)rev );
70
0
  }
71
0
}
72
73
Sap_Apu::Sap_Apu()
74
0
{
75
0
  impl = 0;
76
0
  for ( int i = 0; i < osc_count; i++ )
77
0
    osc_output( i, 0 );
78
0
}
79
80
void Sap_Apu::reset( Sap_Apu_Impl* new_impl )
81
0
{
82
0
  impl      = new_impl;
83
0
  last_time = 0;
84
0
  poly5_pos = 0;
85
0
  poly4_pos = 0;
86
0
  polym_pos = 0;
87
0
  control   = 0;
88
89
0
  for ( int i = 0; i < osc_count; i++ )
90
0
    memset( &oscs [i], 0, offsetof (osc_t,output) );
91
0
}
92
93
inline void Sap_Apu::calc_periods()
94
0
{
95
   // 15/64 kHz clock
96
0
  int divider = 28;
97
0
  if ( this->control & 1 )
98
0
    divider = 114;
99
100
0
  for ( int i = 0; i < osc_count; i++ )
101
0
  {
102
0
    osc_t* const osc = &oscs [i];
103
104
0
    int const osc_reload = osc->regs [0]; // cache
105
0
    int32_t period = (osc_reload + 1) * divider;
106
0
    static byte const fast_bits [osc_count] = { 1 << 6, 1 << 4, 1 << 5, 1 << 3 };
107
0
    if ( this->control & fast_bits [i] )
108
0
    {
109
0
      period = osc_reload + 4;
110
0
      if ( i & 1 )
111
0
      {
112
0
        period = osc_reload * 0x100L + osc [-1].regs [0] + 7;
113
0
        if ( !(this->control & fast_bits [i - 1]) )
114
0
          period = (period - 6) * divider;
115
116
0
        if ( (osc [-1].regs [1] & 0x1F) > 0x10 )
117
0
          debug_printf( "Use of slave channel in 16-bit mode not supported\n" );
118
0
      }
119
0
    }
120
0
    osc->period = period;
121
0
  }
122
0
}
123
124
void Sap_Apu::run_until( blip_time_t end_time )
125
0
{
126
0
  calc_periods();
127
0
  Sap_Apu_Impl* const impl = this->impl; // cache
128
129
  // 17/9-bit poly selection
130
0
  byte const* polym = impl->poly17;
131
0
  int polym_len = poly17_len;
132
0
  if ( this->control & 0x80 )
133
0
  {
134
0
    polym_len = poly9_len;
135
0
    polym = impl->poly9;
136
0
  }
137
0
  polym_pos %= polym_len;
138
139
0
  for ( int i = 0; i < osc_count; i++ )
140
0
  {
141
0
    osc_t* const osc = &oscs [i];
142
0
    blip_time_t time = last_time + osc->delay;
143
0
    blip_time_t const period = osc->period;
144
145
    // output
146
0
    Blip_Buffer* output = osc->output;
147
0
    if ( output )
148
0
    {
149
0
      output->set_modified();
150
151
0
      int const osc_control = osc->regs [1]; // cache
152
0
      int volume = (osc_control & 0x0F) * 2;
153
0
      if ( !volume || osc_control & 0x10 || // silent, DAC mode, or inaudible frequency
154
0
          ((osc_control & 0xA0) == 0xA0 && period < 1789773 / 2 / max_frequency) )
155
0
      {
156
0
        if ( !(osc_control & 0x10) )
157
0
          volume >>= 1; // inaudible frequency = half volume
158
159
0
        int delta = volume - osc->last_amp;
160
0
        if ( delta )
161
0
        {
162
0
          osc->last_amp = volume;
163
0
          impl->synth.offset( last_time, delta, output );
164
0
        }
165
166
        // TODO: doesn't maintain high pass flip-flop (very minor issue)
167
0
      }
168
0
      else
169
0
      {
170
        // high pass
171
0
        static byte const hipass_bits [osc_count] = { 1 << 2, 1 << 1, 0, 0 };
172
0
        blip_time_t period2 = 0; // unused if no high pass
173
0
        blip_time_t time2 = end_time;
174
0
        if ( this->control & hipass_bits [i] )
175
0
        {
176
0
          period2 = osc [2].period;
177
0
          time2 = last_time + osc [2].delay;
178
0
          if ( osc->invert )
179
0
          {
180
            // trick inner wave loop into inverting output
181
0
            osc->last_amp -= volume;
182
0
            volume = -volume;
183
0
          }
184
0
        }
185
186
0
        if ( time < end_time || time2 < end_time )
187
0
        {
188
          // poly source
189
0
          static byte const poly1 [] = { 0x55, 0x55 }; // square wave
190
0
          byte const* poly = poly1;
191
0
          int poly_len = 8 * sizeof poly1; // can be just 2 bits, but this is faster
192
0
          int poly_pos = osc->phase & 1;
193
0
          int poly_inc = 1;
194
0
          if ( !(osc_control & 0x20) )
195
0
          {
196
0
            poly     = polym;
197
0
            poly_len = polym_len;
198
0
            poly_pos = polym_pos;
199
0
            if ( osc_control & 0x40 )
200
0
            {
201
0
              poly     = impl->poly4;
202
0
              poly_len = poly4_len;
203
0
              poly_pos = poly4_pos;
204
0
            }
205
0
            poly_inc = period % poly_len;
206
0
            poly_pos = (poly_pos + osc->delay) % poly_len;
207
0
          }
208
0
          poly_inc -= poly_len; // allows more optimized inner loop below
209
210
          // square/poly5 wave
211
0
          uint32_t wave = poly5;
212
0
          check( poly5 & 1 ); // low bit is set for pure wave
213
0
          int poly5_inc = 0;
214
0
          if ( !(osc_control & 0x80) )
215
0
          {
216
0
            wave = run_poly5( wave, (osc->delay + poly5_pos) % poly5_len );
217
0
            poly5_inc = period % poly5_len;
218
0
          }
219
220
          // Run wave and high pass interleved with each catching up to the other.
221
          // Disabled high pass has no performance effect since inner wave loop
222
          // makes no compromise for high pass, and only runs once in that case.
223
0
          int osc_last_amp = osc->last_amp;
224
0
          do
225
0
          {
226
            // run high pass
227
0
            if ( time2 < time )
228
0
            {
229
0
              int delta = -osc_last_amp;
230
0
              if ( volume < 0 )
231
0
                delta += volume;
232
0
              if ( delta )
233
0
              {
234
0
                osc_last_amp += delta - volume;
235
0
                volume = -volume;
236
0
                impl->synth.offset( time2, delta, output );
237
0
              }
238
0
            }
239
0
            while ( time2 <= time ) // must advance *past* time to avoid hang
240
0
              time2 += period2;
241
242
            // run wave
243
0
            blip_time_t end = end_time;
244
0
            if ( end > time2 )
245
0
              end = time2;
246
0
            while ( time < end )
247
0
            {
248
0
              if ( wave & 1 )
249
0
              {
250
0
                int amp = volume & -(poly [poly_pos >> 3] >> (poly_pos & 7) & 1);
251
0
                if ( (poly_pos += poly_inc) < 0 )
252
0
                  poly_pos += poly_len;
253
0
                int delta = amp - osc_last_amp;
254
0
                if ( delta )
255
0
                {
256
0
                  osc_last_amp = amp;
257
0
                  impl->synth.offset( time, delta, output );
258
0
                }
259
0
              }
260
0
              wave = run_poly5( wave, poly5_inc );
261
0
              time += period;
262
0
            }
263
0
          }
264
0
          while ( time < end_time || time2 < end_time );
265
266
0
          osc->phase = poly_pos;
267
0
          osc->last_amp = osc_last_amp;
268
0
        }
269
270
0
        osc->invert = 0;
271
0
        if ( volume < 0 )
272
0
        {
273
          // undo inversion trickery
274
0
          osc->last_amp -= volume;
275
0
          osc->invert = 1;
276
0
        }
277
0
      }
278
0
    }
279
280
    // maintain divider
281
0
    blip_time_t remain = end_time - time;
282
0
    if ( remain > 0 )
283
0
    {
284
0
      int32_t count = (remain + period - 1) / period;
285
0
      osc->phase ^= count;
286
0
      time += count * period;
287
0
    }
288
0
    osc->delay = time - end_time;
289
0
  }
290
291
  // advance polies
292
0
  blip_time_t duration = end_time - last_time;
293
0
  last_time = end_time;
294
0
  poly4_pos = (poly4_pos + duration) % poly4_len;
295
0
  poly5_pos = (poly5_pos + duration) % poly5_len;
296
0
  polym_pos += duration; // will get %'d on next call
297
0
}
298
299
void Sap_Apu::write_data( blip_time_t time, unsigned addr, int data )
300
0
{
301
0
  run_until( time );
302
0
  int i = (addr ^ 0xD200) >> 1;
303
0
  if ( i < osc_count )
304
0
  {
305
0
    oscs [i].regs [addr & 1] = data;
306
0
  }
307
0
  else if ( addr == 0xD208 )
308
0
  {
309
0
    control = data;
310
0
  }
311
0
  else if ( addr == 0xD209 )
312
0
  {
313
0
    oscs [0].delay = 0;
314
0
    oscs [1].delay = 0;
315
0
    oscs [2].delay = 0;
316
0
    oscs [3].delay = 0;
317
0
  }
318
  /*
319
  // TODO: are polynomials reset in this case?
320
  else if ( addr == 0xD20F )
321
  {
322
    if ( (data & 3) == 0 )
323
      polym_pos = 0;
324
  }
325
  */
326
0
}
327
328
void Sap_Apu::end_frame( blip_time_t end_time )
329
0
{
330
0
  if ( end_time > last_time )
331
0
    run_until( end_time );
332
333
0
  last_time -= end_time;
334
0
}