summaryrefslogtreecommitdiff
path: root/apps/codecs/libgme/nes_apu.c
diff options
context:
space:
mode:
Diffstat (limited to 'apps/codecs/libgme/nes_apu.c')
-rw-r--r--apps/codecs/libgme/nes_apu.c393
1 files changed, 393 insertions, 0 deletions
diff --git a/apps/codecs/libgme/nes_apu.c b/apps/codecs/libgme/nes_apu.c
new file mode 100644
index 0000000000..8f1f37645e
--- /dev/null
+++ b/apps/codecs/libgme/nes_apu.c
@@ -0,0 +1,393 @@
1// Nes_Snd_Emu 0.1.8. http://www.slack.net/~ant/
2
3#include "nes_apu.h"
4
5/* Copyright (C) 2003-2006 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 amp_range = 15;
19
20void Apu_init( struct Nes_Apu* this )
21{
22 this->tempo_ = 1.0;
23 this->dmc.apu = this;
24 this->dmc.prg_reader = NULL;
25 this->irq_notifier_ = NULL;
26
27 Synth_init( &this->square_synth );
28 Synth_init( &this->triangle.synth );
29 Synth_init( &this->noise.synth );
30 Synth_init( &this->dmc.synth );
31
32 Square_set_synth( &this->square1, &this->square_synth );
33 Square_set_synth( &this->square2, &this->square_synth );
34
35 this->oscs [0] = &this->square1.osc;
36 this->oscs [1] = &this->square2.osc;
37 this->oscs [2] = &this->triangle.osc;
38 this->oscs [3] = &this->noise.osc;
39 this->oscs [4] = &this->dmc.osc;
40
41 Apu_output( this, NULL );
42 Apu_volume( this, 1.0 );
43 Apu_reset( this, false, 0 );
44}
45
46static double nonlinear_tnd_gain( void ) { return 0.75; }
47void Apu_enable_nonlinear( struct Nes_Apu* this, double v )
48{
49 this->dmc.nonlinear = true;
50 Synth_volume( &this->square_synth, 1.3 * 0.25751258 / 0.742467605 * 0.25 / amp_range * v );
51
52 const double tnd = 0.48 / 202 * nonlinear_tnd_gain();
53 Synth_volume( &this->triangle.synth, 3.0 * tnd );
54 Synth_volume( &this->noise.synth, 2.0 * tnd );
55 Synth_volume( &this->dmc.synth, tnd );
56
57 this->square1 .osc.last_amp = 0;
58 this->square2 .osc.last_amp = 0;
59 this->triangle.osc.last_amp = 0;
60 this->noise .osc.last_amp = 0;
61 this->dmc .osc.last_amp = 0;
62}
63
64void Apu_volume( struct Nes_Apu* this, double v )
65{
66 this->dmc.nonlinear = false;
67 Synth_volume( &this->square_synth, 0.1128 / amp_range * v );
68 Synth_volume( &this->triangle.synth,0.12765 / amp_range * v );
69 Synth_volume( &this->noise.synth, 0.0741 / amp_range * v );
70 Synth_volume( &this->dmc.synth, 0.42545 / 127 * v );
71}
72
73void Apu_output( struct Nes_Apu* this, struct Blip_Buffer* buffer )
74{
75 int i;
76 for ( i = 0; i < apu_osc_count; i++ )
77 Apu_osc_output( this, i, buffer );
78}
79
80void Apu_set_tempo( struct Nes_Apu* this, double t )
81{
82 this->tempo_ = t;
83 this->frame_period = (this->dmc.pal_mode ? 8314 : 7458);
84 if ( t != 1.0 )
85 this->frame_period = (int) (this->frame_period / t) & ~1; // must be even
86}
87
88void Apu_reset( struct Nes_Apu* this, bool pal_mode, int initial_dmc_dac )
89{
90 this->dmc.pal_mode = pal_mode;
91 Apu_set_tempo( this, this->tempo_ );
92
93 Square_reset( &this->square1 );
94 Square_reset( &this->square2 );
95 Triangle_reset( &this->triangle );
96 Noise_reset( &this->noise );
97 Dmc_reset( &this->dmc );
98
99 this->last_time = 0;
100 this->last_dmc_time = 0;
101 this->osc_enables = 0;
102 this->irq_flag = false;
103 this->earliest_irq_ = apu_no_irq;
104 this->frame_delay = 1;
105 Apu_write_register( this, 0, 0x4017, 0x00 );
106 Apu_write_register( this, 0, 0x4015, 0x00 );
107
108 addr_t addr;
109 for ( addr = apu_io_addr; addr <= 0x4013; addr++ )
110 Apu_write_register( this, 0, addr, (addr & 3) ? 0x00 : 0x10 );
111
112 this->dmc.dac = initial_dmc_dac;
113 if ( !this->dmc.nonlinear )
114 this->triangle.osc.last_amp = 15;
115 if ( !this->dmc.nonlinear ) // TODO: remove?
116 this->dmc.osc.last_amp = initial_dmc_dac; // prevent output transition
117}
118
119void Apu_irq_changed( struct Nes_Apu* this )
120{
121 nes_time_t new_irq = this->dmc.next_irq;
122 if ( this->dmc.irq_flag | this->irq_flag ) {
123 new_irq = 0;
124 }
125 else if ( new_irq > this->next_irq ) {
126 new_irq = this->next_irq;
127 }
128
129 if ( new_irq != this->earliest_irq_ ) {
130 this->earliest_irq_ = new_irq;
131 if ( this->irq_notifier_ )
132 this->irq_notifier_( this->irq_data );
133 }
134}
135
136// frames
137
138void Apu_run_until( struct Nes_Apu* this, nes_time_t end_time )
139{
140 require( end_time >= this->last_dmc_time );
141 if ( end_time > Apu_next_dmc_read_time( this ) )
142 {
143 nes_time_t start = this->last_dmc_time;
144 this->last_dmc_time = end_time;
145 Dmc_run( &this->dmc, start, end_time );
146 }
147}
148
149void run_until_( struct Nes_Apu* this, nes_time_t end_time )
150{
151 require( end_time >= this->last_time );
152
153 if ( end_time == this->last_time )
154 return;
155
156 if ( this->last_dmc_time < end_time )
157 {
158 nes_time_t start = this->last_dmc_time;
159 this->last_dmc_time = end_time;
160 Dmc_run( &this->dmc, start, end_time );
161 }
162
163 while ( true )
164 {
165 // earlier of next frame time or end time
166 nes_time_t time = this->last_time + this->frame_delay;
167 if ( time > end_time )
168 time = end_time;
169 this->frame_delay -= time - this->last_time;
170
171 // run oscs to present
172 Square_run( &this->square1, this->last_time, time );
173 Square_run( &this->square2, this->last_time, time );
174 Triangle_run( &this->triangle, this->last_time, time );
175 Noise_run( &this->noise, this->last_time, time );
176 this->last_time = time;
177
178 if ( time == end_time )
179 break; // no more frames to run
180
181 // take frame-specific actions
182 this->frame_delay = this->frame_period;
183 switch ( this->frame++ )
184 {
185 case 0:
186 if ( !(this->frame_mode & 0xC0) ) {
187 this->next_irq = time + this->frame_period * 4 + 2;
188 this->irq_flag = true;
189 }
190 // fall through
191 case 2:
192 // clock length and sweep on frames 0 and 2
193 Osc_clock_length( &this->square1.osc, 0x20 );
194 Osc_clock_length( &this->square2.osc, 0x20 );
195 Osc_clock_length( &this->noise.osc, 0x20 );
196 Osc_clock_length( &this->triangle.osc, 0x80 ); // different bit for halt flag on triangle
197
198 Square_clock_sweep( &this->square1, -1 );
199 Square_clock_sweep( &this->square2, 0 );
200
201 // frame 2 is slightly shorter in mode 1
202 if ( this->dmc.pal_mode && this->frame == 3 )
203 this->frame_delay -= 2;
204 break;
205
206 case 1:
207 // frame 1 is slightly shorter in mode 0
208 if ( !this->dmc.pal_mode )
209 this->frame_delay -= 2;
210 break;
211
212 case 3:
213 this->frame = 0;
214
215 // frame 3 is almost twice as long in mode 1
216 if ( this->frame_mode & 0x80 )
217 this->frame_delay += this->frame_period - (this->dmc.pal_mode ? 2 : 6);
218 break;
219 }
220
221 // clock envelopes and linear counter every frame
222 Triangle_clock_linear_counter( &this->triangle );
223 Square_clock_envelope( &this->square1 );
224 Square_clock_envelope( &this->square2 );
225 Noise_clock_envelope( &this->noise );
226 }
227}
228
229static inline void zero_apu_osc( struct Nes_Osc* osc, struct Blip_Synth* synth, nes_time_t time )
230{
231 struct Blip_Buffer* output = osc->output;
232 int last_amp = osc->last_amp;
233 osc->last_amp = 0;
234 if ( output && last_amp )
235 Synth_offset( synth, time, -osc->last_amp, output );
236}
237
238void Apu_end_frame( struct Nes_Apu* this, nes_time_t end_time )
239{
240 if ( end_time > this->last_time )
241 run_until_( this, end_time );
242
243 if ( this->dmc.nonlinear )
244 {
245 zero_apu_osc( &this->square1.osc, this->square1.synth, this->last_time );
246 zero_apu_osc( &this->square2.osc, this->square2.synth, this->last_time );
247 zero_apu_osc( &this->triangle.osc, &this->triangle.synth, this->last_time );
248 zero_apu_osc( &this->noise.osc, &this->noise.synth, this->last_time );
249 zero_apu_osc( &this->dmc.osc, &this->dmc.synth, this->last_time );
250 }
251
252 // make times relative to new frame
253 this->last_time -= end_time;
254 require( this->last_time >= 0 );
255
256 this->last_dmc_time -= end_time;
257 require( this->last_dmc_time >= 0 );
258
259 if ( this->next_irq != apu_no_irq ) {
260 this->next_irq -= end_time;
261 check( this->next_irq >= 0 );
262 }
263 if ( this->dmc.next_irq != apu_no_irq ) {
264 this->dmc.next_irq -= end_time;
265 check( this->dmc.next_irq >= 0 );
266 }
267 if ( this->earliest_irq_ != apu_no_irq ) {
268 this->earliest_irq_ -= end_time;
269 if ( this->earliest_irq_ < 0 )
270 this->earliest_irq_ = 0;
271 }
272}
273
274// registers
275
276static const unsigned char length_table [0x20] ICONST_ATTR = {
277 0x0A, 0xFE, 0x14, 0x02, 0x28, 0x04, 0x50, 0x06,
278 0xA0, 0x08, 0x3C, 0x0A, 0x0E, 0x0C, 0x1A, 0x0E,
279 0x0C, 0x10, 0x18, 0x12, 0x30, 0x14, 0x60, 0x16,
280 0xC0, 0x18, 0x48, 0x1A, 0x10, 0x1C, 0x20, 0x1E
281};
282
283void Apu_write_register( struct Nes_Apu* this, nes_time_t time, addr_t addr, int data )
284{
285 require( addr > 0x20 ); // addr must be actual address (i.e. 0x40xx)
286 require( (unsigned) data <= 0xFF );
287
288 // Ignore addresses outside range
289 if ( (unsigned) (addr - apu_io_addr) >= apu_io_size )
290 return;
291
292 run_until_( this, time );
293
294 if ( addr < 0x4014 )
295 {
296 // Write to channel
297 int osc_index = (addr - apu_io_addr) >> 2;
298 struct Nes_Osc* osc = this->oscs [osc_index];
299
300 int reg = addr & 3;
301 osc->regs [reg] = data;
302 osc->reg_written [reg] = true;
303
304 if ( osc_index == 4 )
305 {
306 // handle DMC specially
307 Dmc_write_register( &this->dmc, reg, data );
308 }
309 else if ( reg == 3 )
310 {
311 // load length counter
312 if ( (this->osc_enables >> osc_index) & 1 )
313 osc->length_counter = length_table [(data >> 3) & 0x1F];
314
315 // reset square phase
316 if ( osc_index == 0 ) this->square1.phase = square_phase_range - 1;
317 else if ( osc_index == 1 ) this->square2.phase = square_phase_range - 1;
318 }
319 }
320 else if ( addr == 0x4015 )
321 {
322 // Channel enables
323 int i;
324 for ( i = apu_osc_count; i--; )
325 if ( !((data >> i) & 1) )
326 this->oscs [i]->length_counter = 0;
327
328 bool recalc_irq = this->dmc.irq_flag;
329 this->dmc.irq_flag = false;
330
331 int old_enables = this->osc_enables;
332 this->osc_enables = data;
333 if ( !(data & 0x10) ) {
334 this->dmc.next_irq = apu_no_irq;
335 recalc_irq = true;
336 }
337 else if ( !(old_enables & 0x10) ) {
338 Dmc_start( &this->dmc ); // dmc just enabled
339 }
340
341 if ( recalc_irq )
342 Apu_irq_changed( this );
343 }
344 else if ( addr == 0x4017 )
345 {
346 // Frame mode
347 this->frame_mode = data;
348
349 bool irq_enabled = !(data & 0x40);
350 this->irq_flag &= irq_enabled;
351 this->next_irq = apu_no_irq;
352
353 // mode 1
354 this->frame_delay = (this->frame_delay & 1);
355 this->frame = 0;
356
357 if ( !(data & 0x80) )
358 {
359 // mode 0
360 this->frame = 1;
361 this->frame_delay += this->frame_period;
362 if ( irq_enabled )
363 this->next_irq = time + this->frame_delay + this->frame_period * 3 + 1;
364 }
365
366 Apu_irq_changed( this );
367 }
368}
369
370int Apu_read_status( struct Nes_Apu* this, nes_time_t time )
371{
372 run_until_( this, time - 1 );
373
374 int result = (this->dmc.irq_flag << 7) | (this->irq_flag << 6);
375
376 int i;
377 for ( i = 0; i < apu_osc_count; i++ )
378 if ( this->oscs [i]->length_counter )
379 result |= 1 << i;
380
381 run_until_( this, time );
382
383 if ( this->irq_flag )
384 {
385 result |= 0x40;
386 this->irq_flag = false;
387 Apu_irq_changed( this );
388 }
389
390 //debug_printf( "%6d/%d Read $4015->$%02X\n", frame_delay, frame, result );
391
392 return result;
393}