arduino-audio-tools
PitchShift.h
1 
2 #pragma once
3 #include "AudioConfig.h"
4 #include <math.h>
5 #include <stdio.h>
6 #include <string.h>
7 
8 
9 namespace audio_tools {
10 
15 struct PitchShiftInfo : public AudioInfo {
16  PitchShiftInfo() {
17  channels = 2;
18  sample_rate = 44100;
19  bits_per_sample = 16;
20  }
21  float pitch_shift = 1.4f;
22  int buffer_size = 1000;
23 };
24 
32 template <typename T>
34 public:
35  VariableSpeedRingBufferSimple(int size = 0, float increment = 1.0) {
36  setIncrement(increment);
37  if (size > 0)
38  resize(size);
39  }
40 
41  void setIncrement(float increment) { read_increment = increment; }
42 
43  void resize(int size) {
44  buffer_size = size;
45  buffer.resize(size);
46  }
47 
48  T read() {
49  T result = peek();
50  read_pos_float += read_increment;
51  // on buffer owerflow reset to beginning
52  if (read_pos_float > buffer_size) {
53  read_pos_float -= buffer_size;
54  }
55  return result;
56  }
57 
58  T peek() {
59  if (buffer.size() == 0) {
60  LOGE("buffer has no memory");
61  return 0;
62  }
63  return buffer[(int)read_pos_float];
64  }
65 
66  bool write(T sample) {
67  if (buffer.size() == 0) {
68  LOGE("buffer has no memory");
69  return false;
70  }
71  buffer[write_pos++] = sample;
72  // on buffer owerflow reset to 0
73  if (write_pos >= buffer_size) {
74  write_pos = 0;
75  }
76  return true;
77  }
78 
80  void reset() {
81  read_pos_float = 0;
82  write_pos = 0;
83  memset(buffer.data(), 0, sizeof(T) * buffer_size);
84  }
85 
86  virtual bool isFull() { return false; }
87  virtual int available() { return buffer_size; }
88  virtual int availableForWrite() { return buffer_size; }
89  virtual T *address() { return nullptr; }
90  size_t size() {return buffer_size;}
91 
92 protected:
93  Vector<T> buffer{0};
94  int buffer_size = 0;
95  float read_pos_float = 0.0;
96  float read_increment = 1.0;
97  int write_pos = 0;
98 };
99 
107 template <typename T> class VariableSpeedRingBuffer180 : public BaseBuffer<T> {
108 public:
109  VariableSpeedRingBuffer180(int size = 0, float increment = 1.0) {
110  setIncrement(increment);
111  if (size > 0)
112  resize(size);
113  }
114 
115  void setIncrement(float increment) { pitch_shift = increment; }
116 
117  void resize(int size) {
118  buffer_size = size;
119  overlap = buffer_size / 10;
120  buffer.resize(size);
121  }
122 
123  T read() { return pitchRead(); }
124 
125  T peek() { return -1; }
126 
127  bool write(T sample) {
128  if (buffer.size() == 0) {
129  LOGE("buffer has no memory");
130  return false;
131  }
132  // write_pointer value is used in pitchRead()
133  write_pointer = write_pos;
134  buffer[write_pos++] = sample;
135  // on buffer owerflow reset to 0
136  if (write_pos >= buffer_size) {
137  write_pos = 0;
138  }
139  return true;
140  }
141 
143  void reset() {
144  read_pos_float = 0;
145  write_pos = 0;
146  cross_fade = 1.0f;
147  overlap = buffer_size / 10;
148  memset(buffer.data(), 0, sizeof(T) * buffer_size);
149  }
150 
151  virtual bool isFull() { return false; }
152  virtual int available() { return buffer_size; }
153  virtual int availableForWrite() { return buffer_size; }
154  virtual T *address() { return nullptr; }
155  size_t size() {return buffer_size;}
156 
157 protected:
158  Vector<T> buffer{0};
159  float read_pos_float = 0.0;
160  float cross_fade = 1.0;
161  int write_pos = 0;
162  int write_pointer = 0;
163  int buffer_size = 0;
164  int overlap = 0;
165  float pitch_shift = 0;
166 
168  virtual T pitchRead() {
169  TRACED();
170  assert(pitch_shift > 0);
171  assert(buffer_size > 0);
172 
173  // read fractional readpointer and generate 0° and 180° read-pointer in
174  // integer
175  int read_pointer_int = roundf(read_pos_float);
176  int read_pointer_int180 = 0;
177  if (read_pointer_int >= buffer_size / 2)
178  read_pointer_int180 = read_pointer_int - (buffer_size / 2);
179  else
180  read_pointer_int180 = read_pointer_int + (buffer_size / 2);
181 
182  // read the two samples...
183  float read_sample = (float)buffer[read_pointer_int];
184  float read_sample_180 = (float)buffer[read_pointer_int180];
185 
186  // Check if first readpointer starts overlap with write pointer?
187  // if yes -> do cross-fade to second read-pointer
188  if (overlap >= (write_pointer - read_pointer_int) &&
189  (write_pointer - read_pointer_int) >= 0 && pitch_shift != 1.0f) {
190  int rel = write_pointer - read_pointer_int;
191  cross_fade = ((float)rel) / (float)overlap;
192  } else if (write_pointer - read_pointer_int == 0)
193  cross_fade = 0.0f;
194 
195  // Check if second readpointer starts overlap with write pointer?
196  // if yes -> do cross-fade to first read-pointer
197  if (overlap >= (write_pointer - read_pointer_int180) &&
198  (write_pointer - read_pointer_int180) >= 0 && pitch_shift != 1.0f) {
199  int rel = write_pointer - read_pointer_int180;
200  cross_fade = 1.0f - ((float)rel) / (float)overlap;
201  } else if (write_pointer - read_pointer_int180 == 0)
202  cross_fade = 1.0f;
203 
204  // do cross-fading and sum up
205  T sum = (read_sample * cross_fade + read_sample_180 * (1.0f - cross_fade));
206 
207  // increment fractional read-pointer and write-pointer
208  read_pos_float += pitch_shift;
209  if (roundf(read_pos_float) >= buffer_size)
210  read_pos_float = 0.0f;
211 
212  return sum;
213  }
214 };
215 
223 template <typename T> class VariableSpeedRingBuffer : public BaseBuffer<T> {
224 public:
225  VariableSpeedRingBuffer(int size = 0, float increment = 1.0) {
226  setIncrement(increment);
227  if (size > 0)
228  resize(size);
229  }
230 
231  void setIncrement(float increment) { read_increment = increment; }
232 
233  void resize(int size) {
234  buffer_size = size;
235  // prevent an overrun at the start
236  read_pos_float = size / 2;
237  buffer.resize(size);
238  }
239 
240  T read() {
241  assert(read_increment != 0.0);
242  T result = peek();
243  read_pos_float += read_increment;
244  handleReadWriteOverrun(last_value);
245  if (read_pos_float > buffer_size) {
246  read_pos_float -= buffer_size;
247  }
248  return result;
249  }
250 
251  T peek() {
252  if (buffer.size() == 0)
253  return 0;
254  return interpolate(read_pos_float);
255  }
256 
257  bool write(T sample) {
258  if (buffer.size() == 0)
259  return false;
260  handleReadWriteOverrun(last_value);
261  buffer[write_pos++] = sample;
262  // on buffer owerflow reset to 0
263  if (write_pos >= buffer_size) {
264  write_pos = 0;
265  }
266  return true;
267  }
268 
270  void reset() {
271  read_pos_float = 0;
272  write_pos = 0;
273  memset(buffer.data(), 0, sizeof(T) * buffer_size);
274  }
275 
276  virtual bool isFull() { return false; }
277  virtual int available() { return buffer_size; }
278  virtual int availableForWrite() { return buffer_size; }
279  virtual T *address() { return nullptr; }
280  size_t size() {return buffer_size;}
281 
282 
283 protected:
284  Vector<T> buffer{0};
285  int buffer_size;
286  float read_pos_float = 0.0;
287  float read_increment = 0.0;
288  int write_pos = 0;
289  // used to handle overruns:
290  T last_value = 0; // record last read value
291  bool incrementing; // is last read increasing
292 
294  T interpolate(float read_pos) {
295  int read_pos_int = read_pos;
296  T value1 = getValue(read_pos_int);
297  T value2 = getValue(read_pos_int + 1);
298  incrementing = value2 - value1 >= 0;
299 
300  // make sure that value1 is smaller then value 2
301  if (value2 < value1) {
302  T tmp = value2;
303  value2 = value1;
304  value1 = tmp;
305  }
306  // the result must be between value 1 and value 2: linear interpolation
307  float offset_in = read_pos - read_pos_int; // calculate fraction: e.g 0.5
308  LOGD("read_pos=%f read_pos_int=%d, offset_in=%f", read_pos, read_pos_int, offset_in);
309  float diff_result = abs(value2 - value1); // differrence between values: e.g. 10
310  float offset_result = offset_in * diff_result; // 0.5 * 10 = 5
311  float result = offset_result + value1;
312  LOGD("interpolate %d %d -> %f -> %f", value1, value2, offset_result, result);
313 
314  last_value = result;
315 
316  return result;
317  }
318 
320  T getValue(int pos) { return buffer[pos % buffer_size]; }
321 
324  bool isMatching(T value1, bool incrementing, T v1, T v2) {
325  bool v_incrementing = v2 - v1 >= 0;
326  // eff sample was ascending so we need to select a ascending value
327  if (incrementing && v_incrementing && value1 >= v1 && value1 <= v2) {
328  return true;
329  }
330  // eff sample was descending so we need to select a descending value
331  if (!incrementing && !v_incrementing && value1 <= v1 && value1 >= v2) {
332  return true;
333  }
334  return false;
335  }
336 
339  void handleReadWriteOverrun(T last_value) {
340  // handle overflow - we need to allign the phase
341  int read_pos_int = read_pos_float; // round down
342  if (write_pos == read_pos_int ||
343  write_pos == (buffer_size % (read_pos_int + 1))) {
344  LOGD("handleReadWriteOverrun write_pos=%d read_pos_int=%d", write_pos,
345  read_pos_int);
346  bool found = false;
347 
348  // find the closest match for the last value
349  for (int j = read_increment * 2; j < buffer_size; j++) {
350  int pos = read_pos_int + j;
351  float v1 = getValue(pos);
352  float v2 = getValue(pos + 1);
353  // find corresponging matching sample in buffer for last sample
354  if (isMatching(last_value, incrementing, v1, v2)) {
355  // interpolate new position
356  float diff_value = abs(v1 - v2);
357  float diff_last_value = abs(v1 - last_value);
358  float fraction = 0;
359  if (diff_value>0){
360  fraction = diff_last_value / diff_value;
361  }
362 
363  read_pos_float = fraction + pos;
364  // move to next value
365  read_pos_float += read_increment;
366  // if we are at the end of the buffer we restart from 0
367  if (read_pos_float > buffer_size) {
368  read_pos_float -= buffer_size;
369  }
370  LOGD("handleReadWriteOverrun -> read_pos pos=%d pos_float=%f", pos, read_pos_float);
371  found = true;
372  break;
373  }
374  }
375  if (!found) {
376  LOGW("phase allign failed: maybe the buffer is too small")
377  }
378  }
379  }
380 };
381 
392 template <typename T, class BufferT>
394 public:
395  PitchShiftOutput(Print &out) { p_out = &out; }
396 
397  PitchShiftInfo defaultConfig() {
398  PitchShiftInfo result;
399  result.bits_per_sample = sizeof(T) * 8;
400  return result;
401  }
402 
403  bool begin(PitchShiftInfo info) {
404  TRACED();
405  cfg = info;
407  buffer.resize(info.buffer_size);
408  buffer.reset();
409  buffer.setIncrement(info.pitch_shift);
410  active = true;
411  return active;
412  }
413 
414  size_t write(const uint8_t *data, size_t len) override {
415  TRACED();
416  if (!active)
417  return 0;
418 
419  size_t result = 0;
420  int channels = cfg.channels;
421  T *p_in = (T *)data;
422  int sample_count = len / sizeof(T);
423 
424  for (int j = 0; j < sample_count; j += channels) {
425  float value = 0;
426  for (int ch = 0; ch < channels; ch++) {
427  value += p_in[j + ch];
428  }
429  // calculate avg sample value
430  value /= cfg.channels;
431 
432  // output values
433  T out_value = pitchShift(value);
434  LOGD("PitchShiftOutput %f -> %d", value, (int) out_value);
435  T out_array[channels];
436  for (int ch = 0; ch < channels; ch++) {
437  out_array[ch] = out_value;
438  }
439  result += p_out->write((uint8_t *)out_array, sizeof(T) * channels);
440  }
441  return result;
442  }
443 
444  void end() { active = false; }
445 
446 protected:
447  BufferT buffer;
448  bool active;
449  PitchShiftInfo cfg;
450  Print *p_out = nullptr;
451 
452  // execute the pitch shift by writing one sample and returning the pitch
453  // shifted result sample
454  T pitchShift(T value) {
455  TRACED();
456  if (!active)
457  return 0;
458  buffer.write(value);
459  T out_value = buffer.read();
460  return out_value;
461  }
462 };
463 
464 } // namespace audio_tools
Abstract Audio Ouptut class.
Definition: AudioOutput.h:22
virtual void setAudioInfo(AudioInfo newInfo) override
Defines the input AudioInfo.
Definition: AudioOutput.h:46
Shared functionality of all buffers.
Definition: Buffers.h:30
Pitch Shift: Shifts the frequency up or down w/o impacting the length! We reduce the channels to 1 to...
Definition: PitchShift.h:393
Definition: NoArduino.h:58
Varialbe speed ring buffer where we read with 0 and 180 degree and blend the result to prevent overru...
Definition: PitchShift.h:107
virtual int availableForWrite()
provides the number of entries that are available to write
Definition: PitchShift.h:153
virtual int available()
provides the number of entries that are available to read
Definition: PitchShift.h:152
T peek()
peeks the actual entry from the buffer
Definition: PitchShift.h:125
virtual T * address()
returns the address of the start of the physical read buffer
Definition: PitchShift.h:154
T read()
reads a single value
Definition: PitchShift.h:123
bool write(T sample)
write add an entry to the buffer
Definition: PitchShift.h:127
virtual bool isFull()
checks if the buffer is full
Definition: PitchShift.h:151
void reset()
Reset pointer positions and clear buffer.
Definition: PitchShift.h:143
virtual T pitchRead()
pitch shift for a single sample
Definition: PitchShift.h:168
Optimized Buffer implementation for Pitch Shift. We try to interpolate the samples and restore the ph...
Definition: PitchShift.h:223
T interpolate(float read_pos)
Calculate exact sample value for float position.
Definition: PitchShift.h:294
virtual int availableForWrite()
provides the number of entries that are available to write
Definition: PitchShift.h:278
virtual int available()
provides the number of entries that are available to read
Definition: PitchShift.h:277
T peek()
peeks the actual entry from the buffer
Definition: PitchShift.h:251
virtual T * address()
returns the address of the start of the physical read buffer
Definition: PitchShift.h:279
void handleReadWriteOverrun(T last_value)
Definition: PitchShift.h:339
T read()
reads a single value
Definition: PitchShift.h:240
bool write(T sample)
write add an entry to the buffer
Definition: PitchShift.h:257
virtual bool isFull()
checks if the buffer is full
Definition: PitchShift.h:276
void reset()
Reset pointer positions and clear buffer.
Definition: PitchShift.h:270
T getValue(int pos)
provides the value from the buffer: Allows pos > buffer_size
Definition: PitchShift.h:320
bool isMatching(T value1, bool incrementing, T v1, T v2)
Definition: PitchShift.h:324
Very Simple Buffer implementation for Pitch Shift. We write in constant speed, but reading can be don...
Definition: PitchShift.h:33
virtual int availableForWrite()
provides the number of entries that are available to write
Definition: PitchShift.h:88
virtual int available()
provides the number of entries that are available to read
Definition: PitchShift.h:87
T peek()
peeks the actual entry from the buffer
Definition: PitchShift.h:58
virtual T * address()
returns the address of the start of the physical read buffer
Definition: PitchShift.h:89
T read()
reads a single value
Definition: PitchShift.h:48
bool write(T sample)
write add an entry to the buffer
Definition: PitchShift.h:66
virtual bool isFull()
checks if the buffer is full
Definition: PitchShift.h:86
void reset()
Reset pointer positions and clear buffer.
Definition: PitchShift.h:80
Generic Implementation of sound input and output for desktop environments using portaudio.
Definition: AnalogAudio.h:10
Basic Audio information which drives e.g. I2S.
Definition: AudioTypes.h:50
sample_rate_t sample_rate
Sample Rate: e.g 44100.
Definition: AudioTypes.h:53
uint16_t channels
Number of channels: 2=stereo, 1=mono.
Definition: AudioTypes.h:55
uint8_t bits_per_sample
Number of bits per sample (int16_t = 16 bits)
Definition: AudioTypes.h:57
Configuration for PitchShiftOutput: set the pitch_shift to define the shift.
Definition: PitchShift.h:15