The news is often filled with tales of the latest scientific discoveries, ranging from distant galaxies found by the Hubble Space Telescope, to discoveries by the Large Hadron Collider at CERN, to the latest breakthroughs in robotics and artificial intelligence.
One of the most exciting announcements in the past few years comes from the frontiers of science, and is one of the most important discoveries in the history of astronomy: the first detection of gravitational waves.
Gravitational wave astronomy is not like traditional astronomy — we are observing the Universe using gravity instead of light. An ordinary telescope might showcase discoveries by showing you a picture, but we have no such recourse in gravitational wave astronomy. There is no easy way to represent our data or our discoveries, but you may have heard us turn our data (our “waveforms”) into sound.
This Instructable uses a Lego Mindstorms EV3 Brick to generate and play sounds that correspond to the gravitational waveforms of binary black holes in the mass range of 0.6 solar masses to 10 solar masses. If you don’t have an EV3 Brick, I’ve tried to provide enough of the details behind my methods that you can extrpolate to other devices for making sounds on your own.
There is both astrophysics and coding that went into this project. The ultimate working code is provided in the final step, but all the details are outlined in the body of this Instructable. In the next step, I provide an overview and guide to the Instructable, letting you work your way through by deciding how much of the intricate details you want to learn on your way. I attach to this step a PDF export of the entire code, so even if you aren't an EV3 programmer, you can look at the code as you follow along.
Here is a demo video showing the EV3 in action, simulating two different sounds (just to whet your appetite!).
Step 1: Guide to This Instructable
There are different tracks you can take through this Instructable, depending on how much science you would like to understand, how much of the coding you would like to understand, and whether you just want to build your robot and impress your friends with astrophysical sonifications!
This step is just a simple Table of Contents about the Instructable, provided to make it easier to find what you are interested in.
If you want to know and do everything, proceed and just read every step — they are organized to teach you in a linear fashion. Sections that start with SCIENCE are steps that mostly talk about considerations for how to compute the data or sonify it. Sections that start with CODE talk mostly about programming the EV3 brick.
In the final step, I attach my EV3 program file that contains my working code described in this Instructable.
- Step 2: The EV3 Mindstorms Brick
- Step 3: The EV3 Sound Block
- Step 5: Parameters & Variables
- Step 10: Touch Sensor
- Step 13: Main Sound Loop Overview
- Step 22: A Working Sound Generator! (Link to Complete Code File)
- Step 4: SCIENCE - Signals from the Stellar Graveyard
- Step 5: SCIENCE - Black Hole Masses
- Step 6: SCIENCE - Parameters & Variables
- Step 3: CODE - The EV3 Sound Block
- Step 6: CODE - Parameters & Variables
- Step 7: CODE - Variable Setup
- Step 8: CODE - Computing the Chirp Mass
- Step 9: CODE - Screen Output
- Step 11: CODE - Event Loop
- Step 12: CODE - User Input of Masses
- Step 13: CODE - Main Sound Loop Overview
- Step 14: CODE - Sound Initializations
- Step 15: CODE - Length of Sound
- Step 16: CODE - Reference Frequencies
- Step 17: CODE - Volume Level
- Step 18: CODE - Play the Sound!
- Step 19: CODE - Take a Step in Time
- Step 20: CODE - Update the Frequency
- Step 21: CODE - Loop Counting
Step 2: The EV3 Mindstorms Brick
The EV3 is the third generation of Lego's Mindstorms System. It is a sophisticated electronic computer that has both inputs and outputs that can be connected to various sensors and devices to make robots and contraptions. In addition to the EV3 Brick itself, you will also need:
- 1 -- Touch Sensor
- 2 -- 3L Technic Friction Pins
- 1 -- EV3 Cable
For this Instructable, we'll be using the standard Mindstorms EV3 Graphical Programming Interface (available for free from Lego at this link). You will need to use the desktop version of the software (second image above) if you are going to implement the code yourself; the mobile/tablet version of the software does not contain all the programming blocks (in particular those for variables and mathematics) that were used in this project. There are other ways to write programs for the EV3, but this Instructable is designed around the default environment provided by Lego. If you use one of the alternative programming environments, there should be enough info here to build your own version -- please let me know in the comments if you do this!
Lastly, if you have a previous generation of Mindstorms Brick (the NXT, or the RCX), you can also extend this Instructable to those style of bricks with a little bit of effort, though it will require some tinkering on your part to handle the differences between bricks (for example, how to handle sound). The NXT has its own graphical programming interface that is similar to the EV3 interface shown here.
Step 3: CODE - the EV3 Sound Block
The EV3 Brick is not a high fidelity sound device. It has a single tiny speaker, and rudimentary but usable sound generation technology. The heart of this project is using the EV3 Sound Block to get the most out of the simple capacity offered by the Brick.
The Sound Block (shown above) takes a few pieces of information as input, and outputs the corresponding sound through the EV3 Brick. For this project, we use the "Tone Mode" which is indicated by the first panel on the bottom of the Sound Block as a musical note. The inputs in this mode are:
- Frequency of the sound to generate in Hz
- The length of time (in seconds) to play the sound
- The relative volume to play the sound (a number from 1 to 100)
- A "Sound Play Type" (tells the EV3 to finish the sound before moving on, or multitask while sound is still playing)
Making a sonification of data requires you to map properties of that data onto the properties of the sound you are going to play. We will map specific properties of the gravitational wave onto these Sound Block inputs. For each of the inputs then:
- Frequency will be the gravitational wave frequency, discussed in Step 4 (physical meaning of the frequency), Step 16 (computing relevant frequencies for calculations), and Step 20 (updating the frequency as it changes)
- We will make sounds at regular time intervals, which defines the length of time to play the sound, discussed in Step 15 (total length of time sound is played) and Step 18 (playing small portions of the total sonification)
- Volume is decided by the strength of the gravitational wave at a given moment in time, discussed in Step 17
- The Sound Play Type was chosen to make the sound be smooth and continuous to the ear, discussed in Step 18
One limitation for this project is that the range of frequencies the EV3 Brick can produce is only from around 250 Hz to 10,000 Hz (though after around 5000 Hz or so the speaker does not deliver much sonic power). If you input a frequency outside of its nominal range, it simply plays the closest frequency it can produce. I couldn't find the frequency range in documentation anywhere, so I worked it out by writing a simple test code that ramped up through audio frequencies until I could tell the EV3 was changing sounds. The second image above is a screen capture of my test code; it's a simple few blocks that interfaces with the Sound Block and writes frequency data to the screen. You may find it useful to experiment with if you are new to using the Sound Block. That test code (sndTest.ev3) is attached here to this step for your reference.
Step 4: SCIENCE - Signals From the Stellar Graveyard
The most common source in gravitational wave astronomy is a binary — two massive, compact objects orbiting around each other. To make strong enough signals for us to detect, the objects have to be dead stars — white dwarfs, neutron stars, or black holes.
Their orbits are roughly circular, but over time they come closer and closer together (second figure above), a consequence of emitting gravitational waves. As they get closer and closer together, they move faster and faster, and the gravitational waves get stronger. Eventually, they get so close together they merge, and the gravitational waves end.
In gravitational wave astronomy, we don’t have images the way conventional telescopes do. We typically repesent our data by drawing a graph called “the waveform,” that plots the gravitational wave strength on the vertical axis, and time on the horizontal axis (third figure above). For the inspiral described above, the waveform oscillating back and forth is directly connected to how fast the dead stars are going around in their orbit. As time goes on, the up and down oscillations get closer together because the dead stars are moving faster. The size of the oscillations also goes up, because the gravitational waves are getting stronger. The result is the trumpet shaped waveform shown in the third figure, which is called a “chirp.”
The overall strength of the waves (the size of the oscillations) is fixed by the mass of the dead stars and the distance to the source. The rate at which the waveform compresses (the oscillations getting closer together) is fixed by the mass of the dead stars.
Drawing graphs of gravitational waveforms doesn't usually produce the same visceral response in people that Hubble Space Telescope photographs do. What's a poor gravitational wave astronomer to do if they want to share the awesomeness of the Gravitational Universe with people? We turn to representing our data not with pictures, but with sound -- sonificiation.
For this tutorial, we assume the orbits are quasi-circular (in astronomy language: there is no eccentricity). They aren’t perfectly circular because they are slowly getting smaller. For orbits of this nature, the gravitaitonal wave frequency is exactly twice the orbital frequency. This means if we want to play sounds that a person can hear, in a frequency range from around 20 Hz to 20,000 Hz, then we must only consider orbits that have orbital frequencies in roughly that range
As noted in Step 3, we further limit the range of frequencies we consider based on the abilities of the EV3 Brick to generate sound.
Step 5: SCIENCE - Black Hole Masses
The origin of black holes is a problem that astronomers still spend lots of time investigating. The black holes we are thinking about for this Instructable have masses that are similar to stars, and are born when large, massive stars die in supernova explosions. There are lots of known "stellar mass black holes" created in this fashion, but astronomers have never detected a black hole lighter than about 5 solar masses.
It is possible for a supernova to create a smaller stellar skeleton called a neutron star, from the explosion of slightly smaller stars. Astronomers have detected a couple of thousand neutron stars. They are all heavier than 1.4 solar masses (something called the “Chandrasekhar Limit”), but we have never observed one heavier than about 2 solar masses.
And therein lies one of the great mysteries in astrophysics: between 2 solar masses and 5 solar masses, there is a “mass gap” in our observations: we’ve never seen a stellar skeleton there, and don’t know if something about Nature forbids it, or if our observations are just incomplete. But mathematically there is nothing that precludes a stellar skeleton from being in that mass range. If they do exist, they may be detectable in gravitational waves.
Below 1.4 solar masses there could be tiny black holes, but they can’t be made by stars. If such light black holes do exist, they come from some exotic process in the Cosmos — the most likely case is they might be primordial black holes, born when the Universe was creating itself. If lighter black holes do exist, then gravitational wave observations will someday confirm this.
The properties of the waveforms we are building depend entirely on the masses of the two objects in the binary. The most important things the mass affects are the rate the orbit shrinks (related to the frequency of the sound we are playing), and the total length of the sound we play.
Based on the frequency capability of the EV3 Brick, we limit ourselves to binaries that merge at frequencies between about 200 Hz and 4000 Hz. In the second figure above we show a common formula for the merger frequency of black holes (from Flanagan & Hughes 1998, for the cognoscenti). If you use the formula with mass 1 and mass 2 in solar masses, you find the mass range of interest in this project is roughly 0.6 solar masses to 10 solar masses.
Step 6: SCIENCE/CODE - Parameters & Variables
There are a variety of parameters needed to describe a source of gravitational waves, and they will need to be represented in the code. Additionally, coding always has a variety of additional auxiliary data that needs to be stored and referred to. The auxiliary variables often grow organically out of the code development process.
Here in this step we collect for easy reference all such parameters and variables, and short descriptions of what they are for.
Astrophysical Parameters Used
- m1 = mass 1 in Solar Masses (the mass of one of the black holes)
- m2 = mass 2 in Solar Masses (the mass of the other black hole)
- Mc = chirp mass in Solar Masses (a particular combination of masses that appears repeatedly in gravitational wave astronomy; see Step 8)
- fo = starting frequency in Hertz (frequency of the gravitational wave at the start of our waveform/sound)
- fmrg = merger frequency in Hertz (frequency at which the two black holes touch and become one)
- f = current frequency in Hertz (frequency at a given instant of time)
- t = time in seconds (current elapsed time since the simulation started)
Auxiliary Code Variables
- dT = Time Step in seconds (hard coded; chosen to be 0.125 second; makes the sounds smooth as they play)
- Vol = volume level fed into the Sound Block, computed from gravitational wave strength (see Step 17)
- nDAT = Total number of data points used to play sound; used in the Main Sound Loop (see Step 13 for loop overview, and Step 15 for computation of nDAT)
- Mc53 = chirp mass to the 5/3 power (appears repeatedly in formulae, so computed once to streamline code; see Step 14 for calculation)
Step 7: CODE - Variable Setup
Different coding environments handle variables in different ways. As a matter of good habit, I always initialize my variables — I place a starting value in them rather than depend on the system automatically setting them to zero (or some other value). The first line of the EV3 code contains all my variable initlizations.
The first block in the line is the EV3 Start Block (first figure), followed by a series of variable blocks (second figure). The variables that you see there and their physical meaning in the code are:
- m1 = mass 1 in Solar Masses (initialized to minimum allowed mass of 0.6 Msun)
- m2 = mass 2 in Solar Masses (initialized to minimum allowed mass of 0.6 Msun)
- Vol = volume level for Sound Block (initialized to zero)
- dT = timestep in seconds, defining how often I am going to compute the gravitaitonal wave frequency and play the corresponding part of the sonification (initialized to 0.125 seconds)
Step 8: CODE - Computing the Chirp Mass
The "chirp mass" Mc is a parameter that appears repeatedly in gravitational wave calculations. It is a mathematical combination of the two masses, m1 and m2, whose value influences the way the binary evolves as it inspirals.
To compute the chirp mass value, we will use the EV3 Math Block, set to "ADV" (advanced) mode. It takes 4 total blocks to compute and store the chirp mass. They are:
- m1 Variable Block (red) that reads out the current value of mass 1 and feeds it to the Math Block
- m2 Variable Block (red) that reads out the current value of mass 2 and feeds it to the Math Block
- Math Block that computes the chirp mass
- Mc Variable Block (red) that stores the computed value of the chirp mass
The ADV state of the Math Block has as its argument the formula it computes for the output, which can be a combination of numbers, mathematical operations, and the values of up to 4 variables read through the inputs. In this case, I wired in mass 1 (m1) into the "a" input, and mass 2 (m2) into the "b" input. The formula used then is
- Mc = ((a*b)^(3/5))/((a + b)^(1/5))
You can see the chirp mass formula in the second figure above, and compare it to the ADV Math Block formula. The EV3 should follow standard conventions for order of operations in mathematical calculations, but I'm a big fan of using parenthesis to be explicit about what I want to happen!
Step 9: CODE - Screen Output
The idea of this Instructable is to let the user choose the black hole masses and hear a chirp generated on the fly, then be able to change the masses and listen to a new sound to understand how the sound changes.
The EV3 Brick is set up to be interactive, and it lets the user know what is happening by writing the current values of the two masses, m1 and m2, and the chirp mass Mc, to the EV3 screen, updating it each time the user changes a mass value.
The next set of blocks in the program take the current values of the three parameters, and writes them to screen using the EV3 Display Block, which lets you control where the text is written and the size of the font used.
Writing one of the mass variables to the screen uses three blocks (in the figure you'll see three rows of three blocks -- one row for each of the masses m1, m2, and Mc). The three blocks in order are:
- EV3 Display Block (green) writing static text label for the name of the mass
- Variable Block (red) that reads out the current value of the mass and feeds it to the next Display Block
- EV3 Display Block (green) writes the current value of the mass that is passed to it.
Note that the first Display Block erases the entire screen (removing any previously displayed values), and the proceeds to write m1. The following blocks work in a similar fashion, but do not erase the screen first.
Step 10: Touch Sensor
I wanted the user to have a graceful way to tell the EV3 they were done making gravitational wave chirps. The simplest solution was to use one of the sensor inputs, so I chose the Touch Sensor.
You can use any sensor port you like, but I plugged the Touch Sensor into Port 1 on the bottom of the EV3 using one of the shortest cables in my kit.
I attached two 3L Technic pins (blue) to the rail on the bottom of the Touch Sensor, then mounted it on the side of the EV3. There are several places to mount the Sensor in this fashion; I chose one of the locations near the top of the Brick, away from the buttons -- the buttons are going to be used for user input, and this kept the Touch Sensor out of the way where it would not be accidentally bumped.
The behaviour of the Touch Sensor is connected to a Loop Interrupt Block, described in Step 11.
NB: I should note here that this step is, in principle, not necessary. Any running code on an EV3 can be stopped instantly by pressing the Back Button, which is just under the EV3 screen on the left. However, I think it is fun to use sensors to do odd jobs like this, and it helps flex your programming skills. :-)
Step 11: CODE - Event Loop
I wanted the code to be able to run repeatedly, allowing the user to easily experiment with how changing the masses changes the sound, but without having to push “RUN” every time. The solution is to design the code in an “Event Loop” that repeatedly runs the code, paying attention for some signal from the user that they are ready to finish operations.
I created my event loop with three basic elements: a Loop Block, a Loop Interrupt Block, and a Switch Block.
The basic logic of the Event Loop is defined by two parallel tracks that are established at this point. One branch leads to the Loop and executes the main operations (first figure), and the other branch leads to the Loop Interrupt that the Brick constantly monitors (second figure). The Switch is inside the primary loop and handles user choices related to playing the sounds, including calculating and playing the sounds based on user parameter choices.
The Loop is set to run indefinitely by setting the output condition on the right side of the block to "Unlimited" (the infinity icon). This is the appropriate choice here because the only way we want the program to end is via the Loop Interrupt, triggered by the user.
The Loop Interrupt track consists of 2 blocks: a Wait Block that monitors the Touch Sensor for a push from the user, and a Loop Interrupt Block that runs after the trigger. When the interrupt branch detects that Touch Sensor has been depressed, it immediately terminates operations that are being carried out in the Event Loop.
Inside the Event Loop, the code waits for a button to be pressed using a Wait Block. When a button is pressed, the Wait Block then passes control to the Switch, which then takes action based on which button was pressed. If the diagram above, you'll notice the switch has six tabs, corresponding to each of the button states for the EV3. The use of the Switch is described in the next step.
Step 12: CODE - User Input of Masses
For this project, I wanted there to be a simple way for the user to change the masses we use to compute the sounds, and decided to use the buttons on the front of the EV3 Brick. The buttons are arranged in a “+” shape, so it was intuitive to use “Up” and “Down” to increase and decrease one mass, and “Right” and “Left” to increase and decrease the other mass. Additionally, we need a way to tell the Brick to play the sound — I could have wired up a second touch sensor, but the "Center" button is also available for input.
Inside the Switch, there is a tab for each of the possible button presses on the face of the EV3. I map the following operations onto those buttons:
- LEFT = decrease mass1
- RIGHT = increase mass1
- DOWN = decrease mass2
- UP = increase mass2
- CENTER = play chirp sound for current masses
In the diagrams above, you can see all four of the tabs for increasing or decreasing the masses. They are more or less identical, with some small differences. The basic operation of each sequence is this:
- First, check to see if the mass is inside the range allowed by our program. The smallest allowed mass in this program is 0.6 solar masses, and the largest allowed mass is 10 solar masses (this insures the sounds are audible and interesting). The first two blocks compare the current mass value to one of the limiting mass values. The result is a TRUE or FALSE, that is then passed into a Logic Block.
- If the passed value is TRUE, the Logic Block executes, and either increments or decrements the mass by 0.2 solar masses using a Math Block. The result is written back into the variable block for the mass. I chose to make the step 0.2 solar masses, providing some fine control over the mass values while avoiding almost 100 button presses to go all the way across the mass scale.
- Once the mass is updated, the chirp mass is recalculated (see Step 8) and an update is written to the screen (see Step 9).
Step 13: CODE - Main Sound Loop Overview
Inside the Switch, under the tab for the Center Button, is the core code that takes the values for the masses and generates sound. The bulk of the code complexity is here; this is "where the physics meets the brick."
For reference, the first image above shows the entire contents of the tab. The second image zooms in on the pre-processing needed to make the sound. This is where the parameter choices are used to compute the inputs needed to calculate both the evolution of the sound, and the inputs for the EV3. The third image shows the sound loop itself, which calculates the evolution of the source, the resulting change in the inputs used to create the sound, then plays the next updated part of the sound.
In the next few steps we'll talk about what the source is doing and the choices we make to represent its behviour in terms of sound, them review the corresponding code. There are two parts to this series of steps:
Pre-Sound Loop Preparation (setting up variables)
- Step 14: Sound Initializations (variable setup)
- Step 15: Sound Length (computes how long sound will play based on astrophysics)
- Step 16: Reference Frequencies (computes parameters needed to correctly change the sound)
Main Sound Loop (inside the loop, playing the sound, updating parameters as loop runs)
- Step 17: Volume Level (computes how loud to play sound based on gravitational wave strength)
- Step 18: Play the Sound! (uses Sound Block to generate a bit of the sound)
- Step 19: Take a Step in Time (updates time as loop runs)
- Step 20: Update the Frequency (computes the next value of frequency to play as a sound)
- Step 21: Loop Counting (housekeeping as the loop runs)
Step 14: CODE: Sound Initializations
Just as in Step 7 where we initialized variables for the entire code, there are a few variables that need initialized every time we play a new sound. One of these is the time t from the moment the sound starts playing, to the moment the sound ends. The other is the chirp mass to the 5/3 power, which appears repeatedly in the gravitational waveform (it's simpler and more efficient to compute this and read the value of the variable out when needed rather than recomputing it every time).
The variables in this block sequence are:
- t = time in seconds (initialized to zero)
The chirp mass raised to the 5/3 power is computed using an ADV Math Block, reading in the chirp mass Mc and storing the result in the variable Mc53. The Math Block formula is:
- Mc53 = Mc^(5/3)
Step 15: CODE - Length of Sound
Gravitational waveforms can be arbitrarily long; thus, a sound representing the waveforms can also be arbitrarily long. We only want to play the sounds over the range of frequencies a human can hear, so that limits the total time the waveform covers.
The end of the sound is defined by the merger frequency (formula in Step 5, computed in Step 16). If we take the maximum possible merger frequency to be 4000 Hz, and the starting frequency to be 205 Hz, then the longest possible waveform of interest is about 1.3 seconds long. Light masses have the longest waveforms, and heavier masses have the shortest waveforms.
If we keep our waveforms restricted in the frequency range 205 Hz to 4000 Hz, they are very short and unsatisfying to listen to. So I decided to have the longest sound (lightest mass) around be 20 seconds or so, and the shortest sound (heaviest mass) around just a couple seconds. We've kept the relative evolution of the frequency constant, just play the sound out over a longer period of time.
Because of the way we overlap the playing of the sound, it is hard to calculate the length of the sound exactly, but some experimentation produced a good formula for the parameter Tsnd that can be calculated in terms of the chirp mass:
- Tsnd = 60/Mc
This is calculated using a Math Block then stored for use. As we will see, this ultimately can cause the sound to start outside of the audio range of the EV3, but we'll handle that in the next Step.
We are going to take constant timesteps to generate the sound, so once we know how long the sound is, we can calculate the total number of steps and use that number to run our loop. We calculate the number of points nDAT as:
- nDAT = CEIL(Tsnd/dT)
where the time spacing of data points, dT, was fixed during our initialization in Step 7. The "CEIL( )" function takes the division, and makes the answer the integer next larger than the result. For example
- CEIL(13/3) = CEIL(4.33) = 5
This is also calculated using a Math Block and stored for later use.
Step 16: CODE - Reference Frequencies
We've made aesthetic choices for the length of the sound rather than chosing a fixed starting frequency. So in this step, we compute the appropriate starting frequency that gives the sound length we want. There are three parts in this step.
First, we compute the merger frequency, using the formula given in Step 5. As shown in the first figure, this is executed using a Math Block that takes mass 1 and mass 2 as inputs a and b. The Math Block formula is:
- fmrg = 4100/(a+b)
The value is stored for later use. Once we know the merger frequency and the total length of the sound, we can calculate the starting frequency (the value of the frequency at a time Tsnd before merger). In the second figure above, we calculate this freqeuncy, fo. The Math Block takes as input
- fmrg = merger frequency (as input a)
- Tsnd = total length of sound (as input b)
- Mc53 = chirp mass to the 5/3 power (as input c)
The Math Block formula is:
- fo = (a^(-8/3) + (0.000001546212)*c*b)^(-3/8)
This is stored as both the starting frequency fo, as well as the first value of f used to make sounds.
The third figure above shows one place we are taking liberty with our representation of the sound. Since the EV3 doesn't have frequency fidelity below ~235 Hz, we shift all sounds to higher frequencies. We take the difference between the starting frequency and 250 Hz, and shift every frequency by the same amount as we play the sound. This is heuristically similar to playing a song in a higher octave on the piano -- everything sounds the same, simply shifted up to higher pitch. We store this in the parameter fshift using a Math Block formula as:
- fshift = 250 - fo
At this point, we're ready to enter the main sound loop.
Step 17: CODE - Volume Level
In this project, the amplitude of the gravitational wave will correspond to the volume of the sound played by the EV3 Brick. We only have a limited range of volumes we can play (numerically described on the Brick as a number between 1 and 100).
The amplitude of the gravitational wave depends on several numbers that are constant (like the distance and the chirp mass) over the entire length of the waveform. It also depends on the frequency, which changes as the waveform evolves. Bearing this in mind, we will ignore all the constant factors ("set them equal to 1" as the physics nerds say), and scale the amplitude only by the correct frequency behaviour.
Doing this, we see that the frequency gives the famous trumpet shape of the chirping waveform; in physics and astronomy this is often called an "envelope function."
Note that the frequency has a complicated dependence on time, but that is taken care of in the calculation of the frequency itself (see Step 20), making this Step very easy. Since there is only a limited range of volumes (1 to 100 on the EV3) we set the scale so all signals we expect will fall in this range. A Math Block takes as input the current frequency f as input a, and calculates the volume using the formula
- Vol = 1 + ((a^(2/3))/10)
Step 18: CODE - Play the Sound!
This is what we've been trying to accomplish! Playing the sonification of the gravitational wave chirp waveform! This block sequence is the heart of the Main Sound Loop, and has two parts.
The first part are the inputs for and call to the EV3 Sound Block. For inputs it needs values we've already set:
- f = frequency to play the sound
- dT = length of time to play the sound
- Vol = setting for the speaker volume
Note that we do not feed the frequency f directly to the Sound Block. First, we use a Math Block to shift the value into the EV3 playable range (see Step 16 for details) using the formula:
- f + fshift
Note that the result is not stored; it is passed directly to the Sound Block. The reason is the physics that correctly describes how the waveform changes in time depends on the true frequency f, not the shifted value, so we only keep track of the the true frequency.
The second part are the three blocks after the Sound Block. This was a bit of jury rigging designed to make the sound play smoothly and continuously. I found that if I just have the Sound Block by itself playing successive frequencies on successive passes through the Main Sound Loop, the EV3 just blasts through the loop as fast as it can and all the action happens in a short burst that sounds like a pop of static. If I set the Sound Block to finish its current sound before moving on (Play Type = 0), then it had a bit of a pause between each iteration, making the sonification sound like a series of notes. These three blocks are simply a wait block that pauses for a period of time after the Sound Block starts running.
For the pause to work properly the way we intend, the Sound Play Type (last control on the Sound Block) needs to be set to 1 -- "play once" -- which makes the Brick play the requested sound, but lets the program continue to run while it plays. The first thing the program encounters when it moves on is the Wait Block, which let's the sound play for a period of time your ear can hear, then it sprints on. We calculate the time to wait using a Math Block, taking it to be 1/10th the timestep using the formula
Step 19: CODE: Take a Step in Time
The Main Sound Loop is walking the code through moments of time, playing the appropriate sound at the appropriate moment. The Loop is running a fixed number of times, taking a constant step in time each time it executes the loop. This series of blocks updates the time by the correct amount by taking the current value of the time (stored in the variable t) and adding to it the timestep value we indicated in our variable setup in Step 7 (stored in the variable dT). This is done using a Math Block and the result is stored in the variable t again. The formula is
- t = t + dT
Step 20: CODE - Update the Frequency
The gravitational waveform changes every moment in time, which makes the sonification sound different at each moment in time. Here we compute the frequency the EV3 needs to play for the next moment in time. This formula comes directly from the basic physics of the gravitational wave, and depends on the chirp mass to the 5/3 power (stored in the variable Mc53), the value of the frequency when we started the simulation (stored in the variable fo), and the time at which we want to know the next frequency (computed in the last Step and stored in the variable t).
There is a logic structure at this point in the code (first image above) to handle limitations in the numerical capabilities of the EV3. In testing, the EV3 would never play the last frequency in the sequence I was computing, and many sounds (particularly at higher masses) ended up sounding like they end at the same frequency. As near as I can tell, this is related to numerical precision on the Brick. The last step should end at the merger frequency, fmrg, but when it tried to compute it small differences in the number often produced an invalid numerical result (a root of a negative number, which math aficionados will recall is "imaginary") on the last step. The solution was to watch the time step, and if it exceeded the projected length of the sound Tsnd, that meant we were at the last step, and the frequency should simply be set to fmrg.
Leading into the logic block is simply a comparison of the time t to Tsnd:
- T < Tsnd
If this is FALSE, the logic block takes the lower branch and sets f = fmrg. If this is TRUE, it computes the new frequency using the predicted physical evolution using the top branch (second image). The new frequency is computed using a Math Block with inputs
- fo = frequency when our simulation started (as input a)
- t = current time (as input b)
- Mc53 = chirp mass to the 5/3 power (as input c)
The Math Block is set to ADV mode, using the formula
- f = (a^(-8/3) - (0.000001546212)*c*b)^(-3/8)
Step 21: CODE - Loop Counting
The last step in the loop is simply a block that tells the Main Sound Loop how many times it is supposed to be running. We computed this in Step 15 and stored it in the variable nDAT. This block simply reports the nDAT value to the Main Sound Loop.
Once the program exits the Main Sound Loop, it exits the Switch that is monitoring user button presses (see Step 11).
Step 22: A Working Sound Generator!
Congratulations! Your EV3 should now be capable of generating a suite of reasonable sonifications representing gravitational waves from merging black holes. Simulation and visualization are important parts of modern physics, and this project was completed in that same spirit. Only instead of "visualization" we've done "sonification."
If you've stuck through the Instructable this far, we've done a complete walk through of the code, explaining how I adopted the capabilities of the EV3 Brick to the requirements imposed by the astrophysics I was trying to simulate. This is the first time I've done a project to make the EV3 behave like a simulation computer, rather than as the brain for a robot. It was fun, and now I'm going to go think about other things I might try.
I hope you've enjoyed this journey through EV3 coding and astrophysics, and you try out your own hand at simulating some gravitational waveforms.
I attach to this step the complete final version of my EV3 code, if you'd like to port it directly to your own Brick. Here is a short video of the Brick in action running this simulator for two different sounds.
Runner Up in the
Audio Contest 2018