Mk II Glockenspiel for Sonic Pi

Version I Glockenspiel with old Glockenspiel and “underneath” hammers


Version II Glockenspiel with overhead hammers

Having completed successful project to produce a Glockenspiel driven by OSC messages from Sonic PiI wondered how I might improve the project. One of the difficulties in reproducing the first version  was the difficulty in finding a suitable Glockenspiel which satisfied the constraints of the hammer mechanisms employed. The choice of “gravity return” hammers, which were easy to implement, meant that the Glockenspiel needed to be raised up so that the hammers would fit underneath, and there was also a requirement that any braces underneath the hammers didn’t conflict with their action. This didn’t matter with the Glockenspiel I was using, but this was an old instrument no longer manufactured, and I couldn’t find very many replacements which satisfied the requirements. Many more modern Glockenspiels either came fixed in a case, or the notes were supported in a way which made it difficult to implement hammers from the underneath. Also it seemed more natural to hit the notes from the top which is the way that the instruments were designed to be used.

I decided to bite the bullet and investigate how I might implement hammers from above. The main problem was to devise a suitable spring arrangement to support the hammers above the notes, and to lift them off the notes again once they had been forced downwards to strike the notes. I searched the internet but found it very difficult to identify suitable springs that might be employed. The nearest thing I found was to buy selections of springs of different sizes, in the hope that some of them might work. This did not seem very practical, especially as I would need 13 very similar springs. I knew that the springs needed to be very weak, but that I would also probably want to be able to adjust their tension for optimum performance. In desperation I decided to try making my own, and to that end I took a large “fat” biro and used the body as a former onto which I would a coil of about 10 turns using wire from a reel of 1/0.6mm insulated hookup wire that I had to hand. Fortuitously you could clip the end of the coil into the split end of a standard lego peg to attach it to the hammer beam. The other end of the coil rested on a supporting platform of two lego beams attached to the body of the supporting Lego frame. These springs supported the hammer beam above the Glockenspiel notes, and when the end of the beam on the other side of its pivot point was struck by the solenoid the hammer struck the note before the spring pulled it back up again.

Two springs plus the bits for the hammer assembly for two hammers

Starting to assemble the frame for the hammer assembly
Frame assembly with one solenoid assembly

Solenoid Support (attached to solenoid by rubber band)

Completed hammer assembly rear view

Completed hammer assembly front view showing springs

Looking further at the Glockenspiels available on line I identified the model pp1130 made by Plus Percussion, and available from Amazon and on Ebay. This mid price range instrument had a 13 note diatonic range sounding :c6..:a7 (in the key of C major) which was therefore manageable, and moreover it came with three substitute notes, which allowed you to keep the same range, but alter the tuning to either F major (with a Bb/A#) substitute or to G major changing the two F notes to F# substitutes. The construction looked robust, and I enquired about the spacing of the notes  from the supplier and was told it was approximately 32.5mm. In the event when I got hold of one it turned out to be nearer 30mm, but my design was enable to accommodate this. The other major change in construction, apart from opting for overhead keys, was that the pp1130 Glockenspiel had a much deeper base than the Glockenspiel used in the mark I version. This meant that the hammers had to be raised much higher than before. Fortunately I had already made up from a previous lego project (to produce a computer driven connect 4 game!). a beam of lego 60 studs  long which had previously supported a long gear rack in the central yellow section. This was convenient for me to use to raise the hammer assemblies by a two standard Lego brick height. I added some stabiliser beams at each end as shown below. 

 

In the centre of the 60 stud beam two 4×1 bricks were added to which the circuit board assembly could be tied

Another view of the base beam on which the hammer assemblies were mounted

This picture shows the attachment of the circuit board lego base to the 60 stud support beam.

Showing the mounting of the hammer assemblies onto the 60 stud beam. Most were separated by a black stud, but you can see two placed adjacent on the left. This was to correct for the Glockenspiel note centres being 30mm apart whereas the hammers were 32mm apart.

End view of the completed assembly

Such a support is not essential, but it is convenient also to fix the horizontal spacing of the pairs of hammers with a one stud spacing between. You could alternatively use a piece of wooden batten, and drill holes to take push fit lego pegs at appropriate spacings to fit the hammer assemblies.

Rather than give a detailed construction guide, I have opted for a series of photos which show (hopefully clearly) the construction that I have followed to build the hammer assemblies and supporting structures. The final construction picture below shows the slightly different end hammer assembly where only one hammer is required rather than 2.

I used some lego beams to hold the computer and breadboard in place.

The wiring is slightly simpler than for the first version, because I decided that with the extra two notes (13 instead of 11) it was worth while using two of the ULN2803A integrated circuits (available from The PiHut), each of which can drive 8 outputs. The wiring is simple. The input is connected to a GPIO pin, the output to one side of the solenoid. The other side of the solenoid goes to an external 5v supply, which also feeds the ULN2803A chip. Protection diodes are incorporated in the chip, so do not need to be used.
I also added three LEDS to the RasPiO ProHat board, which were used to indicate when a an solenoid received an input and also the currently selected key, set up for the Glockenspiel. Using the RasPiO ProHat meant that these could be connected directly between the pin socket on the ProHat board and ground. I used outputs  22,23 and 24 for the Red, Yellow and Blue leds respectively.

On the software side, I modified the python script used to receive OSC messages from Sonic Pi, and to control the  hammer key solenoids, so that it could drive the extra three leds and the two extra solenoids. Also, because the hammer keys were placed on the far side of the Glockenspiel I reordered the output GPIO pins to suit. The new code is shown below.

Before using the script you need to install the pythonosc library. In a terminal window type:

sudo apt-get update
sudo pip3 install python-osc
#!/usr/bin/env python3
#OSCglockenspiel.py written by Robin Newman, May 2018,  updated July 2018
#program sets up an OSC server and receives OSC messages sent to "/note"
#Sonic Pi can be running either on the Raspberry Pi (default),
#or on an external networked computer

#All of the libraries required should be standard on the latest Raspbian apart from pythonosc
#To install this use sudo pip3 install python-osc


from gpiozero import LEDBoard,LED
from pythonosc import udp_client
from pythonosc import dispatcher
from pythonosc import osc_server
from time import sleep
import argparse
import sys

leds=LEDBoard(20,19,18,17,16,15,14,13,12,11,10,9,8,7) #pins connected to the two ULN2803A drivers
#via GPIO pins (7),8->20 #7 is dummy note for rests, out of range etc
red=LED(22)
blue=LED(24)
yellow=LED(23)
  
 #This is activated when /note OSC message is received by the server.
 #the arguments n represents the note POSITION in the LEDBoard array 20 down to 8 
def pulse(unused_addr,args,m): #pulse pin corresponding to received number for 0.05 seconds
    print("note",m)
    leds[m].on()
    red.on() #flash red led to indicate activity
    sleep(0.05) #pulse duration
    red.off()
    leds[m].off()

def keyLed(unused_addr,args, n):
    if n == 0: #C major chosen
        print("Leds off: C major");
        blue.off();
        yellow.off();
 
    if n == 1: #F major chosen
        print("Yellow Led; F major");
        blue.off();
        yellow.on();  

    if n == 2: #G major chosen
        print("Blue Led: G major");
        blue.on();
        yellow.off();   

#The main routine called when the program starts up follows
if __name__ == "__main__":
    try: #use try...except to handle possible errors
        #first set up and deal with input args when program starts
        parser = argparse.ArgumentParser()
        #This arg gets the server IP address to use. 127.0.0.1 or
        #The local IP address of the PI, required when using external Sonic Pi
        parser.add_argument("--ip",
        default="127.0.0.1", help="The ip to listen on")
        #This is the port on which the server listens. Usually 8000 is OK
        #but you can specify a different one
        parser.add_argument("--port",
              type=int, default=8000, help="The port to listen on")
        #This is the IP address of the machine running Sonic Pi if remote
        #or you can omit if using Sonic Pi on the local Pi.
        args=parser.parse_args()
        dispatcher = dispatcher.Dispatcher()
        #following dispatcher handles /note osc messages being received
        dispatcher.map("/note",pulse,"n")
        dispatcher.map("/keyLed",keyLed,"n");
        #Now set up and run the OSC server
        server = osc_server.ThreadingOSCUDPServer(
              (args.ip, args.port), dispatcher)
        print("Serving on {}".format(server.server_address))
        #run the server "forever" (till stopped by pressing ctrl-C)
        server.serve_forever()
    #deal with some error events
    except KeyboardInterrupt:
        print("\nServer stopped") #stop program with ctrl+C
    #Used the AttributeError to specify problems with the local ip address
    except AttributeError as err:
        print(err.args[0])
    #handle errors generated by the server
    except OSError as err:
       print("OSC server error",err.args)
    #anything else falls through

The python script should be set as executable by opening a terminal window, and navigating to the folder in which it is stored. (in my case I had it directly in the user pi home folder, and typing chmod +x OSCglockenspiel2.py  The script can then by startd from the same terminal window by typing ./OSCglockenspiel2.py or, if you wish to use it with an external Sonic Pi running on a different computer ./OSCglockenspiel2.py --ip 192.168.1.234 where the ip address should be that of the computer running the python script. The reason is that the OSC server will NOT listen to any external addresses if localhost is used, but will do so if the actual IP address of the computer is used.

On the Sonic Pi side, I altered the notenum function so that it could accommodate three different tunings, according to the actual note sounders fitted to the Glockenspiel. I tried to make this as easy as possible to use, utilising one setup line set :key, key[:C] which could be altered to set :key, key[:F] or set :key, key[:G] as required.

A typical piece in which Sonic Pi plays the Glockenspiel and also accompanies it is shown below:

#SwallowTailJig for Sonic Pi glockenspiel
#arranged by Robin Newman July 2018
use_osc "192.168.1.234",8000 #adjust IP as necessary. Could be localhost 
key = {:C => 0,:F => 1, :G => 2}
set :key,key[:C]
osc "/keyLed",get(:key)
define :notenum do |n,offset|
  f=0;g=0
  f=-1 if get(:key)==1
  g=1 if get(:key)==2
  notes=[0,2,4,5+g,7,9,11+f,12,14,16,17+g,19,21]
  #puts notes,note(n),note(n)-offset
  if (notes.index (note(n)-offset))==nil
    return 13
  else
    return (notes.index (note(n)-offset))
  end
end

2.times do
  use_synth :pluck
  use_bpm 120
  a1=[:D4,:E4,:F4,:D4,:D4,:A4,:D4,:D4,:F4,:D4,:D4,:A4,
      :G4,:F4,:E4,:C4,:C4,:G4,:C4,:C4,:C5,:B4,:C5,:G4,
      :F4,:E4,:F4,:D4,:D4,:A4,:D4,:D4,:F4,:D4,:D4,:A4,
      :B4,:C5,:B4,:C5,:G4,:F4,:E4,:F4,:D4,:D4,:D4,:D4,
      :E4,:F4,:D4,:D4,:A4,:D4,:D4,:F4,:D4,:D4,:A4,:G4,
      :F4,:E4,:C4,:C4,:G4,:C4,:C4,:C5,:B4,:C5,:G4,:F4,
      :E4,:F4,:D4,:D4,:A4,:D4,:D4,:F4,:D4,:D4,:A4,:B4,
      :C5,:B4,:C5,:G4,:F4,:E4,:F4,:D4,:D4,:D4,:A4,:A4,
      :B4,:C5,:D5,:E5,:D5,:E5,:D5,:C5,:A4,:A4,:B4,:C5,
      :D5,:E5,:D5,:C5,:B4,:C5,:A4,:A4,:B4,:C5,:D5,:E5,
      :D5,:E5,:D5,:C5,:A4,:C5,:B4,:C5,:G4,:F4,:E4,:F4,
      :D4,:D4,:D4,:A4,:A4,:B4,:C5,:D5,:E5,:D5,:E5,:D5,
      :C5,:A4,:A4,:B4,:C5,:D5,:E5,:D5,:C5,:B4,:C5,:A4,
      :A4,:B4,:C5,:D5,:E5,:D5,:E5,:D5,:C5,:A4,:C5,:B4,
      :C5,:G4,:F4,:E4,:F4,:D4,:D4,:D4]
  b1=[0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,
      0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,
      0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,1.0,
      0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,
      0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,
      0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,
      0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,1.0,0.5,
      0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,1.0,0.5,0.5,
      0.5,0.5,1.0,0.5,1.0,0.5,0.5,0.5,0.5,0.5,0.5,0.5,
      1.0,0.5,0.5,0.5,0.5,1.0,0.5,0.5,0.5,0.5,1.0,0.5,
      1.0,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,
      0.5,0.5,1.0,0.5,0.5,0.5,0.5,1.0,0.5,1.0,0.5,0.5,
      0.5,0.5,0.5,0.5,0.5,1.0,0.5,0.5,0.5,0.5,1.0,0.5,
      0.5,0.5,0.5,1.0,0.5,1.0,0.5,0.5,0.5,0.5,0.5,0.5,
      0.5,0.5,0.5,0.5,0.5,0.5,0.5,1.5]
  in_thread do
    sleep rt(0.3) #remove this sleep if playing on an external Mac
    for i in 0..a1.length-1
      osc "/note",notenum(note(a1[i]),60)
      sleep b1[i]
    end
  end
  
  use_transpose 0
  with_fx :reverb,room: 0.7,mix: 0.6 do
    a2=[:D4,:E4,:F4,:A3,:A3,:A3,:F4,:A3,:A3,:A3,:F4,:A3,
        :A3,:A3,:F4,:E4,:E4,:F4,:E4,:C4,:C4,:C4,:E4,:C4,
        :C4,:C4,:E4,:F4,:F4,:G4,:E4,:F4,:F4,:G4,:F4,:A3,
        :A3,:A3,:F4,:A3,:A3,:A3,:F4,:A3,:A3,:A3,:F4,:G4,
        :E4,:F4,:G4,:E4,:F4,:G4,:F4,:D4,:D4,:D4,:D4,:D4,
        :E4,:F4,:A3,:A3,:A3,:F4,:A3,:A3,:A3,:F4,:A3,:A3,
        :A3,:F4,:E4,:E4,:F4,:E4,:C4,:C4,:C4,:E4,:C4,:C4,
        :C4,:E4,:F4,:F4,:G4,:E4,:F4,:F4,:G4,:F4,:A3,:A3,
        :A3,:F4,:A3,:A3,:A3,:F4,:A3,:A3,:A3,:F4,:G4,:E4,
        :F4,:G4,:E4,:F4,:G4,:F4,:D4,:D4,:D4,:A4,:A4,:G4,
        :A4,:F4,:G4,:A4,:B4,:C5,:A4,:G4,:A4,:A4,:G4,:A4,
        :F4,:G4,:A4,:G4,:E4,:G4,:E4,:A4,:G4,:A4,:F4,:G4,
        :A4,:B4,:C5,:A4,:G4,:A4,:E4,:F4,:G4,:E4,:F4,:G4,
        :F4,:D4,:D4,:A3,:F4,:A4,:G4,:A4,:F4,:G4,:A4,:B4,
        :C5,:A4,:G4,:A4,:A4,:G4,:A4,:F4,:G4,:A4,:G4,:E4,
        :G4,:E4,:A4,:G4,:A4,:F4,:G4,:A4,:B4,:C5,:A4,:G4,
        :A4,:E4,:F4,:G4,:E4,:F4,:G4,:F4,:D4,:D4,:A3]
    b2=[0.5,0.5,0.5,0.25,0.25,0.5,0.5,0.25,0.25,0.5,0.5,
        0.25,0.25,0.5,0.5,0.25,0.25,0.5,0.5,0.25,0.25,0.5,
        0.5,0.25,0.25,0.5,0.5,0.25,0.25,0.5,0.5,0.25,0.25,
        0.5,0.5,0.25,0.25,0.5,0.5,0.25,0.25,0.5,0.5,0.25,
        0.25,0.5,1.0,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.25,
        0.25,0.5,0.5,0.5,0.5,0.5,0.25,0.25,0.5,0.5,0.25,
        0.25,0.5,0.5,0.25,0.25,0.5,0.5,0.25,0.25,0.5,0.5,
        0.25,0.25,0.5,0.5,0.25,0.25,0.5,0.5,0.25,0.25,0.5,
        0.5,0.25,0.25,0.5,0.5,0.25,0.25,0.5,0.5,0.25,0.25,
        0.5,0.5,0.25,0.25,0.5,1.0,0.5,0.5,0.5,0.5,0.5,0.5,
        0.5,0.5,0.5,0.5,1.0,0.5,0.5,0.5,0.5,1.0,0.5,0.5,
        0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,1.0,0.5,0.5,0.5,
        0.5,1.0,0.5,0.5,0.5,0.5,1.0,0.5,0.5,0.5,0.5,0.5,
        0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,1.0,
        0.5,0.5,0.5,0.5,1.0,0.5,0.5,0.5,0.5,0.5,0.5,0.5,
        0.5,0.5,0.5,1.0,0.5,0.5,0.5,0.5,1.0,0.5,0.5,0.5,
        0.5,1.0,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,
        0.5,0.5,0.5,0.5,0.5,0.5,1.5]
    in_thread do
      for i in 0..a2.length-1
        play a2[i],sustain: b2[i]*0.9,release: b2[i]*0.1
        sleep b2[i]
      end
    end
    
    use_transpose 12
    a3=[:D4,:E4,[:A3,:D4],[:A3,:D4],[:D4,:A4],[:D4,:A4],
        [:A3,:D4],[:A3,:D4],[:D4,:A4],[:D4,:A4],[:C4,:G4],
        [:C4,:G4],[:E4,:C5],[:E4,:G4],[:C4,:G4],[:C4,:G4],
        [:E4,:C5],[:E4,:G4],[:A3,:D4],[:A3,:D4],[:D4,:A4],
        [:D4,:A4],[:A3,:D4],[:A3,:D4],[:D4,:A4],[:D4,:A4],
        [:C4,:G4],[:C4,:G4],[:E4,:C5],[:E4,:G4],:A3,:A3,:A3,
        :F3,:G3,[:A3,:D4],[:A3,:D4],[:D4,:A4],[:D4,:A4],
        [:A3,:D4],[:A3,:D4],[:D4,:A4],[:D4,:A4],[:C4,:G4],
        [:C4,:G4],[:E4,:C5],[:E4,:G4],[:C4,:G4],[:C4,:G4],
        [:E4,:C5],[:E4,:G4],[:A3,:D4],[:A3,:D4],[:D4,:A4],
        [:D4,:A4],[:A3,:D4],[:A3,:D4],[:D4,:A4],[:D4,:A4],
        [:C4,:G4],[:C4,:G4],[:E4,:C5],[:E4,:G4],:A3,:A3,:A3,
        :F3,:C4,:A3,:G3,:A3,:F3,:G3,[:A3,:D4],[:F3,:D4],:A3,
        :G3,:A3,:F3,:G3,[:A3,:D4],[:G3,:C4],:A3,:G3,:A3,:F3,
        :G3,[:A3,:D4],[:F3,:D4],[:A3,:D4],[:G3,:C4],
        [:F3,:F4],[:F3,:D4],[:F3,:D4],:A3,:F3,:A3,:G3,:A3,
        :F3,:G3,[:A3,:D4],[:F3,:D4],:A3,:G3,:A3,:F3,:G3,
        [:A3,:D4],[:G3,:C4],:A3,:G3,:A3,:F3,:G3,[:A3,:D4],
        [:F3,:D4],[:A3,:D4],[:G3,:C4],:A3,:F3,:F3,:F3]
    b3=[0.5,0.5,1.0,0.5,1.0,0.5,1.0,0.5,1.0,0.5,1.0,0.5,
        1.0,0.5,1.0,0.5,1.0,0.5,1.0,0.5,1.0,0.5,1.0,0.5,
        1.0,0.5,1.0,0.5,1.0,0.5,0.5,0.5,0.5,1.0,0.5,1.0,
        0.5,1.0,0.5,1.0,0.5,1.0,0.5,1.0,0.5,1.0,0.5,1.0,
        0.5,1.0,0.5,1.0,0.5,1.0,0.5,1.0,0.5,1.0,0.5,1.0,
        0.5,1.0,0.5,0.5,0.5,0.5,1.0,0.5,0.5,0.5,0.5,1.0,
        0.5,1.5,1.5,0.5,0.5,0.5,1.0,0.5,1.5,1.5,0.5,0.5,
        0.5,1.0,0.5,1.5,1.5,1.5,1.5,0.5,0.5,0.5,1.0,0.5,
        0.5,0.5,0.5,1.0,0.5,1.5,1.5,0.5,0.5,0.5,1.0,0.5,
        1.5,1.5,0.5,0.5,0.5,1.0,0.5,1.5,1.5,1.5,1.5,0.5,0.5,0.5,1.5]
    for i in 0..a3.length-1
      play a3[i],sustain: b3[i]*0.9,release: b3[i]*0.1
      sleep b3[i]
    end
  end
end #reverb

This starts with some code which is used to set up communication with the Glockenspiel by communicating with the python script.

use_osc "192.168.1.234",8000 #adjust IP as necessary. Could be localhost 
key = {:C => 0,:F => 1, :G => 2}
set :key,key[:C]
osc "/keyLed",get(:key)
define :notenum do |n,offset|
  f=0;g=0
  f=-1 if get(:key)==1
  g=1 if get(:key)==2
  notes=[0,2,4,5+g,7,9,11+f,12,14,16,17+g,19,21]
  #puts notes,note(n),note(n)-offset
  if (notes.index (note(n)-offset))==nil
    return 13
  else
    return (notes.index (note(n)-offset))
  end
end

First this sets up the address to which osc messages will be sent. This can usually be set to “localhost” if you are using Sonic Pi on the same computer as the one on which the python script is running AND have started the pythonscript without specifying the actual IP address of the. Otherwise you should put the actual IP address of the computer on which the pythonscript is running. The Glockenspiel I used comes with three alternative “black” notes which can be substituted so that it will play in F major or G major instead its usual C major. This alters the valid notes which can be sent to the glockenspiel, and so I added a mechanism to take care of this. the line key = {:C => 0, :F => 1, :G => 2} sets up a hash which is really a look-up table. So the following line set :key,key[:C] will look up the value 0 corresponding to the key of :C and will store that in the time state under the pointer :key. The notenum function works out which note number to send as data to the python script each time a note is to be played. The notes are accessed by an index in the range 0..12 This corresponds to the 13 notes c,d,e,f,g,a,b,( c,d,e,f,g,a) * octave higher. when the Glockenspiel is in its default state, but needs to change to c,d,e,fsharp,g,a,b,(c,d,e,fsharp,g,a)* up an octave when it is tuned to play in G major and to c d e f g a bflat,(c,d,e,f,g,a)* up an octave when it is tuned to play in F major. The offset additions to each of these note midi values from the starting c are contained in the notes list
notes=[0,2,4,5+g,7,9,11+f,12,14,16,17+g,19,21]
This includes the two variables g and f which are adjusted depending on the key which is selected. The are both 0 for C major, and g is set to 1 for G major to change the f to f shaor, and f is set to -1 for F major to change the b to b flat. The parameter offset is set to a number reflecting the range of notes used to call the notenum function. This if the notes are in the range :c5 to :a6 the offset will be 48, where as if they are in the range :c4 to :a5 the offset will be 60. The actual tuning of the glockenspiel always produces notes in the range :c6. to :a7. The position of the matching note in the notes list is obtained by the .index method in the expression  notes.index (note(n)-offset) which will produce a value between 0 and 12 for valid notes. ff a match cannot be obtained, either because the note is a rest :r or it is out of the range expected by the current offset value, then the function will return the number 13. This code is set to produce no response from the Glockenspiel when it is sent as data.

The remainder of the program has three tune parts held in separate arrays a1,a2 and a3 with their associated durations in b1,b2 and b3. Parts 2 and 3 are played with normal play commands by Sonic Pi. Part 1 sends a series of OSC messages to the python script containing the note number to be played in the range 0..12. 13 is sent when a rest or a note out of range is selected, and this is ignored by the python script. (In fact it sends an output to GPIO pin 7 which is not connected). The note number to send is calculated in the function notenum as described above. When using a Raspberry Pi to run Sonic Pi there is a large latency for the built in Audio. Normally this doesn’t matter, but in this case it causes the glockenspiel to sound BEFORE you hear the audio of the other parts which are delayed by 0.3 seconds. Fortunately this is a constant value, and so by delaying the output to trigger the glockenspiel by 0.3 seconds the two systems can be synchronised together. I used the statement sleep rt(0.3) to accomplish this. The rt function means that real time is used and the 0.3 is NOT affected by any changes in the tempo set by use_bpm which would otherwise alter it.

There is a link below which will let you download a zip file containing a folder of 11 sample pieces which can be played by the glockenspiel, together with one further one which lets you connect a midi keyboard to the computer running sonic pi, and use that to play the glockenspiel directly. Also included in the folder is the OSCglockenspiel2.py script which receives the OSC messages and controls the GPIO pins on the Raspberry Pi.

# simple midi player for Sonic Pi glockenspiel by Robin Newman July 2018

#SET KEY AND IP ADDRESS
use_osc "192.168.1.234",8000
key = {:C => 0,:F => 1, :G => 2}
set :key,key[:C]
osc "/keyLed",get(:key)
define :notenum do |n,offset,k=0|
  f=0;g=0
  f=-1 if k==1
  g=1 if k==2
  notes=[0,2,4,5+g,7,9,11+f,12,14,16,17+g,19,21]
  puts notes,note(n),note(n)-offset
  if (notes.index (note(n)-offset))==nil
    return 13
  else
    return (notes.index (note(n)-offset))
  end
end

live_loop :midiIn do #live loop receives midi input
  use_real_time
  b= sync "/midi/*/*/*/note_on"
  offset = 48 #offset to bring keyboard in range
  puts "current midi note is #{b[0]}"
  puts "Note too low" if b[0]-offset<0 puts "Note too high" if b[0]-offset >21
  osc "/note",notenum(b[0],offset,get(:key)) if b[1]>0 #ignore note_off (vel=0)

The code for the midi player (midiglock.rb) is shown above. It uses the same code to setup the OSC connection as described above. The live_loop :midiIn waits for a midi note to be received, and then sends out an osc command containing the appropriate data number for the glockenspiel note to be triggered. The previous line give an indication as to whether the note is in range or not. You can alter the offset value, or adjust the octave output by the keyboard which ever is easier to get the correct range. Once set up, you can play the glockenspiel using the midi keyboard.

PARTS SOURCES
I based the construction of the key mechanisms on existing Lego that I already owned, which included parts from Lego Mindstorms and Lego EV3 sets. The site www.brickshelf.com is an excellent source for lego bricks.
The solenoids were made by AdaFruit and I obtained mine from thepihut.com from where I also obtained the ULN2803A driver chips I used a RaspIo ProHat for a convenient way to gain access to the GPIO Pins, as it accesses pin connections in a linear order and also provides pin protection and the ability to connect LED outputs without the need for series resistors. It is possible to do without this if you want to wire directly to the pins, but there are a lot of wires to connect. Breadboards are available from many suppliers including `The Pi Hut The connectors I used came from various sources. The orange wires connecting the ProHat to the breadboard were from an Arduino kit I had. They are similar to these from Amazon, and the male to male leads connecting the solenoids to the breadboard for which you will need 13 pairs are best obtained from a strip of connectors similar to these again from Amazon, although there are lots of suppliers and a wide range of prices for both these items.
The 5v external power supply I used for the solenoids was similar to this one from Pimoroni  and you also need a barrel connector which I got from The PiHut, connecting it with two jump leads to the breadboard.

You can download a zip file containing all the software at http://r.newman.ch/rpi/GlockMkII.zip

There is a video describing the project and showing it operating

I hope you have enjoyed reading this article. I would be happy to hear from anyone else who attempts to replicate this project.

 

Advertisements