| /* |
| * Copyright 2018 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| #ifndef SHARED_SYNTH_SOUND_H |
| #define SHARED_SYNTH_SOUND_H |
| #include <cstdint> |
| #include <atomic> |
| #include <math.h> |
| #include <memory> |
| #include "IRenderableAudio.h" |
| constexpr float kDefaultFrequency = 440.0; |
| constexpr int32_t kDefaultSampleRate = 48000; |
| constexpr float kPi = M_PI; |
| constexpr float kTwoPi = kPi * 2; |
| constexpr int32_t kNumSineWaves = 5; |
| constexpr float kSustainMultiplier = 0.99999; |
| constexpr float kReleaseMultiplier = 0.999; |
| // Stop playing music below this cutoff |
| constexpr float kMasterAmplitudeCutOff = 0.01; |
| |
| class SynthSound : public IRenderableAudio { |
| |
| public: |
| SynthSound() { |
| |
| } |
| |
| ~SynthSound() { |
| |
| }; |
| |
| void noteOn() { |
| mTrigger = true; // start a note envelope |
| mAmplitudeScaler = kSustainMultiplier; |
| } |
| |
| void noteOff() { |
| mAmplitudeScaler = kReleaseMultiplier; |
| } |
| |
| void setSampleRate(int32_t sampleRate) { |
| mSampleRate = sampleRate; |
| updatePhaseIncrement(); |
| }; |
| void setFrequency(float frequency) { |
| mFrequency = frequency; |
| updatePhaseIncrement(); |
| }; |
| // Amplitudes from https://epubs.siam.org/doi/pdf/10.1137/S00361445003822 |
| inline void setAmplitude(float amplitude) { |
| mAmplitudes[0] = amplitude * .2f; |
| mAmplitudes[1] = amplitude; |
| mAmplitudes[2] = amplitude * .1f; |
| mAmplitudes[3] = amplitude * .02f; |
| mAmplitudes[4] = amplitude * .15f; |
| }; |
| // From IRenderableAudio |
| void renderAudio(float *audioData, int32_t numFrames) override { |
| for (int i = 0; i < numFrames; ++i) { |
| if (mTrigger.exchange(false)) { |
| mMasterAmplitude = 1.0; |
| mPhase = 0.0f; |
| } else { |
| mMasterAmplitude *= mAmplitudeScaler; |
| } |
| |
| audioData[i] = 0; |
| if (mMasterAmplitude < kMasterAmplitudeCutOff) { |
| continue; |
| } |
| for (int j = 0; j < kNumSineWaves; ++j) { |
| audioData[i] += sinf(mPhase * (j + 1)) * mAmplitudes[j] * mMasterAmplitude; |
| } |
| mPhase += mPhaseIncrement; |
| if (mPhase > kTwoPi) { |
| mPhase -= kTwoPi; |
| } |
| } |
| }; |
| |
| private: |
| std::atomic<bool> mTrigger; |
| float mMasterAmplitude = 0.0f; |
| std::atomic<float> mAmplitudeScaler; |
| std::array<std::atomic<float>, kNumSineWaves> mAmplitudes; |
| float mPhase = 0.0f; |
| std::atomic<float> mPhaseIncrement; |
| std::atomic<float> mFrequency { kDefaultFrequency }; |
| std::atomic<int32_t> mSampleRate { kDefaultSampleRate }; |
| void updatePhaseIncrement(){ |
| // Note how there is a division here. If this file is changed so that updatePhaseIncrement |
| // is called more frequently, please cache 1/mSampleRate. This allows this operation to not |
| // need divisions. |
| mPhaseIncrement = kTwoPi * mFrequency / static_cast<float>(mSampleRate); |
| }; |
| }; |
| #endif //SHARED_SYNTH_SOUND_H |