Skip to content

Commit 23315f2

Browse files
committed
Added SuperSaw oscillator
1 parent 4e8d136 commit 23315f2

3 files changed

Lines changed: 261 additions & 0 deletions

File tree

LibSource/SuperSaw.hpp

Lines changed: 204 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,204 @@
1+
#ifndef __SuperSaw_h__
2+
#define __SuperSaw_h__
3+
4+
5+
/**
6+
* This class is a SuperSaw wave oscillator using multiple free-running non-bandlimited sawtooth oscillators (7 by default).
7+
*
8+
* Controls for mix and detune are provided.
9+
*
10+
* Note: these controls are linear, unlike in the Roland JP-8000.
11+
*
12+
* Main reference is <a href="https://www.nada.kth.se/utbildning/grukth/exjobb/rapportlistor/2010/rapporter10/szabo_adam_10131.pdf">https://www.nada.kth.se/utbildning/grukth/exjobb/rapportlistor/2010/rapporter10/szabo_adam_10131.pdf</a>
13+
*/
14+
15+
class SuperSaw{
16+
private:
17+
enum lastCalls {
18+
kNone,
19+
kSetDetune,
20+
kSetFrequency,
21+
kUpdatePhaseIncrements,
22+
};
23+
int lastCall=kNone;
24+
float samplePeriod;
25+
float detune;
26+
float* frequencyRatios;
27+
float* phaseIncrements;
28+
float* amplitudes;
29+
float* phases;
30+
float* allTheArrays=NULL;
31+
BiquadFilter *filter=NULL;
32+
float frequency;
33+
int numberOfOscillators;
34+
bool filterBypass;
35+
void init(){
36+
frequency=0;
37+
numberOfOscillators=7;
38+
filterBypass=false;
39+
allocate();
40+
setDetune(0);
41+
setMix(0);
42+
}
43+
44+
void destroy(){
45+
free(allTheArrays);
46+
if(filter!=NULL){
47+
BiquadFilter::destroy(filter);
48+
filter=NULL;
49+
}
50+
}
51+
52+
void allocate(){
53+
destroy();
54+
int numArrays=4;
55+
float size=sizeof(float)*numberOfOscillators*numArrays;
56+
allTheArrays=(float*)malloc(size);
57+
memset(allTheArrays,0,size);
58+
frequencyRatios=allTheArrays;
59+
phaseIncrements=allTheArrays+numberOfOscillators*1;
60+
amplitudes=allTheArrays+numberOfOscillators*2;
61+
phases=allTheArrays+numberOfOscillators*3;
62+
float filterStages=2;
63+
filter=BiquadFilter::create(filterStages);
64+
}
65+
66+
void setFilter(float aFrequency){
67+
float cutoff=aFrequency*samplePeriod*0.5;
68+
filter->setHighPass(cutoff, FilterStage::BUTTERWORTH_Q);
69+
}
70+
71+
void updatePhaseIncrements(){
72+
lastCall=kUpdatePhaseIncrements;
73+
for(int n=0; n<numberOfOscillators; n++){
74+
phaseIncrements[n]=frequencyRatios[n]*frequency*samplePeriod * 2;// the *2 multiplier takes into account that the oscillator range is -1 to 1
75+
}
76+
}
77+
public:
78+
/**
79+
* Default constructor.
80+
* @remarks Before using the oscillator you will need to initialize the sample rate with setSampleRate().
81+
*/
82+
SuperSaw(){
83+
init();
84+
}
85+
/**
86+
* Constructor.
87+
* @param[in] aSampleRate the sample rate of the system
88+
*/
89+
SuperSaw(float aSampleRate){
90+
setSampleRate(aSampleRate);
91+
init();
92+
}
93+
/** Destructor
94+
*/
95+
~SuperSaw(){
96+
destroy();
97+
}
98+
99+
/**
100+
* Set the sample rate
101+
* @param[in] aSampleRate the sampling rate of the system
102+
*/
103+
void setSampleRate(float aSampleRate){
104+
samplePeriod=1/aSampleRate;
105+
}
106+
107+
/**
108+
* Set the number of oscillators
109+
* @param[in] aNumberOfOscillators the number of oscillators.
110+
* @remark This method allocates memory. You should avoid calling it while processing audio.
111+
*/
112+
void setNumOscillators(int aNumberOfOscillators){
113+
if(numberOfOscillators==aNumberOfOscillators){ //nothing to do
114+
return;
115+
}
116+
numberOfOscillators=aNumberOfOscillators;
117+
allocate();
118+
}
119+
120+
/**
121+
* Set the frequency.
122+
* @param[in] aFrequency The frequency of the oscillator.
123+
*/
124+
void setFrequency(float aFrequency){
125+
lastCall=kSetFrequency;
126+
frequency=aFrequency;
127+
setFilter(aFrequency);
128+
}
129+
130+
/**
131+
* Set the mix.
132+
* Set the mix parametem that is the mix between the oscillator at the base frequency and the detuned oscillators.
133+
* @param[in] aMix the mix parameter. Values will be clipped in the range 0-1
134+
*/
135+
void setMix(float aMix){
136+
if(aMix>1) {
137+
aMix=1;
138+
} else if (aMix<0) {
139+
aMix=0;
140+
}
141+
float norm=1.0f/(numberOfOscillators-1);
142+
float detunedAmplitude=aMix*norm;
143+
for(int n=0; n<numberOfOscillators; n++){
144+
amplitudes[n]=detunedAmplitude;
145+
}
146+
amplitudes[numberOfOscillators/2]=1-aMix;
147+
debugMessage("detunedamplitude", detunedAmplitude, 1-aMix);
148+
}
149+
150+
/**
151+
* Set the detune.
152+
* @param[in] aDetune the detune parameter.
153+
*/
154+
void setDetune(float aDetune){
155+
lastCall=kSetDetune;
156+
for(int n=0; n<numberOfOscillators; n++){
157+
float spread=(n-(int)(numberOfOscillators/2))/(float)numberOfOscillators; //this will be ==0 for the middle value of n, that is the middle oscillator will always be in tune
158+
float frequencyRatio=1+aDetune*spread;
159+
frequencyRatios[n]=1/frequencyRatio;
160+
}
161+
}
162+
163+
/**
164+
* Get the oscillator samples
165+
* @param[out] output The array where the output is to be written
166+
*/
167+
void getSamples(FloatArray& output){
168+
getSamples((float*)output, output.getSize());
169+
}
170+
171+
/*
172+
* Bypasses the built-in high pass filter.
173+
* Sets the state of the built-in high pass filter tuned at the fundamental frequency, which was used
174+
* in the original Roland design to attenuate aliasing below the fundamental frequency.
175+
* @param bypass If set to *true* the filter will be bypassed, if set to *false* the filter will be applied (default).
176+
*/
177+
void setFilterBypass(bool bypass){
178+
filterBypass=bypass;
179+
}
180+
/**
181+
* Get the oscillator samples
182+
* @param[out] output Pointer to a location in memory where the output is to be written
183+
* @param[in] size Number of samples to be written.
184+
*/
185+
void getSamples(float* output, int size){
186+
if(lastCall!=kUpdatePhaseIncrements){ //if the frequencies have not been updated after setDetune or setFrequency
187+
updatePhaseIncrements(); //this ensures that updatePhaseIncrements gets called at most once when frequency and/or detune are set
188+
//and regardless of the order in which they have been called.
189+
}
190+
for (int i=0; i<size; i++){
191+
output[i]=0;
192+
for(int n=0; n<numberOfOscillators; n++){
193+
output[i]+=phases[n]*amplitudes[n];
194+
phases[n]+=phaseIncrements[n];
195+
if(phases[n]>=1)
196+
phases[n]-=2; // 2 reflects the fact that the oscillator range is -1 to 1
197+
}
198+
}
199+
if(filterBypass==false){
200+
filter->process(output, output, size); //highpass filter
201+
}
202+
}
203+
};
204+
#endif // __SuperSaw_h__

Source/PatchProgram.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
#include "BiquadFilter.hpp"
1111
#include "Resample.h"
1212
#include "PitchDetector.hpp"
13+
#include "SuperSaw.hpp"
1314
#include "patch.h"
1415
#include "main.h"
1516

TestPatches/SuperSawTestPatch.hpp

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
////////////////////////////////////////////////////////////////////////////////////////////////////
2+
3+
/*
4+
5+
6+
LICENSE:
7+
This program is free software: you can redistribute it and/or modify
8+
it under the terms of the GNU General Public License as published by
9+
the Free Software Foundation, either version 3 of the License, or
10+
(at your option) any later version.
11+
12+
This program is distributed in the hope that it will be useful,
13+
but WITHOUT ANY WARRANTY; without even the implied warranty of
14+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15+
GNU General Public License for more details.
16+
17+
You should have received a copy of the GNU General Public License
18+
along with this program. If not, see <http://www.gnu.org/licenses/>.
19+
20+
*/
21+
22+
23+
/* created by the OWL team 2015 */
24+
25+
26+
////////////////////////////////////////////////////////////////////////////////////////////////////
27+
28+
29+
#ifndef __SuperSawTestPatch_hpp__
30+
#define __SuperSawTestPatch_hpp__
31+
32+
#include "StompBox.h"
33+
34+
class SuperSawTestPatch : public Patch {
35+
public:
36+
SuperSaw ss;
37+
SuperSawTestPatch(){
38+
ss.setSampleRate(getSampleRate());
39+
}
40+
void processAudio(AudioBuffer &buffer){
41+
static int count=0;
42+
count++;
43+
float gain=getParameterValue(PARAMETER_A);
44+
float frequency=powf(2,5*getParameterValue(PARAMETER_B))*100;
45+
float detune=getParameterValue(PARAMETER_C);
46+
float mix=getParameterValue(PARAMETER_D);
47+
FloatArray fa=buffer.getSamples(0);
48+
ss.setMix(mix);
49+
ss.setFrequency(frequency);
50+
ss.setDetune(detune);
51+
ss.getSamples(fa);
52+
fa.scale(gain);
53+
}
54+
};
55+
56+
#endif // __SuperSawTestPatch_hpp__

0 commit comments

Comments
 (0)