Fun with MIDI, CNC and vector maths (mid2cnc.py)

More playing:
Castlevania end credits
Tetris
Mario Bros. theme

Update: I fixed up the script to pull (usually) proper timing from the MIDI, threw together some minimal documentation and released it to the public (see link below).

Downloads
mid2cnc.py, sparse documentation and samples

Basically, it’s possible to compute a combination of (distance, feedrate) along an axis that will cause the stepper motor for that axis to spin at an exact frequency corresponding to a musical note. With a little vector magic, the same can be done for (x, y, z, feedrate) to produce chords as the machine follows a 3D line through space.

(For anyone wondering, the song is Jonathan Coulton‘s Still Alive, better known as the end credits theme from Portal. The MIDI is from topazstorm.)

*whew* That was the easy part. The real magic will happen in a future post, if I ever get around to it :-) Hint: The fact that notes can and do swap arbitrarily among different axes (while still sounding passable) is important.

How This Works (for CNC-heads):

We have code G1 [pos]x F[feedrate] for linear interpolation at a specific feedrate. Thus need to convert between feedrate in IPM and frequency in Hz (steps per inch or inches per step). My machine as currently configured is 36000 steps/in, so if we wanted it to play middle A (440Hz) (440*60 = 26400 steps/min) we would want to move along a single axis at feedrate (26400/36000 = 0.7333..) IPM.
or more generally, (freq/600) IPM.

Here are the frequencies for one octave. The formula to convert semitones (notes) to their actual frequencies is

f = fRef*2^(x/12)

where fRef is an arbitrarily chosen reference frequency corresponding to a specific note, and x is the number of semitones difference between the note you want and the reference. Middle A (440Hz) is as good a reference note as any, and its MIDI note number is 69, so the formula to calculate frequency for any MIDI note number becomes:

f = 440*2^((x-69)/12)

; C4 = 261.63Hz
; D4 = 293.66
; E4 = 329.63
; F4 = 349.23
; G4 = 392.00
; A4 = 440.00
; B4 = 493.88
; C5 = 523.25

And the G-code with the resulting feedrates to play this scale on my machine:

G1 X1 F0.43605
G1 X2 F0.48943333333333333333333333333333
G1 X3 F0.54938333333333333333333333333333
G1 X4 F0.58205
G1 X5 F0.65333333333333333333333333333333
G1 X6 F0.73333333333333333333333333333333
G1 X7 F0.82313333333333333333333333333333
G1 X8 F0.87208333333333333333333333333333

; Unfortunately, our note duration is now frequency-dependent. If we wanted it to play for 1 minute, we should make the distance
; equal to the feedrate in IPM (or 1/60 of that to play for 1 second, etc.). Easy-peasy so far.

Now let’s complicate things a bit. Suppose we want to play 2 or 3 notes at once. G-code linear interpolation scheme is that in, say, an XYZ move, all the axes arrive at the same time. Feedrate is the speed the tool moves along this *vector*, not the speed of the fastest/arbitrary axis. In other words, you cannot specify individual feedrates for the (x,y,z) axis moves, only one for the resulting vector as a whole. So, since the vector that results from adding 2 ore more axis moves will always be longer than either of the individual axis moves (for the 2-axis case, think the hypotenuse of a right triangle) the feedrate we set will be faster than the highest note, and will depend on the individual notes and their contributions to that vector.

Assume the bog-standard C-E-G chord. To play each on its own for 1 second…

G1 X0.0072675 F0.43605 ; move this distance
G1 X0.0164238 F0.54938 ; move 0.009156333…
G1 X0.0273126 F0.65333 ; move 0.010888833…

…but we want to combine these into a single (x,y,z) vector at a single feedrate.
The vector is obviously (0,0,0 to .00726, .00915, .01088), and its length is given by sqrt(x^2 + y^2 + z^2). Remember we are playing all three notes for the same length of time. The vector has lengthened, but the desired playing time has not, so we need to choose the feedrate for this new distance that yields the same travel time.

Regardless of how the length or rate changes, the (x,y,z) components remain proportional to one another. Just pick one of the individual axes/notes as a reference, compare the final vector length to the length of the reference note and bump the feedrate proportionally to the change in length. In this case we arbitrarily select the highest note as the reference, and the ratio of the final feedrate (unknown) to the reference feedrate (known) should equal the ratio of the 3D vector length (known) to the reference length (known). It’s almost too easy!

3D Vector length: 0.015975658808286373765422932349422

Feedrate: (newlength/oldlength) * oldfeed

= 1.4671598699591015644580950363551 * 0.65333 = 0.9585395578403798251074072301019

G1 X0.0072675 Y0.009156333 Z0.010888833 F0.95853955

Just remember that *any* change of any note requires computing a fresh new vector, so long notes will have to be split up wherever another concurrent note changes.

Tags: , ,

35 Responses to “Fun with MIDI, CNC and vector maths (mid2cnc.py)”

  1. El Cubico says:

    lol, genius

  2. knurn says:

    LMAO!
    so phun dude. so phun

  3. ridefst says:

    Absolutly amazing!
    I’ve done some basic g-code work, but this is something else.
    I’ll bet you could sync this to a video of a simulated machine run. Not sure if it would be interesting, or just back and forth over the same points over and over again.
    Send me the code and I can probably get a video of it running on a 10 ton Heian :)

  4. Pink_Goblin says:

    This rocks!
    Did you write a midi->g-code-converter? If you did, I seriously want it! E-Mail me plz :)

  5. Dave Eaton says:

    Very cool. Because of the nature of the sound, it has a sort of bagpipe vibe- I wonder if you can find “Scotland the Brave” or a hornpipe midi. I’d bet it would sound awesome.

    Magnificent project. Kudos.

  6. Anonymous says:

    This was a triumph.

  7. kyle007 says:

    This was a triumph.

  8. Benj says:

    CNC & The Music Factory! Ha! I just thought of that right off the top of my head!

  9. Benj says:

    Wait, no. It already said that on the youtube video. Dammit.

  10. Gav says:

    This was a triumph.

    Seriously. This. Is. Brilliant.

    Please, please post your code. I’ve got a Cupcake CNC machine in the mail to me, and I absolutely must commission it by using the Portal Theme.

  11. Ryan H says:

    Great Job!!! I’ve been working on playing music on my CNC machine, but I’ve been transcribing the notes manually. It would be great to be able to use software to convert directly from MIDI to gcode.

    http://www.cnczone.com/forums/showthread.php?t=51348&page=2#17

  12. AGray says:

    Give that man some cake!

  13. Tim says:

    @Pink and others- the gcode is autogenerated from a MIDI file by python script. Drum tracks are stripped/ignored outright, and there is still no good provision for what happens when the MIDI speicifies more than 3 notes on at a time. There are a couple bugs to be worked out (like long songs crashing the machine :p). And having to experimentally determine a time-tweak factor so that the song does not play back arbitrarily (sometimes ridiculously) faster or slower than the source MIDI. A correct time scale *should* be obtainable from the file itself, but I’m far from an expert in MIDI and haven’t gotten that far.

    I’ll post the script once these are fixed :p

  14. dewalt57 says:

    Totaly too cool!!! I love it!! Can’t wait to try your converter…awsome!!!!

  15. gitarrero says:

    Cool Tool!

    You were mentioned by a german news-magazine: http://www.spiegel.de/netzwelt/web/0,1518,620661-3,00.html. You can easily add support for different scales by simply changing ‘machine_ppi’ with a vector like ‘machine_ppi_xyz[254,254,203.2]‘. Since the only time machine_ppi is used for calculating is in the loop ‘for i in range(…)’ everything is well if ‘machine_ppi_xyz[i]‘ is used instead of ‘machine_ppi’. The only other occurence is in the write-comments-section. This will work with ‘machine_ppi_xyz’.
    A nice source of suitable midis are the power-tabs on http://www.ultimate-guitar.com. Export them to midi with the powertab-editor http://www.power-tab.net/guitar.php.

  16. [...] a Day pronounced the next effort "CNC Music Factory". And yes, it is literally that — it's Jonathan [...]

  17. Corey says:

    very cool!
    how do you install this plugin and into what program?

  18. [...] yes, technically I *could* construct a test sequence in such a way as to make the prototype play music. But I haven’t gotten around to it yet choose not to. Yeah, that’s [...]

  19. Topaz says:

    I’m the one who did the arrangement of that Still Alive MIDI, and I must say that it is so awesome to be able to hear it reproduced like this! Well done!

  20. Brendan Lee says:

    You need to grab one of the MIDI’s for “Final Countdown” by Europe. That would be awesome to see! Great Work!

  21. bob johnson says:

    this script sounds like it’s the weighted companion cube!

  22. NoFNclue says:

    It’s hard to overstate my satisfaction!!!!

  23. I hacked mid2cnc to work with a MakerBot Cupcake CNC and created a geeky tribute to Ronnie James Dio and his epically epic “Man On The Silver Mountain” vocal:

    http://www.thingiverse.com/thing:3260

    With your permission, I will upload the hacked version of mid2cnc to github this week.

    Go!

    =ml=

  24. Tim says:

    @Miles- Nice adaptation! Of course feel free to post your hack. And RIP Ronnie.

  25. Thanks, Tim!

    My hack is here: http://github.com/TeamTeamUSA/mid2cnc

    A fellow MakerBotter put his up too: http://github.com/pscht/Makerbot-Music

    Go!

    =ml=

  26. [...] It’s all trigonometry, so computers are useful if you want to calculate multi-part harmonies. Tim Gipson made a Python script to convert MIDI files to G Code, though it’s written for a standard [...]

  27. Jim Steichen says:

    Way off topic: I purchased a Seiko Epson LCD Display (ECM-A0536) from All Electronics. Could you supply a part number (or even a name!) for the connector that mates to the 20-pin I/O connector of the module, Tim? Send me a private email, if so.

  28. [...] website http://tim.cexx.org/?p=633 gives you the necessary midi-to-Gcode converter, but you’ll have to do some tweaking to make [...]

  29. Dustan says:

    i have c couple questions 1 do you have a copy of the portal-still alive midi i cant get your file to play on my machine because i have roller chain and not lead screw so i cant move the distance needed to play it entirely i have a mid file but i cant get it to sound anything like yours. and 2 i converted a song and set my limits and stuff in the file but about 12 seconds into playint the file it wants to move way outta range like 53 inches from 1 inch then continues to play but x axis now goew from 53 to 12 and it shouldnt be able to move past 12 inches in the first place its only happened to me on this song so far but i havent tried very many either if you could help debug this problem plz send me an email thanks

  30. A Guy says:

    Love this script. However a lot of midi files I’m trying to process are throwing this error:

    File “midiparser.py”, line 206, in read
    if not (ord(str[0]) & 0×80):
    IndexError: string index out of range

    For example, this MIDI file will always cause the error, 01 – Prelude on http://www.ffshrine.org/ff7/ff7_midi.php

  31. Jon says:

    Very nice! This is really amazing work :)
    You should continue working on this script,
    make it standalone windows/linux application
    with GUI.
    Thanks alot.
    Best regards Jon

  32. Andrew says:

    I love you.

  33. Jabin Benjamin says:

    Is there a way to get all the notes that played on the y axis to the z axis? is it possible to just switch between different axis like that in your mid2cnc code? if so please help. thanks

Leave a Reply

This blog is protected by Dave\'s Spam Karma 2: 496869 Spams eaten and counting...