Playing songs in a 3D printer - Lucas Seiki Oshiro
You are using an outdated browser. Please upgrade your browser to improve your experience.
Lucas Seiki Oshiro
Follow
GitHub
GitLab
Custom Social Profile Link
-->
GitHub: https://github.com/lucasoshiro/music2gcode
What?
The vibration of the stepper motors cause their sound. Its frequency increases<br>as much as the number of steps that the stepper does per second, consequently,<br>its pitch rises accordingly. Here I describe how I used them to play music.
Manipulate the frequency of a stepper motor individually is easy when we have<br>direct access to them. As an example: when we have an Arduino controlling them<br>through a stepper driver. Even though I assembled my own<br>3D printer<br>using an Arduino and stepper motors, controlling the frequency of each stepper<br>would require to make changes in its firmware, and that’s not in the scope<br>of this project. Here, I aim to play music using only the protocol that is<br>normally used in 3D printing.
In order to do that, I wrote a code in Haskell that receives as its input a song<br>written in a specific format and returns as output G-Code commands that, when<br>executed by a 3D printer, plays a song on its steppers.
Even though G-Code is a universal protocol that is used beyond 3D printers (it<br>is also used by other machines, such as laser cutters), my focus here is the<br>Cartesian FDM 3D printers (perhaps the most common ones…). It would work, for<br>example, in Delta 3D printers.
Input
The input is a song described in a format that is a simplified version of<br>the one that I defined in this project.<br>From the musical perspective of view, it is very limited and not much flexible<br>compared to the most well-known formats, as MIDI, MusicXML, and the formats used<br>by musical notation software (such as Finale, Encore, Sibelius, GuitarPro,<br>Musescore, etc), however, it is easy to be written by humans and parsed by<br>software. Its syntax is the following:
TEMPO
BEGINCH
...<br>ENDCH
BEGINCH
ENDCH
As you may thought, BEGINCH and ENDCH define the beginning and the end of a<br>channel. All the channels are played at the same time.
Each note is represented by , using anglo-saxon<br>notation (CDEFGAB), it can be sharp (#) or flat (b), and the duration is<br>expressed in number of beats. Double sharps and double flats are not supported.<br>Silences are can be declared as - .
In the next drawing I show the initial measures of Brejeiro, a chorinho (a<br>Brazilian traditional genre), composed by Ernesto Nazareth (if you have never<br>head it, check it out on<br>Spotify),<br>written as a sheet music and being transcribed to that format:
Transcription of a sheet music to the format that we expect as<br>input (yeah, I know that my handwriting is terrible).
Output
The output are G-Code commands. More specifically, I’m only generating<br>G0 (Linear Move). They have<br>the following syntax:
G0 X Y Z F
For example, G0 X10 Y20 Z30 F200 will linearly move the printing nozzle to the<br>position (10mm, 20mm, 30mm) at a speed of 200mm/min.
How it works
Using the same example, Brejeiro, the conversion will be performed like<br>described in the next picture. The numbers represent the steps of the conversion:
G-Code conversion steps
Step 1: parser
I’ll skip it because it is boring… We only need to know that we have a<br>function with this signature:
parseSong :: [String] -> Song
In other words, it receives as parameter a list of Strings and return a<br>Song , where:
type Hz = Float<br>type Sec = Float<br>type Bpm = Int
data SongAtom = Silence Sec | Note (String, Int, Sec)<br>type Channel = [SongAtom]<br>type Song = (Bpm, [Channel])
Translating it to English, that means that Hz and Sec are only type synonyms<br>of Float, and Bpm is a type synonym of Int. Thus, Song is composed by<br>the song BPM and a list of channels, where each channel is a list of<br>SongAtoms, that can silences or notes.
Steps 2 and 3: frequency table
Calculating the frequencies
We want to build a table with events showing what frequency will be played by<br>each axis on each time instant, accordingly to the song parsed in the previous<br>step. Forgive me for using musical slang, however, I can’t get rid of it to<br>explain it… Anyway, we want a function freqEventsFromSong, where:
type FreqEvent = (MiliSec, Hz, Hz, Hz)<br>freqEventsFromSong :: Song -> [FreqEvent]
Ok. First of all, we need to know how to find the frequency of each note. Based<br>on the formula in this page,<br>we can calculate it:
c0 = 16.351597831287418<br>baseExp = 1.0594630943592953
freq :: SongAtom -> Hz<br>freq (Silence _) = 0.0<br>freq (Note n) = mult * c0 * baseExp ** (fromIntegral $ fromFigure figure)<br>where (figure, octave, _) = n<br>mult = fromIntegral $ ((2 :: Int) ^ octave)
WHAAAT? Well, the frequency of the silence is obviously 0Hz, so, we can hardcode<br>it. c0 is the frequency of C 0 (check it on the page that I mentioned<br>before), and it will be used to calculate the frequencies of the other<br>notes. mult is the octave multiplier: due to the fact that the frequency of<br>each note...