Skip to content

Commit 6a2bbb7

Browse files
authored
Merge pull request #96 from pingdynasty/feature/synth-updates
Feature/synth updates
2 parents 7a5b5b6 + 2adde87 commit 6a2bbb7

42 files changed

Lines changed: 1171 additions & 327 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

HISTORY.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,16 @@
11
v22.1
22
-----
33

4+
* Added Sample oscillator
5+
* Added Agnesi curve oscillator
6+
* Added MPE processor
7+
* Added VOSIM oscillator
48
* Added methods for sample-rate cutoff frequency modulation in StateVariableFilter and BiquadFilter
59
* Added VelocityCurve, MidiProcessor, Synth and AbstractSynth
610
* New templates for MIDI voicing: PolyphonicProcessor and MonophonicProcessor
711
* Added WavFile for reading wav resources and converting to FloatArray
12+
* Changed enum for BUTTON_A, B, C to BUTTON_1, 2, 3 et c.
13+
* Fix FloatMatrix compilation
814

915
v22.0
1016
-----

LibSource/AbstractSynth.h

Lines changed: 19 additions & 158 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ class AbstractSynth : public Synth, public MidiProcessor, public VelocityCurve {
2222
/**
2323
* Set note in whole semitones and update frequency
2424
*/
25-
void setNote(uint8_t note){
25+
virtual void setNote(uint8_t note){
2626
this->note = note;
2727
setFrequency(noteToFrequency(note+pb));
2828
}
@@ -35,7 +35,7 @@ class AbstractSynth : public Synth, public MidiProcessor, public VelocityCurve {
3535
/**
3636
* Set pitch bend amount in semitones and update frequency
3737
*/
38-
void setPitchBend(float pb){
38+
virtual void setPitchBend(float pb){
3939
this->pb = pb;
4040
setFrequency(noteToFrequency(note+pb));
4141
}
@@ -53,25 +53,35 @@ class AbstractSynth : public Synth, public MidiProcessor, public VelocityCurve {
5353
this->pb_range = range;
5454
}
5555
// MIDI handlers
56-
virtual void noteOn(MidiMessage msg){
56+
virtual void noteOn(MidiMessage msg) override {
5757
setNote(msg.getNote());
5858
setFrequency(noteToFrequency(note+pb));
5959
setGain(velocityToGain(msg.getVelocity()));
6060
gate(true);
6161
}
62-
virtual void noteOff(MidiMessage msg){
62+
virtual void noteOff(MidiMessage msg) override {
6363
gate(false);
6464
}
65-
virtual void controlChange(MidiMessage msg){
66-
if(msg.getControllerNumber() == MIDI_ALL_NOTES_OFF)
65+
virtual void controlChange(MidiMessage msg) override {
66+
if(msg.getControllerNumber() == MIDI_CC_MODULATION)
67+
setModulation(msg.getControllerValue()/127.0f);
68+
else if(msg.getControllerNumber() == MIDI_ALL_NOTES_OFF)
6769
allNotesOff();
6870
}
69-
virtual void allNotesOff(){
70-
gate(false);
71+
virtual void channelPressure(MidiMessage msg) override {
72+
setPressure(msg.getChannelPressure()/127.0f);
73+
}
74+
virtual void polyKeyPressure(MidiMessage msg) override {
75+
setPressure(msg.getPolyKeyPressure()/127.0f);
7176
}
72-
virtual void pitchbend(MidiMessage msg){
77+
virtual void setModulation(float modulation){} // default implementation does nothing
78+
virtual void setPressure(float pressure){}
79+
virtual void pitchbend(MidiMessage msg) override {
7380
setPitchBend(pb_range*msg.getPitchBend()/8192.0f);
7481
}
82+
virtual void allNotesOff(){
83+
gate(false);
84+
}
7585
// static utility methods
7686
static inline float frequencyToNote(float freq){
7787
return 12 * log2f(freq / 440) + 69;
@@ -80,154 +90,5 @@ class AbstractSynth : public Synth, public MidiProcessor, public VelocityCurve {
8090
return 440 * exp2f((note - 69) / 12);
8191
}
8292
};
83-
84-
template<class SynthVoice, int VOICES>
85-
class PolyphonicProcessor : public MidiProcessor {
86-
private:
87-
static const uint16_t TAKEN = 0xffff;
88-
SynthVoice* voice[VOICES];
89-
uint8_t notes[VOICES];
90-
uint16_t allocation[VOICES];
91-
uint16_t allocated;
92-
FloatArray buffer;
93-
protected:
94-
void take(uint8_t ch, MidiMessage msg){
95-
release(ch);
96-
notes[ch] = msg.getNote();
97-
allocation[ch] = TAKEN;
98-
voice[ch]->noteOn(msg);
99-
}
100-
void release(uint8_t ch){
101-
allocation[ch] = ++allocated;
102-
voice[ch]->gate(false);
103-
}
104-
public:
105-
PolyphonicProcessor(FloatArray buffer) : allocated(0), buffer(buffer) {}
106-
virtual ~PolyphonicProcessor(){};
107-
static PolyphonicProcessor<SynthVoice, VOICES>* create(size_t blocksize){
108-
FloatArray buffer = FloatArray::create(blocksize);
109-
return new PolyphonicProcessor<SynthVoice, VOICES>(buffer);
110-
}
111-
static void destroy(PolyphonicProcessor<SynthVoice, VOICES>* obj){
112-
delete obj->buffer;
113-
delete obj;
114-
}
115-
void noteOn(MidiMessage msg){
116-
uint8_t note = msg.getNote();
117-
uint16_t minval = USHRT_MAX;
118-
uint8_t minidx = 0;
119-
// take oldest free voice, to allow voices to ring out
120-
for(int i=0; i<VOICES; ++i){
121-
if(notes[i] == note){
122-
minidx = i;
123-
break;
124-
}
125-
if(allocation[i] < minval){
126-
minidx = i;
127-
minval = allocation[i];
128-
}
129-
}
130-
// take oldest voice
131-
take(minidx, msg);
132-
}
133-
void noteOff(MidiMessage msg){
134-
uint8_t note = msg.getNote();
135-
for(int i=0; i<VOICES; ++i)
136-
if(notes[i] == note)
137-
release(i);
138-
}
139-
float generate(){
140-
float sample = 0.0f;
141-
for(int i=0; i<VOICES; ++i)
142-
sample += voice[i]->generate();
143-
return sample;
144-
}
145-
void generate(FloatArray output){
146-
voice[0]->generate(output);
147-
for(int i=1; i<VOICES; ++i){
148-
voice[i]->generate(buffer);
149-
output.add(buffer);
150-
}
151-
}
152-
void controlChange(MidiMessage msg){
153-
if(msg.getControllerNumber() == MIDI_ALL_NOTES_OFF)
154-
allNotesOff();
155-
}
156-
void pitchbend(MidiMessage msg){
157-
for(int i=0; i<VOICES; ++i)
158-
voice[i]->pitchbend(msg);
159-
}
160-
void allNotesOn() {
161-
for(int i=0; i<VOICES; ++i)
162-
voice[i]->gate(true);
163-
}
164-
void allNotesOff() {
165-
for(int i=0; i<VOICES; ++i)
166-
voice[i]->gate(false);
167-
}
168-
void setVoice(size_t index, SynthVoice* obj){
169-
if(index < VOICES)
170-
voice[index] = obj;
171-
}
172-
SynthVoice* getVoice(size_t index){
173-
if(index < VOICES)
174-
return voice[index];
175-
return NULL;
176-
}
177-
};
178-
179-
template<class SynthVoice>
180-
class MonophonicProcessor : public MidiProcessor {
181-
private:
182-
SynthVoice* voice;
183-
uint8_t notes[16];
184-
uint8_t lastNote = 0;
185-
public:
186-
MonophonicProcessor(SynthVoice* voice) : voice(voice) {}
187-
virtual void noteOn(MidiMessage msg){
188-
if(lastNote < 16)
189-
notes[lastNote++] = msg.getNote();
190-
voice->noteOn(msg);
191-
}
192-
virtual void noteOff(MidiMessage msg){
193-
uint8_t note = msg.getNote();
194-
int i;
195-
for(i = 0; i < lastNote; ++i) {
196-
if(notes[i] == note)
197-
break;
198-
}
199-
if(lastNote > 1) {
200-
lastNote--;
201-
while (i < lastNote) {
202-
notes[i] = notes[i + 1];
203-
i++;
204-
}
205-
voice->setNote(notes[lastNote - 1]);
206-
}else{
207-
voice->gate(false);
208-
lastNote = 0;
209-
}
210-
}
211-
void allNotesOff() {
212-
voice->gate(false);
213-
lastNote = 0;
214-
}
215-
float generate(){
216-
return voice->generate();
217-
}
218-
void generate(FloatArray output){
219-
voice->generate(output);
220-
}
221-
void controlChange(MidiMessage msg){
222-
if(msg.getControllerNumber() == MIDI_ALL_NOTES_OFF)
223-
allNotesOff();
224-
}
225-
void pitchbend(MidiMessage msg){
226-
voice->pitchbend(msg);
227-
}
228-
SynthVoice* getVoice(){
229-
return voice;
230-
}
231-
};
23293

23394
#endif // __AbstractSynth_h__

LibSource/AgnesiOscillator.h

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
#ifndef __AgnesiOscillator_h
2+
#define __AgnesiOscillator_h
3+
4+
#include "Oscillator.h"
5+
6+
/**
7+
* Oscillator that produces an Agnesi curve (Witch of Agnesi)
8+
* With a=0.5, the output is between near 0 (0.038 for N=5) and 1.
9+
* N sets the x range for one half period, which determines the y offset.
10+
*/
11+
class AgnesiOscillator : public Oscillator {
12+
private:
13+
const float sr;
14+
float a;
15+
float N;
16+
float incr = 0;
17+
float x = 0;
18+
float offset = 0;
19+
float gain = 1;
20+
public:
21+
AgnesiOscillator(float sr, float a, float N)
22+
: sr(sr), a(a), N(N) {}
23+
static float agnesi(float x, float a){
24+
return (8*a*a*a) / (x*x + 4*a*a);
25+
}
26+
void setFrequency(float freq){
27+
incr = 2*N*freq/sr;
28+
}
29+
float getFrequency(){
30+
return incr*sr/(2*N);
31+
}
32+
void setPhase(float phase){
33+
x = N*(phase-M_PI)/M_PI;
34+
}
35+
float getPhase(){
36+
return M_PI*x/N+M_PI;
37+
}
38+
/**
39+
* Normalise offset and gain so that signal is between 0 and 1
40+
*/
41+
void normalise(){
42+
offset = agnesi(N, a);
43+
gain = 1/(agnesi(0, a) - offset);
44+
}
45+
float generate(){
46+
float y = agnesi(x, a);
47+
x += incr;
48+
if(x > N)
49+
x -= 2*N;
50+
return gain*(y-offset);
51+
}
52+
float generate(float fm){
53+
// modulate coefficient 'a' instead of rate
54+
float y = agnesi(x, a+fm);
55+
x += incr;
56+
if(x > N)
57+
x -= 2*N;
58+
return gain*(y-offset);
59+
}
60+
using SignalGenerator::generate;
61+
static AgnesiOscillator* create(float sr, float a=0.5, float N=5){
62+
AgnesiOscillator* osc = new AgnesiOscillator(sr, a, N);
63+
osc->normalise();
64+
return osc;
65+
}
66+
static void destroy(AgnesiOscillator* obj){
67+
delete obj;
68+
}
69+
};
70+
71+
#endif /* __AgnesiOscillator_h */

LibSource/AudioBuffer.h

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
#ifndef __AudioBuffer_h__
2+
#define __AudioBuffer_h__
3+
4+
#include "FloatArray.h"
5+
6+
class AudioBuffer {
7+
public:
8+
virtual ~AudioBuffer();
9+
virtual FloatArray getSamples(int channel) = 0;
10+
virtual int getChannels() = 0;
11+
virtual int getSize() = 0;
12+
virtual void clear() = 0;
13+
virtual void add(AudioBuffer& buffer) = 0;
14+
static AudioBuffer* create(int channels, int samples);
15+
static void destroy(AudioBuffer* buffer);
16+
};
17+
18+
#endif // __AudioBuffer_h__

0 commit comments

Comments
 (0)