Breaking a Wine Glass in Python By Detecting the Resonant Frequency
For 50 days straight between January and March of this year, I wrote a creative programming sketch in Python every day.
On day six, I tried to write a program to detect the resonant frequency of a glass, and break it. As you can see below, my desktop computer speaker wasn’t quite loud enough to break the glass alone.
In today’s post, I walk through the journey of writing a Python program to break wine glasses on demand, by detecting their resonant frequency.
Along the way we’ll 3D print a cone, learn about resonant frequencies, and see why I needed an amplifier and compression driver. So, let’s get started.
First, the Inspiration
I first saw the video above, which goes into a lot of detail about how to actually break a wine glass with your voice. A few main points are covered, and Mike Boyd, the creator, quickly touches upon them. He recommends:
- Having a real crystal glass
- Preferring a thinner, longer crystal glass
- Striking the glass to get it to resonate
- Using a spectrum analyzer to find the resonant frequency of the glass
- Using a straw to visualize when the glass is resonating
With that in order, it wasn’t until I tried breaking my third glass that I realized the last thing that ensures you really break a glass on command:
- Having micro abrasions in the glass. It can’t be perfectly new and clean.
Without the micro abrasions (either caused by cleaning the glass with soap and water or a very light sanding), the glass wouldn’t break at all.
An Aside About How Resonance Works
When Mike Boyd breaks the glass in the above video, he’s using the acoustic resonance of the wine glass.
Crystal wine glasses have a natural frequency of vibration that you can hear when you strike them.
Any sounds at this frequency have the potential to make the wine glass absorb more energy than at any other frequency. Our hope is that our program and speakers will generate enough energy in the glass to break it.
Writing a Prototype Sketch
After seeing the video and being inspired to try recreating it, I needed to first get a prototype in place.
Would I be able to get a straw to resonate like in that example video?
Luckily, my Voice Controlled Flappy Bird post already does the difficult part of detecting the current pitch frequency from a microphone.
So, hypothetically, striking the wine glass should produce the proper tone to break the glass itself.
Using the pitch detection code, the only thing that would be left is to play out the pitch frequency detected by a microphone. The code is straightforward enough using Pygame, Music21, and PyAudio:
from threading import Thread
import pygame
import pyaudio
import numpy as np
import time
from rtmidi.midiutil import open_midiinput
from voiceController import q, get_current_note
class MidiInputHandler(object):
def __init__(self, port, freq):
self.port = port
self.base_freq = freq
self._wallclock = time.time()
self.freq_vals = [0 for i in range(6)]
def __call__(self, event, data=None):
global currentFrequency
message, deltatime = event
self._wallclock += deltatime
print("[%s] @%0.6f %r" % (self.port, self._wallclock, message))
if message[1] == 16:
self.freq_vals[0] = (message[2] - 62) * .5
elif message[1] == 17:
self.freq_vals[1] = (message[2] - 62) * .01
elif message[1] == 18:
self.freq_vals[2] = (message[2] - 62) * .005
elif message[1] == 19:
self.freq_vals[3] = (message[2] - 62) * .0001
elif message[1] == 20:
self.freq_vals[4] = (message[2] - 62) * .00001
new_freq = self.base_freq
for i in range(6):
new_freq += self.freq_vals[i]
currentFrequency = new_freq
print(new_freq)
port = 1
try:
midiin, port_name = open_midiinput(port)
except (EOFError, KeyboardInterrupt):
exit()
midiSettings = MidiInputHandler(port_name, 940.0)
midiin.set_callback(midiSettings)
pygame.init()
screenWidth, screenHeight = 512, 512
screen = pygame.display.set_mode((screenWidth, screenHeight))
clock = pygame.time.Clock()
running = True
titleFont = pygame.font.Font("assets/Bungee-Regular.ttf", 24)
titleText = titleFont.render("Hit the Glass Gently", True, (0, 128, 0))
titleCurr = titleFont.render("", True, (0, 128, 0))
noteFont = pygame.font.Font("assets/Roboto-Medium.ttf", 55)
t = Thread(target=get_current_note)
t.daemon = True
t.start()
low_note = ""
high_note = ""
have_low = False
have_high = True
noteHoldLength = 10 # how many samples in a row user needs to hold a note
noteHeldCurrently = 0 # keep track of how long current note is held
noteHeld = "" # string of the current note
centTolerance = 10 # how much deviance from proper note to tolerate
def break_the_internet(frequency, notelength=.1):
p = pyaudio.PyAudio()
volume = 0.9 # range [0.0, 1.0]
fs = 44100 # sampling rate, Hz, must be integer
duration = notelength # in seconds, may be float
f = frequency # sine frequency, Hz, may be float
# generate samples, note conversion to float32 array
# for paFloat32 sample values must be in range [-1.0, 1.0]
stream = p.open(format=pyaudio.paFloat32,
channels=1,
rate=fs,
output=True)
# play. May repeat with different volume values (if done interactively)
samples = (np.sin(2*np.pi*np.arange(fs*duration)*f/fs)).astype(np.float32)
stream.write(volume*samples)
newFrequency = 0
breaking = False
currentFrequency = 0
breaking_zone = False
super_breaking_zone = False
noteLength = 5.0
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
if event.type == pygame.KEYDOWN and event.key == pygame.K_q:
running = False
if event.type == pygame.KEYDOWN and event.key == pygame.K_SPACE and newFrequency != 0:
breaking = True
midiSettings.base_freq = newFrequency
currentFrequency = newFrequency - 10
if event.type == pygame.KEYDOWN and event.key == pygame.K_s:
noteLength = 30.0
breaking_zone = True
if event.type == pygame.KEYDOWN and event.key == pygame.K_a:
super_breaking_zone = True
noteLength = 5.0
screen.fill((0, 0, 0))
if breaking:
titleCurr = titleFont.render("Current Frequency: %f" % currentFrequency, True,
(128, 128, 0))
# our user should be singing if there's a note on the queue
else:
if not q.empty():
b = q.get()
pygame.draw.circle(screen, (0, 128, 0),
(screenWidth // 2 + (int(b['Cents']) * 2),300),
5)
noteText = noteFont.render(b['Note'], True, (0, 128, 0))
if b['Note'] == noteHeldCurrently:
noteHeld += 1
if noteHeld == noteHoldLength:
titleCurr = titleFont.render("Frequency is: %f" % b['Pitch'].frequency, True,
(128, 128, 0))
newFrequency = b['Pitch'].frequency
else:
noteHeldCurrently = b['Note']
noteHeld = 1
screen.blit(noteText, (50, 400))
screen.blit(titleText, (10, 80))
screen.blit(titleCurr, (10, 120))
pygame.display.flip()
clock.tick(30)
if breaking:
break_the_internet(currentFrequency, noteLength)
Now, there are a few things to note here.
One, I use rtmidi
utility to be able to dial in the resonant frequency using a Midi controller. This lets me adjust the knobs in order to find a place where the straw vibrates the most inside of my wine glass.
Secondly, we use pyaudio
to generate our sine wave of the resonant frequency. We could have used another library with more immediate feedback, but this was easiest to get in place.
Now, the way the program works is that you strike the glass, and hopefully the voiceController
library detects a resonant frequency. The resonant frequency of wine glasses should be between 600 - 900 Hz. Once you’ve got that, you press the SPACEBAR, and that starts playing that resonant frequency out the speakers.
With this, I was able to generate the results you see from my daily sketch on my desktop computer speakers.
However, I wasn’t able to break the glass. I had a hunch it was because I wasn’t able to generate the volume necessary.
Bringing in the Big Guns
Since I couldn’t get my glass to break with my desktop speakers, I started looking around the web to see how other people have broken glass with speakers.
The best resource I found was from the University of Salford. They recommend using a 2” “compression driver in order to get results. So, I went off to Amazon, and picked up a 2” compression driver.
With this, the only thing remaining was to get an audio amplifier capable of driving the speaker to an (un)reasonable volume. I used this, and I plan on using both the amplifier and driver in more audio projects in the future.
Finally, I needed to build a stand for the speaker, along with a cone to focus the energy of the wave. For this, I used Blender, along with my Prusa i3 MK2. I printed it in PLA, and it seemed to have done the job well enough.
With the speaker cone 3D printed, I was finally ready to build my setup.
It looks something like this:
It consists of a Snowball USB microphone, plugged into a laptop. The laptop runs the sketch from above, first waiting to detect a resonant frequency.
You strike the glass, and if the detected resonant frequency is in the zone you’d expect (again, 600Hz to around 900Hz), you press SPACEBAR to begin playing back that resonant frequency.
The audio output is hooked up to an amplifier, and that amplifier is hooked up to the driver with the 3D printed code. That cone is pointed directly at the top of the wine glass, as you saw in the gif in the introduction.
With that, the glass breaks!
Where to Go From Here
The code, along with the 3D printable cone STL is at Github.
The program that finally breaks the glass used in the video is slightly different from what I’ve written above. It doesn’t adjust with the MIDI controller, because I found it wasn’t necessary in my case to adjust the detected frequency. The glass broken anyways.
If you plan on trying to recreate my experiment, please wear safety glasses and ear protection.
Ear damage is accumulative, and not wearing proper ear protection means damaging your ears permanently. So please don’t do that.
And the glass breaks all over the place, so please wear eye protection too. This project is fun, but not worth getting seriously injured over.
If you’re still learning Python and Pygame, or you want a visual introduction to programming, check out my book, Make Art with Python. The first three chapters are free.
Finally, feel free to share this post with your friends. It helps me to continue making these sort of tutorials.