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