Analog-sounding Ultrasonic Theremin Synth

octants

New member
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
}
 
Code:
#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
}
Do you think that in future you could enclose your code between code tags (by pressing the # button) like I have done above for your code.
It makes the code much easier to rea, understand and potentially able to help you.
By the way welcome to the Teensy forum.
 
Back
Top