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