In this tutorial, we'll explore how to create an interactive audio system using the versatile UNIHIKER device. By integrating an ultrasonic sensor and a USB speaker, we'll produce sounds that vary based on the distance detected. This project is a fun and educational way to learn about sensors, audio generation, and programming with PyAudio.
By the end of this tutorial, you'll have a working prototype that responds to distance with sound, sparking ideas for further projects and explorations. Whether you're interested in music, electronics, or coding, this project offers something for everyone.
Connect the Ultrasonic Sensor: Attach the sensor to the UNIHIKER.
Attach the USB Speaker: Plug the speaker into the UNIHIKER's USB port.
Power On: Once everything is connected, power up the UNIHIKER.
With the hardware setup complete, it's time to dive into the code. Using Python and the PyAudio library, we'll write a program that:
Read distance data from the ultrasonic sensor.
Map the distance to a frequency range, creating a tone that changes as the distance varies.
Play the corresponding tone through the USB speaker.
from threading import Thread, Lock
from time import sleep
import numpy as np
from pinpong.board import Board
from pinpong.libs.dfrobot_urm09 import URM09
from pyaudio import PyAudio, paFloat32, Stream
RATE: int = 44100
AMPLITUDE: float = 0.5
FREQUENCY_MIN: int = 50
FREQUENCY_MAX: int = 2000
BUFFER_SIZE: int = 1024
DELAY: float = .5
def calculate_frequency(value: float) -> float:
"""
Calculate frequency based on a given value.
:param value: The value used to calculate the frequency.
:type value: float
:return: The calculated frequency.
"""
return FREQUENCY_MAX - (value / 150.0) * (FREQUENCY_MAX - FREQUENCY_MIN)
def generate_tone_thread(output_stream: Stream) -> None:
"""
Generate tone and write it to the output stream.
:param output_stream: The output stream to write the generated tone to.
:type output_stream: Stream
:return: None
"""
global current_value
t = np.arange(BUFFER_SIZE) / RATE
prev_frequency = calculate_frequency(current_value)
while True:
with value_lock:
frequency = calculate_frequency(current_value)
if frequency != prev_frequency:
samples = (np.sin(2 * np.pi * frequency * t)).astype(np.float32)
prev_frequency = frequency
else:
samples = (np.sin(2 * np.pi * prev_frequency * t)).astype(np.float32)
output_stream.write((AMPLITUDE * samples).tobytes())
t += BUFFER_SIZE / RATE
def input_thread(input_sensor: URM09) -> None:
"""
Continuously reads the distance from the sensor and updates the current_value variable.
:param input_sensor: The URM09 sensor instance.
:type input_sensor: URM09
:return: None
"""
global current_value
while True:
distance = float(input_sensor.distance_cm())
if 0 <= distance <= 120:
current_value = distance
if __name__ == '__main__':
Board("UNIHIKER").begin()
sensor = URM09()
sensor.set_mode_range(sensor._MEASURE_MODE_AUTOMATIC, sensor._MEASURE_RANG_150)
p = PyAudio()
stream = p.open(format=paFloat32,
channels=1,
rate=RATE,
frames_per_buffer=BUFFER_SIZE,
output=True,
input=False)
value_lock = Lock()
current_value = 75
tone_thread = Thread(target=generate_tone_thread, args=(stream,))
tone_thread.daemon = True
tone_thread.start()
input_thread = Thread(target=input_thread, args=(sensor,))
input_thread.daemon = True
input_thread.start()
try:
while True:
sleep(DELAY)
except KeyboardInterrupt:
print("Shutting down...")
finally:
stream.stop_stream()
stream.close()
p.terminate()
This example can be used in various creative and practical applications:
Theremin-like Instrument: Create an electronic instrument that changes pitch based on hand distance.
Parking Assist System: Implement a simple audio-based parking aid that alerts drivers as they approach an obstacle.
Interactive Art Installation: Use the setup to make interactive installations where sounds respond to viewers' movements.