3 * (c) Copyright Dave, 2004
\r
4 * (C) notaz, 2006-2009
\r
5 * (C) irixxxx, 2019-2024
\r
7 * This work is licensed under the terms of MAME license.
\r
8 * See COPYING file in the top-level directory.
\r
12 #include "../pico_int.h"
\r
15 #include "sn76496.h"
\r
16 #include "../cd/megasd.h"
\r
17 #include "resampler.h"
\r
20 #define YM2612_CH6PAN 0x1b6 // panning register for channel 6 (used for DAC)
\r
22 void (*PsndMix_32_to_16)(s16 *dest, s32 *src, int count) = mix_32_to_16_stereo;
\r
24 // master int buffer to mix to
\r
25 // +1 for a fill triggered by an instruction overhanging into the next scanline
\r
26 static s32 PsndBuffer[2*(54000+100)/50+2];
\r
28 // cdda output buffer
\r
29 s16 cdda_out_buffer[2*1152];
\r
31 // FM resampling polyphase FIR
\r
32 static resampler_t *fmresampler;
\r
33 static int (*PsndFMUpdate)(s32 *buffer, int length, int stereo, int is_buf_empty);
\r
35 PICO_INTERNAL void PsndInit(void)
\r
37 opll = OPLL_new(OSC_NTSC/15, OSC_NTSC/15/72);
\r
38 OPLL_setChipType(opll,0);
\r
42 PICO_INTERNAL void PsndExit(void)
\r
47 resampler_free(fmresampler); fmresampler = NULL;
\r
50 PICO_INTERNAL void PsndReset(void)
\r
52 // PsndRerate calls YM2612Init, which also resets
\r
57 // FM polyphase FIR resampling
\r
58 #define FMFIR_TAPS 8
\r
60 // resample FM from its native 53267Hz/52781Hz with polyphase FIR filter
\r
62 static void YM2612Update(s32 *buffer, int length, int stereo)
\r
64 ymchans = YM2612UpdateOne(buffer, length, stereo, 1);
\r
67 static int YM2612UpdateFIR(s32 *buffer, int length, int stereo, int is_buf_empty)
\r
69 resampler_update(fmresampler, buffer, length, YM2612Update);
\r
73 // resample SMS FM from its native 49716Hz/49262Hz with polyphase FIR filter
\r
74 static void YM2413Update(s32 *buffer, int length, int stereo)
\r
76 while (length-- > 0) {
\r
77 int16_t getdata = OPLL_calc(opll) * 3;
\r
78 *buffer++ = getdata;
\r
79 buffer += stereo; // only left for stereo, to be mixed to right later
\r
83 static int YM2413UpdateFIR(s32 *buffer, int length, int stereo, int is_buf_empty)
\r
85 if (!is_buf_empty) memset(buffer, 0, (length << stereo) * sizeof(*buffer));
\r
86 resampler_update(fmresampler, buffer, length, YM2413Update);
\r
90 // FIR setup, looks for a close enough rational number matching the ratio
\r
91 static void YMFM_setup_FIR(int inrate, int outrate, int stereo)
\r
95 int minmult = 11, maxmult = 61; // min,max interpolation factor
\r
97 // compute filter ratio with largest multiplier for smallest error
\r
98 for (mul = minmult; mul <= maxmult; mul++) {
\r
99 div = (inrate*mul + outrate/2) / outrate;
\r
100 diff = outrate*div/mul - inrate;
\r
101 if (abs(diff) < abs(mindiff)) {
\r
103 Pico.snd.fm_fir_mul = mul;
\r
104 Pico.snd.fm_fir_div = div;
\r
105 if (abs(mindiff)*1000 <= inrate) break; // below error limit
\r
108 printf("FM polyphase FIR ratio=%d/%d error=%.3f%%\n",
\r
109 Pico.snd.fm_fir_mul, Pico.snd.fm_fir_div, 100.0*mindiff/inrate);
\r
111 resampler_free(fmresampler);
\r
112 fmresampler = resampler_new(FMFIR_TAPS, Pico.snd.fm_fir_mul, Pico.snd.fm_fir_div,
\r
113 0.85, 2, 2*inrate/50, stereo);
\r
116 // wrapper for the YM2612UpdateONE macro
\r
117 static int YM2612UpdateONE(s32 *buffer, int length, int stereo, int is_buf_empty)
\r
119 return YM2612UpdateOne(buffer, length, stereo, is_buf_empty);
\r
122 static int ymclock;
\r
126 // to be called after changing sound rate or chips
\r
127 void PsndRerate(int preserve_state)
\r
129 void *state = NULL;
\r
130 int target_fps = Pico.m.pal ? 50 : 60;
\r
131 int target_lines = Pico.m.pal ? 313 : 262;
\r
132 int sms_clock = Pico.m.pal ? OSC_PAL/15 : OSC_NTSC/15;
\r
133 int ym2413_rate = (sms_clock + 36) / 72;
\r
134 int ym2612_clock = Pico.m.pal ? OSC_PAL/7 : OSC_NTSC/7;
\r
135 int ym2612_rate = YM2612_NATIVE_RATE();
\r
136 int ym2612_init = !preserve_state;
\r
137 int state_size = 4096;
\r
139 // don't init YM2612 if preserve_state and no parameter changes
\r
140 ym2612_init |= ymclock != ym2612_clock || ymopts != (PicoIn.opt & (POPT_DIS_FM_SSGEG|POPT_EN_FM_DAC));
\r
141 ym2612_init |= ymrate != (PicoIn.opt & POPT_EN_FM_FILTER ? ym2612_rate : PicoIn.sndRate);
\r
142 ymclock = ym2612_clock;
\r
143 ymrate = (PicoIn.opt & POPT_EN_FM_FILTER ? ym2612_rate : PicoIn.sndRate);
\r
144 ymopts = PicoIn.opt & (POPT_DIS_FM_SSGEG|POPT_EN_FM_DAC);
\r
146 if (preserve_state && ym2612_init) {
\r
147 state = malloc(state_size);
\r
149 state_size = YM2612PicoStateSave3(state, state_size);
\r
152 if (PicoIn.AHW & PAHW_SMS) {
\r
153 OPLL_setRate(opll, ym2413_rate);
\r
154 if (!preserve_state)
\r
156 YMFM_setup_FIR(ym2413_rate, PicoIn.sndRate, 0);
\r
157 PsndFMUpdate = YM2413UpdateFIR;
\r
158 } else if ((PicoIn.opt & POPT_EN_FM_FILTER) && ym2612_rate != PicoIn.sndRate) {
\r
159 // polyphase FIR resampler, resampling directly from native to output rate
\r
161 YM2612Init(ym2612_clock, ym2612_rate,
\r
162 ((PicoIn.opt&POPT_DIS_FM_SSGEG) ? 0 : ST_SSG) |
\r
163 ((PicoIn.opt&POPT_EN_FM_DAC) ? ST_DAC : 0));
\r
164 YMFM_setup_FIR(ym2612_rate, PicoIn.sndRate, PicoIn.opt & POPT_EN_STEREO);
\r
165 PsndFMUpdate = YM2612UpdateFIR;
\r
168 YM2612Init(ym2612_clock, PicoIn.sndRate,
\r
169 ((PicoIn.opt&POPT_DIS_FM_SSGEG) ? 0 : ST_SSG) |
\r
170 ((PicoIn.opt&POPT_EN_FM_DAC) ? ST_DAC : 0));
\r
171 PsndFMUpdate = YM2612UpdateONE;
\r
175 YM2612PicoStateLoad3(state, state_size);
\r
179 if (preserve_state)
\r
180 SN76496_set_clockrate(Pico.m.pal ? OSC_PAL/15 : OSC_NTSC/15, PicoIn.sndRate);
\r
182 SN76496_init(Pico.m.pal ? OSC_PAL/15 : OSC_NTSC/15, PicoIn.sndRate);
\r
184 // calculate Pico.snd.len
\r
185 Pico.snd.len = PicoIn.sndRate / target_fps;
\r
186 Pico.snd.len_e_add = ((PicoIn.sndRate - Pico.snd.len * target_fps) << 16) / target_fps;
\r
187 Pico.snd.len_e_cnt = 0; // Q16
\r
189 // samples per line (Q16)
\r
190 Pico.snd.smpl_mult = 65536LL * PicoIn.sndRate / (target_fps*target_lines);
\r
191 // samples per z80 clock (Q20)
\r
192 Pico.snd.clkz_mult = 16 * Pico.snd.smpl_mult * 15/7 / 488.5;
\r
193 // samples per 44.1 KHz sample (Q16)
\r
194 Pico.snd.cdda_mult = 65536LL * 44100 / PicoIn.sndRate;
\r
195 Pico.snd.cdda_div = 65536LL * PicoIn.sndRate / 44100;
\r
197 // clear all buffers
\r
198 memset32(PsndBuffer, 0, sizeof(PsndBuffer)/4);
\r
199 memset(cdda_out_buffer, 0, sizeof(cdda_out_buffer));
\r
204 PsndMix_32_to_16 = (PicoIn.opt & POPT_EN_STEREO) ? mix_32_to_16_stereo : mix_32_to_16_mono;
\r
205 mix_reset(PicoIn.opt & POPT_EN_SNDFILTER ? PicoIn.sndFilterAlpha : 0);
\r
207 if (PicoIn.AHW & PAHW_PICO)
\r
212 PICO_INTERNAL void PsndStartFrame(void)
\r
214 // compensate for float part of Pico.snd.len
\r
215 Pico.snd.len_use = Pico.snd.len;
\r
216 Pico.snd.len_e_cnt += Pico.snd.len_e_add;
\r
217 if (Pico.snd.len_e_cnt >= 0x10000) {
\r
218 Pico.snd.len_e_cnt -= 0x10000;
\r
219 Pico.snd.len_use++;
\r
223 PICO_INTERNAL void PsndDoDAC(int cyc_to)
\r
226 int dout = ym2612.dacout;
\r
228 // nothing to do if sound is off
\r
229 if (!PicoIn.sndOut) return;
\r
231 // number of samples to fill in buffer (Q20)
\r
232 len = (cyc_to * Pico.snd.clkz_mult) - Pico.snd.dac_pos;
\r
234 // update position and calculate buffer offset and length
\r
235 pos = (Pico.snd.dac_pos+0x80000) >> 20;
\r
236 Pico.snd.dac_pos += len;
\r
237 len = ((Pico.snd.dac_pos+0x80000) >> 20) - pos;
\r
239 // avoid loss of the 1st sample of a new block (Q rounding issues)
\r
241 len = 1, Pico.snd.dac_pos += 0x80000;
\r
245 // fill buffer, applying a rather weak order 1 bessel IIR on the way
\r
246 // y[n] = (x[n] + x[n-1])*(1/2) (3dB cutoff at 11025 Hz, no gain)
\r
247 // 1 sample delay for correct IIR filtering over audio frame boundaries
\r
248 if (PicoIn.opt & POPT_EN_STEREO) {
\r
249 s16 *d = PicoIn.sndOut + pos*2;
\r
250 int pan = ym2612.REGS[YM2612_CH6PAN];
\r
251 int l = pan & 0x80 ? Pico.snd.dac_val : 0;
\r
252 int r = pan & 0x40 ? Pico.snd.dac_val : 0;
\r
253 *d++ += pan & 0x80 ? Pico.snd.dac_val2 : 0;
\r
254 *d++ += pan & 0x40 ? Pico.snd.dac_val2 : 0;
\r
255 while (--len) *d++ += l, *d++ += r;
\r
257 s16 *d = PicoIn.sndOut + pos;
\r
258 *d++ += Pico.snd.dac_val2;
\r
259 while (--len) *d++ += Pico.snd.dac_val;
\r
261 Pico.snd.dac_val2 = (Pico.snd.dac_val + dout) >> 1;
\r
262 Pico.snd.dac_val = dout;
\r
265 PICO_INTERNAL void PsndDoPSG(int cyc_to)
\r
270 // nothing to do if sound is off
\r
271 if (!PicoIn.sndOut) return;
\r
273 // number of samples to fill in buffer (Q20)
\r
274 len = (cyc_to * Pico.snd.clkz_mult) - Pico.snd.psg_pos;
\r
276 // update position and calculate buffer offset and length
\r
277 pos = (Pico.snd.psg_pos+0x80000) >> 20;
\r
278 Pico.snd.psg_pos += len;
\r
279 len = ((Pico.snd.psg_pos+0x80000) >> 20) - pos;
\r
283 if (!(PicoIn.opt & POPT_EN_PSG))
\r
286 if (PicoIn.opt & POPT_EN_STEREO) {
\r
290 SN76496Update(PicoIn.sndOut + pos, len, stereo);
\r
293 PICO_INTERNAL void PsndDoSMSFM(int cyc_to)
\r
297 s32 *buf32 = PsndBuffer;
\r
298 s16 *buf = PicoIn.sndOut;
\r
300 // nothing to do if sound is off
\r
301 if (!PicoIn.sndOut) return;
\r
303 // number of samples to fill in buffer (Q20)
\r
304 len = (cyc_to * Pico.snd.clkz_mult) - Pico.snd.ym2413_pos;
\r
306 // update position and calculate buffer offset and length
\r
307 pos = (Pico.snd.ym2413_pos+0x80000) >> 20;
\r
308 Pico.snd.ym2413_pos += len;
\r
309 len = ((Pico.snd.ym2413_pos+0x80000) >> 20) - pos;
\r
313 if (!(PicoIn.opt & POPT_EN_YM2413))
\r
316 if (PicoIn.opt & POPT_EN_STEREO) {
\r
321 if (Pico.m.hardware & PMS_HW_FMUSED) {
\r
323 PsndFMUpdate(buf32, len, 0, 0);
\r
327 *buf++ += *buf32++;
\r
331 *buf++ += *buf32++;
\r
336 PICO_INTERNAL void PsndDoFM(int cyc_to)
\r
341 // nothing to do if sound is off
\r
342 if (!PicoIn.sndOut) return;
\r
344 // Q20, number of samples since last call
\r
345 len = (cyc_to * Pico.snd.clkz_mult) - Pico.snd.fm_pos;
\r
347 // update position and calculate buffer offset and length
\r
348 pos = (Pico.snd.fm_pos+0x80000) >> 20;
\r
349 Pico.snd.fm_pos += len;
\r
350 len = ((Pico.snd.fm_pos+0x80000) >> 20) - pos;
\r
355 if (PicoIn.opt & POPT_EN_STEREO) {
\r
359 if (PicoIn.opt & POPT_EN_FM)
\r
360 PsndFMUpdate(PsndBuffer + pos, len, stereo, 1);
\r
363 PICO_INTERNAL void PsndDoPCM(int cyc_to)
\r
368 // nothing to do if sound is off
\r
369 if (!PicoIn.sndOut) return;
\r
371 // Q20, number of samples since last call
\r
372 len = (cyc_to * Pico.snd.clkz_mult) - Pico.snd.pcm_pos;
\r
374 // update position and calculate buffer offset and length
\r
375 pos = (Pico.snd.pcm_pos+0x80000) >> 20;
\r
376 Pico.snd.pcm_pos += len;
\r
377 len = ((Pico.snd.pcm_pos+0x80000) >> 20) - pos;
\r
382 if (PicoIn.opt & POPT_EN_STEREO) {
\r
386 PicoPicoPCMUpdate(PicoIn.sndOut + pos, len, stereo);
\r
390 static void cdda_raw_update(s32 *buffer, int length, int stereo)
\r
392 int ret, cdda_bytes;
\r
394 // apply start offset in frame (offset to 1st lba to play)
\r
395 int offs = Pico_mcd->cdda_frame_offs * Pico.snd.cdda_div >> 16;
\r
397 buffer += offs * (stereo ? 2 : 1);
\r
398 Pico_mcd->cdda_frame_offs = 0;
\r
400 cdda_bytes = (length * Pico.snd.cdda_mult >> 16) * 4;
\r
402 // compute offset of last played sample in this frame (need for save/load)
\r
403 Pico_mcd->m.cdda_lba_offset += cdda_bytes/4;
\r
404 while (Pico_mcd->m.cdda_lba_offset >= 2352/4)
\r
405 Pico_mcd->m.cdda_lba_offset -= 2352/4;
\r
407 ret = pm_read_audio(cdda_out_buffer, cdda_bytes, Pico_mcd->cdda_stream);
\r
408 if (ret < cdda_bytes) {
\r
409 memset((char *)cdda_out_buffer + ret, 0, cdda_bytes - ret);
\r
410 Pico_mcd->cdda_stream = NULL;
\r
414 if (stereo) switch (Pico.snd.cdda_mult) {
\r
415 case 0x10000: mix_16h_to_32(buffer, cdda_out_buffer, length*2); break;
\r
416 case 0x20000: mix_16h_to_32_s1(buffer, cdda_out_buffer, length*2); break;
\r
417 case 0x40000: mix_16h_to_32_s2(buffer, cdda_out_buffer, length*2); break;
\r
418 default: mix_16h_to_32_resample_stereo(buffer, cdda_out_buffer, length, Pico.snd.cdda_mult);
\r
420 mix_16h_to_32_resample_mono(buffer, cdda_out_buffer, length, Pico.snd.cdda_mult);
\r
423 void cdda_start_play(int lba_base, int lba_offset, int lb_len)
\r
425 if (Pico_mcd->cdda_type == CT_MP3)
\r
430 pos1024 = lba_offset * 1024 / lb_len;
\r
432 mp3_start_play(Pico_mcd->cdda_stream, pos1024);
\r
436 // on restart after loading, consider offset of last played sample
\r
437 pm_seek(Pico_mcd->cdda_stream, (lba_base + lba_offset) * 2352 +
\r
438 Pico_mcd->m.cdda_lba_offset * 4, SEEK_SET);
\r
439 if (Pico_mcd->cdda_type == CT_WAV)
\r
441 // skip headers, assume it's 44kHz stereo uncompressed
\r
442 pm_seek(Pico_mcd->cdda_stream, 44, SEEK_CUR);
\r
447 PICO_INTERNAL void PsndClear(void)
\r
449 int len = Pico.snd.len;
\r
450 if (Pico.snd.len_e_add) len++;
\r
452 // drop pos remainder to avoid rounding errors (not entirely correct though)
\r
453 Pico.snd.dac_pos = Pico.snd.fm_pos = Pico.snd.psg_pos = Pico.snd.ym2413_pos = Pico.snd.pcm_pos = 0;
\r
454 if (!PicoIn.sndOut) return;
\r
456 if (PicoIn.opt & POPT_EN_STEREO)
\r
457 memset32((int *) PicoIn.sndOut, 0, len); // assume PicoIn.sndOut to be aligned
\r
459 s16 *out = PicoIn.sndOut;
\r
460 if ((uintptr_t)out & 2) { *out++ = 0; len--; }
\r
461 memset32((int *) out, 0, len/2);
\r
462 if (len & 1) out[len-1] = 0;
\r
464 if (!(PicoIn.opt & POPT_EN_FM))
\r
465 memset32(PsndBuffer, 0, PicoIn.opt & POPT_EN_STEREO ? len*2 : len);
\r
469 static int PsndRender(int offset, int length)
\r
472 int stereo = (PicoIn.opt & 8) >> 3;
\r
473 int fmlen = ((Pico.snd.fm_pos+0x80000) >> 20);
\r
474 int daclen = ((Pico.snd.dac_pos+0x80000) >> 20);
\r
475 int psglen = ((Pico.snd.psg_pos+0x80000) >> 20);
\r
476 int pcmlen = ((Pico.snd.pcm_pos+0x80000) >> 20);
\r
478 buf32 = PsndBuffer+(offset<<stereo);
\r
480 pprof_start(sound);
\r
482 // Add in parts of the PSG output not yet done
\r
483 if (length-psglen > 0 && PicoIn.sndOut) {
\r
484 s16 *psgbuf = PicoIn.sndOut + (psglen << stereo);
\r
485 Pico.snd.psg_pos += (length-psglen) << 20;
\r
486 if (PicoIn.opt & POPT_EN_PSG)
\r
487 SN76496Update(psgbuf, length-psglen, stereo);
\r
490 if (PicoIn.AHW & PAHW_PICO) {
\r
491 // always need to render sound for interrupts
\r
492 s16 *buf16 = PicoIn.sndOut ? PicoIn.sndOut + (pcmlen<<stereo) : NULL;
\r
493 PicoPicoPCMUpdate(buf16, length-pcmlen, stereo);
\r
497 // Fill up DAC output in case of missing samples (Q rounding errors)
\r
498 if (length-daclen > 0 && PicoIn.sndOut) {
\r
499 Pico.snd.dac_pos += (length-daclen) << 20;
\r
500 if (PicoIn.opt & POPT_EN_STEREO) {
\r
501 s16 *d = PicoIn.sndOut + daclen*2;
\r
502 int pan = ym2612.REGS[YM2612_CH6PAN];
\r
503 int l = pan & 0x80 ? Pico.snd.dac_val : 0;
\r
504 int r = pan & 0x40 ? Pico.snd.dac_val : 0;
\r
505 *d++ += pan & 0x80 ? Pico.snd.dac_val2 : 0;
\r
506 *d++ += pan & 0x40 ? Pico.snd.dac_val2 : 0;
\r
507 if (l|r) for (daclen++; length-daclen > 0; daclen++)
\r
508 *d++ += l, *d++ += r;
\r
510 s16 *d = PicoIn.sndOut + daclen;
\r
511 *d++ += Pico.snd.dac_val2;
\r
512 if (Pico.snd.dac_val) for (daclen++; length-daclen > 0; daclen++)
\r
513 *d++ += Pico.snd.dac_val;
\r
515 Pico.snd.dac_val2 = Pico.snd.dac_val;
\r
518 // Add in parts of the FM buffer not yet done
\r
519 if (length-fmlen > 0 && PicoIn.sndOut) {
\r
520 s32 *fmbuf = buf32 + ((fmlen-offset) << stereo);
\r
521 Pico.snd.fm_pos += (length-fmlen) << 20;
\r
522 if (PicoIn.opt & POPT_EN_FM)
\r
523 PsndFMUpdate(fmbuf, length-fmlen, stereo, 1);
\r
527 if (PicoIn.AHW & PAHW_MCD) {
\r
528 pcd_pcm_update(buf32, length-offset, stereo);
\r
532 // CD mode, cdda enabled, not data track, CDC is reading
\r
533 if ((PicoIn.AHW & PAHW_MCD) && (PicoIn.opt & POPT_EN_MCD_CDDA)
\r
534 && Pico_mcd->cdda_stream != NULL
\r
535 && (!(Pico_mcd->s68k_regs[0x36] & 1) || Pico_msd.state == 3))
\r
537 if (Pico_mcd->cdda_type == CT_MP3)
\r
538 mp3_update(buf32, length-offset, stereo);
\r
540 cdda_raw_update(buf32, length-offset, stereo);
\r
543 if ((PicoIn.AHW & PAHW_32X) && (PicoIn.opt & POPT_EN_PWM))
\r
544 p32x_pwm_update(buf32, length-offset, stereo);
\r
546 // convert + limit to normal 16bit output
\r
548 PsndMix_32_to_16(PicoIn.sndOut+(offset<<stereo), buf32, length-offset);
\r
555 PICO_INTERNAL void PsndGetSamples(int y)
\r
557 static int curr_pos = 0;
\r
559 curr_pos = PsndRender(0, Pico.snd.len_use);
\r
561 if (PicoIn.writeSound && PicoIn.sndOut)
\r
562 PicoIn.writeSound(curr_pos * ((PicoIn.opt & POPT_EN_STEREO) ? 4 : 2));
\r
563 // clear sound buffer
\r
567 static int PsndRenderMS(int offset, int length)
\r
569 s32 *buf32 = PsndBuffer;
\r
570 int stereo = (PicoIn.opt & 8) >> 3;
\r
571 int psglen = ((Pico.snd.psg_pos+0x80000) >> 20);
\r
572 int ym2413len = ((Pico.snd.ym2413_pos+0x80000) >> 20);
\r
574 if (!PicoIn.sndOut)
\r
577 pprof_start(sound);
\r
579 // Add in parts of the PSG output not yet done
\r
580 if (length-psglen > 0) {
\r
581 s16 *psgbuf = PicoIn.sndOut + (psglen << stereo);
\r
582 Pico.snd.psg_pos += (length-psglen) << 20;
\r
583 if (PicoIn.opt & POPT_EN_PSG)
\r
584 SN76496Update(psgbuf, length-psglen, stereo);
\r
587 if (length-ym2413len > 0) {
\r
588 s16 *ym2413buf = PicoIn.sndOut + (ym2413len << stereo);
\r
589 Pico.snd.ym2413_pos += (length-ym2413len) << 20;
\r
590 int len = (length-ym2413len);
\r
591 if (Pico.m.hardware & PMS_HW_FMUSED) {
\r
592 PsndFMUpdate(buf32, len, 0, 0);
\r
595 *ym2413buf++ += *buf32;
\r
596 *ym2413buf++ += *buf32++;
\r
600 *ym2413buf++ += *buf32++;
\r
610 PICO_INTERNAL void PsndGetSamplesMS(int y)
\r
612 static int curr_pos = 0;
\r
614 curr_pos = PsndRenderMS(0, Pico.snd.len_use);
\r
616 if (PicoIn.writeSound != NULL && PicoIn.sndOut)
\r
617 PicoIn.writeSound(curr_pos * ((PicoIn.opt & POPT_EN_STEREO) ? 4 : 2));
\r
621 // vim:shiftwidth=2:ts=2:expandtab
\r