diff options
Diffstat (limited to 'apps/codecs/libgme/hes_apu.c')
-rw-r--r-- | apps/codecs/libgme/hes_apu.c | 315 |
1 files changed, 315 insertions, 0 deletions
diff --git a/apps/codecs/libgme/hes_apu.c b/apps/codecs/libgme/hes_apu.c new file mode 100644 index 0000000000..a3af054548 --- /dev/null +++ b/apps/codecs/libgme/hes_apu.c | |||
@@ -0,0 +1,315 @@ | |||
1 | // Game_Music_Emu 0.5.2. http://www.slack.net/~ant/ | ||
2 | |||
3 | #include "hes_apu.h" | ||
4 | #include <string.h> | ||
5 | |||
6 | /* Copyright (C) 2006 Shay Green. This module is free software; you | ||
7 | can redistribute it and/or modify it under the terms of the GNU Lesser | ||
8 | General Public License as published by the Free Software Foundation; either | ||
9 | version 2.1 of the License, or (at your option) any later version. This | ||
10 | module is distributed in the hope that it will be useful, but WITHOUT ANY | ||
11 | WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS | ||
12 | FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more | ||
13 | details. You should have received a copy of the GNU Lesser General Public | ||
14 | License along with this module; if not, write to the Free Software Foundation, | ||
15 | Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ | ||
16 | |||
17 | #include "blargg_source.h" | ||
18 | |||
19 | enum { center_waves = 1 }; // reduces asymmetry and clamping when starting notes | ||
20 | |||
21 | static void Apu_balance_changed( struct Hes_Apu* this, struct Hes_Osc* osc ) ICODE_ATTR; | ||
22 | static void Apu_balance_changed( struct Hes_Apu* this, struct Hes_Osc* osc ) | ||
23 | { | ||
24 | static short const log_table [32] ICONST_ATTR = { // ~1.5 db per step | ||
25 | #define ENTRY( factor ) (short) (factor * amp_range / 31.0 + 0.5) | ||
26 | ENTRY( 0.000000 ),ENTRY( 0.005524 ),ENTRY( 0.006570 ),ENTRY( 0.007813 ), | ||
27 | ENTRY( 0.009291 ),ENTRY( 0.011049 ),ENTRY( 0.013139 ),ENTRY( 0.015625 ), | ||
28 | ENTRY( 0.018581 ),ENTRY( 0.022097 ),ENTRY( 0.026278 ),ENTRY( 0.031250 ), | ||
29 | ENTRY( 0.037163 ),ENTRY( 0.044194 ),ENTRY( 0.052556 ),ENTRY( 0.062500 ), | ||
30 | ENTRY( 0.074325 ),ENTRY( 0.088388 ),ENTRY( 0.105112 ),ENTRY( 0.125000 ), | ||
31 | ENTRY( 0.148651 ),ENTRY( 0.176777 ),ENTRY( 0.210224 ),ENTRY( 0.250000 ), | ||
32 | ENTRY( 0.297302 ),ENTRY( 0.353553 ),ENTRY( 0.420448 ),ENTRY( 0.500000 ), | ||
33 | ENTRY( 0.594604 ),ENTRY( 0.707107 ),ENTRY( 0.840896 ),ENTRY( 1.000000 ), | ||
34 | #undef ENTRY | ||
35 | }; | ||
36 | |||
37 | int vol = (osc->control & 0x1F) - 0x1E * 2; | ||
38 | |||
39 | int left = vol + (osc->balance >> 3 & 0x1E) + (this->balance >> 3 & 0x1E); | ||
40 | if ( left < 0 ) left = 0; | ||
41 | |||
42 | int right = vol + (osc->balance << 1 & 0x1E) + (this->balance << 1 & 0x1E); | ||
43 | if ( right < 0 ) right = 0; | ||
44 | |||
45 | left = log_table [left ]; | ||
46 | right = log_table [right]; | ||
47 | |||
48 | // optimizing for the common case of being centered also allows easy | ||
49 | // panning using Effects_Buffer | ||
50 | osc->outputs [0] = osc->chans [0]; // center | ||
51 | osc->outputs [1] = 0; | ||
52 | if ( left != right ) | ||
53 | { | ||
54 | osc->outputs [0] = osc->chans [1]; // left | ||
55 | osc->outputs [1] = osc->chans [2]; // right | ||
56 | } | ||
57 | |||
58 | if ( center_waves ) | ||
59 | { | ||
60 | osc->last_amp [0] += (left - osc->volume [0]) * 16; | ||
61 | osc->last_amp [1] += (right - osc->volume [1]) * 16; | ||
62 | } | ||
63 | |||
64 | osc->volume [0] = left; | ||
65 | osc->volume [1] = right; | ||
66 | } | ||
67 | |||
68 | void Apu_init( struct Hes_Apu* this ) | ||
69 | { | ||
70 | struct Hes_Osc* osc = &this->oscs [osc_count]; | ||
71 | do | ||
72 | { | ||
73 | osc--; | ||
74 | osc->outputs [0] = 0; | ||
75 | osc->outputs [1] = 0; | ||
76 | osc->chans [0] = 0; | ||
77 | osc->chans [1] = 0; | ||
78 | osc->chans [2] = 0; | ||
79 | } | ||
80 | while ( osc != this->oscs ); | ||
81 | |||
82 | Apu_reset( this ); | ||
83 | } | ||
84 | |||
85 | void Apu_reset( struct Hes_Apu* this ) | ||
86 | { | ||
87 | this->latch = 0; | ||
88 | this->balance = 0xFF; | ||
89 | |||
90 | struct Hes_Osc* osc = &this->oscs [osc_count]; | ||
91 | do | ||
92 | { | ||
93 | osc--; | ||
94 | memset( osc, 0, offsetof (struct Hes_Osc,outputs) ); | ||
95 | osc->noise_lfsr = 1; | ||
96 | osc->control = 0x40; | ||
97 | osc->balance = 0xFF; | ||
98 | } | ||
99 | while ( osc != this->oscs ); | ||
100 | } | ||
101 | |||
102 | void Apu_osc_output( struct Hes_Apu* this, int index, struct Blip_Buffer* center, struct Blip_Buffer* left, struct Blip_Buffer* right ) | ||
103 | { | ||
104 | require( (unsigned) index < osc_count ); | ||
105 | this->oscs [index].chans [0] = center; | ||
106 | this->oscs [index].chans [1] = left; | ||
107 | this->oscs [index].chans [2] = right; | ||
108 | |||
109 | struct Hes_Osc* osc = &this->oscs [osc_count]; | ||
110 | do | ||
111 | { | ||
112 | osc--; | ||
113 | Apu_balance_changed( this, osc ); | ||
114 | } | ||
115 | while ( osc != this->oscs ); | ||
116 | } | ||
117 | |||
118 | void Osc_run_until( struct Hes_Osc* this, struct Blip_Synth* synth_, blip_time_t end_time ) | ||
119 | { | ||
120 | struct Blip_Buffer* const osc_outputs_0 = this->outputs [0]; // cache often-used values | ||
121 | if ( osc_outputs_0 && this->control & 0x80 ) | ||
122 | { | ||
123 | int dac = this->dac; | ||
124 | |||
125 | int const volume_0 = this->volume [0]; | ||
126 | { | ||
127 | int delta = dac * volume_0 - this->last_amp [0]; | ||
128 | if ( delta ) | ||
129 | Synth_offset( synth_, this->last_time, delta, osc_outputs_0 ); | ||
130 | Blip_set_modified( osc_outputs_0 ); | ||
131 | } | ||
132 | |||
133 | struct Blip_Buffer* const osc_outputs_1 = this->outputs [1]; | ||
134 | int const volume_1 = this->volume [1]; | ||
135 | if ( osc_outputs_1 ) | ||
136 | { | ||
137 | int delta = dac * volume_1 - this->last_amp [1]; | ||
138 | if ( delta ) | ||
139 | Synth_offset( synth_, this->last_time, delta, osc_outputs_1 ); | ||
140 | Blip_set_modified( osc_outputs_1 ); | ||
141 | } | ||
142 | |||
143 | blip_time_t time = this->last_time + this->delay; | ||
144 | if ( time < end_time ) | ||
145 | { | ||
146 | if ( this->noise & 0x80 ) | ||
147 | { | ||
148 | if ( volume_0 | volume_1 ) | ||
149 | { | ||
150 | // noise | ||
151 | int const period = (32 - (this->noise & 0x1F)) * 64; // TODO: correct? | ||
152 | unsigned noise_lfsr = this->noise_lfsr; | ||
153 | do | ||
154 | { | ||
155 | int new_dac = 0x1F & -(noise_lfsr >> 1 & 1); | ||
156 | // Implemented using "Galios configuration" | ||
157 | // TODO: find correct LFSR algorithm | ||
158 | noise_lfsr = (noise_lfsr >> 1) ^ (0xE008 & -(noise_lfsr & 1)); | ||
159 | //noise_lfsr = (noise_lfsr >> 1) ^ (0x6000 & -(noise_lfsr & 1)); | ||
160 | int delta = new_dac - dac; | ||
161 | if ( delta ) | ||
162 | { | ||
163 | dac = new_dac; | ||
164 | Synth_offset( synth_, time, delta * volume_0, osc_outputs_0 ); | ||
165 | if ( osc_outputs_1 ) | ||
166 | Synth_offset( synth_, time, delta * volume_1, osc_outputs_1 ); | ||
167 | } | ||
168 | time += period; | ||
169 | } | ||
170 | while ( time < end_time ); | ||
171 | |||
172 | this->noise_lfsr = noise_lfsr; | ||
173 | assert( noise_lfsr ); | ||
174 | } | ||
175 | } | ||
176 | else if ( !(this->control & 0x40) ) | ||
177 | { | ||
178 | // wave | ||
179 | int phase = (this->phase + 1) & 0x1F; // pre-advance for optimal inner loop | ||
180 | int period = this->period * 2; | ||
181 | if ( period >= 14 && (volume_0 | volume_1) ) | ||
182 | { | ||
183 | do | ||
184 | { | ||
185 | int new_dac = this->wave [phase]; | ||
186 | phase = (phase + 1) & 0x1F; | ||
187 | int delta = new_dac - dac; | ||
188 | if ( delta ) | ||
189 | { | ||
190 | dac = new_dac; | ||
191 | Synth_offset( synth_, time, delta * volume_0, osc_outputs_0 ); | ||
192 | if ( osc_outputs_1 ) | ||
193 | Synth_offset( synth_, time, delta * volume_1, osc_outputs_1 ); | ||
194 | } | ||
195 | time += period; | ||
196 | } | ||
197 | while ( time < end_time ); | ||
198 | } | ||
199 | else | ||
200 | { | ||
201 | if ( !period ) | ||
202 | { | ||
203 | // TODO: Gekisha Boy assumes that period = 0 silences wave | ||
204 | //period = 0x1000 * 2; | ||
205 | period = 1; | ||
206 | //if ( !(volume_0 | volume_1) ) | ||
207 | // dprintf( "Used period 0\n" ); | ||
208 | } | ||
209 | |||
210 | // maintain phase when silent | ||
211 | blargg_long count = (end_time - time + period - 1) / period; | ||
212 | phase += count; // phase will be masked below | ||
213 | time += count * period; | ||
214 | } | ||
215 | this->phase = (phase - 1) & 0x1F; // undo pre-advance | ||
216 | } | ||
217 | } | ||
218 | time -= end_time; | ||
219 | if ( time < 0 ) | ||
220 | time = 0; | ||
221 | this->delay = time; | ||
222 | |||
223 | this->dac = dac; | ||
224 | this->last_amp [0] = dac * volume_0; | ||
225 | this->last_amp [1] = dac * volume_1; | ||
226 | } | ||
227 | this->last_time = end_time; | ||
228 | } | ||
229 | |||
230 | void Apu_write_data( struct Hes_Apu* this, blip_time_t time, int addr, int data ) | ||
231 | { | ||
232 | if ( addr == 0x800 ) | ||
233 | { | ||
234 | this->latch = data & 7; | ||
235 | } | ||
236 | else if ( addr == 0x801 ) | ||
237 | { | ||
238 | if ( this->balance != data ) | ||
239 | { | ||
240 | this->balance = data; | ||
241 | |||
242 | struct Hes_Osc* osc = &this->oscs [osc_count]; | ||
243 | do | ||
244 | { | ||
245 | osc--; | ||
246 | Osc_run_until( osc, &this->synth, time ); | ||
247 | Apu_balance_changed( this, this->oscs ); | ||
248 | } | ||
249 | while ( osc != this->oscs ); | ||
250 | } | ||
251 | } | ||
252 | else if ( this->latch < osc_count ) | ||
253 | { | ||
254 | struct Hes_Osc* osc = &this->oscs [this->latch]; | ||
255 | Osc_run_until( osc, &this->synth, time ); | ||
256 | switch ( addr ) | ||
257 | { | ||
258 | case 0x802: | ||
259 | osc->period = (osc->period & 0xF00) | data; | ||
260 | break; | ||
261 | |||
262 | case 0x803: | ||
263 | osc->period = (osc->period & 0x0FF) | ((data & 0x0F) << 8); | ||
264 | break; | ||
265 | |||
266 | case 0x804: | ||
267 | if ( osc->control & 0x40 & ~data ) | ||
268 | osc->phase = 0; | ||
269 | osc->control = data; | ||
270 | Apu_balance_changed( this, osc ); | ||
271 | break; | ||
272 | |||
273 | case 0x805: | ||
274 | osc->balance = data; | ||
275 | Apu_balance_changed( this, osc ); | ||
276 | break; | ||
277 | |||
278 | case 0x806: | ||
279 | data &= 0x1F; | ||
280 | if ( !(osc->control & 0x40) ) | ||
281 | { | ||
282 | osc->wave [osc->phase] = data; | ||
283 | osc->phase = (osc->phase + 1) & 0x1F; | ||
284 | } | ||
285 | else if ( osc->control & 0x80 ) | ||
286 | { | ||
287 | osc->dac = data; | ||
288 | } | ||
289 | break; | ||
290 | |||
291 | case 0x807: | ||
292 | if ( osc >= &this->oscs [4] ) | ||
293 | osc->noise = data; | ||
294 | break; | ||
295 | case 0x809: | ||
296 | if ( !(data & 0x80) && (data & 0x03) != 0 ) { | ||
297 | dprintf( "HES LFO not supported\n" ); | ||
298 | } | ||
299 | } | ||
300 | } | ||
301 | } | ||
302 | |||
303 | void Apu_end_frame( struct Hes_Apu* this, blip_time_t end_time ) | ||
304 | { | ||
305 | struct Hes_Osc* osc = &this->oscs [osc_count]; | ||
306 | do | ||
307 | { | ||
308 | osc--; | ||
309 | if ( end_time > osc->last_time ) | ||
310 | Osc_run_until( osc, &this->synth, end_time ); | ||
311 | assert( osc->last_time >= end_time ); | ||
312 | osc->last_time -= end_time; | ||
313 | } | ||
314 | while ( osc != this->oscs ); | ||
315 | } | ||