Mozzi  version v2.0
sound synthesis library for Arduino
Sample.h
1 /*
2  * Sample.h
3  *
4  * This file is part of Mozzi.
5  *
6  * Copyright 2012-2024 Tim Barrass and the Mozzi Team
7  *
8  * Mozzi is licensed under the GNU Lesser General Public Licence (LGPL) Version 2.1 or later.
9  *
10  */
11 
12 #ifndef SAMPLE_H_
13 #define SAMPLE_H_
14 
15 #include "MozziHeadersOnly.h"
16 #include "mozzi_fixmath.h"
17 #include "mozzi_pgmspace.h"
18 
19 // fractional bits for sample index precision
20 #define SAMPLE_F_BITS 16
21 #define SAMPLE_F_BITS_AS_MULTIPLIER 65536
22 
23 // phmod_proportion is an 1n15 fixed-point number only using
24 // the fractional part and the sign bit
25 #define SAMPLE_PHMOD_BITS 16
26 
27 enum interpolation {INTERP_NONE, INTERP_LINEAR};
28 
29 /** Sample is like Oscil, it plays a wavetable. However, Sample can be
30 set to play once through only, with variable start and end points,
31 or can loop, also with variable start and end points.
32 It defaults to playing once through the whole sound table, from start to finish.
33 @tparam NUM_TABLE_CELLS This is defined in the table ".h" file the Sample will be
34 using. The sound table can be arbitrary length for Sample.
35 It's important that NUM_TABLE_CELLS is either a literal number (eg. "8192") or a
36 defined macro, rather than a const or int, for the Sample to run fast enough.
37 @tparam UPDATE_RATE This will be MOZZI_AUDIO_RATE if the Sample is updated in
38 updateAudio(), or MOZZI_CONTROL_RATE if it's updated each time updateControl() is
39 called. It could also be a fraction of MOZZI_CONTROL_RATE if you are doing some kind
40 of cyclic updating in updateControl(), for example, to spread out the processor load.
41 @section int8_t2mozzi
42 Converting soundfiles for Mozzi.
43 There is a python script called int8_t2mozzi.py in the Mozzi/python folder.
44 The script converts raw sound data saved from a program like Audacity.
45 Instructions are in the int8_t2mozzi.py file.
46 */
47 template <unsigned int NUM_TABLE_CELLS, unsigned int UPDATE_RATE, uint8_t INTERP=INTERP_NONE>
48 class Sample
49 {
50 
51 public:
52 
53  /** Constructor.
54  @param TABLE_NAME the name of the array the Sample will be using. This
55  can be found in the table ".h" file if you are using a table made for
56  Mozzi by the int8_t2mozzi.py python script in Mozzi's python
57  folder. Sound tables can be of arbitrary lengths for Sample().
58  */
59  Sample(const int8_t * TABLE_NAME):table(TABLE_NAME),endpos_fractional((unsigned long) NUM_TABLE_CELLS << SAMPLE_F_BITS) // so isPlaying() will work
60  {
62  //rangeWholeSample();
63  }
64 
65 
66 
67  /** Constructor.
68  Declare a Sample with template TABLE_NUM_CELLS and UPDATE_RATE parameters, without specifying a particular wave table for it to play.
69  The table can be set or changed on the fly with setTable().
70  */
71  Sample():endpos_fractional((unsigned long) NUM_TABLE_CELLS << SAMPLE_F_BITS)
72  {
74  //rangeWholeSample();
75  }
76 
77 
78  /** Change the sound table which will be played by the Sample.
79  @param TABLE_NAME is the name of the array in the table ".h" file you're using.
80  */
81  inline
82  void setTable(const int8_t * TABLE_NAME)
83  {
84  table = TABLE_NAME;
85  }
86 
87 
88  /** Sets the starting position in samples.
89  @param startpos offset position in samples.
90  */
91  inline
92  void setStart(unsigned int startpos)
93  {
94  startpos_fractional = (unsigned long) startpos << SAMPLE_F_BITS;
95  }
96 
97 
98  /** Resets the phase (the playhead) to the start position, which will be 0 unless set to another value with setStart();
99  */
100  inline
101  void start()
102  {
103  phase_fractional = startpos_fractional;
104  }
105 
106 
107  /** Sets a new start position plays the sample from that position.
108  @param startpos position in samples from the beginning of the sound.
109  */
110  inline
111  void start(unsigned int startpos)
112  {
113  setStart(startpos);
114  start();
115  }
116 
117 
118  /** Sets the end position in samples from the beginning of the sound.
119  @param end position in samples.
120  */
121  inline
122  void setEnd(unsigned int end)
123  {
124  endpos_fractional = (unsigned long) end << SAMPLE_F_BITS;
125  }
126 
127 
128  /** Sets the start and end points to include the range of the whole sound table.
129  */
130  inline
132  {
133  startpos_fractional = 0;
134  endpos_fractional = (unsigned long) NUM_TABLE_CELLS << SAMPLE_F_BITS;
135  }
136 
137 
138  /** Turns looping on.
139  */
140  inline
142  {
143  looping=true;
144  }
145 
146 
147  /** Turns looping off.
148  */
149  inline
151  {
152  looping=false;
153  }
154 
155 
156 
157  /**
158  Returns the sample at the current phase position, or 0 if looping is off
159  and the phase overshoots the end of the sample. Updates the phase
160  according to the current frequency.
161  @return the next sample value from the table, or 0 if it's finished playing.
162  @todo in next(), incrementPhase() happens in a different position than for Oscil - check if it can be standardised
163  */
164  inline
165  int8_t next() { // 4us
166 
167  if (phase_fractional>endpos_fractional){
168  if (looping) {
169  phase_fractional = startpos_fractional + (phase_fractional - endpos_fractional);
170  }else{
171  return 0;
172  }
173  }
174  int8_t out;
175  if(INTERP==INTERP_LINEAR){
176  // WARNNG this is hard coded for when SAMPLE_F_BITS is 16
177  unsigned int index = phase_fractional >> SAMPLE_F_BITS;
178  out = FLASH_OR_RAM_READ<const int8_t>(table + index);
179  int8_t difference = FLASH_OR_RAM_READ<const int8_t>((table + 1) + index) - out;
180  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
181  out += diff_fraction;
182  }else{
183  out = FLASH_OR_RAM_READ<const int8_t>(table + (phase_fractional >> SAMPLE_F_BITS));
184  }
185  incrementPhase();
186  return out;
187  }
188 
189 
190  /** Checks if the sample is playing by seeing if the phase is within the limits of its end position.
191  @return true if the sample is playing
192  */
193  inline
195  return phase_fractional<endpos_fractional;
196  }
197 
198 
199  // Not readjusted for arbitrary table length yet
200  //
201  // Returns the next sample given a phase modulation value.
202  // @param phmod_proportion phase modulation value given as a proportion of the wave. The
203  // phmod_proportion parameter is a Q15n16 fixed-point number where to fractional
204  // n16 part represents -1 to 1, modulating the phase by one whole table length in
205  // each direction.
206  // @return a sample from the table.
207  //
208  // inline
209  // int8_t phMod(long phmod_proportion)
210  // {
211  // incrementPhase();
212  // return FLASH_OR_RAM_READ<const int8_t>(table + (((phase_fractional+(phmod_proportion * NUM_TABLE_CELLS))>>SAMPLE_SAMPLE_F_BITS) & (NUM_TABLE_CELLS - 1)));
213  // }
214 
215 
216 
217  /** Set the oscillator frequency with an unsigned int.
218  This is faster than using a float, so it's useful when processor time is tight,
219  but it can be tricky with low and high frequencies, depending on the size of the
220  wavetable being used. If you're not getting the results you expect, try
221  explicitly using a float, or try setFreq_Q24n8.
222  @param frequency to play the wave table.
223  */
224  inline
225  void setFreq (int frequency) {
226  phase_increment_fractional = ((((unsigned long)NUM_TABLE_CELLS<<ADJUST_FOR_NUM_TABLE_CELLS)*frequency)/UPDATE_RATE) << (SAMPLE_F_BITS - ADJUST_FOR_NUM_TABLE_CELLS);
227  }
228 
229 
230  /** Set the sample frequency with a float. Using a float is the most reliable
231  way to set frequencies, -Might- be slower than using an int but you need either
232  this or setFreq_Q24n8 for fractional frequencies.
233  @param frequency to play the wave table.
234  */
235  inline
236  void setFreq(float frequency)
237  { // 1 us - using float doesn't seem to incur measurable overhead with the oscilloscope
238  phase_increment_fractional = (unsigned long)((((float)NUM_TABLE_CELLS * frequency)/UPDATE_RATE) * SAMPLE_F_BITS_AS_MULTIPLIER);
239  }
240 
241 
242  /** Set the frequency using Q24n8 fixed-point number format.
243  This might be faster than the float version for setting low frequencies
244  such as 1.5 Hz, or other values which may not work well with your table
245  size. Note: use with caution because it's prone to overflow with higher
246  frequencies and larger table sizes. An Q24n8 representation of 1.5 is 384
247  (ie. 1.5 * 256).
248  @param frequency in Q24n8 fixed-point number format.
249  */
250  inline
251  void setFreq_Q24n8(Q24n8 frequency)
252  {
253  //phase_increment_fractional = (frequency* (NUM_TABLE_CELLS>>3)/(UPDATE_RATE>>6)) << (F_BITS-(8-3+6));
254  phase_increment_fractional = (((((unsigned long)NUM_TABLE_CELLS<<ADJUST_FOR_NUM_TABLE_CELLS)>>3)*frequency)/(UPDATE_RATE>>6))
255  << (SAMPLE_F_BITS - ADJUST_FOR_NUM_TABLE_CELLS - (8-3+6));
256  }
257 
258 
259  /** Returns the sample at the given table index.
260  @param index between 0 and the table size.
261  @return the sample at the given table index.
262  */
263  inline
264  int8_t atIndex(unsigned int index)
265  {
266  return FLASH_OR_RAM_READ<const int8_t>(table + index);
267  }
268 
269 
270  /** phaseIncFromFreq() and setPhaseInc() are for saving processor time when sliding between frequencies.
271  Instead of recalculating the phase increment for each
272  frequency in between, you can just calculate the phase increment for each end
273  frequency with phaseIncFromFreq(), then use a Line to interpolate on the fly and
274  use setPhaseInc() to set the phase increment at each step. (Note: I should
275  really profile this with the oscilloscope to see if it's worth the extra
276  confusion!)
277  @param frequency for which you want to calculate a phase increment value.
278  @return the phase increment value which will produce a given frequency.
279  */
280  inline
281  unsigned long phaseIncFromFreq(unsigned int frequency)
282  {
283  return (((unsigned long)frequency * NUM_TABLE_CELLS)/UPDATE_RATE) << SAMPLE_F_BITS;
284  }
285 
286 
287  /** Set a specific phase increment. See phaseIncFromFreq().
288  @param phaseinc_fractional a phase increment value as calculated by phaseIncFromFreq().
289  */
290  inline
291  void setPhaseInc(unsigned long phaseinc_fractional)
292  {
293  phase_increment_fractional = phaseinc_fractional;
294  }
295 
296 
297 private:
298 
299 
300  /** Used for shift arithmetic in setFreq() and its variations.
301  */
302 static const uint8_t ADJUST_FOR_NUM_TABLE_CELLS = (NUM_TABLE_CELLS<2048) ? 8 : 0;
303 
304 
305  /** Increments the phase of the oscillator without returning a sample.
306  */
307  inline
308  void incrementPhase()
309  {
310  phase_fractional += phase_increment_fractional;
311  }
312 
313 
314  volatile unsigned long phase_fractional;
315  volatile unsigned long phase_increment_fractional;
316  const int8_t * table;
317  bool looping;
318  unsigned long startpos_fractional, endpos_fractional;
319 };
320 
321 
322 /**
323 @example 08.Samples/Sample/Sample.ino
324 This example demonstrates the Sample class.
325 */
326 
327 #endif /* SAMPLE_H_ */