summaryrefslogtreecommitdiff
path: root/lib/rbcodec/codecs/libgme/nes_fds_apu.c
diff options
context:
space:
mode:
Diffstat (limited to 'lib/rbcodec/codecs/libgme/nes_fds_apu.c')
-rw-r--r--lib/rbcodec/codecs/libgme/nes_fds_apu.c291
1 files changed, 291 insertions, 0 deletions
diff --git a/lib/rbcodec/codecs/libgme/nes_fds_apu.c b/lib/rbcodec/codecs/libgme/nes_fds_apu.c
new file mode 100644
index 0000000000..dc0775d5d3
--- /dev/null
+++ b/lib/rbcodec/codecs/libgme/nes_fds_apu.c
@@ -0,0 +1,291 @@
1// Game_Music_Emu 0.6-pre. http://www.slack.net/~ant/
2
3#include "nes_fds_apu.h"
4
5/* Copyright (C) 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 fract_range = 65536;
19
20void Fds_init( struct Nes_Fds_Apu* this )
21{
22 Synth_init( &this->synth );
23
24 this->lfo_tempo = lfo_base_tempo;
25 Fds_set_output( this, 0, NULL );
26 Fds_volume( this, (int)FP_ONE_VOLUME );
27 Fds_reset( this );
28}
29
30void Fds_reset( struct Nes_Fds_Apu* this )
31{
32 memset( this->regs_, 0, sizeof this->regs_ );
33 memset( this->mod_wave, 0, sizeof this->mod_wave );
34
35 this->last_time = 0;
36 this->env_delay = 0;
37 this->sweep_delay = 0;
38 this->wave_pos = 0;
39 this->last_amp = 0;
40 this->wave_fract = fract_range;
41 this->mod_fract = fract_range;
42 this->mod_pos = 0;
43 this->mod_write_pos = 0;
44
45 static byte const initial_regs [0x0B] = {
46 0x80, // disable envelope
47 0, 0, 0xC0, // disable wave and lfo
48 0x80, // disable sweep
49 0, 0, 0x80, // disable modulation
50 0, 0, 0xFF // LFO period // TODO: use 0xE8 as FDS ROM does?
51 };
52 int i;
53 for ( i = 0; i < (int) sizeof initial_regs; i++ )
54 {
55 // two writes to set both gain and period for envelope registers
56 Fds_write_( this, fds_io_addr + fds_wave_size + i, 0 );
57 Fds_write_( this, fds_io_addr + fds_wave_size + i, initial_regs [i] );
58 }
59}
60
61void Fds_write_( struct Nes_Fds_Apu* this, unsigned addr, int data )
62{
63 unsigned reg = addr - fds_io_addr;
64 if ( reg < fds_io_size )
65 {
66 if ( reg < fds_wave_size )
67 {
68 if ( *regs_nes (this, 0x4089) & 0x80 )
69 this->regs_ [reg] = data & fds_wave_sample_max;
70 }
71 else
72 {
73 this->regs_ [reg] = data;
74 switch ( addr )
75 {
76 case 0x4080:
77 if ( data & 0x80 )
78 this->env_gain = data & 0x3F;
79 else
80 this->env_speed = (data & 0x3F) + 1;
81 break;
82
83 case 0x4084:
84 if ( data & 0x80 )
85 this->sweep_gain = data & 0x3F;
86 else
87 this->sweep_speed = (data & 0x3F) + 1;
88 break;
89
90 case 0x4085:
91 this->mod_pos = this->mod_write_pos;
92 *regs_nes (this, 0x4085) = data & 0x7F;
93 break;
94
95 case 0x4088:
96 if ( *regs_nes (this, 0x4087) & 0x80 )
97 {
98 int pos = this->mod_write_pos;
99 data &= 0x07;
100 this->mod_wave [pos ] = data;
101 this->mod_wave [pos + 1] = data;
102 this->mod_write_pos = (pos + 2) & (fds_wave_size - 1);
103 this->mod_pos = (this->mod_pos + 2) & (fds_wave_size - 1);
104 }
105 break;
106 }
107 }
108 }
109}
110
111void Fds_set_tempo( struct Nes_Fds_Apu* this, int t )
112{
113 this->lfo_tempo = lfo_base_tempo;
114 if ( t != (int)FP_ONE_TEMPO )
115 {
116 this->lfo_tempo = (int) ((lfo_base_tempo * FP_ONE_TEMPO) / t);
117 if ( this->lfo_tempo <= 0 )
118 this->lfo_tempo = 1;
119 }
120}
121
122void Fds_run_until( struct Nes_Fds_Apu* this, blip_time_t final_end_time )
123{
124 int const wave_freq = (*regs_nes (this, 0x4083) & 0x0F) * 0x100 + *regs_nes (this, 0x4082);
125 struct Blip_Buffer* const output_ = this->output_;
126 if ( wave_freq && output_ && !((*regs_nes (this, 0x4089) | *regs_nes (this, 0x4083)) & 0x80) )
127 {
128 Blip_set_modified( output_ );
129
130 // master_volume
131 #define MVOL_ENTRY( percent ) (fds_master_vol_max * percent + 50) / 100
132 static unsigned char const master_volumes [4] = {
133 MVOL_ENTRY( 100 ), MVOL_ENTRY( 67 ), MVOL_ENTRY( 50 ), MVOL_ENTRY( 40 )
134 };
135 int const master_volume = master_volumes [*regs_nes (this, 0x4089) & 0x03];
136
137 // lfo_period
138 blip_time_t lfo_period = *regs_nes (this, 0x408A) * this->lfo_tempo;
139 if ( *regs_nes (this, 0x4083) & 0x40 )
140 lfo_period = 0;
141
142 // sweep setup
143 blip_time_t sweep_time = this->last_time + this->sweep_delay;
144 blip_time_t const sweep_period = lfo_period * this->sweep_speed;
145 if ( !sweep_period || *regs_nes (this, 0x4084) & 0x80 )
146 sweep_time = final_end_time;
147
148 // envelope setup
149 blip_time_t env_time = this->last_time + this->env_delay;
150 blip_time_t const env_period = lfo_period * this->env_speed;
151 if ( !env_period || *regs_nes (this, 0x4080) & 0x80 )
152 env_time = final_end_time;
153
154 // modulation
155 int mod_freq = 0;
156 if ( !(*regs_nes (this, 0x4087) & 0x80) )
157 mod_freq = (*regs_nes (this, 0x4087) & 0x0F) * 0x100 + *regs_nes (this, 0x4086);
158
159 blip_time_t end_time = this->last_time;
160 do
161 {
162 // sweep
163 if ( sweep_time <= end_time )
164 {
165 sweep_time += sweep_period;
166 int mode = *regs_nes (this, 0x4084) >> 5 & 2;
167 int new_sweep_gain = this->sweep_gain + mode - 1;
168 if ( (unsigned) new_sweep_gain <= (unsigned) 0x80 >> mode )
169 this->sweep_gain = new_sweep_gain;
170 else
171 *regs_nes (this, 0x4084) |= 0x80; // optimization only
172 }
173
174 // envelope
175 if ( env_time <= end_time )
176 {
177 env_time += env_period;
178 int mode = *regs_nes (this, 0x4080) >> 5 & 2;
179 int new_env_gain = this->env_gain + mode - 1;
180 if ( (unsigned) new_env_gain <= (unsigned) 0x80 >> mode )
181 this->env_gain = new_env_gain;
182 else
183 *regs_nes (this, 0x4080) |= 0x80; // optimization only
184 }
185
186 // new end_time
187 blip_time_t const start_time = end_time;
188 end_time = final_end_time;
189 if ( end_time > env_time ) end_time = env_time;
190 if ( end_time > sweep_time ) end_time = sweep_time;
191
192 // frequency modulation
193 int freq = wave_freq;
194 if ( mod_freq )
195 {
196 // time of next modulation clock
197 blip_time_t mod_time = start_time + (this->mod_fract + mod_freq - 1) / mod_freq;
198 if ( end_time > mod_time )
199 end_time = mod_time;
200
201 // run modulator up to next clock and save old sweep_bias
202 int sweep_bias = *regs_nes (this, 0x4085);
203 this->mod_fract -= (end_time - start_time) * mod_freq;
204 if ( this->mod_fract <= 0 )
205 {
206 this->mod_fract += fract_range;
207 check( (unsigned) this->mod_fract <= fract_range );
208
209 static short const mod_table [8] = { 0, +1, +2, +4, 0, -4, -2, -1 };
210 int mod = this->mod_wave [this->mod_pos];
211 this->mod_pos = (this->mod_pos + 1) & (fds_wave_size - 1);
212 int new_sweep_bias = (sweep_bias + mod_table [mod]) & 0x7F;
213 if ( mod == 4 )
214 new_sweep_bias = 0;
215 *regs_nes (this, 0x4085) = new_sweep_bias;
216 }
217
218 // apply frequency modulation
219 sweep_bias = (sweep_bias ^ 0x40) - 0x40;
220 int factor = sweep_bias * this->sweep_gain;
221 int extra = factor & 0x0F;
222 factor >>= 4;
223 if ( extra )
224 {
225 factor--;
226 if ( sweep_bias >= 0 )
227 factor += 3;
228 }
229 if ( factor > 193 ) factor -= 258;
230 if ( factor < -64 ) factor += 256;
231 freq += (freq * factor) >> 6;
232 if ( freq <= 0 )
233 continue;
234 }
235
236 // wave
237 int wave_fract = this->wave_fract;
238 blip_time_t delay = (wave_fract + freq - 1) / freq;
239 blip_time_t time = start_time + delay;
240
241 if ( time <= end_time )
242 {
243 // at least one wave clock within start_time...end_time
244
245 blip_time_t const min_delay = fract_range / freq;
246 int wave_pos = this->wave_pos;
247
248 int volume = this->env_gain;
249 if ( volume > fds_vol_max )
250 volume = fds_vol_max;
251 volume *= master_volume;
252
253 int const min_fract = min_delay * freq;
254
255 do
256 {
257 // clock wave
258 int amp = this->regs_ [wave_pos] * volume;
259 wave_pos = (wave_pos + 1) & (fds_wave_size - 1);
260 int delta = amp - this->last_amp;
261 if ( delta )
262 {
263 this->last_amp = amp;
264 Synth_offset_inline( &this->synth, time, delta, output_ );
265 }
266
267 wave_fract += fract_range - delay * freq;
268 check( unsigned (fract_range - wave_fract) < freq );
269
270 // delay until next clock
271 delay = min_delay;
272 if ( wave_fract > min_fract )
273 delay++;
274 check( delay && delay == (wave_fract + freq - 1) / freq );
275
276 time += delay;
277 }
278 while ( time <= end_time ); // TODO: using < breaks things, but <= is wrong
279
280 this->wave_pos = wave_pos;
281 }
282 this->wave_fract = wave_fract - (end_time - (time - delay)) * freq;
283 check( this->wave_fract > 0 );
284 }
285 while ( end_time < final_end_time );
286
287 this->env_delay = env_time - final_end_time; check( env_delay >= 0 );
288 this->sweep_delay = sweep_time - final_end_time; check( sweep_delay >= 0 );
289 }
290 this->last_time = final_end_time;
291}