summaryrefslogtreecommitdiff
path: root/apps/codecs/libgme/sms_apu.c
diff options
context:
space:
mode:
Diffstat (limited to 'apps/codecs/libgme/sms_apu.c')
-rw-r--r--apps/codecs/libgme/sms_apu.c310
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
6can redistribute it and/or modify it under the terms of the GNU Lesser
7General Public License as published by the Free Software Foundation; either
8version 2.1 of the License, or (at your option) any later version. This
9module is distributed in the hope that it will be useful, but WITHOUT ANY
10WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
11FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
12details. You should have received a copy of the GNU Lesser General Public
13License along with this module; if not, write to the Free Software Foundation,
14Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
15
16#include "blargg_source.h"
17
18int const noise_osc = 3;
19
20void 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
26inline 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
32void 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
63static 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
74void 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
106void 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
123static 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
239void 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
269void 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
303void 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}