forked from sensorium/Mozzi
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Sample.h
327 lines (268 loc) · 10 KB
/
Sample.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
/*
* Sample.h
*
* This file is part of Mozzi.
*
* 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 SAMPLE_H_
#define SAMPLE_H_
#include "MozziHeadersOnly.h"
#include "mozzi_fixmath.h"
#include "mozzi_pgmspace.h"
// fractional bits for sample index precision
#define SAMPLE_F_BITS 16
#define SAMPLE_F_BITS_AS_MULTIPLIER 65536
// phmod_proportion is an 1n15 fixed-point number only using
// the fractional part and the sign bit
#define SAMPLE_PHMOD_BITS 16
enum interpolation {INTERP_NONE, INTERP_LINEAR};
/** Sample is like Oscil, it plays a wavetable. However, Sample can be
set to play once through only, with variable start and end points,
or can loop, also with variable start and end points.
It defaults to playing once through the whole sound table, from start to finish.
@tparam NUM_TABLE_CELLS This is defined in the table ".h" file the Sample will be
using. The sound table can be arbitrary length for Sample.
It's important that NUM_TABLE_CELLS is either a literal number (eg. "8192") or a
defined macro, rather than a const or int, for the Sample to run fast enough.
@tparam UPDATE_RATE This will be MOZZI_AUDIO_RATE if the Sample 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.
@section int8_t2mozzi
Converting soundfiles for Mozzi.
There is a python script called int8_t2mozzi.py in the Mozzi/python folder.
The script converts raw sound data saved from a program like Audacity.
Instructions are in the int8_t2mozzi.py file.
*/
template <unsigned int NUM_TABLE_CELLS, unsigned int UPDATE_RATE, uint8_t INTERP=INTERP_NONE>
class Sample
{
public:
/** Constructor.
@param TABLE_NAME the name of the array the Sample 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. Sound tables can be of arbitrary lengths for Sample().
*/
Sample(const int8_t * TABLE_NAME):table(TABLE_NAME),endpos_fractional((unsigned long) NUM_TABLE_CELLS << SAMPLE_F_BITS) // so isPlaying() will work
{
setLoopingOff();
//rangeWholeSample();
}
/** Constructor.
Declare a Sample 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().
*/
Sample():endpos_fractional((unsigned long) NUM_TABLE_CELLS << SAMPLE_F_BITS)
{
setLoopingOff();
//rangeWholeSample();
}
/** Change the sound table which will be played by the Sample.
@param TABLE_NAME is the name of the array in the table ".h" file you're using.
*/
inline
void setTable(const int8_t * TABLE_NAME)
{
table = TABLE_NAME;
}
/** Sets the starting position in samples.
@param startpos offset position in samples.
*/
inline
void setStart(unsigned int startpos)
{
startpos_fractional = (unsigned long) startpos << SAMPLE_F_BITS;
}
/** Resets the phase (the playhead) to the start position, which will be 0 unless set to another value with setStart();
*/
inline
void start()
{
phase_fractional = startpos_fractional;
}
/** Sets a new start position plays the sample from that position.
@param startpos position in samples from the beginning of the sound.
*/
inline
void start(unsigned int startpos)
{
setStart(startpos);
start();
}
/** Sets the end position in samples from the beginning of the sound.
@param end position in samples.
*/
inline
void setEnd(unsigned int end)
{
endpos_fractional = (unsigned long) end << SAMPLE_F_BITS;
}
/** Sets the start and end points to include the range of the whole sound table.
*/
inline
void rangeWholeSample()
{
startpos_fractional = 0;
endpos_fractional = (unsigned long) NUM_TABLE_CELLS << SAMPLE_F_BITS;
}
/** Turns looping on.
*/
inline
void setLoopingOn()
{
looping=true;
}
/** Turns looping off.
*/
inline
void setLoopingOff()
{
looping=false;
}
/**
Returns the sample at the current phase position, or 0 if looping is off
and the phase overshoots the end of the sample. Updates the phase
according to the current frequency.
@return the next sample value from the table, or 0 if it's finished playing.
@todo in next(), incrementPhase() happens in a different position than for Oscil - check if it can be standardised
*/
inline
int8_t next() { // 4us
if (phase_fractional>endpos_fractional){
if (looping) {
phase_fractional = startpos_fractional + (phase_fractional - endpos_fractional);
}else{
return 0;
}
}
int8_t out;
if(INTERP==INTERP_LINEAR){
// WARNNG this is hard coded for when SAMPLE_F_BITS is 16
unsigned int index = phase_fractional >> SAMPLE_F_BITS;
out = FLASH_OR_RAM_READ<const int8_t>(table + index);
int8_t difference = FLASH_OR_RAM_READ<const int8_t>((table + 1) + index) - out;
int8_t diff_fraction = (int8_t)(((((unsigned int) phase_fractional)>>8)*difference)>>8); // (unsigned int) phase_fractional keeps low word, then>> for only 8 bit precision
out += diff_fraction;
}else{
out = FLASH_OR_RAM_READ<const int8_t>(table + (phase_fractional >> SAMPLE_F_BITS));
}
incrementPhase();
return out;
}
/** Checks if the sample is playing by seeing if the phase is within the limits of its end position.
@return true if the sample is playing
*/
inline
boolean isPlaying(){
return phase_fractional<endpos_fractional;
}
// Not readjusted for arbitrary table length yet
//
// Returns the next sample given a phase modulation value.
// @param phmod_proportion phase modulation value given as a proportion of the wave. The
// phmod_proportion parameter is a Q15n16 fixed-point number where to fractional
// n16 part represents -1 to 1, modulating the phase by one whole table length in
// each direction.
// @return a sample from the table.
//
// inline
// int8_t phMod(long phmod_proportion)
// {
// incrementPhase();
// return FLASH_OR_RAM_READ<const int8_t>(table + (((phase_fractional+(phmod_proportion * NUM_TABLE_CELLS))>>SAMPLE_SAMPLE_F_BITS) & (NUM_TABLE_CELLS - 1)));
// }
/** 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.
@param frequency to play the wave table.
*/
inline
void setFreq (int frequency) {
phase_increment_fractional = ((((unsigned long)NUM_TABLE_CELLS<<ADJUST_FOR_NUM_TABLE_CELLS)*frequency)/UPDATE_RATE) << (SAMPLE_F_BITS - ADJUST_FOR_NUM_TABLE_CELLS);
}
/** Set the sample 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 or setFreq_Q24n8 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 = (unsigned long)((((float)NUM_TABLE_CELLS * frequency)/UPDATE_RATE) * SAMPLE_F_BITS_AS_MULTIPLIER);
}
/** 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. Note: use with caution because it's prone to overflow with higher
frequencies and larger table sizes. An Q24n8 representation of 1.5 is 384
(ie. 1.5 * 256).
@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));
phase_increment_fractional = (((((unsigned long)NUM_TABLE_CELLS<<ADJUST_FOR_NUM_TABLE_CELLS)>>3)*frequency)/(UPDATE_RATE>>6))
<< (SAMPLE_F_BITS - ADJUST_FOR_NUM_TABLE_CELLS - (8-3+6));
}
/** Returns the sample at the given table index.
@param index between 0 and 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);
}
/** 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
unsigned long phaseIncFromFreq(unsigned int frequency)
{
return (((unsigned long)frequency * NUM_TABLE_CELLS)/UPDATE_RATE) << SAMPLE_F_BITS;
}
/** Set a specific phase increment. See phaseIncFromFreq().
@param phaseinc_fractional a phase increment value as calculated by phaseIncFromFreq().
*/
inline
void setPhaseInc(unsigned long 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;
}
volatile unsigned long phase_fractional;
volatile unsigned long phase_increment_fractional;
const int8_t * table;
bool looping;
unsigned long startpos_fractional, endpos_fractional;
};
/**
@example 08.Samples/Sample/Sample.ino
This example demonstrates the Sample class.
*/
#endif /* SAMPLE_H_ */