diff options
author | Filippos Karapetis | 2013-09-24 11:25:21 +0300 |
---|---|---|
committer | Filippos Karapetis | 2013-09-24 11:30:46 +0300 |
commit | eb84b9fc02976c220b4b1d573d8ca84041de2e49 (patch) | |
tree | 4764999248c42c8d21bed02942bc4bee465ee0b5 | |
parent | 8fe2fe8a15a6f3db61f7dbe101ef5edd64b51d46 (diff) | |
download | scummvm-rg350-eb84b9fc02976c220b4b1d573d8ca84041de2e49.tar.gz scummvm-rg350-eb84b9fc02976c220b4b1d573d8ca84041de2e49.tar.bz2 scummvm-rg350-eb84b9fc02976c220b4b1d573d8ca84041de2e49.zip |
MT-32: Update to munt 1.3.0
-rw-r--r-- | audio/softsynth/mt32/LA32FloatWaveGenerator.cpp | 28 | ||||
-rw-r--r-- | audio/softsynth/mt32/LA32WaveGenerator.cpp | 51 | ||||
-rw-r--r-- | audio/softsynth/mt32/LA32WaveGenerator.h | 2 | ||||
-rw-r--r-- | audio/softsynth/mt32/Partial.cpp | 119 | ||||
-rw-r--r-- | audio/softsynth/mt32/Partial.h | 15 | ||||
-rw-r--r-- | audio/softsynth/mt32/Synth.cpp | 52 | ||||
-rw-r--r-- | audio/softsynth/mt32/Synth.h | 11 |
7 files changed, 173 insertions, 105 deletions
diff --git a/audio/softsynth/mt32/LA32FloatWaveGenerator.cpp b/audio/softsynth/mt32/LA32FloatWaveGenerator.cpp index 486942b75c..0c9687b4d8 100644 --- a/audio/softsynth/mt32/LA32FloatWaveGenerator.cpp +++ b/audio/softsynth/mt32/LA32FloatWaveGenerator.cpp @@ -315,15 +315,29 @@ void LA32PartialPair::generateNextSample(const PairType useMaster, const Bit32u } } +static inline float produceDistortedSample(float sample) { + if (sample < -1.0f) { + return sample + 2.0f; + } else if (1.0f < sample) { + return sample - 2.0f; + } + return sample; +} + float LA32PartialPair::nextOutSample() { - float outputSample; - if (ringModulated) { - float ringModulatedSample = masterOutputSample * slaveOutputSample; - outputSample = mixed ? masterOutputSample + ringModulatedSample : ringModulatedSample; - } else { - outputSample = masterOutputSample + slaveOutputSample; + if (!ringModulated) { + return masterOutputSample + slaveOutputSample; } - return outputSample; + /* + * SEMI-CONFIRMED: Ring modulation model derived from sample analysis of specially constructed patches which exploit distortion. + * LA32 ring modulator found to produce distorted output in case if the absolute value of maximal amplitude of one of the input partials exceeds 8191. + * This is easy to reproduce using synth partials with resonance values close to the maximum. It looks like an integer overflow happens in this case. + * As the distortion is strictly bound to the amplitude of the complete mixed square + resonance wave in the linear space, + * it is reasonable to assume the ring modulation is performed also in the linear space by sample multiplication. + * Most probably the overflow is caused by limited precision of the multiplication circuit as the very similar distortion occurs with panning. + */ + float ringModulatedSample = produceDistortedSample(masterOutputSample) * produceDistortedSample(slaveOutputSample); + return mixed ? masterOutputSample + ringModulatedSample : ringModulatedSample; } void LA32PartialPair::deactivate(const PairType useMaster) { diff --git a/audio/softsynth/mt32/LA32WaveGenerator.cpp b/audio/softsynth/mt32/LA32WaveGenerator.cpp index 9ffc2ca32e..1d115c12ef 100644 --- a/audio/softsynth/mt32/LA32WaveGenerator.cpp +++ b/audio/softsynth/mt32/LA32WaveGenerator.cpp @@ -370,18 +370,12 @@ void LA32PartialPair::generateNextSample(const PairType useMaster, const Bit32u } } -Bit16s LA32PartialPair::unlogAndMixWGOutput(const LA32WaveGenerator &wg, const LogSample * const ringModulatingLogSample) { - if (!wg.isActive() || ((ringModulatingLogSample != NULL) && (ringModulatingLogSample->logValue == SILENCE.logValue))) { +Bit16s LA32PartialPair::unlogAndMixWGOutput(const LA32WaveGenerator &wg) { + if (!wg.isActive()) { return 0; } - LogSample firstLogSample = wg.getOutputLogSample(true); - LogSample secondLogSample = wg.getOutputLogSample(false); - if (ringModulatingLogSample != NULL) { - LA32Utilites::addLogSamples(firstLogSample, *ringModulatingLogSample); - LA32Utilites::addLogSamples(secondLogSample, *ringModulatingLogSample); - } - Bit16s firstSample = LA32Utilites::unlog(firstLogSample); - Bit16s secondSample = LA32Utilites::unlog(secondLogSample); + Bit16s firstSample = LA32Utilites::unlog(wg.getOutputLogSample(true)); + Bit16s secondSample = LA32Utilites::unlog(wg.getOutputLogSample(false)); if (wg.isPCMWave()) { return Bit16s(firstSample + ((Bit32s(secondSample - firstSample) * wg.getPCMInterpolationFactor()) >> 7)); } @@ -389,19 +383,32 @@ Bit16s LA32PartialPair::unlogAndMixWGOutput(const LA32WaveGenerator &wg, const L } Bit16s LA32PartialPair::nextOutSample() { - if (ringModulated) { - LogSample slaveFirstLogSample = slave.getOutputLogSample(true); - LogSample slaveSecondLogSample = slave.getOutputLogSample(false); - Bit16s sample = unlogAndMixWGOutput(master, &slaveFirstLogSample); - if (!slave.isPCMWave()) { - sample += unlogAndMixWGOutput(master, &slaveSecondLogSample); - } - if (mixed) { - sample += unlogAndMixWGOutput(master, NULL); - } - return sample; + if (!ringModulated) { + return unlogAndMixWGOutput(master) + unlogAndMixWGOutput(slave); } - return unlogAndMixWGOutput(master, NULL) + unlogAndMixWGOutput(slave, NULL); + + /* + * SEMI-CONFIRMED: Ring modulation model derived from sample analysis of specially constructed patches which exploit distortion. + * LA32 ring modulator found to produce distorted output in case if the absolute value of maximal amplitude of one of the input partials exceeds 8191. + * This is easy to reproduce using synth partials with resonance values close to the maximum. It looks like an integer overflow happens in this case. + * As the distortion is strictly bound to the amplitude of the complete mixed square + resonance wave in the linear space, + * it is reasonable to assume the ring modulation is performed also in the linear space by sample multiplication. + * Most probably the overflow is caused by limited precision of the multiplication circuit as the very similar distortion occurs with panning. + */ + Bit16s nonOverdrivenMasterSample = unlogAndMixWGOutput(master); // Store master partial sample for further mixing + Bit16s masterSample = nonOverdrivenMasterSample << 2; + masterSample >>= 2; + + /* SEMI-CONFIRMED from sample analysis: + * We observe that for partial structures with ring modulation the interpolation is not applied to the slave PCM partial. + * It's assumed that the multiplication circuitry intended to perform the interpolation on the slave PCM partial + * is borrowed by the ring modulation circuit (or the LA32 chip has a similar lack of resources assigned to each partial pair). + */ + Bit16s slaveSample = slave.isPCMWave() ? LA32Utilites::unlog(slave.getOutputLogSample(true)) : unlogAndMixWGOutput(slave); + slaveSample <<= 2; + slaveSample >>= 2; + Bit16s ringModulatedSample = Bit16s(((Bit32s)masterSample * (Bit32s)slaveSample) >> 13); + return mixed ? nonOverdrivenMasterSample + ringModulatedSample : ringModulatedSample; } void LA32PartialPair::deactivate(const PairType useMaster) { diff --git a/audio/softsynth/mt32/LA32WaveGenerator.h b/audio/softsynth/mt32/LA32WaveGenerator.h index 4bc04c78d3..b5f4dedff4 100644 --- a/audio/softsynth/mt32/LA32WaveGenerator.h +++ b/audio/softsynth/mt32/LA32WaveGenerator.h @@ -209,7 +209,7 @@ class LA32PartialPair { bool ringModulated; bool mixed; - static Bit16s unlogAndMixWGOutput(const LA32WaveGenerator &wg, const LogSample * const ringModulatingLogSample); + static Bit16s unlogAndMixWGOutput(const LA32WaveGenerator &wg); public: enum PairType { diff --git a/audio/softsynth/mt32/Partial.cpp b/audio/softsynth/mt32/Partial.cpp index c7848f02d8..75e674074f 100644 --- a/audio/softsynth/mt32/Partial.cpp +++ b/audio/softsynth/mt32/Partial.cpp @@ -24,15 +24,10 @@ namespace MT32Emu { -#ifdef INACCURATE_SMOOTH_PAN -// Mok wanted an option for smoother panning, and we love Mok. -static const float PAN_NUMERATOR_NORMAL[] = {0.0f, 0.5f, 1.0f, 1.5f, 2.0f, 2.5f, 3.0f, 3.5f, 4.0f, 4.5f, 5.0f, 5.5f, 6.0f, 6.5f, 7.0f}; -#else -// CONFIRMED by Mok: These NUMERATOR values (as bytes, not floats, obviously) are sent exactly like this to the LA32. -static const float PAN_NUMERATOR_NORMAL[] = {0.0f, 0.0f, 1.0f, 1.0f, 2.0f, 2.0f, 3.0f, 3.0f, 4.0f, 4.0f, 5.0f, 5.0f, 6.0f, 6.0f, 7.0f}; -#endif -static const float PAN_NUMERATOR_MASTER[] = {0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f}; -static const float PAN_NUMERATOR_SLAVE[] = {0.0f, 1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 7.0f, 7.0f, 7.0f, 7.0f, 7.0f, 7.0f, 7.0f}; +static const Bit8u PAN_NUMERATOR_MASTER[] = {0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7}; +static const Bit8u PAN_NUMERATOR_SLAVE[] = {0, 1, 2, 3, 4, 5, 6, 7, 7, 7, 7, 7, 7, 7, 7}; + +static const Bit32s PAN_FACTORS[] = {0, 18, 37, 55, 73, 91, 110, 128, 146, 165, 183, 201, 219, 238, 256}; Partial::Partial(Synth *useSynth, int useDebugPartialNum) : synth(useSynth), debugPartialNum(useDebugPartialNum), sampleNum(0) { @@ -116,24 +111,30 @@ void Partial::startPartial(const Part *part, Poly *usePoly, const PatchCache *us structurePosition = patchCache->structurePosition; Bit8u panSetting = rhythmTemp != NULL ? rhythmTemp->panpot : part->getPatchTemp()->panpot; - float panVal; if (mixType == 3) { if (structurePosition == 0) { - panVal = PAN_NUMERATOR_MASTER[panSetting]; + panSetting = PAN_NUMERATOR_MASTER[panSetting] << 1; } else { - panVal = PAN_NUMERATOR_SLAVE[panSetting]; + panSetting = PAN_NUMERATOR_SLAVE[panSetting] << 1; } // Do a normal mix independent of any pair partial. mixType = 0; pairPartial = NULL; } else { - panVal = PAN_NUMERATOR_NORMAL[panSetting]; + // Mok wanted an option for smoother panning, and we love Mok. +#ifndef INACCURATE_SMOOTH_PAN + // CONFIRMED by Mok: exactly bytes like this (right shifted?) are sent to the LA32. + panSetting &= 0x0E; +#endif } - // FIXME: Sample analysis suggests that the use of panVal is linear, but there are some some quirks that still need to be resolved. - // FIXME: I suppose this should be panVal / 8 and undoubtly integer, clarify ASAP - stereoVolume.leftVol = panVal / 7.0f; - stereoVolume.rightVol = 1.0f - stereoVolume.leftVol; + leftPanValue = synth->reversedStereoEnabled ? 14 - panSetting : panSetting; + rightPanValue = 14 - leftPanValue; + +#if !MT32EMU_USE_FLOAT_SAMPLES + leftPanValue = PAN_FACTORS[leftPanValue]; + rightPanValue = PAN_FACTORS[rightPanValue]; +#endif // SEMI-CONFIRMED: From sample analysis: // Found that timbres with 3 or 4 partials (i.e. one using two partial pairs) are mixed in two different ways. @@ -150,8 +151,8 @@ void Partial::startPartial(const Part *part, Poly *usePoly, const PatchCache *us // For my personal taste, this behaviour rather enriches the sounding and should be emulated. // Also, the current partial allocator model probably needs to be refined. if (debugPartialNum & 8) { - stereoVolume.leftVol = -stereoVolume.leftVol; - stereoVolume.rightVol = -stereoVolume.rightVol; + leftPanValue = -leftPanValue; + rightPanValue = -rightPanValue; } if (patchCache->PCMPartial) { @@ -230,39 +231,6 @@ Bit32u Partial::getCutoffValue() { return (tvf->getBaseCutoff() << 18) + cutoffModifierRampVal; } -unsigned long Partial::generateSamples(Sample *partialBuf, unsigned long length) { - if (!isActive() || alreadyOutputed) { - return 0; - } - if (poly == NULL) { - synth->printDebug("[Partial %d] *** ERROR: poly is NULL at Partial::generateSamples()!", debugPartialNum); - return 0; - } - alreadyOutputed = true; - - for (sampleNum = 0; sampleNum < length; sampleNum++) { - if (!tva->isPlaying() || !la32Pair.isActive(LA32PartialPair::MASTER)) { - deactivate(); - break; - } - la32Pair.generateNextSample(LA32PartialPair::MASTER, getAmpValue(), tvp->nextPitch(), getCutoffValue()); - if (hasRingModulatingSlave()) { - la32Pair.generateNextSample(LA32PartialPair::SLAVE, pair->getAmpValue(), pair->tvp->nextPitch(), pair->getCutoffValue()); - if (!pair->tva->isPlaying() || !la32Pair.isActive(LA32PartialPair::SLAVE)) { - pair->deactivate(); - if (mixType == 2) { - deactivate(); - break; - } - } - } - *(partialBuf++) = la32Pair.nextOutSample(); - } - unsigned long renderedSamples = sampleNum; - sampleNum = 0; - return renderedSamples; -} - bool Partial::hasRingModulatingSlave() const { return pair != NULL && structurePosition == 0 && (mixType == 1 || mixType == 2); } @@ -305,19 +273,52 @@ bool Partial::produceOutput(Sample *leftBuf, Sample *rightBuf, unsigned long len synth->printDebug("[Partial %d] *** ERROR: poly is NULL at Partial::produceOutput()!", debugPartialNum); return false; } - Sample buffer[MAX_SAMPLES_PER_RUN]; - unsigned long numGenerated = generateSamples(buffer, length); - for (unsigned int i = 0; i < numGenerated; i++) { + alreadyOutputed = true; + + for (sampleNum = 0; sampleNum < length; sampleNum++) { + if (!tva->isPlaying() || !la32Pair.isActive(LA32PartialPair::MASTER)) { + deactivate(); + break; + } + la32Pair.generateNextSample(LA32PartialPair::MASTER, getAmpValue(), tvp->nextPitch(), getCutoffValue()); + if (hasRingModulatingSlave()) { + la32Pair.generateNextSample(LA32PartialPair::SLAVE, pair->getAmpValue(), pair->tvp->nextPitch(), pair->getCutoffValue()); + if (!pair->tva->isPlaying() || !la32Pair.isActive(LA32PartialPair::SLAVE)) { + pair->deactivate(); + if (mixType == 2) { + deactivate(); + break; + } + } + } + + // Although, LA32 applies panning itself, we assume here it is applied in the mixer, not within a pair. + // Applying the pan value in the log-space looks like a waste of unlog resources. Though, it needs clarification. + Sample sample = la32Pair.nextOutSample(); + + // FIXME: Sample analysis suggests that the use of panVal is linear, but there are some quirks that still need to be resolved. #if MT32EMU_USE_FLOAT_SAMPLES - *(leftBuf++) += buffer[i] * stereoVolume.leftVol; - *(rightBuf++) += buffer[i] * stereoVolume.rightVol; + Sample leftOut = (sample * (float)leftPanValue) / 14.0f; + Sample rightOut = (sample * (float)rightPanValue) / 14.0f; + *(leftBuf++) += leftOut; + *(rightBuf++) += rightOut; #else - *leftBuf = Synth::clipBit16s((Bit32s)*leftBuf + Bit32s(buffer[i] * stereoVolume.leftVol)); - *rightBuf = Synth::clipBit16s((Bit32s)*rightBuf + Bit32s(buffer[i] * stereoVolume.rightVol)); + // FIXME: Dividing by 7 (or by 14 in a Mok-friendly way) looks of course pointless. Need clarification. + // FIXME2: LA32 may produce distorted sound in case if the absolute value of maximal amplitude of the input exceeds 8191 + // when the panning value is non-zero. Most probably the distortion occurs in the same way it does with ring modulation, + // and it seems to be caused by limited precision of the common multiplication circuit. + // From analysis of this overflow, it is obvious that the right channel output is actually found + // by subtraction of the left channel output from the input. + // Though, it is unknown whether this overflow is exploited somewhere. + Sample leftOut = Sample((sample * leftPanValue) >> 8); + Sample rightOut = Sample((sample * rightPanValue) >> 8); + *leftBuf = Synth::clipBit16s((Bit32s)*leftBuf + (Bit32s)leftOut); + *rightBuf = Synth::clipBit16s((Bit32s)*rightBuf + (Bit32s)rightOut); leftBuf++; rightBuf++; #endif } + sampleNum = 0; return true; } diff --git a/audio/softsynth/mt32/Partial.h b/audio/softsynth/mt32/Partial.h index 358cb9d2d9..05c1c740c4 100644 --- a/audio/softsynth/mt32/Partial.h +++ b/audio/softsynth/mt32/Partial.h @@ -25,24 +25,22 @@ class Part; class TVA; struct ControlROMPCMStruct; -struct StereoVolume { - float leftVol; - float rightVol; -}; - // A partial represents one of up to four waveform generators currently playing within a poly. class Partial { private: Synth *synth; const int debugPartialNum; // Only used for debugging - // Number of the sample currently being rendered by generateSamples(), or 0 if no run is in progress + // Number of the sample currently being rendered by produceOutput(), or 0 if no run is in progress // This is only kept available for debugging purposes. unsigned long sampleNum; + // Actually, this is a 4-bit register but we abuse this to emulate inverted mixing. + // Also we double the value to enable INACCURATE_SMOOTH_PAN, with respect to MoK. + Bit32s leftPanValue, rightPanValue; + int ownerPart; // -1 if unassigned int mixType; int structurePosition; // 0 or 1 of a structure pair - StereoVolume stereoVolume; // Only used for PCM partials int pcmNum; @@ -103,9 +101,6 @@ public: // This function (unlike the one below it) returns processed stereo samples // made from combining this single partial with its pair, if it has one. bool produceOutput(Sample *leftBuf, Sample *rightBuf, unsigned long length); - - // This function writes mono sample output to the provided buffer, and returns the number of samples written - unsigned long generateSamples(Sample *partialBuf, unsigned long length); }; } diff --git a/audio/softsynth/mt32/Synth.cpp b/audio/softsynth/mt32/Synth.cpp index b76dc58b5f..efba9b7514 100644 --- a/audio/softsynth/mt32/Synth.cpp +++ b/audio/softsynth/mt32/Synth.cpp @@ -76,6 +76,7 @@ Synth::Synth(ReportHandler *useReportHandler) { isOpen = false; reverbEnabled = true; reverbOverridden = false; + partialCount = DEFAULT_MAX_PARTIALS; if (useReportHandler == NULL) { reportHandler = new ReportHandler; @@ -95,6 +96,7 @@ Synth::Synth(ReportHandler *useReportHandler) { setMIDIDelayMode(MIDIDelayMode_DELAY_SHORT_MESSAGES_ONLY); setOutputGain(1.0f); setReverbOutputGain(1.0f); + setReversedStereoEnabled(false); partialManager = NULL; midiQueue = NULL; lastReceivedMIDIEventTimestamp = 0; @@ -172,6 +174,8 @@ MIDIDelayMode Synth::getMIDIDelayMode() const { return midiDelayMode; } +#if MT32EMU_USE_FLOAT_SAMPLES + void Synth::setOutputGain(float newOutputGain) { outputGain = newOutputGain; } @@ -188,6 +192,39 @@ float Synth::getReverbOutputGain() const { return reverbOutputGain; } +#else // #if MT32EMU_USE_FLOAT_SAMPLES + +void Synth::setOutputGain(float newOutputGain) { + if (newOutputGain < 0.0f) newOutputGain = -newOutputGain; + if (256.0f < newOutputGain) newOutputGain = 256.0f; + outputGain = int(newOutputGain * 256.0f); +} + +float Synth::getOutputGain() const { + return outputGain / 256.0f; +} + +void Synth::setReverbOutputGain(float newReverbOutputGain) { + if (newReverbOutputGain < 0.0f) newReverbOutputGain = -newReverbOutputGain; + float maxValue = 256.0f / CM32L_REVERB_TO_LA32_ANALOG_OUTPUT_GAIN_FACTOR; + if (maxValue < newReverbOutputGain) newReverbOutputGain = maxValue; + reverbOutputGain = int(newReverbOutputGain * 256.0f); +} + +float Synth::getReverbOutputGain() const { + return reverbOutputGain / 256.0f; +} + +#endif // #if MT32EMU_USE_FLOAT_SAMPLES + +void Synth::setReversedStereoEnabled(bool enabled) { + reversedStereoEnabled = enabled; +} + +bool Synth::isReversedStereoEnabled() { + return reversedStereoEnabled; +} + bool Synth::loadControlROM(const ROMImage &controlROMImage) { if (&controlROMImage == NULL) return false; Common::File *file = controlROMImage.getFile(); @@ -1445,16 +1482,19 @@ void Synth::convertSamplesToOutput(Sample *target, const Sample *source, Bit32u *(target++) = *(source++) * gain; } #else - float gain = reverb ? reverbOutputGain * CM32L_REVERB_TO_LA32_ANALOG_OUTPUT_GAIN_FACTOR : outputGain; - if (!reverb) { + int gain; + if (reverb) { + gain = int(reverbOutputGain * CM32L_REVERB_TO_LA32_ANALOG_OUTPUT_GAIN_FACTOR); + } else { + gain = outputGain; switch (dacInputMode) { case DACInputMode_NICE: // Since we're not shooting for accuracy here, don't worry about the rounding mode. - gain *= 2.0f; + gain <<= 1; break; case DACInputMode_GENERATION1: while (len--) { - *target = clipBit16s(Bit32s(*source * gain)); + *target = clipBit16s(Bit32s((*source * gain) >> 8)); *target = (*target & 0x8000) | ((*target << 1) & 0x7FFE); source++; target++; @@ -1462,7 +1502,7 @@ void Synth::convertSamplesToOutput(Sample *target, const Sample *source, Bit32u return; case DACInputMode_GENERATION2: while (len--) { - *target = clipBit16s(Bit32s(*source * gain)); + *target = clipBit16s(Bit32s((*source * gain) >> 8)); *target = (*target & 0x8000) | ((*target << 1) & 0x7FFE) | ((*target >> 14) & 0x0001); source++; target++; @@ -1473,7 +1513,7 @@ void Synth::convertSamplesToOutput(Sample *target, const Sample *source, Bit32u } } while (len--) { - *(target++) = clipBit16s(Bit32s(*(source++) * gain)); + *(target++) = clipBit16s(Bit32s((*(source++) * gain) >> 8)); } #endif } diff --git a/audio/softsynth/mt32/Synth.h b/audio/softsynth/mt32/Synth.h index 783d6e2747..8816711bf3 100644 --- a/audio/softsynth/mt32/Synth.h +++ b/audio/softsynth/mt32/Synth.h @@ -343,8 +343,16 @@ private: MIDIDelayMode midiDelayMode; DACInputMode dacInputMode; + +#if MT32EMU_USE_FLOAT_SAMPLES float outputGain; float reverbOutputGain; +#else + int outputGain; + int reverbOutputGain; +#endif + + bool reversedStereoEnabled; bool isOpen; @@ -477,6 +485,9 @@ public: void setReverbOutputGain(float); float getReverbOutputGain() const; + void setReversedStereoEnabled(bool enabled); + bool isReversedStereoEnabled(); + // Renders samples to the specified output stream. // The length is in frames, not bytes (in 16-bit stereo, // one frame is 4 bytes). |