/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 | } |