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