Posted   —   № 15
153 Views

We took seven Marty robots and gave them a set of tuned bells to play! Each Marty had two bells (apart from one), and we wrote a Python script that controlled all seven robots to let us play songs with the bells.

Watch the video above to see the Marty orchestra playing Jingle Bells. Keep scrolling down to see more songs!

How we made it

This isn’t something terribly complicated, but it is a nice example of how to control lots of Martys in a coordinated way. We do this using Python as it’s an easy way to send different commands to each robot. The timing can be a little bit funky, especially since we’re operating the robots over WiFi and there can be network delays.

Ideally this won’t be your first introduction to Python, but I’ve tried to explain everything as we go, so even if you’re not a python expert it’ll hopefully all make sense.

If you happen to have a set of tuned bells at home or in school, and you also have a set of Martys - why not try it yourself! Alternatively you could get Martys to play other percussion instruments, I’m sure Marty playing the drums would be stupendously mellifluous.

The Piano HAT for a Raspberry Pi

Part 1: Equipment

We used:

Part 2: Setup

Experimenting with Marty and the Bells

We experimented with a few different ways of getting Marty to ring the bells. The bells we got can be played by pushing down a button on the end, but that turned out to require so much force that Marty tended to lift himself off the ground rather than pushing the button down!

We could also ring the bells by having Marty pick them up and move them quickly. In practice this worked best if Marty hit the bell off something, as a sudden change in velocity was needed to get these bell to ring - they have sprung beaters in the middle.

Both these methods of bell ringing put quite a lof of stress on Martys arms - and it would be difficult to ring the bells as quickly as we’d like. A better solution would be to attach little beaters on springs to Marty’s arms, so he could hit them off the bells.

Rather than make any special hardware changes, we decided to go with something in between. We turned the bells upside down, and let Marty hit the sprung beaters inside the bells directly.

With the method for bell ringing decided, we secured all 13 bells to a couple of planks of wood using some spare Marty hands - which turned out to grab the bells very well. Just using more duct tape would also have worked!

We connected all the Martys to the same WiFi network, in this case we used a command hub min wifi router, to make the setup portable in case we wanted to show it off anywhere.

Part 3: Coding

Getting started with Python & MartyPy

The easiest way to control multiple Martys is over WiFi using Python, and that’s what we’ll be using here.

If you’ve never programmed a Marty in Python before, have a look at the Getting started with MartyPy instructions for full information. As a quick overview though:

  1. Maake sure you have Python3 and Pip installed
  2. From the command line, type pip3 install martypy
  3. Open your favourite Python editor. I like to use Sublime Text, but you can use something like IDLE, which can be easier and comes pre-installed on a Raspberry Pi.

Starting the Python Script

We need to tell Python that we’ll be working with Marty, and load a couple of other useful libraries.

import martypy
import time
import random

martypy has lots of functions for working with Marty, we’ll use time for its sleep function, and random to let our Martys make some random eye movements while they play.

These three lines will be at the top of our complete Python script

A Quick Demo Script

In Python, you connect to a Marty using code like mymarty = martypy.Marty('socket://192.168.8.150'), replacing the IP address there with the IP of the Marty you want to connect to.

mymarty = martypy.Marty('socket://192.168.8.159', default_lifelike=False)
mymarty.enable_motors(True)
mymarty.hello()
mymarty.arms(50, 50, 500)
time.sleep(4)

def whack_left():
    mymarty.arms(-10,  50, 100)
    mymarty.arms(50, 50, 70)

def whack_right():
    mymarty.arms(50, -10,  100)
    mymarty.arms(50, 50, 70)

whack_left()
time.sleep(0.5)
whack_right()
for i in range(2):
    time.sleep(0.5)
    whack_left()

What does this do?

The first three lines connect to and activate a Marty on the IP address 192.168.8.159. The default_lifelike=False is a way of turning off the “lifelike behaviours” that Marty will do every minute by default when activated from Python. These behaviours include movements like swinging arms, tapping a foot, etc, which might be annoying if Marty is supposed to be staying still and playing bells!

mymarty.enable_motors(True) turns on the motors on this Marty, and the hello() command makes it stand up straight and wiggle its eyebrows.

Next we use the arms function, which takes three arguments. The first sets the target position of Marty’s left arm, from -128 to 127, the second sets the target position of the right arm, and the third sets the time in milliseconds to make this move. So, mymarty.arms(50, 50, 500) will move the arms of mymarty to the positions 50 and 50 over 500ms, or half a second.

Next, we define two functions whack_left and whack_right. These both make use of the arms function. whack_left will only move the left arm, and whack_right will only move the right arm. For both functions, the chosen arm will move down to position -10 over 100ms, and then back up to position 50 over the next 70ms. This makes a quick down-up movement which will be perfect for whacking a bell!

The bottom chunk of code uses the whack_left and whack_right functions to actually play a few bells. We whack the left bell, then wait for half a second using time.sleep(0.5), then whack the right bell. The final three lines use a for loop to carry out the indented lines below two times.

So, this code will ring the left bell, then the right bell, then the left bell two times, pausing for half a second between each. Note that time.sleep() takes its argument in seconds, not milliseconds!

An important Note

Python won’t wait for a Marty movement command to finish happening on Marty before moving on. Instead, each of the commands will be sent to Marty as quickly as possible, and each Marty will queue up its movements and carry them out one after another. That lets us do lots of cool things, like controlling lots of joints on a Marty at the same time, and controlling lots of Martys simultaneously.

The Full Code! Connecting to lots of Martys

In Python, you can connect to as many Martys as you’d like! We’ll make a dictionary of all the Martys we’d like to connect to:

'''
A set of Martys to connect to by name
'''
martys = {
    'Finlay':       '192.168.8.192',
    'Greeneyes':    '192.168.8.174',
    'Ghost':        '192.168.8.210',
    'Christine':    '192.168.8.168',
    'Jerry':        '192.168.8.223',
    'Marty Marty':  '192.168.8.112',
    'Flower Power': '192.168.8.183'
}

These are the names and IPs of the seven Martys we’ll be using in our orchestra. We found the IPs using the Marty app, although you could also use the discovery tool in the martypy library

We also need a way of telling Python which Martys play which notes, so we can do that with another dictionary:

'''
Here we list the players that will be playing the bells.
Each player is a tuple.
 - The 1st element is the name of a Marty, referencing `martys`
 - The 2nd element is which hand to use - 'left' or 'right'
'''
players = {
    'C': ('Marty Marty', 'right'),
    'C#': ('Marty Marty', 'left'),
    'D': ('Greeneyes', 'right'),
    'D#': ('Greeneyes', 'left'),
    'E': ('Ghost', 'right'),
    'F': ('Ghost', 'left'),
    'F#': ('Finlay', 'right'),
    'G': ('Finlay', 'left'),
    'G#': ('Flower Power', 'right'),
    'A': ('Flower Power', 'left'),
    'A#': ('Jerry', 'right'),
    'B': ('Jerry', 'left'),
    'C2': ('Christine', 'right'),
}

So here, we’ve said that the C note is on Marty Marty’s right hand, C# is on Marty Marty’s left hand, and so on.

You should change the two sections above to match your Martys, and then the rest of the code will work for you!

Now, to actually connect to all our Martys:

def setup():
    '''
    Called once when we start
    '''
    # Now, connect to all the Martys:
    for name, ip in martys.items():
        martys[name] = martypy.Marty('socket://{ip}'.format(ip=ip), default_lifelike=False)
        martys[name].lifelike_behaviour(False)
        martys[name].enable_motors(True)
        martys[name].hello()
        martys[name].arms(100, 100, 1000)
        martys[name].arms(50, 50, 500)
    time.sleep(4)

Right, what’s happening here then? Well, firstly, this function will be called when our script starts, and it’ll set up the martys dictionary to contain references to actual Marty objects, not just their IP addresses.

for name, ip in martys.items(): will iterate through the list of Martys we defined earlier, the first part of each entry (the key) will be stored in the name variable, and the second part (the value) will be stored in the ip variable.

Then, for each marty in the list, we change that entry to connect to a Marty object, with the appropriate ip address. The martys[name] = martypy.Marty('socket://{ip}'.format(ip=ip), default_lifelike=False) line does this, inserting the ip address into the command.

The next lines get Marty ready to go. First we enable the motors on the Marty, then call hello() to make sure Marty is standing up straight. The next two arm commands produce a slow up and down movement on both arms, which is a cool way to see all the Martys limbering up ahead of their performance.

Remember that in Python a for loop will iterate the indented code under it, so since the time.sleep(4) line is not indented from the for loop, it is only run once at the end of the setup function.

Making a whack function that can play any note!

We’ve already defined which notes are played by which Martys. Now we need a way of making a single “whack” function which can be given a note and make the right Marty play it. Let’s just do that then.

def whack(key):
    '''
    This function takes a Piano key, like C#
    and tells the Marty to play that note!
    '''
    player = players[key]
    if player is None:
        return
    player_name, arm = player
    player = martys[player_name]
    # `player` will now be a martypy.Marty instance
    player.stop('clear queue')
    player.enable_motors()
    if arm == 'left':
        player.arms(-10,  50, 100)
        player.eyes(90)
        player.arms(50, 50, 70)
        player.eyes(random.randrange(10, 50, 10))
    elif arm == 'right':
        player.arms(50, -10,  100)
        player.eyes(90)
        player.arms(50, 50, 70)
        player.eyes(random.randrange(-20, 20, 10))

In this block of code we’ve defined a function called whack(key), which takes key as an argument, so it can be called with something like whack('B'), and that would make the right Marty play a B

The line player = players[key] is the one that selects the right marty, from the players dictionary we defined earlier. Now the variable player is set to the (‘name’, ‘arm’) of the Marty we want. So for a ‘B’ note for us that would be (‘Jerry’, ‘left’). We next do a quick check to make sure we’ve found a Marty that can play the given note: if player is None: return. If there was no Marty matching the note in the players dictionary, we’ll just exit the function and not play anything.

player_name, arm = player extracts the name and the arm specifically (remember player is set to something like (‘Jerry’, ‘left’)), so in our example here would set player_name to ‘Jerry’, and arm to ‘left’. With the name of the Marty, we then set player = martys[player_name], so that the player variable now actually refers to a Marty object that we can control. This uses the martys dictionary that we’ve already set up in the setup() function above.

A bit of housekeeping next, just in case that Marty is still busy, we’ll clear its movement queue with player.stop('clear queue'). For good measure we’ll also make sure it’s motors are still enabled with player.enable_motors().

We’ve got our Marty selected, and the arm variable will contain either ‘left’ or ‘right’. The if and elif (else-if) sections select which arm to control as necessary. These functions are like the whack_left and whack_right functions from our little example code above, except also have some eye movements. These eye movements serve to really maximise the amount of fun we’re having.

player.eyes(90) moves that Marty’s eyebrows in the way, so Marty looks a bit strained as he exerts himself to ring the bell, and then player.eyes(random.randrange(-20, 20, 10)) picks a random position between -20 and 20 to move the eyes back to after the movment. That’ll be a fairly neutral position.

Representing a Song

It would be nice if we could easily input new songs for our Marty orchestra to play. Let’s give that a go for Noel, Noel:

def noel():
    # P is a pause
    bpm = 170
    noel = [
      'E', 'D', 'C', 'P', 'D', 'E','F', 'G', 'P',
      'C2', 'B', 'A', 'P', 'A', 'G', 'P', 'P', 'P',
      'C2', 'P', 'B', 'P', 'A', 'P', 'G', 'P',
      'A', 'P', 'B', 'P', 'C2', 'P', 'G', 'P', 'F', 'P', 'E', 'P', 'P', 'P',
      'P','P','P','P','P','P'
    ]
    showtune(noel, bpm)

Here, we’ve defined a function called noel(), and in that function we’ve defined a bpm (beats-per-minute) which will specify how fast to play the song. We’ve also put in a set of notes, represented here as a big list called noel. Each note here will be played for the same amount of time, and where we don’t want to play a note we can write ‘P’ for pause.

At the end of that function we pass the tune and the speed to a function called showtune. Let’s define that now!

Actually playing a song!

Ok, quick recap. We’ve made:

  1. A list of Martys, and connected to all of them
  2. A list of which note is played by which Marty
  3. A function that, when given a note, will tell the right Marty to play that note
  4. A function that encodes a song and a speed to play it

So pretty much all we’re missing now is something to actually go through the song and play each note!

To keep things simple, we’re only playing one note at a time. In the noel() function above, we called the showtune function, so let’s define that now:

def showtune(tune, bpm):
    '''
    Plays a list of notes at a given BPM
    '''
    period = 60.0/float(bpm)

    for note in tune:
        whack(note)
        time.sleep(period)

There we are, not too complicated!

We first take the bpm and convert it to the length of the pause we should take between notes, in seconds, using period = 60.0/float(set_bpm) to divide 60 seconds by the number of beats per minute. Note the float function here - since the result is not going to be a whole number, we need to tell python that it should treat the bpm number using floating point (all numbers) representation rather than as an integer (just whole numbers).

The last three lines do all the magic. We iterate through the notes of the tune we’ve been passed. For each note we call the whack function, and then time.sleep for the pause between the notes.

Remember that the whack function will play any note it knows, and ignore any it doesn’t. So when we send it a real note (‘A’, ‘A#’, ‘B’, etc.), it’ll play it, and when we send it something else, like a ‘P’ for pause, it’ll ignore it.

Making it run

We’ve written a bunch of functions. Lets make them run when we run the script.

def main():
    '''
    Main Event Loop
    '''
    noel()


def cleanup():
    '''
    Called once just before quitting
    '''
    return


if __name__ == '__main__':
    try:
        setup()
        while True:
            main()
    except KeyboardInterrupt:
        # Clean up and quit
        cleanup()

Let’s start a the bottom function here, if __name__ == '__main__': means that the code in this bottom block will be run when we start the python script. Then it’ll try to run the setup function (which we defined before), before running the main() function in a continuous while loop. It’ll try to do this, except when it receives a KeyboardInterrupt, in which case it’ll run the cleanup() function and leave. What’s a KeyboardInterrupt? That’d be Ctrl-C on your keyboard, and it’s used all the time to stop programs running when you’re at the command line. Not to be confused with what Ctrl-C does when you’re in a normal window, which is to copy…

So basically, we’ll do setup() once, and then main() load until somebody presses Ctrl-C, or stops the program another way, when it’ll run cleanup() and exit.

The cleanup() function here doesn’t actually do anything, but we could put code there if we wanted to - like making sure all the Martys end up in a nice position, or turning off all the motors.

The main() loop is the fun bit. Here, all it does is call noel(), so this code will play Noel, Noel over and over and over and over again! Yay!

All the code, together

Putting all that together, we get a Python script that can make seven Martys play Noel, Noel:

import martypy
import time
import random

'''
A set of Martys to connect to by name
'''
martys = {
    'Finlay':       '192.168.8.192',
    'Greeneyes':    '192.168.8.174',
    'Ghost':        '192.168.8.210',
    'Christine':    '192.168.8.168',
    'Jerry':        '192.168.8.223',
    'Marty Marty':  '192.168.8.112',
    'Flower Power': '192.168.8.183'
}

'''
Here we list the players that will be playing the bells.
Each player is a tuple.
 - The 1st element is the name of a Marty, referencing `martys`
 - The 2nd element is which hand to use - 'left' or 'right'
'''
players = {
    'C': ('Marty Marty', 'right'),
    'C#': ('Marty Marty', 'left'),
    'D': ('Greeneyes', 'right'),
    'D#': ('Greeneyes', 'left'),
    'E': ('Ghost', 'right'),
    'F': ('Ghost', 'left'),
    'F#': ('Finlay', 'right'),
    'G': ('Finlay', 'left'),
    'G#': ('Flower Power', 'right'),
    'A': ('Flower Power', 'left'),
    'A#': ('Jerry', 'right'),
    'B': ('Jerry', 'left'),
    'C2': ('Christine', 'right'),
}

def setup():
    '''
    Called once when we start
    '''
    # Now, connect to all the Martys:
    for name, ip in martys.items():
        martys[name] = martypy.Marty('socket://{ip}'.format(ip=ip), default_lifelike=False)
        martys[name].lifelike_behaviour(False)
        martys[name].enable_motors(True)
        martys[name].hello()
        martys[name].arms(100, 100, 1000)
        martys[name].arms(50, 50, 500)
    time.sleep(4)

def whack(key):
    '''
    This function takes a Piano key, like C#
    and tells the Marty to play that note!
    '''
    player = players[key]
    if player is None:
        return
    player_name, arm = player
    player = martys[player_name]
    # `player` will now be a martypy.Marty instance
    player.stop('clear queue')
    player.enable_motors()
    if arm == 'left':
        player.arms(-10,  50, 100)
        player.eyes(90)
        player.arms(50, 50, 70)
        player.eyes(random.randrange(10, 50, 10))
    elif arm == 'right':
        player.arms(50, -10,  100)
        player.eyes(90)
        player.arms(50, 50, 70)
        player.eyes(random.randrange(-20, 20, 10))

def noel():
    # P is a pause
    bpm = 170
    noel = [
      'E', 'D', 'C', 'P', 'D', 'E','F', 'G', 'P',
      'C2', 'B', 'A', 'P', 'A', 'G', 'P', 'P', 'P',
      'C2', 'P', 'B', 'P', 'A', 'P', 'G', 'P',
      'A', 'P', 'B', 'P', 'C2', 'P', 'G', 'P', 'F', 'P', 'E', 'P', 'P', 'P',
      'P','P','P','P','P','P'
    ]
    showtune(noel, bpm)

def showtune(tune, bpm):
    '''
    Plays a list of notes at a given BPM
    '''
    period = 60.0/float(bpm)

    for note in tune:
        whack(note)
        time.sleep(period)

def main():
    '''
    Main Event Loop
    '''
    noel()


def cleanup():
    '''
    Called once just before quitting
    '''
    return


if __name__ == '__main__':
    try:
        setup()
        while True:
            main()
    except KeyboardInterrupt:
        # Clean up and quit
        cleanup()

Some other songs!

We went the trouble of making the code be able to take lists of notes, so let’s make some more songs!

Here are a few we made:

def jingle():
    # Jingle Bells!
    # P is a pause
    bpm = 180

    jinglebells = ['C', 'A', 'G', 'F', 'C', 'P', 'P',
                   'C', 'C', 'A', 'G', 'F', 'D', 'P','P', 'P',
                   'D', 'A#', 'A', 'G', 'E', 'P', 'P', 'P',
                   'C2', 'C2', 'A#', 'G', 'A', 'P', 'P',
                   'C', 'A', 'G', 'F', 'C', 'P', 'P',
                   'C', 'C', 'A', 'G', 'F', 'D', 'P','P', 'P',
                   'C', 'D', 'A#', 'A', 'G', 'C2', 'C2', 'C2',
                   'C2', 'P', 'C2', 'A#', 'G', 'F','P', 
                   'C2', 'P', 'P', 'P',
                   'A', 'A', 'A', 'P', 'A', 'A', 'A', 'P', 
                   'A', 'C2', 'F', 'G', 'A', 'P', 'P', 'P',
                   'A#', 'A#', 'A#', 'A#', 'A#', 'A', 'A', 'A',
                   'A', 'G', 'G', 'A', 'G', 'P', 'C2', 'P',
                   'A', 'A', 'A', 'P', 'A', 'A', 'A', 'P', 
                   'A', 'C2', 'F', 'G', 'A', 'P', 'P', 'P',
                   'A#', 'A#', 'A#', 'A#', 'A#', 'A', 'A', 'A',
                   'C2', 'P', 'C2', 'P', 'A#', 'P', 'G', 'P', 
                   'F', 'P', 'P', 'P', 'P', 'E', 'F', 'P',
                   'P', 'P', 'P', 'P', 'P', 'P', 'P', 'P'
                   ]
    showtune(jinglebells, bpm)


def ships():
    # I saw three ships
    # P is a pause
    bpm = 200
    threeships = [
        'C', 'F', 'P', 'F', 'G', 'P', 'A',
        'C2', 'P', 'A', 'G', 'P', 'A#',
        'A', 'P', 'F', 'F', 'P', 'A',
        'G', 'P', 'E', 'C', 'P', 'C',
        'F', 'P', 'F', 'G', 'P', 'A',
        'C2', 'P', 'A', 'G', 'P', 'A#',
        'A', 'P', 'F', 'F', 'G', 'A',
        'G', 'P', 'P', 'F', 'P', 'C',
        'F', 'P', 'F', 'G', 'P', 'A',
        'C2', 'P', 'A', 'G', 'P', 'A#',
        'A', 'P', 'F', 'F', 'P', 'A',
        'G', 'P', 'E', 'C', 'P', 'C',
        'F', 'P', 'F', 'G', 'P', 'A',
        'C2', 'P', 'A', 'G', 'P', 'A#',
        'A', 'P', 'F', 'F', 'G', 'A',
        'G', 'P', 'P', 'F', 'P', 'P','P','P','P','P','P','P']
    showtune(threeships, bpm)


def silentnight():
    # silent night
    bpm = 130
    snite = [
        'F','P','G','P','F','P','D','P','P','F','P','G','P','F','P','D','P','P',
        'C','P','C','A','P','P','A#','P','A#','F','P','P',
        'G','P','G','A#','A','G','F','P','G','F','D','P',
        'G','P','G','A#','A','G','F','P','G','F','D','P',
        'C','P','P','C','D#','C','A','A#','P','D',
        'A#','F','D','F','E','C','A#','P','P','P','P','P','P'
    ]
    showtune(snite, bpm)

def joytoworld():
    # joy to the world
    bpm = 170
    joy = [
        'C2','P','B','P','A','G','P','F','P','D','P','C','P','P',
        'G','P','A','A','B','P','P','B','P','C2','P','P',
        'C2','C2','B','A','G','G','F','E','P','P',
        'C2','C2','B','P','A','G','P','G','F','E','P','P',
        'E','P','E','E','E','E','F','P','G','P','P',
        'F','E','P','D','P','D','D','D','E','P','F','P'
        'E','D','P','C','C2','P','A','G','F','E','F','P','E','D','P','C','P',
        'P','P','P','P','P','P'
    ]
    showtune(joy, bpm)

def noel():
    # P is a pause
    bpm = 170
    noel = [
      'E', 'D', 'C', 'P', 'D', 'E','F', 'G', 'P',
      'C2', 'B', 'A', 'P', 'A', 'G', 'P', 'P', 'P',
      'C2', 'P', 'B', 'P', 'A', 'P', 'G', 'P',
      'A', 'P', 'B', 'P', 'C2', 'P', 'G', 'P', 'F', 'P', 'E', 'P', 'P', 'P',
      'P','P','P','P','P','P'
    ]
    showtune(noel, bpm)

A full-fledged Python program that works with the Pimoroni PianoHAT can be downloaded here!

Suggest More Songs!

What would you like the Marty bell orchestra to play? I’m not sure how long we’ll have it set up for, but if you send us a list of notes like above, we’ll get our Martys to play it and film it for you!

Remember we have bells that cover just one octave+1 note from C-C, so ideally pick a song that fits within one octave.

See also:

Discussion

We're talking about this article on the Forum:


Marty Bell Orchestra

0 Comment Replies   |   Posted   |   Edited 11th December 2018   |   Thread № 129

No responses yet... be the first to write one!