diff options
Diffstat (limited to 'apps/codecs/libgme/sms_apu.c')
-rw-r--r-- | apps/codecs/libgme/sms_apu.c | 310 |
1 files changed, 310 insertions, 0 deletions
diff --git a/apps/codecs/libgme/sms_apu.c b/apps/codecs/libgme/sms_apu.c new file mode 100644 index 0000000000..4be63db073 --- /dev/null +++ b/apps/codecs/libgme/sms_apu.c | |||
@@ -0,0 +1,310 @@ | |||
1 | // Sms_Snd_Emu 0.1.1. http://www.slack.net/~ant/ | ||
2 | |||
3 | #include "sms_apu.h" | ||
4 | |||
5 | /* Copyright (C) 2003-2008 Shay Green. This module is free software; you | ||
6 | can redistribute it and/or modify it under the terms of the GNU Lesser | ||
7 | General Public License as published by the Free Software Foundation; either | ||
8 | version 2.1 of the License, or (at your option) any later version. This | ||
9 | module is distributed in the hope that it will be useful, but WITHOUT ANY | ||
10 | WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS | ||
11 | FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more | ||
12 | details. You should have received a copy of the GNU Lesser General Public | ||
13 | License along with this module; if not, write to the Free Software Foundation, | ||
14 | Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ | ||
15 | |||
16 | #include "blargg_source.h" | ||
17 | |||
18 | int const noise_osc = 3; | ||
19 | |||
20 | void Sms_apu_volume( struct Sms_Apu* this, double vol ) | ||
21 | { | ||
22 | vol *= 0.85 / sms_osc_count / 64; | ||
23 | Synth_volume( &this->synth, vol ); | ||
24 | } | ||
25 | |||
26 | inline int calc_output( struct Sms_Apu* this, int i ) | ||
27 | { | ||
28 | int flags = this->ggstereo >> i; | ||
29 | return (flags >> 3 & 2) | (flags & 1); | ||
30 | } | ||
31 | |||
32 | void Sms_apu_set_output( struct Sms_Apu* this, int i, struct Blip_Buffer* center, struct Blip_Buffer* left, struct Blip_Buffer* right ) | ||
33 | { | ||
34 | #if defined(ROCKBOX) | ||
35 | (void) left; | ||
36 | (void) right; | ||
37 | #endif | ||
38 | |||
39 | // Must be silent (all NULL), mono (left and right NULL), or stereo (none NULL) | ||
40 | require( !center || (center && !left && !right) || (center && left && right) ); | ||
41 | require( (unsigned) i < sms_osc_count ); // fails if you pass invalid osc index | ||
42 | |||
43 | if ( center ) | ||
44 | { | ||
45 | unsigned const divisor = 16384 * 16 * 2; | ||
46 | this->min_tone_period = ((unsigned) Blip_clock_rate( center ) + divisor/2) / divisor; | ||
47 | } | ||
48 | |||
49 | if ( !center || !left || !right ) | ||
50 | { | ||
51 | left = center; | ||
52 | right = center; | ||
53 | } | ||
54 | |||
55 | struct Osc* o = &this->oscs [i]; | ||
56 | o->outputs [0] = NULL; | ||
57 | o->outputs [1] = right; | ||
58 | o->outputs [2] = left; | ||
59 | o->outputs [3] = center; | ||
60 | o->output = o->outputs [calc_output( this, i )]; | ||
61 | } | ||
62 | |||
63 | static inline unsigned fibonacci_to_galois_lfsr( unsigned fibonacci, int width ) | ||
64 | { | ||
65 | unsigned galois = 0; | ||
66 | while ( --width >= 0 ) | ||
67 | { | ||
68 | galois = (galois << 1) | (fibonacci & 1); | ||
69 | fibonacci >>= 1; | ||
70 | } | ||
71 | return galois; | ||
72 | } | ||
73 | |||
74 | void Sms_apu_reset( struct Sms_Apu* this, unsigned feedback, int noise_width ) | ||
75 | { | ||
76 | this->last_time = 0; | ||
77 | this->latch = 0; | ||
78 | this->ggstereo = 0; | ||
79 | |||
80 | // Calculate noise feedback values | ||
81 | if ( !feedback || !noise_width ) | ||
82 | { | ||
83 | feedback = 0x0009; | ||
84 | noise_width = 16; | ||
85 | } | ||
86 | this->looped_feedback = 1 << (noise_width - 1); | ||
87 | this->noise_feedback = fibonacci_to_galois_lfsr( feedback, noise_width ); | ||
88 | |||
89 | // Reset oscs | ||
90 | int i; | ||
91 | for ( i = sms_osc_count; --i >= 0; ) | ||
92 | { | ||
93 | struct Osc* o = &this->oscs [i]; | ||
94 | o->output = NULL; | ||
95 | o->last_amp = 0; | ||
96 | o->delay = 0; | ||
97 | o->phase = 0; | ||
98 | o->period = 0; | ||
99 | o->volume = 15; // silent | ||
100 | } | ||
101 | |||
102 | this->oscs [noise_osc].phase = 0x8000; | ||
103 | Sms_apu_write_ggstereo( this, 0, 0xFF ); | ||
104 | } | ||
105 | |||
106 | void Sms_apu_init( struct Sms_Apu* this ) | ||
107 | { | ||
108 | this->min_tone_period = 7; | ||
109 | |||
110 | Synth_init( &this->synth ); | ||
111 | |||
112 | // Clear outputs to NULL FIRST | ||
113 | this->ggstereo = 0; | ||
114 | |||
115 | int i; | ||
116 | for ( i = sms_osc_count; --i >= 0; ) | ||
117 | Sms_apu_set_output( this, i, NULL, NULL, NULL ); | ||
118 | |||
119 | Sms_apu_volume( this, 1.0 ); | ||
120 | Sms_apu_reset( this, 0, 0 ); | ||
121 | } | ||
122 | |||
123 | static void run_until( struct Sms_Apu* this, blip_time_t end_time ) | ||
124 | { | ||
125 | require( end_time >= this->last_time ); | ||
126 | if ( end_time <= this->last_time ) | ||
127 | return; | ||
128 | |||
129 | // Synthesize each oscillator | ||
130 | int idx; | ||
131 | for ( idx = sms_osc_count; --idx >= 0; ) | ||
132 | { | ||
133 | struct Osc* osc = &this->oscs [idx]; | ||
134 | int vol = 0; | ||
135 | int amp = 0; | ||
136 | |||
137 | // Determine what will be generated | ||
138 | struct Blip_Buffer* const out = osc->output; | ||
139 | if ( out ) | ||
140 | { | ||
141 | // volumes [i] ~= 64 * pow( 1.26, 15 - i ) / pow( 1.26, 15 ) | ||
142 | static unsigned char const volumes [16] ICONST_ATTR = { | ||
143 | 64, 50, 40, 32, 25, 20, 16, 13, 10, 8, 6, 5, 4, 3, 2, 0 | ||
144 | }; | ||
145 | |||
146 | vol = volumes [osc->volume]; | ||
147 | amp = (osc->phase & 1) * vol; | ||
148 | |||
149 | // Square freq above 16 kHz yields constant amplitude at half volume | ||
150 | if ( idx != noise_osc && osc->period < this->min_tone_period ) | ||
151 | { | ||
152 | amp = vol >> 1; | ||
153 | vol = 0; | ||
154 | } | ||
155 | |||
156 | // Update amplitude | ||
157 | int delta = amp - osc->last_amp; | ||
158 | if ( delta ) | ||
159 | { | ||
160 | osc->last_amp = amp; | ||
161 | /* norm_synth.offset( last_time, delta, out ); */ | ||
162 | Synth_offset( &this->synth, this->last_time, delta, out ); | ||
163 | /* out->set_modified(); */ | ||
164 | Blip_set_modified( out ); | ||
165 | } | ||
166 | } | ||
167 | |||
168 | // Generate wave | ||
169 | blip_time_t time = this->last_time + osc->delay; | ||
170 | if ( time < end_time ) | ||
171 | { | ||
172 | // Calculate actual period | ||
173 | int period = osc->period; | ||
174 | if ( idx == noise_osc ) | ||
175 | { | ||
176 | period = 0x20 << (period & 3); | ||
177 | if ( period == 0x100 ) | ||
178 | period = this->oscs [2].period * 2; | ||
179 | } | ||
180 | period *= 0x10; | ||
181 | if ( !period ) | ||
182 | period = 0x10; | ||
183 | |||
184 | // Maintain phase when silent | ||
185 | int phase = osc->phase; | ||
186 | if ( !vol ) | ||
187 | { | ||
188 | int count = (end_time - time + period - 1) / period; | ||
189 | time += count * period; | ||
190 | if ( idx != noise_osc ) // TODO: maintain noise LFSR phase? | ||
191 | phase ^= count & 1; | ||
192 | } | ||
193 | else | ||
194 | { | ||
195 | int delta = amp * 2 - vol; | ||
196 | |||
197 | if ( idx != noise_osc ) | ||
198 | { | ||
199 | // Square | ||
200 | do | ||
201 | { | ||
202 | delta = -delta; | ||
203 | /* norm_synth.offset( time, delta, out ); */ | ||
204 | Synth_offset( &this->synth, time, delta, out ); | ||
205 | time += period; | ||
206 | } | ||
207 | while ( time < end_time ); | ||
208 | phase = (delta >= 0); | ||
209 | } | ||
210 | else | ||
211 | { | ||
212 | // Noise | ||
213 | unsigned const feedback = (osc->period & 4 ? this->noise_feedback : this->looped_feedback); | ||
214 | do | ||
215 | { | ||
216 | unsigned changed = phase + 1; | ||
217 | phase = ((phase & 1) * feedback) ^ (phase >> 1); | ||
218 | if ( changed & 2 ) // true if bits 0 and 1 differ | ||
219 | { | ||
220 | delta = -delta; | ||
221 | /* fast_synth.offset_inline( time, delta, out ); */ | ||
222 | Synth_offset_inline( &this->synth, time, delta, out ); | ||
223 | } | ||
224 | time += period; | ||
225 | } | ||
226 | while ( time < end_time ); | ||
227 | check( phase ); | ||
228 | } | ||
229 | osc->last_amp = (phase & 1) * vol; | ||
230 | Blip_set_modified( out ); | ||
231 | } | ||
232 | osc->phase = phase; | ||
233 | } | ||
234 | osc->delay = time - end_time; | ||
235 | } | ||
236 | this->last_time = end_time; | ||
237 | } | ||
238 | |||
239 | void Sms_apu_write_ggstereo( struct Sms_Apu* this, blip_time_t time, int data ) | ||
240 | { | ||
241 | require( (unsigned) data <= 0xFF ); | ||
242 | |||
243 | run_until( this, time ); | ||
244 | this->ggstereo = data; | ||
245 | |||
246 | int i; | ||
247 | for ( i = sms_osc_count; --i >= 0; ) | ||
248 | { | ||
249 | struct Osc* osc = &this->oscs [i]; | ||
250 | |||
251 | struct Blip_Buffer* old = osc->output; | ||
252 | osc->output = osc->outputs [calc_output( this, i )]; | ||
253 | if ( osc->output != old ) | ||
254 | { | ||
255 | int delta = -osc->last_amp; | ||
256 | if ( delta ) | ||
257 | { | ||
258 | osc->last_amp = 0; | ||
259 | if ( old ) | ||
260 | { | ||
261 | Blip_set_modified( old ); | ||
262 | Synth_offset( &this->synth, this->last_time, delta, old ); | ||
263 | } | ||
264 | } | ||
265 | } | ||
266 | } | ||
267 | } | ||
268 | |||
269 | void Sms_apu_write_data( struct Sms_Apu* this, blip_time_t time, int data ) | ||
270 | { | ||
271 | require( (unsigned) data <= 0xFF ); | ||
272 | |||
273 | run_until( this, time ); | ||
274 | |||
275 | if ( data & 0x80 ) | ||
276 | this->latch = data; | ||
277 | |||
278 | // We want the raw values written so our save state format can be | ||
279 | // as close to hardware as possible and unspecific to any emulator. | ||
280 | int idx = this->latch >> 5 & 3; | ||
281 | struct Osc* osc = &this->oscs [idx]; | ||
282 | if ( this->latch & 0x10 ) | ||
283 | { | ||
284 | osc->volume = data & 0x0F; | ||
285 | } | ||
286 | else | ||
287 | { | ||
288 | if ( idx == noise_osc ) | ||
289 | osc->phase = 0x8000; // reset noise LFSR | ||
290 | |||
291 | // Replace high 6 bits/low 4 bits of register with data | ||
292 | int lo = osc->period; | ||
293 | int hi = data << 4; | ||
294 | if ( idx == noise_osc || (data & 0x80) ) | ||
295 | { | ||
296 | hi = lo; | ||
297 | lo = data; | ||
298 | } | ||
299 | osc->period = (hi & 0x3F0) | (lo & 0x00F); | ||
300 | } | ||
301 | } | ||
302 | |||
303 | void Sms_apu_end_frame( struct Sms_Apu* this, blip_time_t end_time ) | ||
304 | { | ||
305 | if ( end_time > this->last_time ) | ||
306 | run_until( this, end_time ); | ||
307 | |||
308 | this->last_time -= end_time; | ||
309 | assert( this->last_time >= 0 ); | ||
310 | } | ||