-
Notifications
You must be signed in to change notification settings - Fork 187
/
Oscil.h
414 lines (348 loc) · 16.2 KB
/
Oscil.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
/*
* Oscil.h
*
* Oscil.h owes much to AF_precision_synthesis.pde, 2009, Adrian Freed.
*
* This file is part of Mozzi.
*
* Copyright 20009 Arian Freed
* Copyright 2012-2024 Tim Barrass and the Mozzi Team
*
* Mozzi is licensed under the GNU Lesser General Public Licence (LGPL) Version 2.1 or later.
*
*/
#ifndef OSCIL_H_
#define OSCIL_H_
#include "Arduino.h"
#include "MozziHeadersOnly.h"
#include "mozzi_fixmath.h"
#include "FixMath.h"
#include "mozzi_pgmspace.h"
#ifdef OSCIL_DITHER_PHASE
#include "mozzi_rand.h"
#endif
// fractional bits for oscillator index precision
#define OSCIL_F_BITS 16
#define OSCIL_F_BITS_AS_MULTIPLIER 65536
// phmod_proportion is an 15n16 fixed-point number
#define OSCIL_PHMOD_BITS 16
/**
Oscil plays a wavetable, cycling through the table to generate an audio or
control signal. The frequency of the signal can be set or changed with
setFreq(), and the output of an Oscil can be produced with next() for a simple
cycling oscillator, or atIndex() for a particular sample in the table.
@tparam NUM_TABLE_CELLS This is defined in the table ".h" file the Oscil will be
using. It's important that it's a power of 2, and either a literal number (eg. "8192") or a
defined macro, rather than a const or int, for the Oscil to run fast enough.
@tparam UPDATE_RATE This will be MOZZI_AUDIO_RATE if the Oscil is updated in
updateAudio(), or MOZZI_CONTROL_RATE if it's updated each time updateControl() is
called. It could also be a fraction of MOZZI_CONTROL_RATE if you are doing some kind
of cyclic updating in updateControl(), for example, to spread out the processor load.
@todo Use conditional compilation to optimise setFreq() variations for different table
sizes.
@note If you #define OSCIL_DITHER_PHASE before you #include <Oscil.h>,
the phase increments will be dithered, which reduces spurious frequency spurs
in the audio output, at the cost of some extra processing and memory.
@section int8_t2mozzi
Converting soundfiles for Mozzi
There is a python script called char2mozzi.py in the Mozzi/python folder.
The usage is:
char2mozzi.py infilename outfilename tablename samplerate
*/
//template <unsigned int NUM_TABLE_CELLS, unsigned int UPDATE_RATE, bool DITHER_PHASE=false>
template <uint16_t NUM_TABLE_CELLS, uint16_t UPDATE_RATE>
class Oscil
{
public:
/** Constructor.
@param TABLE_NAME the name of the array the Oscil will be using. This
can be found in the table ".h" file if you are using a table made for
Mozzi by the int8_t2mozzi.py python script in Mozzi's python
folder.*/
Oscil(const int8_t * TABLE_NAME):table(TABLE_NAME)
{}
/** Constructor.
Declare an Oscil with template TABLE_NUM_CELLS and UPDATE_RATE
parameters, without specifying a particular wave table for it to play.
The table can be set or changed on the fly with setTable(). Any tables
used by the Oscil must be the same size.
*/
Oscil()
{}
/** Updates the phase according to the current frequency and returns the sample at the new phase position.
@return the next sample.
*/
inline
int8_t next()
{
incrementPhase();
return readTable();
}
/** Change the sound table which will be played by the Oscil.
@param TABLE_NAME is the name of the array in the table ".h" file you're using.
*/
void setTable(const int8_t * TABLE_NAME)
{
table = TABLE_NAME;
}
/** Set the phase of the Oscil. This does the same thing as Sample::start(offset). Just different ways of thinking about oscillators and samples.
@param phase a position in the wavetable.
*/
// This could be called in the control interrupt, so phase_fractional should really be volatile,
// but that could limit optimisation. Since phase_fractional gets changed often in updateAudio()
// (in loop()), it's probably worth keeping it nonvolatile until it causes problems
void setPhase(unsigned int phase)
{
phase_fractional = (uint32_t)phase << OSCIL_F_BITS;
}
/** Set the phase of the Oscil. Might be useful with getPhaseFractional().
@param phase a position in the wavetable.
*/
// This could be called in the control interrupt, so phase_fractional should really be volatile,
// but that could limit optimisation. Since phase_fractional gets changed often in updateAudio()
// (in loop()), it's probably worth keeping it nonvolatile until it causes problems
void setPhaseFractional(uint32_t phase)
{
phase_fractional = phase;
}
/** Get the phase of the Oscil in fractional format.
@return position in the wavetable, shifted left by OSCIL_F_BITS (which is 16 when this was written).
*/
uint32_t getPhaseFractional()
{
return phase_fractional;
}
/** Returns the next sample given a phase modulation value.
@param phmod_proportion a phase modulation value given as a proportion of the wave. The
phmod_proportion parameter is a Q15n16 fixed-point number where the fractional
n16 part represents almost -1 to almost 1, modulating the phase by one whole table length in
each direction.
@return a sample from the table.
*/
// PM: cos((angle += incr) + change)
// FM: cos(angle += (incr + change))
// The ratio of deviation to modulation frequency is called the "index of modulation". ( I = d / Fm )
inline
int8_t phMod(Q15n16 phmod_proportion)
{
incrementPhase();
return FLASH_OR_RAM_READ<const int8_t>(table + (((phase_fractional+(phmod_proportion * NUM_TABLE_CELLS))>>OSCIL_F_BITS) & (NUM_TABLE_CELLS - 1)));
}
/** Returns the next sample given a phase modulation value.
@param phmod_proportion a phase modulation value given as a proportion of the wave. The
phmod_proportion parameter is a SFix<NI,NF> fixed-point number where the fractional part represents almost -1 to almost 1, modulating the phase by one whole table length in
each direction. This fixed point math number is interpreted as a SFix<15,16> internally.
@return a sample from the table.
*/
template <int8_t NI, int8_t NF, uint8_t RANGE>
inline
int8_t phMod(SFix<NI,NF,RANGE> phmod_proportion)
{
return phMod(SFix<15,16>(phmod_proportion).asRaw());
}
/** Returns the next sample given a phase modulation value.
@param phmod_proportion a phase modulation value given as a proportion of the wave. The
phmod_proportion parameter is a SFix<15,16> fixed-point number where the fractional part represents almost -1 to almost 1, modulating the phase by one whole table length in
each direction.
@return a sample from the table.
*/
inline
int8_t phMod(SFix<15,16> phmod_proportion)
{
return phMod(phmod_proportion.asRaw());
}
/** Set the oscillator frequency with an unsigned int. This is faster than using a
float, so it's useful when processor time is tight, but it can be tricky with
low and high frequencies, depending on the size of the wavetable being used. If
you're not getting the results you expect, try explicitly using a float, or try
setFreq_Q24n8() or or setFreq_Q16n16().
@param frequency to play the wave table.
*/
inline
void setFreq (int frequency) {
// TB2014-8-20 change this following Austin Grossman's suggestion on user list
// https://groups.google.com/forum/?utm_medium=email&utm_source=footer#!msg/mozzi-users/u4D5NMzVnQs/pCmiWInFvrkJ
//phase_increment_fractional = ((((uint32_t)NUM_TABLE_CELLS<<ADJUST_FOR_NUM_TABLE_CELLS)*frequency)/UPDATE_RATE) << (OSCIL_F_BITS - ADJUST_FOR_NUM_TABLE_CELLS);
// to this:
phase_increment_fractional = ((uint32_t)frequency) * ((OSCIL_F_BITS_AS_MULTIPLIER*NUM_TABLE_CELLS)/UPDATE_RATE);
}
/** Set the oscillator frequency with a float. Using a float is the most reliable
way to set frequencies, -Might- be slower than using an int but you need either
this, setFreq_Q24n8() or setFreq_Q16n16() for fractional frequencies.
@param frequency to play the wave table.
*/
inline
void setFreq(float frequency)
{ // 1 us - using float doesn't seem to incur measurable overhead with the oscilloscope
phase_increment_fractional = (uint32_t)((((float)NUM_TABLE_CELLS * frequency)/UPDATE_RATE) * OSCIL_F_BITS_AS_MULTIPLIER);
}
/** Set the frequency using UFix<NI,NF> fixed-point number format. This falls back to using UFix<16,16> internally and is provided as a fallout for other UFix types. If possible try to use directly UFix<16,16> or UFix<24,8> for well defined (and well tested) behaviors.
@note This should work OK with tables 2048 cells or smaller and
frequencies up to 4096 Hz. Can't be used with UPDATE_RATE less than 64 Hz.
@note This didn't run faster than float last time it was tested, after 2014 code changes. Need to see if 2014 changes improved or worsened performance.
@param frequency in UFix<NI,NF> fixed-point number format.
*/
template <int8_t NI, int8_t NF, uint64_t RANGE>
inline
void setFreq(UFix<NI,NF,RANGE> frequency)
{
setFreq_Q16n16(UFix<16,16>(frequency).asRaw());
}
/** Set the frequency using Q24n8 fixed-point number format.
This might be faster than the float version for setting low frequencies such as
1.5 Hz, or other values which may not work well with your table size. A Q24n8
representation of 1.5 is 384 (ie. 1.5 * 256). Can't be used with UPDATE_RATE
less than 64 Hz.
@param frequency in Q24n8 fixed-point number format.
*/
inline
void setFreq_Q24n8(Q24n8 frequency)
{
//phase_increment_fractional = (frequency* (NUM_TABLE_CELLS>>3)/(UPDATE_RATE>>6)) << (F_BITS-(8-3+6));
// TB2016-10-2 line below might have been left in accidentally while making the 2014 change below, remove for now
// phase_increment_fractional = (((((uint32_t)NUM_TABLE_CELLS<<ADJUST_FOR_NUM_TABLE_CELLS)>>3)*frequency)/(UPDATE_RATE>>6))
// << (OSCIL_F_BITS - ADJUST_FOR_NUM_TABLE_CELLS - (8-3+6));
// TB2014-8-20 change this following Austin Grossman's suggestion on user list
// https://groups.google.com/forum/?utm_medium=email&utm_source=footer#!msg/mozzi-users/u4D5NMzVnQs/pCmiWInFvrkJ
if ((256UL*NUM_TABLE_CELLS) >= UPDATE_RATE) {
phase_increment_fractional = ((uint32_t)frequency) * ((256UL*NUM_TABLE_CELLS)/UPDATE_RATE);
} else {
phase_increment_fractional = ((uint32_t)frequency) / (UPDATE_RATE/(256UL*NUM_TABLE_CELLS));
}
}
/** Set the frequency using UFix<24,8> fixed-point number format.
This might be faster than the float version for setting low frequencies such as
1.5 Hz, or other values which may not work well with your table size. A UFix<24,8>
representation of 1.5 is 384 (ie. 1.5 * 256). Can't be used with UPDATE_RATE
less than 64 Hz.
@param frequency in UFix<24,8> fixed-point number format.
*/
template <uint64_t RANGE>
inline
void setFreq(UFix<24,8,RANGE> frequency)
{
setFreq_Q24n8(frequency.asRaw());
}
/** Set the frequency using Q16n16 fixed-point number format. This is useful in
combination with Q16n16_mtof(), a fast alternative to mtof(), using Q16n16
fixed-point format instead of floats.
@note This should work OK with tables 2048 cells or smaller and
frequencies up to 4096 Hz. Can't be used with UPDATE_RATE less than 64 Hz.
@note This didn't run faster than float last time it was tested, after 2014 code changes. Need to see if 2014 changes improved or worsened performance.
@param frequency in Q16n16 fixed-point number format.
*/
inline
void setFreq_Q16n16(Q16n16 frequency)
{
//phase_increment_fractional = ((frequency * (NUM_TABLE_CELLS>>7))/(UPDATE_RATE>>6)) << (F_BITS-16+1);
// TB2014-8-20 change this following Austin Grossman's suggestion on user list
// https://groups.google.com/forum/?utm_medium=email&utm_source=footer#!msg/mozzi-users/u4D5NMzVnQs/pCmiWInFvrkJ
//phase_increment_fractional = (((((uint32_t)NUM_TABLE_CELLS<<ADJUST_FOR_NUM_TABLE_CELLS)>>7)*frequency)/(UPDATE_RATE>>6))
// << (OSCIL_F_BITS - ADJUST_FOR_NUM_TABLE_CELLS - 16 + 1);
if (NUM_TABLE_CELLS >= UPDATE_RATE) {
phase_increment_fractional = ((uint32_t)frequency) * (NUM_TABLE_CELLS/UPDATE_RATE);
} else {
phase_increment_fractional = ((uint32_t)frequency) / (UPDATE_RATE/NUM_TABLE_CELLS);
}
}
/** Set the frequency using UFix<16,16> fixed-point number format. This is useful in
combination with Q16n16_mtof(), a fast alternative to mtof(), using UFix<16,16>
fixed-point format instead of fractional numbers.
@note This should work OK with tables 2048 cells or smaller and
frequencies up to 4096 Hz. Can't be used with UPDATE_RATE less than 64 Hz.
@note This didn't run faster than float last time it was tested, after 2014 code changes. Need to see if 2014 changes improved or worsened performance.
@param frequency in UFix<16,16> fixed-point number format.
*/
template <uint64_t RANGE>
inline
void setFreq(UFix<16,16,RANGE> frequency)
{
setFreq_Q16n16(frequency.asRaw());
}
/** Set the frequency using SFix<NI,NF> fixed-point number format. This falls back to using UFix<16,16> internally and is provided as a fallout for other UFix types. If possible try to use directly UFix<16,16> or UFix<24,8> for well defined (and well tested) behaviors.
@note This should work OK with tables 2048 cells or smaller and
frequencies up to 4096 Hz. Can't be used with UPDATE_RATE less than 64 Hz.
@note This didn't run faster than float last time it was tested, after 2014 code changes. Need to see if 2014 changes improved or worsened performance.
@param frequency in SFix<16,16> fixed-point number format.
*/
template <int8_t NI, int8_t NF, uint64_t RANGE>
inline
void setFreq(SFix<NI,NF,RANGE> frequency)
{
setFreq_Q16n16(UFix<16,16>(frequency).asRaw());
}
/*
inline
void setFreqMidi(int8_t note_num) {
setFreq_Q16n16(mtof(note_num));
}
*/
/** Returns the sample at the given table index.
@param index between 0 and the table size.The
index rolls back around to 0 if it's larger than the table size.
@return the sample at the given table index.
*/
inline
int8_t atIndex(unsigned int index)
{
return FLASH_OR_RAM_READ<const int8_t>(table + (index & (NUM_TABLE_CELLS - 1)));
}
/** phaseIncFromFreq() and setPhaseInc() are for saving processor time when sliding between frequencies.
Instead of recalculating the phase increment for each
frequency in between, you can just calculate the phase increment for each end
frequency with phaseIncFromFreq(), then use a Line to interpolate on the fly and
use setPhaseInc() to set the phase increment at each step. (Note: I should
really profile this with the oscilloscope to see if it's worth the extra
confusion!)
@param frequency for which you want to calculate a phase increment value.
@return the phase increment value which will produce a given frequency.
*/
inline
uint32_t phaseIncFromFreq(int frequency)
{
// TB2014-8-20 change this following Austin Grossman's suggestion on user list
// https://groups.google.com/forum/?utm_medium=email&utm_source=footer#!msg/mozzi-users/u4D5NMzVnQs/pCmiWInFvrkJ
//return (((uint32_t)frequency * NUM_TABLE_CELLS)/UPDATE_RATE) << OSCIL_F_BITS;
return ((uint32_t)frequency) * ((OSCIL_F_BITS_AS_MULTIPLIER*NUM_TABLE_CELLS)/UPDATE_RATE);
}
/** Set a specific phase increment. See phaseIncFromFreq().
@param phaseinc_fractional a phase increment value as calculated by phaseIncFromFreq().
*/
inline
void setPhaseInc(uint32_t phaseinc_fractional)
{
phase_increment_fractional = phaseinc_fractional;
}
private:
/** Used for shift arithmetic in setFreq() and its variations.
*/
static const uint8_t ADJUST_FOR_NUM_TABLE_CELLS = (NUM_TABLE_CELLS<2048) ? 8 : 0;
/** Increments the phase of the oscillator without returning a sample.
*/
inline
void incrementPhase()
{
//phase_fractional += (phase_increment_fractional | 1); // odd phase incr, attempt to reduce frequency spurs in output
phase_fractional += phase_increment_fractional;
}
/** Returns the current sample.
*/
inline
int8_t readTable()
{
#ifdef OSCIL_DITHER_PHASE
return FLASH_OR_RAM_READ<const int8_t>(table + (((phase_fractional + ((int)(xorshift96()>>16))) >> OSCIL_F_BITS) & (NUM_TABLE_CELLS - 1)));
#else
return FLASH_OR_RAM_READ<const int8_t>(table + ((phase_fractional >> OSCIL_F_BITS) & (NUM_TABLE_CELLS - 1)));
//return FLASH_OR_RAM_READ<int8_t>(table + (((phase_fractional >> OSCIL_F_BITS) | 1 ) & (NUM_TABLE_CELLS - 1))); odd phase, attempt to reduce frequency spurs in output
#endif
}
uint32_t phase_fractional;
uint32_t phase_increment_fractional;
const int8_t * table;
};
/**
@example 01.Basics/Vibrato/Vibrato.ino
This is an example using Oscil::phMod to produce vibrato using phase modulation.
*/
#endif /* OSCIL_H_ */