xref: /aosp_15_r20/external/oboe/apps/OboeTester/app/src/main/cpp/synth/SimpleVoice.h (revision 05767d913155b055644481607e6fa1e35e2fe72c)
1 /*
2  * Copyright (C) 2016 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #ifndef SYNTHMARK_SIMPLE_VOICE_H
18 #define SYNTHMARK_SIMPLE_VOICE_H
19 
20 #include <cstdint>
21 #include <math.h>
22 #include "SynthTools.h"
23 #include "VoiceBase.h"
24 #include "SawtoothOscillator.h"
25 #include "SawtoothOscillatorDPW.h"
26 #include "SquareOscillatorDPW.h"
27 #include "SineOscillator.h"
28 #include "EnvelopeADSR.h"
29 #include "PitchToFrequency.h"
30 #include "BiquadFilter.h"
31 
32 namespace marksynth {
33 /**
34  * Classic subtractive synthesizer voice with
35  * 2 LFOs, 2 audio oscillators, filter and envelopes.
36  */
37 class SimpleVoice : public VoiceBase
38 {
39 public:
SimpleVoice()40     SimpleVoice()
41     : VoiceBase()
42     , mLfo1()
43     , mOsc1()
44     , mOsc2()
45     , mPitchToFrequency()
46     , mFilter()
47     , mFilterEnvelope()
48     , mAmplitudeEnvelope()
49       // The following values are arbitrary but typical values.
50     , mDetune(1.0001f) // slight phasing
51     , mVibratoDepth(0.03f)
52     , mVibratoRate(6.0f)
53     , mFilterEnvDepth(3000.0f)
54     , mFilterCutoff(400.0f)
55     {
56         mFilter.setQ(2.0);
57         // Randomize attack times to smooth out CPU load for envelope state transitions.
58         mFilterEnvelope.setAttackTime(0.05 + (0.2 * SynthTools::nextRandomDouble()));
59         mFilterEnvelope.setDecayTime(7.0 + (1.0 * SynthTools::nextRandomDouble()));
60         mAmplitudeEnvelope.setAttackTime(0.02 + (0.05 * SynthTools::nextRandomDouble()));
61         mAmplitudeEnvelope.setDecayTime(1.0 + (0.2 * SynthTools::nextRandomDouble()));
62     }
63 
64     virtual ~SimpleVoice() = default;
65 
setPitch(synth_float_t pitch)66     void setPitch(synth_float_t pitch) {
67         mPitch = pitch;
68     }
69 
noteOn(synth_float_t pitch,synth_float_t velocity)70     void noteOn(synth_float_t pitch, synth_float_t velocity) {
71         (void) velocity; // TODO use velocity?
72         mPitch = pitch;
73         mFilterEnvelope.setGate(true);
74         mAmplitudeEnvelope.setGate(true);
75     }
76 
noteOff()77     void noteOff() {
78         mFilterEnvelope.setGate(false);
79         mAmplitudeEnvelope.setGate(false);
80     }
81 
generate(int32_t numFrames)82     void generate(int32_t numFrames) {
83         assert(numFrames <= kSynthmarkFramesPerRender);
84 
85         // LFO #1 - vibrato
86         mLfo1.generate(mVibratoRate, numFrames);
87         synth_float_t *pitches = mBuffer1;
88         SynthTools::scaleOffsetBuffer(mLfo1.output, pitches, numFrames, mVibratoDepth, mPitch);
89         synth_float_t *frequencies = mBuffer2;
90         mPitchToFrequency.generate(pitches, frequencies, numFrames);
91 
92         // OSC #1 - sawtooth
93         mOsc1.generate(frequencies, numFrames);
94 
95         // OSC #2 - detuned square wave oscillator
96         SynthTools::scaleBuffer(frequencies, frequencies, numFrames, mDetune);
97         mOsc2.generate(frequencies, numFrames);
98 
99         // Mix the two oscillators
100         synth_float_t *mixed = frequencies;
101         SynthTools::mixBuffers(mOsc1.output, 0.6, mOsc2.output, 0.4, mixed, numFrames);
102 
103         // Filter envelope
104         mFilterEnvelope.generate(numFrames);
105         synth_float_t *cutoffFrequencies = pitches;  // reuse unneeded buffer
106         SynthTools::scaleOffsetBuffer(mFilterEnvelope.output, cutoffFrequencies, numFrames,
107                                       mFilterEnvDepth, mFilterCutoff);
108 
109         // Biquad resonant low-pass filter
110         mFilter.generate(mixed, cutoffFrequencies, numFrames);
111 
112         // Amplitude ADSR
113         mAmplitudeEnvelope.generate(numFrames);
114         SynthTools::multiplyBuffers(mFilter.output, mAmplitudeEnvelope.output, output, numFrames);
115     }
116 
117 private:
118     SineOscillator mLfo1;
119     SawtoothOscillatorDPW mOsc1;
120     SquareOscillatorDPW mOsc2;
121     PitchToFrequency mPitchToFrequency;
122     BiquadFilter mFilter;
123     EnvelopeADSR mFilterEnvelope;
124     EnvelopeADSR mAmplitudeEnvelope;
125 
126     synth_float_t mDetune;          // frequency scaler
127     synth_float_t mVibratoDepth;    // in semitones
128     synth_float_t mVibratoRate;     // in Hertz
129     synth_float_t mFilterEnvDepth;  // in Hertz
130     synth_float_t mFilterCutoff;    // in Hertz
131 
132     // Buffers for storing signals that are being passed between units.
133     synth_float_t mBuffer1[kSynthmarkFramesPerRender];
134     synth_float_t mBuffer2[kSynthmarkFramesPerRender];
135 };
136 };
137 #endif // SYNTHMARK_SIMPLE_VOICE_H
138