Forum Rule: Always post complete source code & details to reproduce any issue!
Results 1 to 2 of 2

Thread: Analog-sounding Ultrasonic Theremin Synth

  1. #1
    Junior Member
    Join Date
    Jun 2022
    Posts
    1

    Analog-sounding Ultrasonic Theremin Synth

    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
    }

  2. #2
    Senior Member BriComp's Avatar
    Join Date
    Apr 2014
    Location
    Cheltenham, UK
    Posts
    756
    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.

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •