Fabacademy 2017
Eleventh Week. Machine design
Index
Eleventh task
Automate our group machine
Our group machine is a music roll perforatorOur group machine is a music roll perforator, the process of making your own music rolls is quite labor intensive as you have to punch the roll manually, we are designing a machine that will automate this tedious process. My individual contribution within this second part of the project was coding the automation script together with Pilar and also making a MIDI parser so we can transform MIDI files into our machine instructions.
Setting up Pygelstat enviroment
Havent used Python before but have limited experience in javascript, ruby and the like. I found Python sintax clean and confortable but I an still trying to set up a confortable developing enviroment for now I just used the terminal and Github's atom
The setting up steps would be: 1. install Python, 2. install pyserial, 3. install websocket, 4. install pygelstalt,
brew install python
pip install pyserial
npm install ws
git clone http://github.com/nadya/pygestalt
sudo python setup.py install
Documentation
Initial dissapointed happened fast when I realized that there was no proper documentation on the pygestalt framework. The structure feels messy and there is no description of folder contents, examples and so on. I think some automated documentation could be created using sphinx and would be really helpful. Great code without adequate documentation is difficult to use. UPDATE: Actually Massimo Menichinelli has already make a sphinx documentation of the framework and it is already here gives some clues about the framework but a proper documentation would be neccesary.
Automating the machine
We used the suggested example as the base of our code. I will be extracting the most relevant parts of the code
# x axis has 30 notes with 2mm of space between notes, UNITS: MM
x_notes = {
'C3': 0, 'D3': 2, 'G3': 4, 'A3': 6, 'B3': 8,
'C4': 10, 'D4': 12, 'E4': 14, 'F4': 16, 'Gb4': 18, 'G4': 20, 'Ab4': 22, 'A4': 24, 'Bb4': 26, 'B4': 28,
'C5': 30, 'Db5': 32, 'D5': 34, 'Eb5': 36, 'E5': 38, 'F5': 40, 'Gb5': 42, 'G5': 44, 'Ab5': 46, 'A5': 48, 'Bb5': 50, 'B5': 52,
'C6': 54, 'D6': 56, 'E6': 58
}
# y axis is the stepper motor that does the perforation it needs to move a full turn to do a perforation UNITS: rev
y_perforation = 1
# z axis is the roller motor, there is a 4mm space between each line of the song UNITS: MM
z_roller = 4
First, we create the variables that we will be using to generate our moves array. The X axis values in mm for each note of the music roll –stored in a associative array so I can consult the data with a simple command–, the value of the y axis in order to make a perforation and finally the distance in millimeters to each line of the roll.
song = [['Bb5', 'Bb4', 'F5'], [], [], [], [], [], ['Bb4'], [], [], [], [], [], ['Bb4', 'F5', 'D5'], [], [], [], [], [], ['Bb4'], [], [], [], [], [], ['Eb5', 'Bb4'], [], ['D5'], [], ['C5'], [], ['Bb5', 'Bb4', 'F5'], [], [], [], [], [], ['Bb4'], [], [], [], [], [], ['Bb4', 'D5', 'F5'], [], [], [], [], [], ['Eb5', 'Bb4'], [], ['D5'], [], ['C5'], [], ['Eb5', 'Bb5', 'Bb4'], [], [], [], [], [], ['Bb4'], [], [], [], [], [], ['Bb4', 'D5', 'F5'], [], [], [], [], [], ['Eb5', 'C5'], [], ['B4', 'D5'], [], ['Eb5', 'C5'], [], ['A4', 'C5'], [], [], [], [], [], ['A4'], [], [], [], [], [], ['F4'], [], [], [], ['F4'], [], ['G4'], [], [], [], [], [], ['Bb4'], [], [], ['G4'], [], [], ['Eb5', 'Bb4'], [], [], ['D5'], [], [], ['C5'], [], [], ['Bb4'], [], [], ['Bb4'], [], ['C5'], [], ['D5'], [], ['C5'], [], [], [], ['G4'], [], ['A4'], [], [], [], [], [], ['F4'], [], [], [], ['F4'], [], ['G4'], [], [], [], [], [], ['Bb4'], [], [], ['G4'], [], [], ['Eb5', 'Bb4'], [], [], ['D5'], [], [], ['C5'], [], [], ['Bb4'], [], [], ['Db5', 'F5'], [], [], [], [], [], ['A4', 'C5'], [], [], [], [], [], ['F4'], [], [], [], [], [], ['C4'], [], [], ['F4'], [], [], [], ['G4'], [], [], [], [], [], ['Bb4'], [], [], ['G4'], [], [], ['Eb5', 'Bb4'], [], [], ['D5'], [], [], ['C5'], [], [], ['Bb4'], [], [], ['Bb4'], [], ['C5'], [], ['D5'], [], ['C5'], [], [], [], ['G4'], [], ['F4', 'A4'], [], [], [], [], [], ['F4', 'F5'], [], [], [], ['F5'], [], ['Bb5', 'Gb5'], [], [], [], ['F5', 'Ab5'], [], ['Eb5', 'Gb5'], [], [], [], ['Db5', 'F5'], [], ['Eb5', 'C5'], [], [], [], ['Bb4', 'Db5'], [], ['Ab4', 'C5'], [], [], [], ['Gb4', 'Bb4'], [], ['F4', 'F5']]
The song data is stored in a bidimensional array, first dimension are the lines of each song and second dimension are the notes within each line. I used this great online editor to understand the notes that the music box used and how the roll sheets are structured. By the way the above song array is the intro theme of Star Wars ;-)
# JAVIBG
# Movement is defined
# 0. for each line in song
# 1. position the z_roller
# 2. if not empty then for each note
# 3. move the first x_note in the row
# 4. make the perforation with y_perforation one revolution
# 5. go back to the x_note 0
# 6. end for each note
# 7. end for each line
moves = [[0,0,0]]
current_z = 0
for line in song:
if line:
for note in line:
aux = [x_notes[note],0, current_z]
moves.append(aux)
aux2 = [x_notes[note], y_perforation, current_z]
moves.append(aux2)
aux3 = [0,0,current_z]
moves.append(aux3)
current_z += z_roller
moves.append([0,0,current_z])
for move in moves:
stages.move(move, 0)
status = stages.xAxisNode.spinStatusRequest()
# This checks to see if the move is done.
while status['stepsRemaining'] > 0:
time.sleep(0.001)
status = stages.xAxisNode.spinStatusRequest()
This is where all the magic happens: we iterate for each line of the song, creating the neccesary instuctions for the machine and appending each movement within the moves array, in pseudocode:
- for each line in song position the z_roller
- if not empty then for each note move the first x_note in the row
- make the perforation with y_perforation one revolution
- go back to the position x = 0
- move to next line
The last for loop is taken straight from the example code and feeds the machine with the moves array and checks that the last move has happened before sending the next one.
Parsing a MIDI file
Without the ability of parsing song files and writting them into machine movement the machine can't be considered completed. Initially I though it could be really difficult to implement but I think I found a decent and pragmatic approach for this.
Coding a full GUI for writting the song was out of reach for this week so I spend some time looking for preexisting music roll editors and found this really cool project Musicboxmaniacs.com online editor. The app allows to make your own songs, play them online and export as mp3, PDF guides and more interestingly MIDI files.

MIDI is a standard for exchanging music. The protocol is from the 80's and was created when electronic devices speeds and communication protocols where much more limited. Files are not human readable and parsing them are not a trivial task, fortunately there are many libraries in many different languages that will do that for you. I found particulary suitable python library as it is small, simple and able to read midi files and output a python object that can be parsed later using the same approach I used for the song array.
# midi.NoteOnEvent(tick=720, channel=0, data=[62, 127]),
# midi.NoteOnEvent(tick=0, channel=0, data=[60, 127]),
# midi.NoteOffEvent(tick=240, channel=0, data=[62, 127]),
# midi.NoteOffEvent(tick=0, channel=0, data=[60, 127]),
# midi.NoteOnEvent(tick=0, channel=0, data=[64, 127]),
# midi.NoteOnEvent(tick=0, channel=0, data=[65, 127]),
# midi.NoteOffEvent(tick=240, channel=0, data=[64, 127]),
# midi.NoteOffEvent(tick=0, channel=0, data=[65, 127]),
This is the object that the libray produces. Tick is an odd way of dettermining the note duration and position, fortunately there is plenty of documentation on internet. data is a vector in which the first value is the note and the second is the velocity or how strong the note is played, obviously for a music box velocity is always constant.I had problems to extract the correct note values as I used this reference first and didnt seem to correspond to the actual values. I soved this by creating a test song with the creator that plays all the notes and then reversed the codes generated in the MIDI file. This would be the associative array that identifies MIDI note codes with notes:
midinotes = {
48: 'C3', 50: 'D3', 55: 'G3', 57: 'A3', 59: 'B3',
60: 'C4', 62: 'D4', 64: 'E4', 65: 'F4', 66: 'Gb4', 67: 'G4', 68: 'Ab4', 69: 'A4', 70: 'Bb4', 71: 'B4',
72: 'C5', 73: 'Db5', 74: 'D5', 75: 'Eb5', 76: 'E5', 77: 'F5', 78: 'Gb5', 79: 'G5', 80: 'Ab5', 81: 'A5', 82: 'Bb5', 83: 'B5',
84: 'C6', 86: 'D6', 88: 'E6'
}
The last step would be to create a for loop to parse the midi object, extract the position and note values and write them using the same structure of the song array. I found particulary useful the isinstance() fuction which doublecheck that the extracted elment of the midi object is a midi.NoteOnEvent or a midi.NoteOffEvent so I can extract different values and generate song lines easily.
# define two arrays the full song and each line
song = []
line = []
# pattern[1] is the part of the midi object that I need where notes are described
for note in pattern[1]:
if isinstance(note, midi.NoteOnEvent):
linejump = note.tick/240
for i in range(0,linejump):
song.append([])
aux = note.data[0]
line.append(midinotes[aux])
if isinstance(note, midi.NoteOffEvent):
if line:
song.append(line)
line = []
print song

Starwars theme song described with our machine movement.
starwarsmoves = [[0, 0, 0], [50, 0, 0], [50, 1, 0], [26, 0, 0], [26, 1, 0], [40, 0, 0], [40, 1, 0], [0, 0, 0], [0, 0, 4], [0, 0, 8], [0, 0, 12], [0, 0, 16], [0, 0, 20], [0, 0, 24], [26, 0, 24], [26, 1, 24], [0, 0, 24], [0, 0, 28], [0, 0, 32], [0, 0, 36], [0, 0, 40], [0, 0, 44], [0, 0, 48], [26, 0, 48], [26, 1, 48], [40, 0, 48], [40, 1, 48], [34, 0, 48], [34, 1, 48], [0, 0, 48], [0, 0, 52], [0, 0, 56], [0, 0, 60], [0, 0, 64], [0, 0, 68], [0, 0, 72], [26, 0, 72], [26, 1, 72], [0, 0, 72], [0, 0, 76], [0, 0, 80], [0, 0, 84], [0, 0, 88], [0, 0, 92], [0, 0, 96], [36, 0, 96], [36, 1, 96], [26, 0, 96], [26, 1, 96], [0, 0, 96], [0, 0, 100], [0, 0, 104], [34, 0, 104], [34, 1, 104], [0, 0, 104], [0, 0, 108], [0, 0, 112], [30, 0, 112], [30, 1, 112], [0, 0, 112], [0, 0, 116], [0, 0, 120], [50, 0, 120], [50, 1, 120], [26, 0, 120], [26, 1, 120], [40, 0, 120], [40, 1, 120], [0, 0, 120], [0, 0, 124], [0, 0, 128], [0, 0, 132], [0, 0, 136], [0, 0, 140], [0, 0, 144], [26, 0, 144], [26, 1, 144], [0, 0, 144], [0, 0, 148], [0, 0, 152], [0, 0, 156], [0, 0, 160], [0, 0, 164], [0, 0, 168], [26, 0, 168], [26, 1, 168], [34, 0, 168], [34, 1, 168], [40, 0, 168], [40, 1, 168], [0, 0, 168], [0, 0, 172], [0, 0, 176], [0, 0, 180], [0, 0, 184], [0, 0, 188], [0, 0, 192], [36, 0, 192], [36, 1, 192], [26, 0, 192], [26, 1, 192], [0, 0, 192], [0, 0, 196], [0, 0, 200], [34, 0, 200], [34, 1, 200], [0, 0, 200], [0, 0, 204], [0, 0, 208], [30, 0, 208], [30, 1, 208], [0, 0, 208], [0, 0, 212], [0, 0, 216], [36, 0, 216], [36, 1, 216], [50, 0, 216], [50, 1, 216], [26, 0, 216], [26, 1, 216], [0, 0, 216], [0, 0, 220], [0, 0, 224], [0, 0, 228], [0, 0, 232], [0, 0, 236], [0, 0, 240], [26, 0, 240], [26, 1, 240], [0, 0, 240], [0, 0, 244], [0, 0, 248], [0, 0, 252], [0, 0, 256], [0, 0, 260], [0, 0, 264], [26, 0, 264], [26, 1, 264], [34, 0, 264], [34, 1, 264], [40, 0, 264], [40, 1, 264], [0, 0, 264], [0, 0, 268], [0, 0, 272], [0, 0, 276], [0, 0, 280], [0, 0, 284], [0, 0, 288], [36, 0, 288], [36, 1, 288], [30, 0, 288], [30, 1, 288], [0, 0, 288], [0, 0, 292], [0, 0, 296], [28, 0, 296], [28, 1, 296], [34, 0, 296], [34, 1, 296], [0, 0, 296], [0, 0, 300], [0, 0, 304], [36, 0, 304], [36, 1, 304], [30, 0, 304], [30, 1, 304], [0, 0, 304], [0, 0, 308], [0, 0, 312], [24, 0, 312], [24, 1, 312], [30, 0, 312], [30, 1, 312], [0, 0, 312], [0, 0, 316], [0, 0, 320], [0, 0, 324], [0, 0, 328], [0, 0, 332], [0, 0, 336], [24, 0, 336], [24, 1, 336], [0, 0, 336], [0, 0, 340], [0, 0, 344], [0, 0, 348], [0, 0, 352], [0, 0, 356], [0, 0, 360], [16, 0, 360], [16, 1, 360], [0, 0, 360], [0, 0, 364], [0, 0, 368], [0, 0, 372], [0, 0, 376], [16, 0, 376], [16, 1, 376], [0, 0, 376], [0, 0, 380], [0, 0, 384], [20, 0, 384], [20, 1, 384], [0, 0, 384], [0, 0, 388], [0, 0, 392], [0, 0, 396], [0, 0, 400], [0, 0, 404], [0, 0, 408], [26, 0, 408], [26, 1, 408], [0, 0, 408], [0, 0, 412], [0, 0, 416], [0, 0, 420], [20, 0, 420], [20, 1, 420], [0, 0, 420], [0, 0, 424], [0, 0, 428], [0, 0, 432], [36, 0, 432], [36, 1, 432], [26, 0, 432], [26, 1, 432], [0, 0, 432], [0, 0, 436], [0, 0, 440], [0, 0, 444], [34, 0, 444], [34, 1, 444], [0, 0, 444], [0, 0, 448], [0, 0, 452], [0, 0, 456], [30, 0, 456], [30, 1, 456], [0, 0, 456], [0, 0, 460], [0, 0, 464], [0, 0, 468], [26, 0, 468], [26, 1, 468], [0, 0, 468], [0, 0, 472], [0, 0, 476], [0, 0, 480], [26, 0, 480], [26, 1, 480], [0, 0, 480], [0, 0, 484], [0, 0, 488], [30, 0, 488], [30, 1, 488], [0, 0, 488], [0, 0, 492], [0, 0, 496], [34, 0, 496], [34, 1, 496], [0, 0, 496], [0, 0, 500], [0, 0, 504], [30, 0, 504], [30, 1, 504], [0, 0, 504], [0, 0, 508], [0, 0, 512], [0, 0, 516], [0, 0, 520], [20, 0, 520], [20, 1, 520], [0, 0, 520], [0, 0, 524], [0, 0, 528], [24, 0, 528], [24, 1, 528], [0, 0, 528], [0, 0, 532], [0, 0, 536], [0, 0, 540], [0, 0, 544], [0, 0, 548], [0, 0, 552], [16, 0, 552], [16, 1, 552], [0, 0, 552], [0, 0, 556], [0, 0, 560], [0, 0, 564], [0, 0, 568], [16, 0, 568], [16, 1, 568], [0, 0, 568], [0, 0, 572], [0, 0, 576], [20, 0, 576], [20, 1, 576], [0, 0, 576], [0, 0, 580], [0, 0, 584], [0, 0, 588], [0, 0, 592], [0, 0, 596], [0, 0, 600], [26, 0, 600], [26, 1, 600], [0, 0, 600], [0, 0, 604], [0, 0, 608], [0, 0, 612], [20, 0, 612], [20, 1, 612], [0, 0, 612], [0, 0, 616], [0, 0, 620], [0, 0, 624], [36, 0, 624], [36, 1, 624], [26, 0, 624], [26, 1, 624], [0, 0, 624], [0, 0, 628], [0, 0, 632], [0, 0, 636], [34, 0, 636], [34, 1, 636], [0, 0, 636], [0, 0, 640], [0, 0, 644], [0, 0, 648], [30, 0, 648], [30, 1, 648], [0, 0, 648], [0, 0, 652], [0, 0, 656], [0, 0, 660], [26, 0, 660], [26, 1, 660], [0, 0, 660], [0, 0, 664], [0, 0, 668], [0, 0, 672], [32, 0, 672], [32, 1, 672], [40, 0, 672], [40, 1, 672], [0, 0, 672], [0, 0, 676], [0, 0, 680], [0, 0, 684], [0, 0, 688], [0, 0, 692], [0, 0, 696], [24, 0, 696], [24, 1, 696], [30, 0, 696], [30, 1, 696], [0, 0, 696], [0, 0, 700], [0, 0, 704], [0, 0, 708], [0, 0, 712], [0, 0, 716], [0, 0, 720], [16, 0, 720], [16, 1, 720], [0, 0, 720], [0, 0, 724], [0, 0, 728], [0, 0, 732], [0, 0, 736], [0, 0, 740], [0, 0, 744], [10, 0, 744], [10, 1, 744], [0, 0, 744], [0, 0, 748], [0, 0, 752], [0, 0, 756], [16, 0, 756], [16, 1, 756], [0, 0, 756], [0, 0, 760], [0, 0, 764], [0, 0, 768], [0, 0, 772], [20, 0, 772], [20, 1, 772], [0, 0, 772], [0, 0, 776], [0, 0, 780], [0, 0, 784], [0, 0, 788], [0, 0, 792], [0, 0, 796], [26, 0, 796], [26, 1, 796], [0, 0, 796], [0, 0, 800], [0, 0, 804], [0, 0, 808], [20, 0, 808], [20, 1, 808], [0, 0, 808], [0, 0, 812], [0, 0, 816], [0, 0, 820], [36, 0, 820], [36, 1, 820], [26, 0, 820], [26, 1, 820], [0, 0, 820], [0, 0, 824], [0, 0, 828], [0, 0, 832], [34, 0, 832], [34, 1, 832], [0, 0, 832], [0, 0, 836], [0, 0, 840], [0, 0, 844], [30, 0, 844], [30, 1, 844], [0, 0, 844], [0, 0, 848], [0, 0, 852], [0, 0, 856], [26, 0, 856], [26, 1, 856], [0, 0, 856], [0, 0, 860], [0, 0, 864], [0, 0, 868], [26, 0, 868], [26, 1, 868], [0, 0, 868], [0, 0, 872], [0, 0, 876], [30, 0, 876], [30, 1, 876], [0, 0, 876], [0, 0, 880], [0, 0, 884], [34, 0, 884], [34, 1, 884], [0, 0, 884], [0, 0, 888], [0, 0, 892], [30, 0, 892], [30, 1, 892], [0, 0, 892], [0, 0, 896], [0, 0, 900], [0, 0, 904], [0, 0, 908], [20, 0, 908], [20, 1, 908], [0, 0, 908], [0, 0, 912], [0, 0, 916], [16, 0, 916], [16, 1, 916], [24, 0, 916], [24, 1, 916], [0, 0, 916], [0, 0, 920], [0, 0, 924], [0, 0, 928], [0, 0, 932], [0, 0, 936], [0, 0, 940], [16, 0, 940], [16, 1, 940], [40, 0, 940], [40, 1, 940], [0, 0, 940], [0, 0, 944], [0, 0, 948], [0, 0, 952], [0, 0, 956], [40, 0, 956], [40, 1, 956], [0, 0, 956], [0, 0, 960], [0, 0, 964], [50, 0, 964], [50, 1, 964], [42, 0, 964], [42, 1, 964], [0, 0, 964], [0, 0, 968], [0, 0, 972], [0, 0, 976], [0, 0, 980], [40, 0, 980], [40, 1, 980], [46, 0, 980], [46, 1, 980], [0, 0, 980], [0, 0, 984], [0, 0, 988], [36, 0, 988], [36, 1, 988], [42, 0, 988], [42, 1, 988], [0, 0, 988], [0, 0, 992], [0, 0, 996], [0, 0, 1000], [0, 0, 1004], [32, 0, 1004], [32, 1, 1004], [40, 0, 1004], [40, 1, 1004], [0, 0, 1004], [0, 0, 1008], [0, 0, 1012], [36, 0, 1012], [36, 1, 1012], [30, 0, 1012], [30, 1, 1012], [0, 0, 1012], [0, 0, 1016], [0, 0, 1020], [0, 0, 1024], [0, 0, 1028], [26, 0, 1028], [26, 1, 1028], [32, 0, 1028], [32, 1, 1028], [0, 0, 1028], [0, 0, 1032], [0, 0, 1036], [22, 0, 1036], [22, 1, 1036], [30, 0, 1036], [30, 1, 1036], [0, 0, 1036], [0, 0, 1040], [0, 0, 1044], [0, 0, 1048], [0, 0, 1052], [18, 0, 1052], [18, 1, 1052], [26, 0, 1052], [26, 1, 1052], [0, 0, 1052], [0, 0, 1056], [0, 0, 1060], [16, 0, 1060], [16, 1, 1060], [40, 0, 1060], [40, 1, 1060], [0, 0, 1060], [0, 0, 1064]]