Laser Cut Piano Rolls for a Music Box

by JohanH44 in Workshop > Laser Cutting

1560 Views, 21 Favorites, 0 Comments

Laser Cut Piano Rolls for a Music Box

project.jpg

No, I don't have a pianola or player piano, but I have a music box with paper strips that you load and start cranking. The strips have small holes, which define the music to be played. Just like on a pianola roll. Throughout this instructable, I will be talking about strips, not rolls.

You can buy these music boxes from say Amazon. The music box comes usually with a bunch of strips, some with markings you have to perforate yourself with a special tool included, some with only lines marking the notes and the timing. You can also buy actual rolls with 60 m (!) of paper to perforate. That is almost an hour of music!

I also have a laser cutter, so I thought of developing a system, where I write music in MuseScore, run a plugin, do some Inkscape magic, then I have a file for the laser cutter. After it does its job, I'll have a nice strip for the music box with my music on it. My first idea was to cut in the included strips. But the strips are 660 mm long and my laser cutter can only deal with 500 wide objects. Besides, the included strips are strongly curled and it is difficult to make them flat along the laser cutter bed.

So I decided to cut everything from 500 mm * 600 mm wide sheets of 240 g paper, which I buy from a book store as flat, unrolled sheets.

The steps very breafly explained

  • Write the music in MuseScore 3.6
  • run my plugin that converts the music to an svg file
  • use Inkscape to convert the svg file into a dxf file
  • use a laser cutter to cut the strips and the holes.

Almost forgot...

  • Load the roll in the music box and enjoy the music.

Supplies

  • A music box, which reads the music from perforated paper strips
  • A laser cutter
  • 250 g paper, as wide as your laser cutter bed, for instance 500 mm * 600 mm
  • MuseScore 3.6
  • Inkscape

Writing the Music

music_box_template.png
Ah_Vous_Dirai-Je_Maman.--_Mozart.png

Basics about the music box

The music box has a range of 30 notes from F3 to A6. The included paper strip measures 70 mm * 660 mm. It has a printed grid, where the 30 notes have their own horizontal lines, 2 mm apart. Vertical lines, 4 mm apart, mark 8th notes. Every second line is dashed. Think of the full lines as quarter note beats and the dashed lines as 8th note subdivisions. You normally use the included tool for perforating the strip, but this instructable is about using a laser cutter instead.

The tuning of the music box is so that when you mark a C on the strip, it will sound as an F. That's how the note names are printed on the strips. When I decided on how I want to write music in MuseScore, I went for the same peculiar transposing system. The transposed range is hence C2 to E5.

Writing the music in MuseScore

Image 1 shows the available tones you can use. Note that not all notes in the range C2 to E5 are available. Another restriction is that the same note can't be repeated immediately. It needs at least one tick line inbetween. Technically you can use any note values, say 128th notes, as long as you don't repeat same note sooner than two tick lines. Of course, the note values don't affect the sustain of one note at all. A quarter note sustains as long as a 32nd note.

So you can't repeat same note as 8th notes. The reason is that the cogs that rotate as the holes in the paper drag them, are 8 mm apart. If two holes are only 4 mm apart, there's a cog for the first hole, but no cog for the second hole. While the cogwheels rotate, the next cog is ready only after 8 mm of paper has passed.

One more restriction is that not too many notes can be played simultaneously. One hole makes one cogwheel rotate. One cog goes into the hole and the next cog plucks the tongue, which needs force. Friction rolls drag the paper strip and if the force gets too high, the rolls slip.

As an example, the theme from Mozart's variations on "Ah, vous dirai-je, Maman" is 24 bars long, in 2/4 time. Without repeats, it would need a strip of 368 mm. The grace notes would not be implemented. More about that in the next step.

To help you out, I have attached a template file for Musescore. Since Instructables doesn't accept Musescore files as attachments, I changed the file extension from .mscx to .xml, which is not actually bending rules, because Musescore files are a kind of xml files. So download the file, change the file name extension from.xml to .mscx and open the file in Musescore. For your convenience, it will show the available notes, same as in image 1. Just delete them when you are confident in writing correct notes. When you write a note, the score transposes the sounding note one octave and a perfect fourth higher, just like the music box does.

Some Musescore file types explained

.mscz, a standard compressed Musescore file, the most used file type.

.mscx, a standard uncompressed Musescore file. You can open it with a text editor and read or edit the actual xml like code that builds up the score.

.mxl, a music xml file, pure xml code following a standard that most modern sheet music editors understand.

Using the Plugin

musescore.png
creator.png

Below is the listing of the plugin code. Open Musescore 3.6 and create there a new plugin and paste this code into the built in editor. You may want to read more about Musescore plugins here. But in short, in MuseScore select Plugins, then Plugin Creator. You will get a code editor window, where you can write code. Instead of writing copy-paste the code from below and save the plugin. You might need to activate the plugin by selecting Plugins, then Plugin Manager, then Reload Plugins, then tick the plugin. I named the file pianoroll, hence that name in the list of plugins. In the code, one line defines the menu name of the plugin. The line:

menuPath: "Plugins.Notes." + qsTr("Create piano roll svg")

...defines that the plugin be under menu Plugins, Notes, and the name of the plugin in the pulldown menu is Create piano roll svg.

The plugin does the following.

  • Write an svg file containing the graphics to be cut on the roll
  • Start with a blue curve at the beginning, which will cut the paper in a suitable form.
  • Draw a black circle for each note at the x and y coordinate corresponding to the note number and its place on the time line
  • Draw a green horizontal line at the top and the bottom, 70 mm from each other. These lines will cut your paper to the correct strip width for the music box.
  • Draw a blue vertical line, 20 mm from the last note, which will cut the strip.
  • Mark all invalid note heads red, these will point out mistakes done while writing the music
  • Mark notes with orange, if they result in holes less than 8 mm apart from each other (when same note).
  • The idea of using black, green and blue line colours for different objects is that they will automatically turn into different layers, the order of which will be easier to handle, when they are being cut out with the laser.
  • The svg file is saves with the name roll.svg. Carelessly writing over any existing file, so be aware!

The reason for using an intermediate svg file is that the svg file format is very easy to comprehend. It's just plain text, something a simple Musescore plugin can create. The produced svg file is in all means a complete svg file. But unfortunately the Thunder Laser software I'm using for the laser cutter can't import svg files. But in turn, Inkscape can read the svg file and export it to the next file format, which is dxf-14. If you have a laser cutter with software which reads svg (my guess is most do!), you might still want to edit the file in Inkscape for a few reasons, e.g. adding images.

The plugin skips grace notes. If you want them, you have to re-write them and define them using ordinary notes. But bare in mind the restriction of 8 mm (or a quarter note) as the minimum between repeating the same note. Most grace notes lead to repeating same note very fast.

Improvements

The plugin is working, but is far from a user friendly thing. It always outputs to a file called roll.svg, overwriting the previous one. And the only feedback it gives is one or two coloured noteheads, if you placed them wrong. So, the todo-list goes as follows:

  • include a settings menu as a UI element
  • include a separate file saving window
  • include a "The file roll.svg successfully created" message
  • The check for repeating notes needs improvement. It doesn't check for notes written on different staves, like on a piano stave. Right now, if you really need to detect all notes that might be too near each other, write all music in just one stave!

The plugin code listed

//=============================================================================
//  MuseScore
//  Music Composition & Notation
//
//  Music box roll plugin (originally Note Names Plugin)
//
//  Copyright (C) 2012 Werner Schweer
//  Copyright (C) 2013 - 2020 Joachim Schmitz
//  Copyright (C) 2014 Jörn Eichler
//  Copyright (C) 2020 Johan Temmerman
//
//  Modified by Johan Halmén 28th of December 2022
//  The modification just uses the note parsing from the original plugin
//
//  This program is free software; you can redistribute it and/or modify
//  it under the terms of the GNU General Public License version 2
//  as published by the Free Software Foundation and appearing in
//  the file LICENCE.GPL
//=============================================================================

import QtQuick 2.2
import MuseScore 3.0
import FileIO 3.0

MuseScore {
   version: "3.4.2.1"
   description: qsTr("This plugin creates a piano roll for a music box")
   menuPath: "Plugins.Notes." + qsTr("Create piano roll svg")

   // Small note name size is fraction of the full font size.
   property var fontSizeMini: 0.7;
   property variant last_x : [ -10, -10, -10, -10, -10, -10, -10, -10, -10, -10, 
                               -10, -10, -10, -10, -10, -10, -10, -10, -10, -10, 
                               -10, -10, -10, -10, -10, -10, -10, -10, -10, -10];
   
   property variant max_x : 0.0;

    FileIO {
        id: outFile
        source: "/tmp/roll.svg"
    }
      
   property variant mystring: ""
   property variant item_count: 0;

   // This function takes all notes in one chord and deals with them one by one
   function nameChord (notes, small, tick) {
   
      // These are the MIDI tone numbers of the tongues of the music box
   var tongues = new Array(53, 55, 60, 62, 64, 65, 67, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 91, 93 );
   var time_coord;
   var note_coord;
         
      for (var i = 0; i < notes.length; i++) {
         var found = 0;
         
         notes[i].color = "#000000";
         
         // Is our note one of the 30 of the music box?
         for (var j = 0; j < 30 && !found; j++) {
            if (notes[i].pitch == tongues[j]) {
                  // Draw the punch into the svg
                  //            |-> 4.0: grid is 8th notes, 8.0: grid is 16th note
                  //            |
                  //            |                  |-> how many mm from left edge music starts 
                  //            |                  | 
                  time_coord = 4.0 * tick / 240 + 40; // standard 4.0
                  note_coord = 2.0 * (29 - j) + 6.0;
                  mystring += "  <circle\n";
                  mystring += "     style=\"fill:none;stroke:#000000;stroke-width:0.1;stroke-linecap:round;stroke-linejoin:round;stop-color:#000000\"\n";
                  mystring += "     id=\"path";
                  mystring += item_count++;
                  mystring += "\"\n";
                  mystring += "     cx=\"";
                  mystring += time_coord;                  
                  mystring += "\"\n";
                  mystring += "     cy=\"";
                  mystring += note_coord;                  
                  mystring += "\"\n";
                  mystring += "     r=\"1.1\" />\n";
                  found = 1;
                  if ((time_coord - last_x[j]) < 8.0 &&
                      (time_coord - last_x[j]) >= -8.0)
                        notes[i].color = "#FF8000";
                  last_x[j] = time_coord;
                  if (max_x < time_coord)
                        max_x = time_coord;
                  
            }
         }
         if (found == 0)
            // Mark notes not matching the music box 
            // so user can correct them
            notes[i].color = "#FF0000";


         /*         else {
            // This fixes the colour of corrected notes
            notes[i].color = this_colour;
         }
         */
         //last_x[j] = time_coord;


      }  // end for note
   }

   onRun: {
      var cursor = curScore.newCursor();
      var startStaff;
      var endStaff;
      var endTick;
      var fullScore = false;
      cursor.rewind(1);
      mystring = "";

      for (var i = 0; i < 30; i++) {
            last_x[i] = -10;
      }

      // Create the beginning of the svg file
      mystring += "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n";
      mystring += "<!-- Created with MuseScore PianoRoll plugin  -->\n";
      mystring += "\n";
      mystring += "<svg\n";
      mystring += "   width=\"500mm\"\n";
      mystring += "   height=\"70mm\"\n";
      mystring += "   viewBox=\"0 0 500 70\"\n";
      mystring += "   version=\"1.1\"\n";
      mystring += "   id=\"svg5\"\n";
      mystring += "   xmlns=\"http://www.w3.org/2000/svg\"\n";
      mystring += "   xmlns:svg=\"http://www.w3.org/2000/svg\">\n";
      mystring += "  <defs\n";
      mystring += "     id=\"defs2\" />\n";
      
      mystring += "      <path\n";
      mystring += "         style=\"vector-effect:non-scaling-stroke;fill:none;stroke:#0000ff;stroke-width:0.264583;stroke-dasharray:none;-inkscape-stroke:hairline\"\n";
      mystring += "         d=\"M 24.9774,0 C 17.4842,17.5 0,21 0,35 0,49 17.4842,52.50001 24.9774,70.00\"\n";
      mystring += "         id=\"path9999\" />\n";
      mystring += "      \n";
      if (!cursor.segment) { // no selection
         fullScore = true;
         startStaff = 0; // start with 1st staff
         endStaff  = curScore.nstaves - 1; // and end with last
      } else {
         startStaff = cursor.staffIdx;
         cursor.rewind(2);
         if (cursor.tick === 0) {
            // this happens when the selection includes
            // the last measure of the score.
            // rewind(2) goes behind the last segment (where
            // there's none) and sets tick=0
            endTick = curScore.lastSegment.tick + 1;
         } else {
            endTick = cursor.tick;
         }
         endStaff = cursor.staffIdx;
      }
      console.log(startStaff + " - " + endStaff + " - " + endTick)

      for (var staff = startStaff; staff <= endStaff; staff++) {
         for (var voice = 0; voice < 4; voice++) {
            cursor.rewind(1); // beginning of selection
            cursor.voice    = voice;
            cursor.staffIdx = staff;


            if (fullScore)  // no selection
               cursor.rewind(0); // beginning of score
            while (cursor.segment && (fullScore || cursor.tick < endTick)) {
               if (cursor.element && cursor.element.type === Element.CHORD) {
                  //var text = newElement(Element.STAFF_TEXT);      // Make a STAFF_TEXT


                  // First...we need to scan grace notes for existence and break them
                  // into their appropriate lists with the correct ordering of notes.
                  var leadingLifo = new Array();   // List for leading grace notes
                  var trailingFifo = new Array();  // List for trailing grace notes
                  var graceChords = cursor.element.graceNotes;
                  // Build separate lists of leading and trailing grace note chords.
                  if (graceChords.length > 0) {
                     for (var chordNum = 0; chordNum < graceChords.length; chordNum++) {
                        var noteType = graceChords[chordNum].notes[0].noteType
                        if (noteType === NoteType.GRACE8_AFTER || noteType === NoteType.GRACE16_AFTER ||
                              noteType === NoteType.GRACE32_AFTER) {
                           trailingFifo.unshift(graceChords[chordNum])
                        } else {
                           leadingLifo.push(graceChords[chordNum])
                        }
                     }
                  }


                  // Next process the leading grace notes, should they exist...


                  // Now handle the note names on the main chord...
                  var notes = cursor.element.notes;
                  nameChord(notes, false, cursor.tick);


               } // end if CHORD
               cursor.next();
            } // end while segment
         } // end for voice
      } // end for staff


      // Draw ending cut curve 20 mm past the last note
      mystring += "  <path\n";
      mystring += "     style=\"vector-effect:non-scaling-stroke;fill:none;stroke:#0000ff;stroke-width:0.264583;-inkscape-stroke:hairline;stop-color:#000000\"\n";
      mystring += "     d=\"M ";
      mystring += (max_x + 20);
      mystring += ",0 V 70\"\n";
      mystring += "     id=\"path99998\" />\n";
      
      if (max_x > 500)
            max_x = 500;
            
      mystring += "    <path\n";
      mystring += "       style=\"vector-effect:non-scaling-stroke;fill:none;stroke:#00ff00;stroke-width:0.264583;-inkscape-stroke:hairline;stop-color:#000000\"\n";
      mystring += "       d=\"M 0,0 H ";
      mystring += max_x;
      mystring += "\"\n";
      mystring += "       id=\"path99997\" />\n";
            
      mystring += "    <path\n";
      mystring += "       style=\"vector-effect:non-scaling-stroke;fill:none;stroke:#00ff00;stroke-width:0.264583;-inkscape-stroke:hairline;stop-color:#000000\"\n";
      mystring += "       d=\"M 0,70 H ";
      mystring += max_x;
      mystring += "\"\n";
      mystring += "       id=\"path99996\" />\n";
            
      mystring += "\n</svg>\n";
      outFile.write(mystring);
      Qt.quit();
   } // end onRun
}


Using Inkscape

harry_potter.png

When I open the svg file in Inkscape, I get an image with the final size of 500 mm * 70 mm. That's the displayed drawing area. If the whole tune fits inside this area, I'm fine, the horizontal lines end 20 mm after the last note and the ending vertical line is at that same spot. If not, I can take further steps at this point. See step 4 for that.

You may want to add an image, some text, anything, at the start of the strip. I prefer to rotate such an image 90 degrees to the left. To make it fit in, I first move the starting cut curve to the left, then I fit in the image and adjust its horizontal position together with the starting cut curve. When that looks fine, I can adjust everything so that the starting cut curve is 1 mm from the left.

In step 4 I explain how I cut the image into 500 mm segments, if the music extends over 500 mm in the svg file the plugin created. In step 5 I explain how to work with the dxf file in Thunder Laser.

From Inkscape I export the image into a dxf-14 file. The dxf file is a binary file, not editable with a simple text editor, and hence not very convenient to create in a MuseScore plugin. But Inkscape does a good job in exporting to a dxf file.

Working With Longer SVG Images

longstrip.png

So you wrote music longer than 30 seconds. When opened in Inkscape, the graphixs extend over the right edge of the drawing area in Inkscape. You have to cut the long sequence of holes into segments under 500 mm wide. Look at image 1. It shows a tune divided into 4 segments. I've coloured with same colour the edges that are to be concatenated. Note how the curves are identical in pairs. I start by drawing cutting curves, not more than 500 mm apart from each other. After that I copy the segments including the cutting curves at both ends of the segment, and place the segments underneath each other. Understand the concept at this point and you can work out the rest yourself, perhaps even smarter than I have done. Understand how each segment has one edge that fits with the previous segment and one segment that fits with the next one.

Detailed description

  1. When you have the tune in one long strip of graphics extending way over the right edge of the 500 mm drawing area, draw a cutting curve near the right edge, which would cut neatly between the holes, not too near the holes. Imagine you have to tape the parts back together without covering any hole with the adhessive tape. Adjust the curve so that it starts at Y=0 and ends at Y=70. Achieve this by writing 0 to the Y field at the top. And 70 to the H field (height).
  2. Make a selection in inkscape including the newly drawn cutting curve and all note marks to the right of it, as well as the final cutting vertical line. It doesn't matter if you got one or two holes selected to the left of the newly drawn cutting curve.
  3. Copy (ctrl+c). Paste in place (ctrl+alt+v).
  4. Move this selection to the left edge of the drawing area, below the beginning of the graphics. Do this by writing directly to the X and Y fields at the top of the screen. X should be 1, that's 1 mm from the left edge of the drawing area. And Y should be 70.
  5. Now you have a new, shorter strip below the original strip. This might still extend over the right edge of the drawing area. Repeat what you did in step 1. Minding that the new cutting curve at the right end should go from Y=70 to Y=140. After copying and pasting in place, you move the copy to X=1 and Y=140. And so on.
  6. You end up in a few strips with their cutting curves at the right edge of the drawing area. And each one has stuff to the right of the cutting curve. And each strip has a cutting curve at the left edge, which is identical to the cutting curve at the right edge of the previous strip!
  7. Delete the extra stuff to the right of the cutting curve on each strip.
  8. You have horizontal lines at the top and bottom of the first strip. You need to extend them to the right. The plugin left them too short, so that they wouldn't disturb your selecting and copying in steps 2 and 3. You want similar lines between all stripes and below the last stripe. Use same copy and paste in place technique, changing the Y coordinate of each pasted line to 140, 210, 280 and so on. These lines will cut the strips apart from each other as the final stage in the actual laser cutting process.
  9. When you tape all strips together, you might end up in covering some of the holes with tape. Just cut out the holes again with the special tool that came with the music box. Cutting 5 holes by hand is still easier than cutting 500 holes.

Working in Thunder Laser

bach1.png

Thunder Laser is the software I'm using with my laser cutter. I hope the concepts are somewhat similar in other software, too. When I import the dxf-14 file, which I prepared in Inkscape, into Thunder Laser, I get a lot of vector objects. Lines and curves to be cut, tiny circles which are the note data, also to be cut. And maybe an image I just want to be burned slightly onto the paper. You might want to skip the image part, or you might want to import the image at this stage instead of in Inkscape, maybe as a bitmap and not a vector image, but I won't go into that.

Since I aim to put in a sheet of paper into the laser cutter and take out a bunch of ready cut and perforated strips, all in one go, I want the cut process to go in a precise order. Looking at image 1, I want the red layer to happen first, that's layer 1. It's a vector image of J. S. Bach. It is set to scan mode, which means the image is burnt on the paper line by line, like an image on an old CRT television. With the perfect speed and power I will get a nice image, which won't make the paper too fragile.

To the second I want the tiny holes to be cut. They are all set to layer 2, black. The next layer number 3, the blue one, which cuts the ends on each strip. At this point, the whole sheet stays still together, which is crucial for each layer that has been cut so far. Doing it in the wrong order might cause parts and pieces moving a bit on the bed due to vibrations, before everything has been cut. Finally, the green layer, number 4, the horizontal lines, cuts the strips from each other. If at this point, due to whatever reason, the strips start to curle or move due to vibrations and will be cut wrong, an extra precaution could be made. If we draw the curves and lines in the blue and green layers so that they start and end 1 mm off their current start and end points, the strips will stick to the sheet all the way to the end. And you have to tear by hand the last 1 mm, when you take out the sheet from the laser cutter.

Layers 2, 3 and 4 are all set to cut mode and can probably have the same effect and speed to make a proper cut without burning too much. The only reason why they are on separate layers is to decide the order in which the cutting happens.

Showtime

Harry Potter Theme
Shchedryk
J. S. Bach: Pr&auml;ludium 1

Theme from Harry Potter

I tried to write this tune as short as possible, to fit into the 500 mm strip, but it turned out to be 820 mm long. This would have required two strips to be taped together. Instead I tweaked the plugin to make the holes nearer each other. I wanted the title image in the beginning, so I left 450 mm for the actual holes. This line in the plugin:

         time_coord = 4.0 * tick / 240 + 20; // standard 4.0

...I changed to:

         time_coord = 2.68 * tick / 240 + 20; // standard 4.0


Shchedryk (Carol of the Bells)

Although we know this song as a Christmas carol, it is originally an Ukrainian New Year's song. The lyrics go as follows:


A little swallow flew (into the household)

And started to twitter

To summon the master

"Come out, come out, O master (of the household)

Look at the sheep pen

There the ewes are nestling

And the lambkin have been born

Your goods (livestock) are great

You will have a lot of money, (by selling them)

Your goods (livestock) are great

You will have a lot of money, (by selling them)

If not money, then chaff: (from all the grain you will harvest)

You have a dark-eyebrowed (beautiful) wife"

Shchedryk, shchedryk, a shchedrivka

A little swallow flew


Bach: Prelude no 1, from das Wohltemperierte Klavier 1

This tune required five strips à 500 mm. This was my first attempt to actually cut everything out of a 350 mm * 500 mm sheet of 240 g paper. I downloaded the sheet music from Musescore.com and edited it a bit to fit the range of the music box. I missed some note in one bar. Can you spot it?

Todo

As some of the videos show, I have a motor attached to the music box. I will present that in a new instructable in the future. This instructable focuses on creating the strips using a laser cutter. The motor already works, but it will require some speed controlling electronics as well as a sensor detecting the presence of a paper strip. The idea is to have a wooden (plywood) box for the music box and music strips with coded tempo markings including tempo changes. The user just feeds the paper strip into a slot and the machine starts, plays the music in the right tempo and stops when the strip ends. The big question is, will the motor have too much noise. I'm still running tests.