Changeset 179618 in webkit


Ignore:
Timestamp:
Feb 4, 2015 11:37:36 AM (9 years ago)
Author:
jer.noble@apple.com
Message:

[WebAudio] AudioBufferSourceNodes should accurately play backwards if given a negative playbackRate.
https://bugs.webkit.org/show_bug.cgi?id=140955

Reviewed by Eric Carlson.

Source/WebCore:

Tests: webaudio/audiobuffersource-negative-playbackrate-interpolated.html

webaudio/audiobuffersource-negative-playbackrate.html

Add support for playing an AudioBufferSourceNode at a negative playbackRate. Change the meaning of
start() to set the initial playback position at the end of the play range if the rate of playback
is negtive.

  • Modules/webaudio/AudioBufferSourceNode.cpp:

(WebCore::AudioBufferSourceNode::AudioBufferSourceNode): Allow the playbackRate AudioParam to range from [-32, 32].
(WebCore::AudioBufferSourceNode::renderFromBuffer): Change variable names from "start" and "end" to "min" and "max"

for clarity. Add a non-interpolated and interpolated render step for negative playback.

(WebCore::AudioBufferSourceNode::start): Drive-by fix: default value of grainDuration is not 0.02.
(WebCore::AudioBufferSourceNode::startPlaying): Start playing at the end of the buffer for negative playback.
(WebCore::AudioBufferSourceNode::totalPitchRate): Allow the pitch to be negative.

LayoutTests:

  • webaudio/audiobuffersource-negative-playbackrate-expected.wav: Added.
  • webaudio/audiobuffersource-negative-playbackrate-interpolated-expected.wav: Added.
  • webaudio/audiobuffersource-negative-playbackrate-interpolated.html: Added.
  • webaudio/audiobuffersource-negative-playbackrate.html: Added.

Get rid of extra HRTF padding as it's now unnecessary.

  • webaudio/resources/note-grain-on-testing.js:

(createSignalBuffer):
(verifyStartAndEndFrames):

Location:
trunk
Files:
4 added
4 edited

Legend:

Unmodified
Added
Removed
  • trunk/LayoutTests/ChangeLog

    r179612 r179618  
     12015-02-04  Jer Noble  <jer.noble@apple.com>
     2
     3        [WebAudio] AudioBufferSourceNodes should accurately play backwards if given a negative playbackRate.
     4        https://bugs.webkit.org/show_bug.cgi?id=140955
     5
     6        Reviewed by Eric Carlson.
     7
     8        * webaudio/audiobuffersource-negative-playbackrate-expected.wav: Added.
     9        * webaudio/audiobuffersource-negative-playbackrate-interpolated-expected.wav: Added.
     10        * webaudio/audiobuffersource-negative-playbackrate-interpolated.html: Added.
     11        * webaudio/audiobuffersource-negative-playbackrate.html: Added.
     12
     13        Get rid of extra HRTF padding as it's now unnecessary.
     14
     15        * webaudio/resources/note-grain-on-testing.js:
     16        (createSignalBuffer):
     17        (verifyStartAndEndFrames):
     18
    1192015-02-04  Eric Carlson  <eric.carlson@apple.com>
    220
  • trunk/LayoutTests/webaudio/resources/note-grain-on-testing.js

    r129260 r179618  
    11var sampleRate = 44100.0;
    2 
    3 // HRTF extra frames.  This is a magic constant currently in
    4 // AudioBufferSourceNode::process that always extends the
    5 // duration by this number of samples.  See bug 77224
    6 // (https://bugs.webkit.org/show_bug.cgi?id=77224).
    7 var extraFramesHRTF = 512;
    82
    93// How many grains to play.
     
    137var duration = 0.01;
    148
    15 // Time step between the start of each grain.  We need to add a little
    16 // bit of silence so we can detect grain boundaries and also account
    17 // for the extra frames for HRTF.
    18 var timeStep = duration + .005 + extraFramesHRTF / sampleRate;
     9// Time step between the start of each grain.
     10var timeStep = duration + .005;
    1911
    2012// Time step between the start for each grain.
     
    3426    // grain offsets and durations.  Need to include the extra frames
    3527    // for HRTF.  The additional 1 is for any round-off errors.
    36     var signalLength = Math.floor(1 + extraFramesHRTF + sampleRate * (numberOfTests * grainOffsetStep + duration));
     28    var signalLength = Math.floor(1 + sampleRate * (numberOfTests * grainOffsetStep + duration));
    3729
    3830    var buffer = context.createBuffer(2, signalLength, sampleRate);
     
    136128        // The end point is the duration, plus the extra frames
    137129        // for HRTF.
    138         var expectedEnd = extraFramesHRTF + expectedStart + grainLengthInSampleFrames(k * grainOffsetStep, duration, sampleRate);
     130        var expectedEnd = expectedStart + grainLengthInSampleFrames(k * grainOffsetStep, duration, sampleRate);
    139131
    140132        if (startFrames[k] != expectedStart) {
  • trunk/Source/WebCore/ChangeLog

    r179612 r179618  
     12015-02-04  Jer Noble  <jer.noble@apple.com>
     2
     3        [WebAudio] AudioBufferSourceNodes should accurately play backwards if given a negative playbackRate.
     4        https://bugs.webkit.org/show_bug.cgi?id=140955
     5
     6        Reviewed by Eric Carlson.
     7
     8        Tests: webaudio/audiobuffersource-negative-playbackrate-interpolated.html
     9               webaudio/audiobuffersource-negative-playbackrate.html
     10
     11        Add support for playing an AudioBufferSourceNode at a negative playbackRate. Change the meaning of
     12        start() to set the initial playback position at the end of the play range if the rate of playback
     13        is negtive.
     14
     15        * Modules/webaudio/AudioBufferSourceNode.cpp:
     16        (WebCore::AudioBufferSourceNode::AudioBufferSourceNode): Allow the playbackRate AudioParam to range from [-32, 32].
     17        (WebCore::AudioBufferSourceNode::renderFromBuffer): Change variable names from "start" and "end" to "min" and "max"
     18            for clarity. Add a non-interpolated and interpolated render step for negative playback.
     19        (WebCore::AudioBufferSourceNode::start): Drive-by fix: default value of grainDuration is not 0.02.
     20        (WebCore::AudioBufferSourceNode::startPlaying): Start playing at the end of the buffer for negative playback.
     21        (WebCore::AudioBufferSourceNode::totalPitchRate): Allow the pitch to be negative.
     22
    1232015-02-04  Eric Carlson  <eric.carlson@apple.com>
    224
  • trunk/Source/WebCore/Modules/webaudio/AudioBufferSourceNode.cpp

    r177733 r179618  
    7171
    7272    m_gain = AudioParam::create(context, "gain", 1.0, 0.0, 1.0);
    73     m_playbackRate = AudioParam::create(context, "playbackRate", 1.0, 0.0, MaxRate);
     73    m_playbackRate = AudioParam::create(context, "playbackRate", 1.0, -MaxRate, MaxRate);
    7474
    7575    // Default to mono.  A call to setBuffer() will set the number of output channels to that of the buffer.
     
    201201    size_t bufferLength = buffer()->length();
    202202    double bufferSampleRate = buffer()->sampleRate();
     203    double pitchRate = totalPitchRate();
     204    bool reverse = pitchRate < 0;
    203205
    204206    // Avoid converting from time to sample-frames twice by computing
    205207    // the grain end time first before computing the sample frame.
    206     unsigned endFrame = m_isGrain ? AudioUtilities::timeToSampleFrame(m_grainOffset + m_grainDuration, bufferSampleRate) : bufferLength;
    207    
    208     // This is a HACK to allow for HRTF tail-time - avoids glitch at end.
    209     // FIXME: implement tailTime for each AudioNode for a more general solution to this problem.
    210     // https://bugs.webkit.org/show_bug.cgi?id=77224
     208    unsigned maxFrame;
    211209    if (m_isGrain)
    212         endFrame += 512;
     210        maxFrame = AudioUtilities::timeToSampleFrame(m_grainOffset + m_grainDuration, bufferSampleRate);
     211    else
     212        maxFrame = bufferLength;
    213213
    214214    // Do some sanity checking.
    215     if (endFrame > bufferLength)
    216         endFrame = bufferLength;
    217     if (m_virtualReadIndex >= endFrame)
     215    if (maxFrame > bufferLength)
     216        maxFrame = bufferLength;
     217    if (reverse && m_virtualReadIndex <= 0)
     218        m_virtualReadIndex = maxFrame;
     219    else if (!reverse && m_virtualReadIndex >= maxFrame)
    218220        m_virtualReadIndex = 0; // reset to start
    219221
    220222    // If the .loop attribute is true, then values of m_loopStart == 0 && m_loopEnd == 0 implies
    221223    // that we should use the entire buffer as the loop, otherwise use the loop values in m_loopStart and m_loopEnd.
    222     double virtualEndFrame = endFrame;
    223     double virtualDeltaFrames = endFrame;
     224    double virtualMaxFrame = maxFrame;
     225    double virtualMinFrame = 0;
     226    double virtualDeltaFrames = maxFrame;
    224227
    225228    if (loop() && (m_loopStart || m_loopEnd) && m_loopStart >= 0 && m_loopEnd > 0 && m_loopStart < m_loopEnd) {
    226229        // Convert from seconds to sample-frames.
    227         double loopStartFrame = m_loopStart * buffer()->sampleRate();
    228         double loopEndFrame = m_loopEnd * buffer()->sampleRate();
    229 
    230         virtualEndFrame = std::min(loopEndFrame, virtualEndFrame);
    231         virtualDeltaFrames = virtualEndFrame - loopStartFrame;
    232     }
    233 
    234 
    235     double pitchRate = totalPitchRate();
     230        double loopMinFrame = m_loopStart * buffer()->sampleRate();
     231        double loopMaxFrame = m_loopEnd * buffer()->sampleRate();
     232
     233        virtualMaxFrame = std::min(loopMaxFrame, virtualMaxFrame);
     234        virtualMinFrame = std::max(loopMinFrame, virtualMinFrame);
     235        virtualDeltaFrames = virtualMaxFrame - virtualMinFrame;
     236    }
     237
    236238
    237239    // Sanity check that our playback rate isn't larger than the loop size.
    238     if (pitchRate >= virtualDeltaFrames)
     240    if (fabs(pitchRate) >= virtualDeltaFrames)
    239241        return false;
    240242
     
    242244    double virtualReadIndex = m_virtualReadIndex;
    243245
     246    bool needsInterpolation = virtualReadIndex != floor(virtualReadIndex)
     247        || virtualDeltaFrames != floor(virtualDeltaFrames)
     248        || virtualMaxFrame != floor(virtualMaxFrame)
     249        || virtualMinFrame != floor(virtualMinFrame);
     250
    244251    // Render loop - reading from the source buffer to the destination using linear interpolation.
    245252    int framesToProcess = numberOfFrames;
     
    250257    // Optimize for the very common case of playing back with pitchRate == 1.
    251258    // We can avoid the linear interpolation.
    252     if (pitchRate == 1 && virtualReadIndex == floor(virtualReadIndex)
    253         && virtualDeltaFrames == floor(virtualDeltaFrames)
    254         && virtualEndFrame == floor(virtualEndFrame)) {
     259    if (pitchRate == 1 && !needsInterpolation) {
    255260        unsigned readIndex = static_cast<unsigned>(virtualReadIndex);
    256261        unsigned deltaFrames = static_cast<unsigned>(virtualDeltaFrames);
    257         endFrame = static_cast<unsigned>(virtualEndFrame);
     262        maxFrame = static_cast<unsigned>(virtualMaxFrame);
    258263        while (framesToProcess > 0) {
    259             int framesToEnd = endFrame - readIndex;
     264            int framesToEnd = maxFrame - readIndex;
    260265            int framesThisTime = std::min(framesToProcess, framesToEnd);
    261266            framesThisTime = std::max(0, framesThisTime);
     
    269274
    270275            // Wrap-around.
    271             if (readIndex >= endFrame) {
     276            if (readIndex >= maxFrame) {
    272277                readIndex -= deltaFrames;
    273278                if (renderSilenceAndFinishIfNotLooping(bus, writeIndex, framesToProcess))
     
    276281        }
    277282        virtualReadIndex = readIndex;
     283    } else if (pitchRate == -1 && !needsInterpolation) {
     284        unsigned readIndex = static_cast<unsigned>(virtualReadIndex);
     285        unsigned deltaFrames = static_cast<unsigned>(virtualDeltaFrames);
     286        unsigned minFrame = static_cast<unsigned>(virtualMinFrame);
     287        while (framesToProcess > 0) {
     288            unsigned framesToEnd = readIndex - minFrame;
     289            unsigned framesThisTime = std::min<unsigned>(framesToProcess, framesToEnd);
     290            framesThisTime = std::max<unsigned>(0, framesThisTime);
     291
     292            while (framesThisTime--) {
     293                for (unsigned i = 0; i < numberOfChannels; ++i) {
     294                    float* destination = destinationChannels[i];
     295                    const float* source = sourceChannels[i];
     296
     297                    destination[writeIndex] = source[readIndex];
     298                }
     299
     300                ++writeIndex;
     301                --readIndex;
     302                --framesToProcess;
     303            }
     304
     305            // Wrap-around.
     306            if (!readIndex) {
     307                readIndex = deltaFrames;
     308                if (renderSilenceAndFinishIfNotLooping(bus, writeIndex, framesToProcess))
     309                    break;
     310            }
     311        }
     312        virtualReadIndex = readIndex;
     313    } else if (!pitchRate) {
     314        unsigned readIndex = static_cast<unsigned>(virtualReadIndex);
     315
     316        for (unsigned i = 0; i < numberOfChannels; ++i)
     317            std::fill_n(destinationChannels[i], framesToProcess, sourceChannels[i][readIndex]);
     318    } else if (reverse) {
     319        while (framesToProcess--) {
     320            unsigned readIndex = static_cast<unsigned>(virtualReadIndex);
     321            double interpolationFactor = virtualReadIndex - readIndex;
     322
     323            unsigned readIndex2;
     324            if (!readIndex) {
     325                if (loop())
     326                    readIndex2 = static_cast<unsigned>(virtualReadIndex + virtualDeltaFrames + 1);
     327                else
     328                    readIndex2 = readIndex;
     329            } else
     330                readIndex2 = readIndex - 1;
     331
     332            // Linear interpolation.
     333            for (unsigned i = 0; i < numberOfChannels; ++i) {
     334                float* destination = destinationChannels[i];
     335                const float* source = sourceChannels[i];
     336
     337                double sample1 = source[readIndex];
     338                double sample2 = source[readIndex2];
     339                double sample = (1.0 - interpolationFactor) * sample1 + interpolationFactor * sample2;
     340
     341                destination[writeIndex] = narrowPrecisionToFloat(sample);
     342            }
     343
     344            writeIndex++;
     345
     346            virtualReadIndex += pitchRate;
     347
     348            // Wrap-around, retaining sub-sample position since virtualReadIndex is floating-point.
     349            if (virtualReadIndex <= virtualMinFrame) {
     350                virtualReadIndex += virtualDeltaFrames;
     351                if (renderSilenceAndFinishIfNotLooping(bus, writeIndex, framesToProcess))
     352                    break;
     353            }
     354        }
    278355    } else {
    279356        while (framesToProcess--) {
     
    312389
    313390            // Wrap-around, retaining sub-sample position since virtualReadIndex is floating-point.
    314             if (virtualReadIndex >= virtualEndFrame) {
     391            if (virtualReadIndex >= virtualMaxFrame) {
    315392                virtualReadIndex -= virtualDeltaFrames;
    316393                if (renderSilenceAndFinishIfNotLooping(bus, writeIndex, framesToProcess))
     
    383460void AudioBufferSourceNode::start(double when, double grainOffset, ExceptionCode& ec)
    384461{
    385     startPlaying(Partial, when, grainOffset, 0, ec);
     462    startPlaying(Partial, when, grainOffset, buffer() ? buffer()->duration() - grainOffset : 0, ec);
    386463}
    387464
     
    432509    } else {
    433510        m_grainOffset = 0.0;
    434         m_grainDuration = DefaultGrainDuration;
     511        m_grainDuration = buffer()->duration();
    435512    }
    436513
     
    441518    // When aligned to the sample-frame the playback will be identical to the PCM data stored in the buffer.
    442519    // Since playbackRate == 1 is very common, it's worth considering quality.
    443     m_virtualReadIndex = AudioUtilities::timeToSampleFrame(m_grainOffset, buffer()->sampleRate());
     520    double grainStart = totalPitchRate() < 0 ? m_grainOffset + m_grainDuration : m_grainOffset;
     521    m_virtualReadIndex = AudioUtilities::timeToSampleFrame(grainStart, buffer()->sampleRate());
    444522   
    445523    m_playbackState = SCHEDULED_STATE;
     
    472550    double totalRate = dopplerRate * sampleRateFactor * basePitchRate;
    473551
    474     // Sanity check the total rate.  It's very important that the resampler not get any bad rate values.
    475     totalRate = std::max(0.0, totalRate);
    476     if (!totalRate)
    477         totalRate = 1; // zero rate is considered illegal
    478     totalRate = std::min(MaxRate, totalRate);
     552    totalRate = std::max(-MaxRate, std::min(MaxRate, totalRate));
    479553   
    480554    bool isTotalRateValid = !std::isnan(totalRate) && !std::isinf(totalRate);
Note: See TracChangeset for help on using the changeset viewer.