blob: 34bd49c4993bf74d6f05982acf744adcbc8af9fc [file] [log] [blame]
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <cmath>
#include <cstdint>
#include "garnet/examples/media/tones/tone_generator.h"
#include "lib/fxl/logging.h"
namespace examples {
ToneGenerator::ToneGenerator(uint32_t frames_per_second, float frequency,
float volume, float decay)
: frames_per_second_(frames_per_second),
frequency_(frequency),
volume_(volume),
decay_factor_(pow(1.0f - decay, 1.0f / frames_per_second)),
real_sample_(0.0f),
imaginary_sample_(1.0f) {}
void ToneGenerator::MixSamples(float* dest, uint32_t frame_count,
uint32_t channel_count) {
// We're using the 'slope iteration method' here to avoid calling |sin| for
// every sample or having to build a lookup table. While this method is
// theoretically correct, rounding errors will cause the resulting wave to
// deviate from the results we would get using |sin|. We get the best results
// when the wave frequency is much lower than the sample frequency. Given
// that we're producing a low-frequency transient tone with decay, the results
// are reasonable.
//
// The principle is that |realSample| and |imaginarySample| are x and y
// values on a unit circle centered on the origin. We start with 0,1 and
// rotate the point slightly around the origin for each sample. We use only
// the real values, which we scale to get the desired amplitude.
float constant = (2.0f * M_PI * frequency_) / frames_per_second_;
float realSample = real_sample_;
float imaginarySample = imaginary_sample_;
float volume = volume_;
for (uint32_t i = 0; i < frame_count; i++) {
// Note that we're only producing one channel here, as descibed in the
// documentation for the method.
*dest += realSample * volume;
// Rotate |realSample|,|imaginarySample| around the origin.
realSample -= imaginarySample * constant;
imaginarySample += realSample * constant;
volume = volume * decay_factor_;
dest += channel_count;
}
// Capture these values so we pick up where we left off.
real_sample_ = realSample;
imaginary_sample_ = imaginarySample;
volume_ = volume;
}
} // namespace examples