int MixBuffer(bool stereo, short[] buf, ref int bufOffset, byte[] data, Offset offset, int rate, int neededSamples, int bufSize, byte volume, byte panning) { int samples; for (samples = 0; samples < neededSamples && offset.int_off < bufSize; ++samples) { var d = (sbyte)data[offset.int_off]; var tmp = d * volume; if (stereo) { buf[bufOffset++] += (short)((tmp * (255 - panning)) >> 7); buf[bufOffset++] += (short)((tmp * panning) >> 7); } else { buf[bufOffset++] += (short)tmp; } // Step to next source sample offset.rem_off += rate; if (offset.rem_off >= FixedPointFractionHelper.FRAC_ONE) { offset.int_off += FixedPointFractionHelper.FracToInt(offset.rem_off); offset.rem_off = (int)(offset.rem_off & FixedPointFractionHelper.FRAC_LO_MASK); } } return(samples); }
int ReadBufferIntern(bool stereo, short[] buffer, int count) { var bufOffset = 0; var numSamples = count; int samples = _stereo ? numSamples / 2 : numSamples; while (samples > 0) { // Handle 'interrupts'. This gives subclasses the chance to adjust the channel data // (e.g. insert new samples, do pitch bending, whatever). if (SingleInterrupt == 0) { SingleInterrupt = _intFreq; Interrupt(); } // Compute how many samples to generate: at most the requested number of samples, // of course, but we may stop earlier when an 'interrupt' is expected. uint nSamples = Math.Min((uint)samples, SingleInterrupt); // Loop over the four channels of the emulated Paula chip for (int voice = 0; voice < NUM_VOICES; voice++) { // No data, or paused -> skip channel if (_voice[voice].data == null || (_voice[voice].period <= 0)) { continue; } // The Paula chip apparently run at 7.0937892 MHz in the PAL // version and at 7.1590905 MHz in the NTSC version. We divide this // by the requested the requested output sampling rate _rate // (typically 44.1 kHz or 22.05 kHz) obtaining the value _periodScale. // This is then divided by the "period" of the channel we are // processing, to obtain the correct output 'rate'. var rate = FixedPointFractionHelper.DoubleToFrac(_periodScale / _voice[voice].period); // Cap the volume _voice[voice].volume = Math.Min((byte)0x40, _voice[voice].volume); var ch = _voice[voice]; var p = buffer; var pOff = bufOffset; int neededSamples = (int)nSamples; // NOTE: A Protracker (or other module format) player might actually // push the offset past the sample length in its interrupt(), in which // case the first mixBuffer() call should not mix anything, and the loop // should be triggered. // Thus, doing an assert(ch.offset.int_off < ch.length) here is wrong. // An example where this happens is a certain Protracker module played // by the OS/2 version of Hopkins FBI. // Mix the generated samples into the output buffer neededSamples -= MixBuffer(stereo, p, ref pOff, ch.data, ch.offset, rate, neededSamples, ch.length, ch.volume, ch.panning); // Wrap around if necessary if (ch.offset.int_off >= ch.length) { // Important: Wrap around the offset *before* updating the voice length. // Otherwise, if length != lengthRepeat we would wrap incorrectly. // Note: If offset >= 2*len ever occurs, the following would be wrong; // instead of subtracting, we then should compute the modulus using "%=". // Since that requires a division and is slow, and shouldn't be necessary // in practice anyway, we only use subtraction. ch.offset.int_off -= ch.length; ch.dmaCount++; ch.data = ch.dataRepeat; ch.length = ch.lengthRepeat; } // If we have not yet generated enough samples, and looping is active: loop! if (neededSamples > 0 && ch.length > 2) { // Repeat as long as necessary. while (neededSamples > 0) { // Mix the generated samples into the output buffer neededSamples -= MixBuffer(stereo, p, ref pOff, ch.data, ch.offset, rate, neededSamples, ch.length, ch.volume, ch.panning); if (ch.offset.int_off >= ch.length) { // Wrap around. See also the note above. ch.offset.int_off -= ch.length; ch.dmaCount++; } } } } bufOffset += _stereo ? (int)nSamples * 2 : (int)nSamples; SingleInterrupt -= nSamples; samples -= (int)nSamples; } return(numSamples); }