arduino-audio-tools
Loading...
Searching...
No Matches
PitchShift.h
Go to the documentation of this file.
1
37#pragma once
38#include <math.h>
39#include <stdio.h>
40#include <string.h>
41
42#include "AudioTools/CoreAudio/AudioOutput.h"
43#include "AudioTools/CoreAudio/AudioTypes.h"
44#include "AudioToolsConfig.h"
45
46namespace audio_tools {
47
59struct PitchShiftInfo : public AudioInfo {
61 channels = 2;
62 sample_rate = 44100;
63 bits_per_sample = 16;
64 }
65
67 float pitch_shift = 1.4f;
68
70 int buffer_size = 1000;
71};
72
87template <typename T>
89 public:
95 VariableSpeedRingBufferSimple(int size = 0, float increment = 1.0) {
96 setIncrement(increment);
97 if (size > 0) resize(size);
98 }
99
104 void setIncrement(float increment) { read_increment = increment; }
105
111 bool resize(int size) {
112 buffer_size = size;
113 return buffer.resize(size);
114 }
115
126 bool read(T &result) {
127 peek(result);
128 read_pos_float += read_increment;
129 // on buffer overflow reset to beginning
130 if (read_pos_float > buffer_size) {
131 read_pos_float -= buffer_size;
132 }
133 return true;
134 }
135
141 bool peek(T &result) {
142 if (buffer.size() == 0) {
143 LOGE("buffer has no memory");
144 result = 0;
145 } else {
146 result = buffer[(int)read_pos_float];
147 }
148 return true;
149 }
150
156 bool write(T sample) {
157 if (buffer.size() == 0) {
158 LOGE("buffer has no memory");
159 return false;
160 }
161 buffer[write_pos++] = sample;
162 // on buffer overflow reset to 0
163 if (write_pos >= buffer_size) {
164 write_pos = 0;
165 }
166 return true;
167 }
168
170 void reset() {
171 read_pos_float = 0;
172 write_pos = 0;
173 memset(buffer.data(), 0, sizeof(T) * buffer_size);
174 }
175
176 virtual bool isFull() { return false; }
177 virtual int available() { return buffer_size; }
178 virtual int availableForWrite() { return buffer_size; }
179 virtual T *address() { return nullptr; }
180 size_t size() { return buffer_size; }
181
182 protected:
183 Vector<T> buffer{0};
184 int buffer_size = 0;
185 float read_pos_float = 0.0;
186 float read_increment = 1.0;
187 int write_pos = 0;
188};
189
204template <typename T>
206 public:
212 VariableSpeedRingBuffer180(int size = 0, float increment = 1.0) {
213 setIncrement(increment);
214 if (size > 0) resize(size);
215 }
216
221 void setIncrement(float increment) { pitch_shift = increment; }
222
228 bool resize(int size) {
229 buffer_size = size;
230 overlap = buffer_size / 10;
231 return buffer.resize(size);
232 }
233
239 bool read(T &result) {
240 result = pitchRead();
241 return true;
242 }
243
249 bool peek(T &result) { return false; }
250
256 bool write(T sample) {
257 if (buffer.size() == 0) {
258 LOGE("buffer has no memory");
259 return false;
260 }
261 // write_pointer value is used in pitchRead()
262 write_pointer = write_pos;
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 cross_fade = 1.0f;
276 overlap = buffer_size / 10;
277 memset(buffer.data(), 0, sizeof(T) * buffer_size);
278 }
279
280 virtual bool isFull() { return false; }
281 virtual int available() { return buffer_size; }
282 virtual int availableForWrite() { return buffer_size; }
283 virtual T *address() { return nullptr; }
284 size_t size() { return buffer_size; }
285
286 protected:
287 Vector<T> buffer{0};
288 float read_pos_float = 0.0;
289 float cross_fade = 1.0;
290 int write_pos = 0;
291 int write_pointer = 0;
292 int buffer_size = 0;
293 int overlap = 0;
294 float pitch_shift = 0;
295
311 virtual T pitchRead() {
312 TRACED();
313 assert(pitch_shift > 0);
314 assert(buffer_size > 0);
315
316 // read fractional readpointer and generate 0° and 180° read-pointer in
317 // integer
318 int read_pointer_int = roundf(read_pos_float);
319 int read_pointer_int180 = 0;
320 if (read_pointer_int >= buffer_size / 2)
321 read_pointer_int180 = read_pointer_int - (buffer_size / 2);
322 else
323 read_pointer_int180 = read_pointer_int + (buffer_size / 2);
324
325 // read the two samples...
326 float read_sample = (float)buffer[read_pointer_int];
327 float read_sample_180 = (float)buffer[read_pointer_int180];
328
329 // Check if first readpointer starts overlap with write pointer?
330 // if yes -> do cross-fade to second read-pointer
331 if (overlap >= (write_pointer - read_pointer_int) &&
332 (write_pointer - read_pointer_int) >= 0 && pitch_shift != 1.0f) {
333 int rel = write_pointer - read_pointer_int;
334 cross_fade = ((float)rel) / (float)overlap;
335 } else if (write_pointer - read_pointer_int == 0)
336 cross_fade = 0.0f;
337
338 // Check if second readpointer starts overlap with write pointer?
339 // if yes -> do cross-fade to first read-pointer
340 if (overlap >= (write_pointer - read_pointer_int180) &&
341 (write_pointer - read_pointer_int180) >= 0 && pitch_shift != 1.0f) {
342 int rel = write_pointer - read_pointer_int180;
343 cross_fade = 1.0f - ((float)rel) / (float)overlap;
344 } else if (write_pointer - read_pointer_int180 == 0)
345 cross_fade = 1.0f;
346
347 // do cross-fading and sum up
348 T sum = (read_sample * cross_fade + read_sample_180 * (1.0f - cross_fade));
349
350 // increment fractional read-pointer and write-pointer
351 read_pos_float += pitch_shift;
352 if (roundf(read_pos_float) >= buffer_size) read_pos_float = 0.0f;
353
354 return sum;
355 }
356};
357
374template <typename T>
376 public:
382 VariableSpeedRingBuffer(int size = 0, float increment = 1.0) {
383 setIncrement(increment);
384 if (size > 0) resize(size);
385 }
386
391 void setIncrement(float increment) { read_increment = increment; }
392
398 bool resize(int size) {
399 buffer_size = size;
400 // prevent an overrun at the start
401 read_pos_float = size / 2;
402 return buffer.resize(size);
403 }
404
405 bool read(T &result) {
406 assert(read_increment != 0.0f);
407 peek(result);
408 read_pos_float += read_increment;
410 if (read_pos_float > buffer_size) {
411 read_pos_float -= buffer_size;
412 }
413 return true;
414 }
415
416 bool peek(T &result) {
417 if (buffer.size() == 0) {
418 result = 0;
419 } else {
420 result = interpolate(read_pos_float);
421 }
422 return true;
423 }
424
425 bool write(T sample) {
426 if (buffer.size() == 0) return false;
428 buffer[write_pos++] = sample;
429 // on buffer overflow reset to 0
430 if (write_pos >= buffer_size) {
431 write_pos = 0;
432 }
433 return true;
434 }
435
437 void reset() {
438 read_pos_float = 0;
439 write_pos = 0;
440 memset(buffer.data(), 0, sizeof(T) * buffer_size);
441 }
442
443 virtual bool isFull() { return false; }
444 virtual int available() { return buffer_size; }
445 virtual int availableForWrite() { return buffer_size; }
446 virtual T *address() { return nullptr; }
447 size_t size() { return buffer_size; }
448
449 protected:
450 Vector<T> buffer{0};
451 int buffer_size;
452 float read_pos_float = 0.0f;
453 float read_increment = 0.0f;
454 int write_pos = 0;
455 // used to handle overruns:
458
469 T interpolate(float read_pos) {
470 int read_pos_int = read_pos;
471 T value1 = getValue(read_pos_int);
472 T value2 = getValue(read_pos_int + 1);
473 incrementing = value2 - value1 >= 0;
474
475 // make sure that value1 is smaller then value 2
476 if (value2 < value1) {
477 T tmp = value2;
478 value2 = value1;
479 value1 = tmp;
480 }
481 // the result must be between value 1 and value 2: linear interpolation
482 float offset_in = read_pos - read_pos_int; // calculate fraction: e.g 0.5
483 LOGD("read_pos=%f read_pos_int=%d, offset_in=%f", read_pos, read_pos_int,
484 offset_in);
485 float diff_result =
486 abs(value2 - value1); // differrence between values: e.g. 10
487 float offset_result = offset_in * diff_result; // 0.5 * 10 = 5
488 float result = offset_result + value1;
489 LOGD("interpolate %d %d -> %f -> %f", value1, value2, offset_result,
490 result);
491
492 last_value = result;
493
494 return result;
495 }
496
502 T getValue(int pos) { return buffer[pos % buffer_size]; }
503
517 bool isMatching(T value1, bool incrementing, T v1, T v2) {
518 bool v_incrementing = v2 - v1 >= 0;
519 // eff sample was ascending so we need to select a ascending value
520 if (incrementing && v_incrementing && value1 >= v1 && value1 <= v2) {
521 return true;
522 }
523 // eff sample was descending so we need to select a descending value
524 if (!incrementing && !v_incrementing && value1 <= v1 && value1 >= v2) {
525 return true;
526 }
527 return false;
528 }
529
547 // handle overflow - we need to allign the phase
548 int read_pos_int = read_pos_float; // round down
549 if (write_pos == read_pos_int ||
550 write_pos == (buffer_size % (read_pos_int + 1))) {
551 LOGD("handleReadWriteOverrun write_pos=%d read_pos_int=%d", write_pos,
552 read_pos_int);
553 bool found = false;
554
555 // find the closest match for the last value
556 for (int j = read_increment * 2; j < buffer_size; j++) {
557 int pos = read_pos_int + j;
558 float v1 = getValue(pos);
559 float v2 = getValue(pos + 1);
560 // find corresponging matching sample in buffer for last sample
561 if (isMatching(last_value, incrementing, v1, v2)) {
562 // interpolate new position
563 float diff_value = abs(v1 - v2);
564 float diff_last_value = abs(v1 - last_value);
565 float fraction = 0;
566 if (diff_value > 0) {
567 fraction = diff_last_value / diff_value;
568 }
569
570 read_pos_float = fraction + pos;
571 // move to next value
572 read_pos_float += read_increment;
573 // if we are at the end of the buffer we restart from 0
574 if (read_pos_float > buffer_size) {
575 read_pos_float -= buffer_size;
576 }
577 LOGD("handleReadWriteOverrun -> read_pos pos=%d pos_float=%f", pos,
578 read_pos_float);
579 found = true;
580 break;
581 }
582 }
583 if (!found) {
584 LOGW("phase allign failed: maybe the buffer is too small")
585 }
586 }
587 }
588};
589
614template <typename T, class BufferT>
616 public:
621 PitchShiftOutput(Print &out) { p_out = &out; }
622
628 PitchShiftInfo result;
629 result.bits_per_sample = sizeof(T) * 8;
630 return result;
631 }
632
639 TRACED();
640 cfg = info;
641 return begin();
642 }
643
648 bool begin() {
650 buffer.resize(cfg.buffer_size);
651 buffer.reset();
652 buffer.setIncrement(cfg.pitch_shift);
653 active = true;
654 return active;
655 }
656
669 size_t write(const uint8_t *data, size_t len) override {
670 LOGD("PitchShiftOutput::write %d bytes", (int)len);
671 if (!active) return 0;
672
673 size_t result = 0;
674 int channels = cfg.channels;
675 T *p_in = (T *)data;
676 int sample_count = len / sizeof(T);
677
678 for (int j = 0; j < sample_count; j += channels) {
679 float value = 0;
680 for (int ch = 0; ch < channels; ch++) {
681 value += p_in[j + ch];
682 }
683 // calculate avg sample value
684 value /= cfg.channels;
685
686 // output values
687 T out_value = pitchShift(value);
688 LOGD("PitchShiftOutput %f -> %d", value, (int)out_value);
689 T out_array[channels];
690 for (int ch = 0; ch < channels; ch++) {
691 out_array[ch] = out_value;
692 }
693 result += p_out->write((uint8_t *)out_array, sizeof(T) * channels);
694 }
695 return result;
696 }
697
701 void end() { active = false; }
702
703 protected:
704 BufferT buffer;
706 Print *p_out = nullptr;
707 bool active = false;
708
720 T pitchShift(T value) {
721 TRACED();
722 if (!active) return 0;
723 buffer.write(value);
724 T result = 0;
725 buffer.read(result);
726 return result;
727 }
728};
729
730} // 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:22
Real-time pitch shifting audio effect.
Definition PitchShift.h:615
bool active
Whether pitch shifting is currently active.
Definition PitchShift.h:707
bool begin(PitchShiftInfo info)
Initialize pitch shifting with configuration.
Definition PitchShift.h:638
bool begin()
Initialize pitch shifting with current configuration.
Definition PitchShift.h:648
size_t write(const uint8_t *data, size_t len) override
Process and write audio data with pitch shifting applied.
Definition PitchShift.h:669
Print * p_out
Output stream for processed audio.
Definition PitchShift.h:706
void end()
Stop pitch shifting and deactivate the effect.
Definition PitchShift.h:701
BufferT buffer
Variable speed buffer for pitch shifting.
Definition PitchShift.h:704
T pitchShift(T value)
Execute pitch shift on a single sample.
Definition PitchShift.h:720
PitchShiftInfo cfg
Current configuration.
Definition PitchShift.h:705
PitchShiftInfo defaultConfig()
Get default configuration for pitch shifting.
Definition PitchShift.h:627
PitchShiftOutput(Print &out)
Constructor.
Definition PitchShift.h:621
Definition NoArduino.h:62
Variable speed ring buffer with 180-degree phase shifting.
Definition PitchShift.h:205
virtual T * address()
returns the address of the start of the physical read buffer
Definition PitchShift.h:283
void setIncrement(float increment)
Set the pitch shift factor.
Definition PitchShift.h:221
virtual int availableForWrite()
provides the number of entries that are available to write
Definition PitchShift.h:282
virtual int available()
provides the number of entries that are available to read
Definition PitchShift.h:281
bool peek(T &result)
Peek operation not supported in this buffer implementation.
Definition PitchShift.h:249
bool resize(int size)
Resize the internal buffer and recalculate overlap region.
Definition PitchShift.h:228
bool write(T sample)
Write a sample to the buffer.
Definition PitchShift.h:256
virtual bool isFull()
checks if the buffer is full
Definition PitchShift.h:280
bool read(T &result)
Read the next pitch-shifted sample.
Definition PitchShift.h:239
void reset()
Reset pointer positions and clear buffer.
Definition PitchShift.h:272
virtual T pitchRead()
Core pitch shifting algorithm with 180° phase offset blending.
Definition PitchShift.h:311
VariableSpeedRingBuffer180(int size=0, float increment=1.0)
Constructor.
Definition PitchShift.h:212
Optimized buffer implementation for pitch shifting with interpolation.
Definition PitchShift.h:375
virtual T * address()
returns the address of the start of the physical read buffer
Definition PitchShift.h:446
VariableSpeedRingBuffer(int size=0, float increment=1.0)
Constructor.
Definition PitchShift.h:382
void setIncrement(float increment)
Set the reading speed increment for pitch shifting.
Definition PitchShift.h:391
T interpolate(float read_pos)
Calculate exact sample value for fractional buffer position using linear interpolation.
Definition PitchShift.h:469
virtual int availableForWrite()
provides the number of entries that are available to write
Definition PitchShift.h:445
virtual int available()
provides the number of entries that are available to read
Definition PitchShift.h:444
bool peek(T &result)
peeks the actual entry from the buffer
Definition PitchShift.h:416
void handleReadWriteOverrun(T last_value)
Handle read/write pointer collisions with phase alignment.
Definition PitchShift.h:546
bool resize(int size)
Resize buffer and set initial read position to prevent immediate overrun.
Definition PitchShift.h:398
bool write(T sample)
write add an entry to the buffer
Definition PitchShift.h:425
virtual bool isFull()
checks if the buffer is full
Definition PitchShift.h:443
bool incrementing
Track if last read trend was increasing or decreasing.
Definition PitchShift.h:457
bool read(T &result)
reads a single value
Definition PitchShift.h:405
void reset()
Reset pointer positions and clear buffer.
Definition PitchShift.h:437
T getValue(int pos)
Get buffer value with wraparound support.
Definition PitchShift.h:502
T last_value
Record last read value for phase alignment.
Definition PitchShift.h:456
bool isMatching(T value1, bool incrementing, T v1, T v2)
Check if a value fits between two samples considering trend direction.
Definition PitchShift.h:517
Very Simple Buffer implementation for Pitch Shift.
Definition PitchShift.h:88
virtual T * address()
returns the address of the start of the physical read buffer
Definition PitchShift.h:179
void setIncrement(float increment)
Set the reading speed increment.
Definition PitchShift.h:104
virtual int availableForWrite()
provides the number of entries that are available to write
Definition PitchShift.h:178
virtual int available()
provides the number of entries that are available to read
Definition PitchShift.h:177
bool peek(T &result)
Peek at the current sample without advancing the read pointer.
Definition PitchShift.h:141
VariableSpeedRingBufferSimple(int size=0, float increment=1.0)
Constructor.
Definition PitchShift.h:95
bool resize(int size)
Resize the internal buffer.
Definition PitchShift.h:111
bool write(T sample)
Write a sample to the buffer.
Definition PitchShift.h:156
virtual bool isFull()
checks if the buffer is full
Definition PitchShift.h:176
bool read(T &result)
Read the next sample and advance the read pointer.
Definition PitchShift.h:126
void reset()
Reset pointer positions and clear buffer.
Definition PitchShift.h:170
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.
Definition PitchShift.h:59
float pitch_shift
Pitch shift factor: 1.0 = no change, >1.0 = higher pitch, <1.0 = lower pitch.
Definition PitchShift.h:67
int buffer_size
Size of the internal buffer used for pitch shifting (affects quality and latency)
Definition PitchShift.h:70