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