summaryrefslogtreecommitdiff
path: root/apps/codecs/libgme/hes_apu.c
diff options
context:
space:
mode:
Diffstat (limited to 'apps/codecs/libgme/hes_apu.c')
-rw-r--r--apps/codecs/libgme/hes_apu.c315
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
7can redistribute it and/or modify it under the terms of the GNU Lesser
8General Public License as published by the Free Software Foundation; either
9version 2.1 of the License, or (at your option) any later version. This
10module is distributed in the hope that it will be useful, but WITHOUT ANY
11WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
12FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
13details. You should have received a copy of the GNU Lesser General Public
14License along with this module; if not, write to the Free Software Foundation,
15Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
16
17#include "blargg_source.h"
18
19enum { center_waves = 1 }; // reduces asymmetry and clamping when starting notes
20
21static void Apu_balance_changed( struct Hes_Apu* this, struct Hes_Osc* osc ) ICODE_ATTR;
22static 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
68void 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
85void 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
102void 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
118void 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
230void 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
303void 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}