diff options
Diffstat (limited to 'apps/codecs/libgme/nes_fds_apu.c')
-rw-r--r-- | apps/codecs/libgme/nes_fds_apu.c | 291 |
1 files changed, 291 insertions, 0 deletions
diff --git a/apps/codecs/libgme/nes_fds_apu.c b/apps/codecs/libgme/nes_fds_apu.c new file mode 100644 index 0000000000..51421415e6 --- /dev/null +++ b/apps/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 | ||
6 | can redistribute it and/or modify it under the terms of the GNU Lesser | ||
7 | General Public License as published by the Free Software Foundation; either | ||
8 | version 2.1 of the License, or (at your option) any later version. This | ||
9 | module is distributed in the hope that it will be useful, but WITHOUT ANY | ||
10 | WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS | ||
11 | FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more | ||
12 | details. You should have received a copy of the GNU Lesser General Public | ||
13 | License along with this module; if not, write to the Free Software Foundation, | ||
14 | Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ | ||
15 | |||
16 | #include "blargg_source.h" | ||
17 | |||
18 | int const fract_range = 65536; | ||
19 | |||
20 | void 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, 1.0 ); | ||
27 | Fds_reset( this ); | ||
28 | } | ||
29 | |||
30 | void 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] ICONST_ATTR = { | ||
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 | |||
61 | void 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 | |||
111 | void Fds_set_tempo( struct Nes_Fds_Apu* this, double t ) | ||
112 | { | ||
113 | this->lfo_tempo = lfo_base_tempo; | ||
114 | if ( t != 1.0 ) | ||
115 | { | ||
116 | this->lfo_tempo = (int) ((double) lfo_base_tempo / t + 0.5); | ||
117 | if ( this->lfo_tempo <= 0 ) | ||
118 | this->lfo_tempo = 1; | ||
119 | } | ||
120 | } | ||
121 | |||
122 | void 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 | } | ||