I am trying to smooth values from an ultrasonic sensor so that there is no "stair-stepping" as the pitch changes. It needs to track and have its pitch slide between notes more like an analog theremin. I can't find anyone on the internet who has successfully achieved this with an ultrasonic sensor, however. That seems like a red flag because theremins are very popular and this is not a very original concept.
Perhaps ultrasonic sensors are a lost cause and I should try something else like a TOF sensor? My intuition, however, tells me it might be possible to take readings from the sensor at even a slower sample rate and create sliding transitions between them like portamento on the keyboard of an analog synth.
I have tried many strategies without success - still getting the pitch stepping up and down instead of sliding like a proper analog theremin. I wish there was a library that could do this – one that can take any old smelly jittery sensor and get a fast analog-like smoothed values with 0 jitter or stepping.
Just a quick note by "stepping" and "stair-stepping", I mean that the values go up and down as the sensor value is read but there is a jump between readings which is heard in the way it modulates the frequency of an AudioSynthWaveform.
Code below.
#include <Audio.h>
#include <Wire.h>
#include <SPI.h>
#include <SD.h>
#include <SerialFlash.h>
#include <NewPing.h>
// GUItool: begin automatically generated code
AudioSynthWaveform waveform1; //xy=343,338
AudioMixer4 mixer2; //xy=497,352
AudioOutputI2S i2s1; //xy=634,352
AudioConnection patchCord1(waveform1, 0, mixer2, 0);
AudioConnection patchCord2(mixer2, 0, i2s1, 1);
AudioConnection patchCord3(mixer2, 0, i2s1, 0);
AudioControlSGTL5000 sgtl5000_1; //xy=628,410
// GUItool: end automatically generated code
#define TRIGGER_PIN 4
#define ECHO_PIN 5
#define MAX_DISTANCE 400
NewPing sonar(TRIGGER_PIN, ECHO_PIN, MAX_DISTANCE);
float duration, distance;
float follow1 = 1.0;
float rise_rate = 1.001;
float fall_rate = .999;
int update_rate = 3000;
int follow_rate = 20;
int iterations = 4;
uint32_t current_ms, current_us, prev_us0, prev_us1, prev_ms1, prev_ms2;
void setup() {
Serial.begin(9600);
AudioMemory(20);
sgtl5000_1.enable();
sgtl5000_1.volume(0.5);
waveform1.begin(WAVEFORM_SINE);
waveform1.amplitude(0.3);
waveform1.frequency(0);
waveform1.pulseWidth(0.3);
}
void loop() {
current_us = micros();
if (current_us - prev_us0 > update_rate) {
prev_us0 = current_us;
duration = sonar.ping_median(iterations);
//or smooth it
//smooth(unique insatace number for each variable to smooth, how many sampels to take, input)
int raw = sonar.ping_median(iterations);
duration = smooth(0,8,raw);
distance = ((duration / 2) * 0.0343);
distance = map(distance, 2.0, 70.0, 5000.0, 40.0); //does map work on foats? It's supped to but I had an ossue in another thing
//waveform1.frequency(follow1);
waveform1.frequency(distance);
}
if (current_us - prev_us1 > follow_rate) {
prev_us1 = current_us;
if (follow1 < (distance * rise_rate)-2) { // the 2 is there so there's a little gap between rising and falling so it can jsut get set to the distance
follow1 *= rise_rate;
}
else if (follow1 > (distance * fall_rate)+2) {
follow1 *= fall_rate;
}
else {
follow1 = distance;
}
if (follow1 < 1) {
follow1 = 1.0; // it can't be 0 if we're multiplying
}
}
current_ms = millis();
if (current_ms - prev_ms1 > 25) {
prev_ms1 = current_ms;
Serial.print(distance);
Serial.print(" ");
Serial.println(follow1);
//for JMR testing
//distance += random(-40, 50);
if (distance < 0) {
distance = random(400.0, 2000.0);
}
}
if (current_ms - prev_ms2 > 2000) {
prev_ms2 = current_ms;
//for JMR testing
//distance = random(40, 1000);
}
}
////////////smooth function
//based on https://playground.arduino.cc/Main/DigitalSmooth/
#define maxarrays 8 //max number of different variables to smooth
#define maxsamples 99 //max number of points to sample and
//reduce these numbers to save RAM
unsigned int smoothArray[maxarrays][maxsamples];
// sel should be a unique number for each occurrence
// samples should be an odd number greater that 7. It's the length of the array. The larger the more smooth but less responsive
// raw_in is the input. positive numbers in and out only.
unsigned int smooth(byte sel, unsigned int samples, unsigned int raw_in) {
int j, k, temp, top, bottom;
long total;
static int i[maxarrays];
static int sorted[maxarrays][maxsamples];
boolean done;
i[sel] = (i[sel] + 1) % samples; // increment counter and roll over if necessary. - % (modulo operator) rolls over variable
smoothArray[sel][i[sel]] = raw_in; // input new data into the oldest slot
for (j = 0; j < samples; j++) { // transfer data array into anther array for sorting and averaging
sorted[sel][j] = smoothArray[sel][j];
}
done = 0; // flag to know when we're done sorting
while (done != 1) { // simple swap sort, sorts numbers from lowest to highest
done = 1;
for (j = 0; j < (samples - 1); j++) {
if (sorted[sel][j] > sorted[sel][j + 1]) { // numbers are out of order - swap
temp = sorted[sel][j + 1];
sorted[sel] [j + 1] = sorted[sel][j] ;
sorted[sel] [j] = temp;
done = 0;
}
}
}
// throw out top and bottom 15% of samples - limit to throw out at least one from top and bottom
bottom = max(((samples * 15) / 100), 1);
top = min((((samples * 85) / 100) + 1 ), (samples - 1)); // the + 1 is to make up for asymmetry caused by integer rounding
k = 0;
total = 0;
for ( j = bottom; j < top; j++) {
total += sorted[sel][j]; // total remaining indices
k++;
}
return total / k; // divide by number of samples
}
Perhaps ultrasonic sensors are a lost cause and I should try something else like a TOF sensor? My intuition, however, tells me it might be possible to take readings from the sensor at even a slower sample rate and create sliding transitions between them like portamento on the keyboard of an analog synth.
I have tried many strategies without success - still getting the pitch stepping up and down instead of sliding like a proper analog theremin. I wish there was a library that could do this – one that can take any old smelly jittery sensor and get a fast analog-like smoothed values with 0 jitter or stepping.
Just a quick note by "stepping" and "stair-stepping", I mean that the values go up and down as the sensor value is read but there is a jump between readings which is heard in the way it modulates the frequency of an AudioSynthWaveform.
Code below.
#include <Audio.h>
#include <Wire.h>
#include <SPI.h>
#include <SD.h>
#include <SerialFlash.h>
#include <NewPing.h>
// GUItool: begin automatically generated code
AudioSynthWaveform waveform1; //xy=343,338
AudioMixer4 mixer2; //xy=497,352
AudioOutputI2S i2s1; //xy=634,352
AudioConnection patchCord1(waveform1, 0, mixer2, 0);
AudioConnection patchCord2(mixer2, 0, i2s1, 1);
AudioConnection patchCord3(mixer2, 0, i2s1, 0);
AudioControlSGTL5000 sgtl5000_1; //xy=628,410
// GUItool: end automatically generated code
#define TRIGGER_PIN 4
#define ECHO_PIN 5
#define MAX_DISTANCE 400
NewPing sonar(TRIGGER_PIN, ECHO_PIN, MAX_DISTANCE);
float duration, distance;
float follow1 = 1.0;
float rise_rate = 1.001;
float fall_rate = .999;
int update_rate = 3000;
int follow_rate = 20;
int iterations = 4;
uint32_t current_ms, current_us, prev_us0, prev_us1, prev_ms1, prev_ms2;
void setup() {
Serial.begin(9600);
AudioMemory(20);
sgtl5000_1.enable();
sgtl5000_1.volume(0.5);
waveform1.begin(WAVEFORM_SINE);
waveform1.amplitude(0.3);
waveform1.frequency(0);
waveform1.pulseWidth(0.3);
}
void loop() {
current_us = micros();
if (current_us - prev_us0 > update_rate) {
prev_us0 = current_us;
duration = sonar.ping_median(iterations);
//or smooth it
//smooth(unique insatace number for each variable to smooth, how many sampels to take, input)
int raw = sonar.ping_median(iterations);
duration = smooth(0,8,raw);
distance = ((duration / 2) * 0.0343);
distance = map(distance, 2.0, 70.0, 5000.0, 40.0); //does map work on foats? It's supped to but I had an ossue in another thing
//waveform1.frequency(follow1);
waveform1.frequency(distance);
}
if (current_us - prev_us1 > follow_rate) {
prev_us1 = current_us;
if (follow1 < (distance * rise_rate)-2) { // the 2 is there so there's a little gap between rising and falling so it can jsut get set to the distance
follow1 *= rise_rate;
}
else if (follow1 > (distance * fall_rate)+2) {
follow1 *= fall_rate;
}
else {
follow1 = distance;
}
if (follow1 < 1) {
follow1 = 1.0; // it can't be 0 if we're multiplying
}
}
current_ms = millis();
if (current_ms - prev_ms1 > 25) {
prev_ms1 = current_ms;
Serial.print(distance);
Serial.print(" ");
Serial.println(follow1);
//for JMR testing
//distance += random(-40, 50);
if (distance < 0) {
distance = random(400.0, 2000.0);
}
}
if (current_ms - prev_ms2 > 2000) {
prev_ms2 = current_ms;
//for JMR testing
//distance = random(40, 1000);
}
}
////////////smooth function
//based on https://playground.arduino.cc/Main/DigitalSmooth/
#define maxarrays 8 //max number of different variables to smooth
#define maxsamples 99 //max number of points to sample and
//reduce these numbers to save RAM
unsigned int smoothArray[maxarrays][maxsamples];
// sel should be a unique number for each occurrence
// samples should be an odd number greater that 7. It's the length of the array. The larger the more smooth but less responsive
// raw_in is the input. positive numbers in and out only.
unsigned int smooth(byte sel, unsigned int samples, unsigned int raw_in) {
int j, k, temp, top, bottom;
long total;
static int i[maxarrays];
static int sorted[maxarrays][maxsamples];
boolean done;
i[sel] = (i[sel] + 1) % samples; // increment counter and roll over if necessary. - % (modulo operator) rolls over variable
smoothArray[sel][i[sel]] = raw_in; // input new data into the oldest slot
for (j = 0; j < samples; j++) { // transfer data array into anther array for sorting and averaging
sorted[sel][j] = smoothArray[sel][j];
}
done = 0; // flag to know when we're done sorting
while (done != 1) { // simple swap sort, sorts numbers from lowest to highest
done = 1;
for (j = 0; j < (samples - 1); j++) {
if (sorted[sel][j] > sorted[sel][j + 1]) { // numbers are out of order - swap
temp = sorted[sel][j + 1];
sorted[sel] [j + 1] = sorted[sel][j] ;
sorted[sel] [j] = temp;
done = 0;
}
}
}
// throw out top and bottom 15% of samples - limit to throw out at least one from top and bottom
bottom = max(((samples * 15) / 100), 1);
top = min((((samples * 85) / 100) + 1 ), (samples - 1)); // the + 1 is to make up for asymmetry caused by integer rounding
k = 0;
total = 0;
for ( j = bottom; j < top; j++) {
total += sorted[sel][j]; // total remaining indices
k++;
}
return total / k; // divide by number of samples
}