Arduino Ultrasonic Theremin - Can custom audio be run?

Hey all! So I am a 100% newbie but I am trying to work out the feasibility of a project I want to try. Basically, I want to make a theremin like this one using an arduino and ultrasonic sensor, and the code and setup look easy enough for the tonal scale it automatically runs, as well as shutting off when the hand pulls away (crucial). I want to push it further and have it play variations on some field recordings I took, is there a way to mess with the coding and have it play custom recordings? I know nothing about coding so I am so sorry if this sounds stupid. Even at it's simplest, have the ultrasonic sensor pick up the proximity of a hand and play the custom sound, and increase volume as hand gets closer (ideally), or have the tone shift? I appreciate anyones help, I really want to make this work if at all possible!

Most arduino can't play recordings. Or at least not more than a very small number of very short, very low quality recordings. It may be practical to bend the pitch of those recordings (by slowing down our speeding up the playback speed) but not too adjust the volume, because that would involve scaling the values on the fly.

But maybe a more advanced Arduino can achieve this. An Arduino with a fast clock speed, large amounts of memory and DAC(s), like ESP32 perhaps.

I'm totally fine with bending the pitch like that instead of volume adjustment. I'm also down to try out the ESP32, or just the limited short low quality recordings, experiment and see. I'm super new to this, what would I have to do to accommodate this, or are the reference docs on how to edit the code to tailor to needs like this? Thank you for your quick reply as well!!

Examples of two modules that can "play" audio are DFPlayerMini and MP3 Player. Both have a software command to increase or decrease volume. The DFPlayerMini has a pin that can be used to increase, and another pin available to decrease, volume.

As far as "bending"... I do not know how... BUT... you can. An old friend has a music band. One crowd-favorite instrument is a home-made, Arduino adjustable waveform generator into a talkbox. He teaches game theory at a world-renowned uni... so he's got a bit of a head start.

Your link has an invalid certificate. You want to make one LIKE that link I won't open, or EXACTLY like it?
For just your project goal, yes it's possible if you can keep your Arduino connected to a laptop and use Arduino and Processing together.
https://processing.org/reference/libraries/sound/index.html
At least, this is how I would make it.
I did something exactly like this for my laser maze. The real secret to mine is using a particular Magiquest wand to slow time, which includes sensor detection (laser trips) and the audio track and video (there's no audio in the video clips I use).

Here's how I do the Arduino side. It detects a IR wand cast, if it's a Unicorn or Alicorn wand (I have 11 different ones altogether), it temporarily slows the other Arduino side task to a crawl, allowing kids to get in and out before they are detected by laser tripwires. The Arduino also sends a String over serial to the PC running Processing to slow the audio track, etc.

void readTimeState() {
  led1.Update();
  if (irrecv.decode(&results)) {
    decodeMagiQuest(&results, &data);
    if ((data.cmd.wand_id == 289273217) || (data.cmd.wand_id == 988539521)) {
      Serial.println("timewarp");// string that's sent to Processing
      unsigned long rightNow = millis();
      unsigned long unicornPower = rightNow + warpVal;
      while (millis() <= unicornPower) {
        recheckAlarm();  // this will keep checking for trips during unicorn sequence
        for (int i = 0; i < 128; i++) { // count up to 126 by 7s: 19 values/set * avg # of sets (6) = 114
          if ( (i % 7) == 0) {
            delay(i);
            for (int j = 0; j < 7; j++) {
              delayMicroseconds(10000);
              delay(7);
              led2.Update();
            }
          }
        }
      }
      Serial.println("regtime");// string that's sent to Processing
    }
    else {}
    data.cmd.wand_id = 0;
    irrecv.resume();
    delay(50);
  }
}


and the associated Processing sketch section:

 else if (inString.equals("timewarp")) {
      currentTimeVal = 10000.0;     
      // update the soundtrack
      file.amp(0.0);
      currentSong[trackCount].amp(0.8);
      //slow the music down
      for (float i = 0.1; i <= 9; i++) {
        currentSong[trackCount].rate(audioTopRate - i);
        delay(150);
      }
      currentSong[trackCount].rate(0.1);
      timeEventState = true;
      gameEventState = true;
      state = 5;
    }
 else if (inString.equals("regtime")) {
      currentTimeVal = 1000.0;
      currentSong[trackCount].jump(3.5);
      currentSong[trackCount].rate(0.1);
      // speed the music back up
      for (float i = 0.1; i <= 9; i++) {
        currentSong[trackCount].rate(audioBottomRate + i);
        delay(150);
      }
      currentSong[trackCount].rate(1.0);
      timeEventState = false;
      gameEventState = false;
      state = 3;
    }

@sawyer000000
+1 for trying the DFR Mini MP3 Player module. At a specified distance it would start playing the associated pre-recorded MP3. And contuinue until the distance is detected as having changed to an adjacent range. It would be interesting to see how short the gaps could be made. Overall it would no doubt deliver a radically different audio effect to a traditional theremin!

Thank you for your help!! Okay that makes sense, a mini SD seems like a great option. Now -- I am truly lost with anything more than copying and pasting code (lol) are there any resources you would recommend for me to learn how to incorporate these elements? Like, where/why would I put it on the breadboard, what would the code look like to trigger what I want it to, etc? I'm unfortunately very much a novice and won't be able to do much other than carefully following exact instructions, but I want to learn!

Wow thank you for coming through like this, I really appreciate it! I have no allegiances to the instructions I linked to, really any way to reach my project goal I'm happy with. I'm unfortunately truly a novice and want to learn but have no way of adapting your code for my project I don't think, and think I may be over my heard, are there resources you would recommend for beginners?

1 Like

Very cool! Do you think it could easily be added to existing code for similar projects like this, and it would work? My coding knowledge stops with copying and pasting exact instructions at the moment unfortunately lol. Any resources also that you may know of for beginner would be so appreciated!

For the DFPlayerMini - Three modes: 1. Serial (commands via TX/RX), 2. A/D (commands using a buttonbox using a voltage divider), 3. I/O mode - Power, Ground, Speaker (3W) ground pins to select things.

You can search "arduino mp3 player" and get various devices that all work the same.

You will want a cheap micro SD card. I use very little audio, so I chose the cheapest. Some say the cheap SD cards do not work, but I have had no problem with any.

Well, there are examples in Processing in the Sound library to do all sorts of things with your sound files. I used .wav audio for this in Processing 4, and since I don't know your background with audio files (like editing or mastering in Audacity for ex) I will say spend an hour or so in Processing, try a couple of the examples and see if it's something you're comfortable with.

I mean, the suggestions here from @xfpd , @PaulRB , @Terrypin might well be the way to go for you. All depends. Do you have a specific audio device (small speakers?) that you wish to play it on? Stuff like that.

I have also used Arduino without Processing for audio stuff, but just playback of .wav files on the Adafruit Wave Shield. Adafruit may well have a device that does exactly what you're after, they are a go to company for projects like yours, I don't know.

I will say that, with Processing, you will have your PC as a resource for the project. So keyboard and mouse are built in, and such. So whatever you use to listen to audio on your PC, that's your speakers and amp done. You can even build it into a standalone Java app.

I always like to qualify my suggestions with the fact that I have no qualifications. I'm a regular joe hobbyist in my late 40s and if I can do it, anyone can. You just have to stick with it, whatever route you decide to go.

Here's some Processing code to make ^ task a little easier:

import processing.sound.*;
import processing.serial.*;
Serial myPort;
SoundFile freshActionB;
SoundFile Hydra;
SoundFile burglar;
int state;
int lastState;
// as long as your Arduino is between COM2-COM26, we can find you
String[] com = {"COM2", "COM3", "COM4",
  "COM5", "COM6", "COM7", "COM8", "COM9", "COM10", "COM11",
  "COM12", "COM13", "COM14", "COM15", "COM16", "COM17", "COM18",
  "COM19", "COM20", "COM21", "COM22", "COM23", "COM24", "COM25", "COM26"};
// zillions more definitions

void settings() {
  fullScreen(P3D); // set up monitor
}
void setup() {
  background(0, 59, 0); // just sets monitor color
  noStroke();
  fill(204);
  font = createFont("ethnocentric rg.ttf", 70); // use builtin fonts or roll your own
  scoreFont = loadFont("MakeMeACyborg-48.vlw");
  textAlign(CENTER);
  imageMode(CENTER);
  textFont(font, 40);
  fill(0, 200, 0);
  // Images, music and movies
  filesLoaded = false;
  println("loading files");
  loadMovies();
  freshActionB = new SoundFile(this, "Fresh Action Groove B.wav");
  Hydra = new SoundFile(this, "Hydra.wav");
  burglar = new SoundFile(this, "burglar.wav");
// bunch of other file loading irrelevant to this example
  thread("load8283"); // loads jpgs from diff tab
  reset();
  // port bus, array index number of COM ports 2-25 (array 24-0 +=), attempts to connect
  connect(1, 24, 200); // 25 com ports*4 port numbers (buses)*2 tries each
  while (fileSum != 1) {
    println("loading files");
  }
  println("files loaded");
  ready(); // time for some action
}
void draw() {
  if ((state == 0) && (idling == true)) {
    checkCreditsRequest();
    esc();
  }
  if (state  == 900) {
    checkCreditsRequest();
    esc();
  }
// a whole bunch of other stuff
lastState = state; // last line in loop, this keeps track of last event called in game, important
}

boolean connect(int portNumber, int numCom, int limit) {
  background (0, 14, 255);
  int tries = 0;
  while (tries < limit) {
    try {
      printArray (Serial.list()[portNumber]);
      myPort = new Serial(this, com[numCom], 115200);
    }
    catch(Exception e) {
      System.err.println("Retrying " + "Port " + com[numCom] + " on Port Bus " + portNumber);
      // numCom -= 1;
      numCom += 1;
      tries++;
      if (numCom < 0) { // prevent ArrayIndexOutOfBoundsException
        numCom = 24;
      } else if (numCom > 24) { // prevent ArrayIndexOutOfBoundsException
        numCom = 0;
      }
      if (tries % 25 == 0) {
        portNumber += 1;
        if (portNumber > 4) {
          portNumber = 0;
        }
      }
      delay(200);
      continue;
    }
    break;
  }
  if (tries < limit) {
    println("Connected in " + str(tries + 1) + " tries.");
    background(0, 255, 0);
    delay(200);
    background(0);
    return true;
  } else {
    System.err.println("Connection failed.");
    return false;
  }
}

// serial data comms
void serialEvent (Serial myPort) {
  String inString = myPort.readStringUntil('\n'); // why Arduino always Serial.println("stuff");
  if (inString != null) {  // if there's something
    inString = trim(inString); // prepare String
    idling = false;
    test = 0; // just another control variable
    val = 0; // ditto, in Processing, you can do stuff right in SerialEvent like flow control
    simMode = true; // might be a Classified just scanned
    if (inString.equals("intruder")) { // check if String in said "intruder"
      background(0); // clear monitor screen for new video sequence
      state = 670; // game control state of this stage in game
    }

    // 1982 Figures then vehicles
    else if (inString.equals("bre")) {
      state = 1; // state machine control variable
      classifiedDNA = state; // borrows from above
      classIndex = 11; // to know what to display
      roleIndex = 1; // etc etc etc
      masVal = 2; // none of these are needed, just showing where lots of things can be driven
    }
 }
}

This is only a snippet of the void serialEvent (Serial myPort){} function, since everything happens in here.

So what boolean connect(int portNumber, int numCom, int limit) function does is make the connection automatically to your Arduino, it shouldn't matter what COM number it was assigned, but it will have to be 115200 baud (unless you change it). It will find your Arduino in case your dev computer isn't your presentation computer.

This was not from the same game as the laser maze code before, but it works pretty much the same way. For this game, a 90% complete dossier of GI Joe A Real American Hero action figures and vehicles box art and figure file cards, with interactive game elements built in.

The Arduino receives data from RFID tags and based on the tag ID, prints a String to the Processing sketch to change the card displayed on screen. You could easily make that call up an audio clip or a whole audio bending function based on some range from your ultrasonic sensor(s). The Arduino also listens through Serial for chars (keyboard presses) from Processing to lock the dossier "hacker" out during a particular game stage and also to easily navigate through all 650-ish Gi Joe file cards and box art.

boolean locked;
String lastString; // just for the RFID cards, you would have definitions and such for your sensor

void setup() {
  Serial.begin(115200); // you'd need this for sure
  SPI.begin(); //  for the RFID reader
  mfrc522.PCD_Init(); // mo RFID stuff - fire it up! 
  mfrc522.PCD_SetAntennaGain(mfrc522.RxGain_max); // turn RFID reader up to 11
  locked = false; // and this if you like the switch/case (mode thing) in the loop that follows
}

void loop() {
  if (Serial.available() > 0) {
    char in = Serial.read();
    // lock or unlock the Arduino
    if ((in == 'e') || (in == 'E')) {
      locked = true;
    } else if ((in == 'a') || (in == 'A')) {
      locked = false;
    }
  }
  switch (locked) {
    case false:
      if ( ! mfrc522.PICC_IsNewCardPresent()) {
        return;
      }
      // Select one of the cards (RFID stuff)
      if ( ! mfrc522.PICC_ReadCardSerial()) {
        return;
      }
      String content = "";
      for (byte i = 0; i < mfrc522.uid.size; i++) {
        Serial.print(mfrc522.uid.uidByte[i] < 0x10 ? "0" : "");
        Serial.print(mfrc522.uid.uidByte[i], HEX);
        content.concat(String(mfrc522.uid.uidByte[i] < 0x10 ? "0" : ""));
        content.concat(String(mfrc522.uid.uidByte[i], HEX));
      }
      Serial.println();
      content.toUpperCase();
      if ((lastv == true) && (content.substring(1) != lastString)) {
        veh = true;
      }

      // 1982 figures(16)
      if (content.substring(1) == "EDBFA3C") { //was the RFID this one?
        Serial.println(F("bre")); //String to tell Processing to display Breaker file card
      }
      else if (content.substring(1) == "4331004") {
        Serial.println(F("clut")); // Clutch
      }
      // hundreds more else ifs, my 15 year old son thinks it's hilarious
      break;

    case true:
      // other stuff but I had nothing, effectively "locking" Arduino
      break;
  } // end of switch
} // end of loop

Biggest drawback of course is; however, that you would of course be tied to your PC for the project. For a portable option, ie standalone device, definitely go the DFPlayer route.
I have also done that route with the same sensors you're using. In mine, I used three to trigger audio clips of a Dr Who Dalek blasting and saying different things.

Note that this won't work on its own, it's just an example of how to connect Arduino to Processing and do some fun things if you decide to go this route, for example, this shows the intro video and then some gameplay of the code above.

GI Joe Dossier

For those about to build, I salute you! :saluting_face:

I would tackle that or other relatively simple projects first. Then try a simple DFR Player project or two before “pushing it further”.

Lots of help online and on paper for all aspects of what you’ll probably need.

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.