Skip to content

Commit bf13aaa

Browse files
authored
Merge pull request #124 from tomcombriat/MetaOsc
Metaoscillators to non-aliased sounds
2 parents b55c9f1 + d9ff2bf commit bf13aaa

File tree

483 files changed

+27399
-0
lines changed

Some content is hidden

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

483 files changed

+27399
-0
lines changed

MetaOscil.h

Lines changed: 231 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,231 @@
1+
/*
2+
* MetaOscil.h
3+
*
4+
* A wrap-up to swap between different oscillators seemlessly, allowing to produce non-aliased sounds by automatically switching between oscillators.
5+
*
6+
* This file is part of Mozzi.
7+
*/
8+
9+
#ifndef META_OSCIL_H
10+
#define META_OSCIL_H
11+
12+
13+
#if ARDUINO >= 100
14+
#include "Arduino.h"
15+
#else
16+
#include "WProgram.h"
17+
#endif
18+
19+
#include "Oscil.h"
20+
#include "mozzi_fixmath.h"
21+
22+
23+
/**
24+
MetaOscil is a wrapper for several Oscil. Once constructed it will behave exactly as an Oscil except that it will automatically switch between Oscil depending on the asked frequency. This allows to produce non-aliased sounds by switching between tables with less and less harmonics as the frequency increases.
25+
*/
26+
27+
28+
template<uint16_t NUM_TABLE_CELLS, uint16_t UPDATE_RATE, byte N_OSCIL>
29+
class MetaOscil
30+
31+
{
32+
public:
33+
/** Constructor
34+
Declare a MetaOscil containing any number of Oscil pointers. Every Oscil should have the same TABLE_NUM_CELLS and UPDATE_RATE which are also passed in the MetaOscil constructor.
35+
@param N_OSCIL is the number of Oscil contained in the MetaOscil. This cannot be changed after construction. */
36+
template<class... T> MetaOscil(Oscil<NUM_TABLE_CELLS, UPDATE_RATE>* first, T*... elements):oscillators{first, elements...} {
37+
current_osc=oscillators[0];};
38+
39+
MetaOscil(){};
40+
41+
/* Add one oscil to the MetaOscil.
42+
@param osc is a pointer toward an Oscil
43+
@param cutoff_freq is the cutoff frequency of this Oscil
44+
void addOscil(Oscil<NUM_TABLE_CELLS, UPDATE_RATE>* osc, int cutoff_freq)
45+
{
46+
oscillators[current_rank] = osc;
47+
cutoff_freqs[current_rank] = cutoff_freq;
48+
if (current_rank == 0) current_osc=oscillators[0];
49+
current_rank += 1;
50+
}*/
51+
52+
53+
/** Set all Oscil of a MetaOscil.
54+
@param first... is a list of pointers towards several Oscil */
55+
template<typename ... T > void setOscils(Oscil<NUM_TABLE_CELLS, UPDATE_RATE>* first,T... elements)
56+
{
57+
oscillators[current_rank]=first;
58+
if (current_rank == 0) current_osc=oscillators[0];
59+
current_rank+=1;
60+
setOscils(elements...);
61+
current_rank = 0;
62+
}
63+
64+
void setOscils(){};
65+
66+
67+
/** Set all the cutoff frequencies for changing between Oscil. They have to be sorted in increasing values and contain at least N_OSCIL-1 values. Note that the last Oscil will be used by default for frequencies higher than the higher cutoff, hence the last value can be discarded.
68+
@param first, elements... a set of int cutoff frequencies.*/
69+
template<typename ... T > void setCutoffFreqs(int first,T... elements)
70+
{
71+
cutoff_freqs[current_rank]=first;
72+
current_rank+=1;
73+
setCutoffFreqs(elements...);
74+
current_rank = 0;
75+
}
76+
77+
void setCutoffFreqs() {};
78+
79+
/** Set or change the cutoff frequency of one Oscil.
80+
@param rank is the rank of the Oscil.
81+
@param freq is the cutoff frequency. */
82+
void setCutoffFreq(int freq, byte rank)
83+
{
84+
cutoff_freqs[rank] = freq;
85+
}
86+
87+
/** Updates the phase according to the current frequency and returns the sample at the new phase position.
88+
@return the next sample.
89+
*/
90+
inline
91+
int8_t next() {return current_osc->next();}
92+
93+
/** Change the sound table which will be played by the Oscil of rank.
94+
@param TABLE_NAME is the name of the array in the table ".h" file you're using.
95+
@param rank is the Oscil.*/
96+
void setTable(const int8_t * TABLE_NAME, byte rank) {oscillators[rank]->setTable(TABLE_NAME);}
97+
98+
99+
/** Set the phase of the currently playing Oscil.
100+
@param phase a position in the wavetable.*/
101+
void setPhase(unsigned int phase) {current_osc->setPhase(phase);}
102+
103+
104+
/** Set the phase of the currently playing Oscil in fractional format.
105+
@param phase a position in the wavetable.*/
106+
void setPhaseFractional(unsigned long phase) {current_osc->setPhaseFractional(phase);}
107+
108+
109+
/** Get the phase of the currently playin Oscil in fractional format.
110+
@return position in the wavetable, shifted left by OSCIL_F_BITS (which is 16 when this was written).
111+
*/
112+
unsigned long getPhaseFractional() {return current_osc->getPhaseFractional();}
113+
114+
115+
116+
/** Returns the next sample given a phase modulation value.
117+
@param phmod_proportion a phase modulation value given as a proportion of the wave. The
118+
phmod_proportion parameter is a Q15n16 fixed-point number where the fractional
119+
n16 part represents almost -1 to almost 1, modulating the phase by one whole table length in
120+
each direction.
121+
@return a sample from the table.*/
122+
inline
123+
int8_t phMod(Q15n16 phmod_proportion) {return current_osc->phMod(phmod_proportion);}
124+
125+
126+
/** Set the MetaOsc frequency with an unsigned int.
127+
@param frequency to play the wave table.*/
128+
inline
129+
void setFreq(int frequency, bool apply = true)
130+
{
131+
if (frequency < cutoff_freqs[0]) //getting out the extreme cases
132+
{
133+
oscillators[0]->setPhaseFractional(current_osc->getPhaseFractional());
134+
current_osc = oscillators[0];
135+
current_osc->setFreq(frequency);
136+
}
137+
138+
else if (frequency > cutoff_freqs[N_OSCIL-1])
139+
{
140+
oscillators[N_OSCIL-1]->setPhaseFractional(current_osc->getPhaseFractional());
141+
current_osc = oscillators[N_OSCIL-1];
142+
current_osc->setFreq(frequency);
143+
}
144+
else // dichotomic search
145+
{
146+
byte low_point = 0, high_point = N_OSCIL-1, mid_point = (N_OSCIL-1)>>1;
147+
while(low_point != high_point)
148+
{
149+
if (frequency > cutoff_freqs[mid_point]) low_point = mid_point+1;
150+
else if (frequency < cutoff_freqs[mid_point]) high_point = mid_point;
151+
else
152+
{
153+
break;
154+
}
155+
mid_point = (low_point + high_point)>>1;
156+
}
157+
oscillators[mid_point]->setPhaseFractional(current_osc->getPhaseFractional());
158+
current_osc = oscillators[mid_point];
159+
if (apply) current_osc->setFreq(frequency);
160+
}
161+
162+
}
163+
164+
165+
/** Set the MetaOsc frequency with a float.
166+
@param frequency to play the wave table.*/
167+
inline
168+
void setFreq(float frequency)
169+
{
170+
setFreq((int) frequency, false);
171+
current_osc->setFreq(frequency);
172+
}
173+
174+
175+
/** Set the MetaOsc frequency with a Q24n8 fixed-point number format.
176+
@param frequency to play the wave table.*/
177+
inline
178+
void setFreq_Q24n8(Q24n8 frequency)
179+
{
180+
setFreq((int) (frequency>>8), false);
181+
current_osc->setFreq_Q24n8(frequency);
182+
}
183+
184+
185+
/** Set the MetaOsc frequency with a Q16n16 fixed-point number format.
186+
@param frequency to play the wave table.*/
187+
inline
188+
void setFreq_Q16n16(Q16n16 frequency)
189+
{
190+
setFreq((int) (frequency>>16), false);
191+
current_osc->setFreq_Q16n16(frequency);
192+
}
193+
194+
195+
/** Returns the sample at the given table index of the current Oscil.
196+
@param index between 0 and the table size.The
197+
index rolls back around to 0 if it's larger than the table size.
198+
@return the sample at the given table index.
199+
*/
200+
inline
201+
int8_t atIndex(unsigned int index) {return current_osc->atIndex(index);}
202+
203+
204+
/** phaseIncFromFreq() and setPhaseInc() are for saving processor time when sliding between frequencies.
205+
@param frequency for which you want to calculate a phase increment value.
206+
@return the phase increment value which will produce a given frequency.*/
207+
inline
208+
unsigned long phaseIncFromFreq(int frequency) {return current_osc->phaseIncFromFreq(frequency);}
209+
210+
/** Set a specific phase increment.
211+
@param phaseinc_fractional a phase increment value as calculated by phaseIncFromFreq().
212+
*/
213+
inline
214+
void setPhaseInc(unsigned long phaseinc_fractional) {current_osc->setPhaseInc(phaseinc_fractional);}
215+
216+
217+
218+
private:
219+
Oscil<NUM_TABLE_CELLS, UPDATE_RATE> * oscillators[N_OSCIL];
220+
Oscil<NUM_TABLE_CELLS, UPDATE_RATE> * current_osc = NULL;
221+
int cutoff_freqs[N_OSCIL];
222+
byte current_rank = 0;
223+
224+
};
225+
226+
/**
227+
@example 06.Synthesis/NonAlias_MetaOscil/NonAlias_MetaOscil.ino
228+
This example demonstrates the Meta_Oscil class.
229+
*/
230+
231+
#endif /* META_OSCIL_H */
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
/* Example using a MetaOscil to generate an alias free square wave on a sweep
2+
using Mozzi sonification library.
3+
4+
Waveforms which are not only sines (Saw, square, triangle) are composed by a lot of harmonics which are at frequencies multiple of the fundamental frequency.
5+
If the frequency of one of these harmonics is higher than half the sampling frequency (https://en.wikipedia.org/wiki/Nyquist_frequency) (AUDIO_RATE/2 here)
6+
it will create "aliases" (https://en.wikipedia.org/wiki/Aliasing) which will sound out of tune are they not harmonically related to the fundamental.
7+
The higher the pitch, the more harmonics are above the Nyquist limit and the more aliased will be present for a given waveform.
8+
9+
One way to avoid aliases is to use "band-limited" waveforms which have a limited sets of harmonics in order to avoid them to reach the Nyquist limit.
10+
As these waveforms are band-limited they will sound less "crunchy" if they are used at frequencies lower than what they are meant to be because they
11+
lack the high frequency contents.
12+
13+
In order to paliate that, a common technique is to "swap" wave tables on the fly in order to keep the frequency content up to the Nyquist frequency but
14+
not above. This is the principal usage of MetaOscil.
15+
16+
MetaOscil can be used (after construction) as a proper Oscil but really is a bunch of oscillators with only one playing.
17+
It will switch between different oscils seemlessly depending on the asked frequency. This allows to switch between oscillators with less
18+
and less harmonics as the pitch goes up, in order to avoid aliases, which is demonstrated by this example.
19+
20+
The bandlimited tables are nammed according to the max frequency they can play without bringing aliases at a given frequency. For example:
21+
square_max_90_at_16384_512_int8.h ensures that no aliases will be present up to 90Hz at 16384Hz sampling rate (the default for Arduino).
22+
If your sampling rate is higher (say 32768 which is the default for most 32bits platforms) this table will be able to play up to
23+
180=90*2Hz without aliases, as the Nyquist frequency is two times higher.
24+
25+
Circuit: Audio output on digital pin 9 on a Uno or similar, or
26+
DAC/A14 on Teensy 3.1, or
27+
check the README or http://sensorium.github.com/Mozzi/
28+
29+
Mozzi documentation/API
30+
https://sensorium.github.io/Mozzi/doc/html/index.html
31+
32+
Mozzi help/discussion/announcements:
33+
https://groups.google.com/forum/#!forum/mozzi-users
34+
35+
Tim Barrass 2012, Combriat T. 2021, CC by-nc-sa.
36+
*/
37+
38+
#include <MozziGuts.h>
39+
#include <Oscil.h> // oscillator template
40+
#include <MetaOscil.h>
41+
42+
// All the tables used for the MetaOscil need to be included
43+
#include <tables/BandLimited_SQUARE/512/square_max_90_at_16384_512_int8.h> // band limited table that guarantee no alias up to a frequency of 90Hz at a sampling frequency of 16384 (or 180Hz at a sampling frequency of 32768Hz)
44+
#include <tables/BandLimited_SQUARE/512/square_max_101_at_16384_512_int8.h> // band limited table that guarantee no alias up to a frequency of 101Hz at a sampling frequency of 16384
45+
#include <tables/BandLimited_SQUARE/512/square_max_122_at_16384_512_int8.h> // band limited table that guarantee no alias up to a frequency of 122Hz at a sampling frequency of 16384
46+
#include <tables/BandLimited_SQUARE/512/square_max_138_at_16384_512_int8.h> // band limited table that guarantee no alias up to a frequency of 138Hz at a sampling frequency of 16384
47+
#include <tables/BandLimited_SQUARE/512/square_max_154_at_16384_512_int8.h> // band limited table that guarantee no alias up to a frequency of 154Hz at a sampling frequency of 16384
48+
#include <tables/BandLimited_SQUARE/512/square_max_174_at_16384_512_int8.h> // band limited table that guarantee no alias up to a frequency of 174Hz at a sampling frequency of 16384
49+
#include <tables/BandLimited_SQUARE/512/square_max_210_at_16384_512_int8.h> // band limited table that guarantee no alias up to a frequency of 210Hz at a sampling frequency of 16384
50+
#include <tables/BandLimited_SQUARE/512/square_max_264_at_16384_512_int8.h> // band limited table that guarantee no alias up to a frequency of 264Hz at a sampling frequency of 16384
51+
#include <tables/BandLimited_SQUARE/512/square_max_327_at_16384_512_int8.h> // band limited table that guarantee no alias up to a frequency of 327Hz at a sampling frequency of 16384
52+
#include <tables/BandLimited_SQUARE/512/square_max_431_at_16384_512_int8.h> // band limited table that guarantee no alias up to a frequency of 431Hz at a sampling frequency of 16384
53+
#include <tables/BandLimited_SQUARE/512/square_max_546_at_16384_512_int8.h> // band limited table that guarantee no alias up to a frequency of 546Hz at a sampling frequency of 16384
54+
#include <tables/BandLimited_SQUARE/512/square_max_744_at_16384_512_int8.h> // band limited table that guarantee no alias up to a frequency of 744Hz at a sampling frequency of 16384
55+
#include <tables/BandLimited_SQUARE/512/square_max_910_at_16384_512_int8.h> // band limited table that guarantee no alias up to a frequency of 910Hz at a sampling frequency of 16384
56+
#include <tables/BandLimited_SQUARE/512/square_max_1170_at_16384_512_int8.h> // band limited table that guarantee no alias up to a frequency of 1170Hz at a sampling frequency of 16384
57+
#include <tables/BandLimited_SQUARE/512/square_max_1638_at_16384_512_int8.h> // band limited table that guarantee no alias up to a frequency of 1638Hz at a sampling frequency of 16384
58+
#include <tables/BandLimited_SQUARE/512/square_max_2730_at_16384_512_int8.h> // band limited table that guarantee no alias up to a frequency of 2730Hz at a sampling frequency of 16384
59+
#include <tables/BandLimited_SQUARE/512/square_max_8192_at_16384_512_int8.h> // band limited table that guarantee no alias up to a frequency of 8192Hz at a sampling frequency of 16384 (this is basically a sine wave)
60+
61+
// The proper Oscillators that will be managed by the MetaOscil
62+
// use: Oscil <table_size, update_rate> oscilName (wavetable), look in .h file of table #included above
63+
Oscil <SQUARE_MAX_90_AT_16384_512_NUM_CELLS, AUDIO_RATE> aSq90(SQUARE_MAX_90_AT_16384_512_DATA);
64+
Oscil <SQUARE_MAX_101_AT_16384_512_NUM_CELLS, AUDIO_RATE> aSq101(SQUARE_MAX_101_AT_16384_512_DATA);
65+
Oscil <SQUARE_MAX_122_AT_16384_512_NUM_CELLS, AUDIO_RATE> aSq122(SQUARE_MAX_122_AT_16384_512_DATA);
66+
Oscil <SQUARE_MAX_138_AT_16384_512_NUM_CELLS, AUDIO_RATE> aSq138(SQUARE_MAX_138_AT_16384_512_DATA);
67+
Oscil <SQUARE_MAX_154_AT_16384_512_NUM_CELLS, AUDIO_RATE> aSq154(SQUARE_MAX_154_AT_16384_512_DATA);
68+
Oscil <SQUARE_MAX_174_AT_16384_512_NUM_CELLS, AUDIO_RATE> aSq174(SQUARE_MAX_174_AT_16384_512_DATA);
69+
Oscil <SQUARE_MAX_210_AT_16384_512_NUM_CELLS, AUDIO_RATE> aSq210(SQUARE_MAX_210_AT_16384_512_DATA);
70+
Oscil <SQUARE_MAX_264_AT_16384_512_NUM_CELLS, AUDIO_RATE> aSq264(SQUARE_MAX_264_AT_16384_512_DATA);
71+
Oscil <SQUARE_MAX_327_AT_16384_512_NUM_CELLS, AUDIO_RATE> aSq327(SQUARE_MAX_327_AT_16384_512_DATA);
72+
Oscil <SQUARE_MAX_431_AT_16384_512_NUM_CELLS, AUDIO_RATE> aSq431(SQUARE_MAX_431_AT_16384_512_DATA);
73+
Oscil <SQUARE_MAX_546_AT_16384_512_NUM_CELLS, AUDIO_RATE> aSq546(SQUARE_MAX_546_AT_16384_512_DATA);
74+
Oscil <SQUARE_MAX_744_AT_16384_512_NUM_CELLS, AUDIO_RATE> aSq744(SQUARE_MAX_744_AT_16384_512_DATA);
75+
Oscil <SQUARE_MAX_910_AT_16384_512_NUM_CELLS, AUDIO_RATE> aSq910(SQUARE_MAX_910_AT_16384_512_DATA);
76+
Oscil <SQUARE_MAX_1170_AT_16384_512_NUM_CELLS, AUDIO_RATE> aSq1170(SQUARE_MAX_1170_AT_16384_512_DATA);
77+
Oscil <SQUARE_MAX_1638_AT_16384_512_NUM_CELLS, AUDIO_RATE> aSq1638(SQUARE_MAX_1638_AT_16384_512_DATA);
78+
Oscil <SQUARE_MAX_2730_AT_16384_512_NUM_CELLS, AUDIO_RATE> aSq2730(SQUARE_MAX_2730_AT_16384_512_DATA);
79+
Oscil <SQUARE_MAX_8192_AT_16384_512_NUM_CELLS, AUDIO_RATE> aSq8192(SQUARE_MAX_8192_AT_16384_512_DATA);
80+
81+
// use: MetaOscil <table_size, update_rate, number_of_oscil> MetaoscilName. All oscils used should have the same table_size and **have to be put in increasing order of cutoff_frequencies**.
82+
MetaOscil<SQUARE_MAX_90_AT_16384_512_NUM_CELLS, AUDIO_RATE, 16> BL_aSq {&aSq90, &aSq101, &aSq122, &aSq138, &aSq154, &aSq174, &aSq210, &aSq264, &aSq327, &aSq431, &aSq546, &aSq744, &aSq1170, &aSq1638, &aSq2730, &aSq8192};
83+
84+
85+
// use #define for CONTROL_RATE, not a constant
86+
#define CONTROL_RATE 256 // Hz, powers of 2 are most reliable
87+
88+
int freq = 10;
89+
90+
91+
void setup() {
92+
// Set the cutoff frequencies for all the Oscil in the MetaOscil ie at which frequency the MetaOscil will switch to the next Oscillator. Note that these are the same frequencies than the tables names in this case.
93+
// This have to follow the order given at the MetaOscil creation: this needs to be in increasing order.
94+
BL_aSq.setCutoffFreqs(90, 101, 122, 138, 154, 174, 210, 264, 327, 431, 546, 744, 1170, 1638, 2730, 8192);
95+
96+
// Cutoff frequencies can also be set or changed individually.
97+
BL_aSq.setCutoffFreq(3000, 14);
98+
startMozzi(CONTROL_RATE);
99+
}
100+
101+
102+
void updateControl() {
103+
// Manually increasing the frequency by 1Hz
104+
freq += 1;
105+
if (freq > 3000) freq = 10;
106+
107+
aSq90.setFreq(freq);
108+
BL_aSq.setFreq(freq); //BL_aSq which is a metaOscil can be used just as a regular Oscil.
109+
}
110+
111+
112+
AudioOutput_t updateAudio() {
113+
//return MonoOutput::from8Bit(aSq90.next()); // try to use this line instead to hear the non-band limited version, sounds a bit like a radio
114+
return MonoOutput::from8Bit(BL_aSq.next()); // return a sample of the correct oscil to acheive no alias
115+
116+
}
117+
118+
119+
void loop() {
120+
audioHook(); // required here
121+
}

keywords.txt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -372,8 +372,15 @@ setPDEnv KEYWORD2
372372
update KEYWORD2
373373
next KEYWORD2
374374

375+
MetaOscil KEYWORD1
376+
setCutoffFreqs KEYWORD2
377+
setCutoffFreq KEYWORD2
378+
addOscil KEYWORD2
379+
addOscils KEYWORD2
380+
375381
WaveFolder KEYWORD1
376382
setHighLimit KEYWORD2
377383
setLowLimit KEYWORD2
378384
setLimits KEYWORD2
379385
next KEYWORD2
386+

0 commit comments

Comments
 (0)