A traditional glockenspiel has metal bars that you hit with mallets. The bars have very distinctive pitches and the instrument can therefore be tuned to play melodies and even chords. Tubular bells on the other hand have a sound resembling church bells, with a lot of inharmonic partials. For a metal tube to have a church bell like sound, it needs to be long, 1 m or more. But the shorter tubes you have, the less audible inharmonic partials you have. The tubes will sound almost like a glockenspiel, but yet with a slightly different timbre. I bought a 3 m aluminium tube, outer diameter 20 mm, inner diameter 18 mm. It costed 8 €. A 200 mm piece turned out to sound just great!
Tube basicsA piece of metal tube always has some inharmonic partials, but in most cases a fundamental pitch can be heard. Each audible partial has its own nodes along the tube. A node is a point that kind of stands still, while the rest of the tube is vibrating. The fundamental pitch has nodes at 22.4 % of the tube length at each end:
When you attach the piece of tube to the frame, use these nodes. The other partials will most probably be filtered when the fundamental tone nodes are used as attach points, which will improve the quality of the sound.
The frequency as a function of lengthOne can use a very complex formula for calculating the length of the tube for a particular pitch, when one knows the characteristics of the metal and the dimensions of the tube. But it is way more simple to just cut one piece and measure its length and its frequency. From that one can calculate a constant for that particular tube type. The formula goes as follows:
l * √f = k
...where l is the length of the tube, f is the frequency and k is the constant for your tube.
Choosing a set of tubesI aim for creating a 2½ octave glockenspiel, which means 32 keys, from C5 to G7. I cut two test tubes of lengths 242 mm and 223 mm. Their fundamental frequencies were 1938 Hz and 2260 Hz. Using the formula above, I got a constant of 10653 and 10601. That's close enough for a "constant".
I will tune the instrument to A4 = 442 Hz. You might ask why not 440. Well, 440 might be the US standard, but 442 tend to be standard in Europe. And since it is a glockenspiel like instrument with rather high tone range, it's better to assure a bright timbre, which is achieved with a slightly sharp tuning.
Oval tubes?I made one key with the pitch Bb5. It appeared to have two frequencies. The target frequency should be 992.26 Hz. I tried to tune it to that, but noticed my tuner could pick up two frequencies. One was some 988 Hz and the other was 993 Hz. And I could hear a 5 Hz tremolo. I already drilled the holes at the 22.4 % nodes, before I had the idea that the two frequencies are probably due to the tube not being perfectly circular. My guess is that it's slightly oval. Probably the outer periphery is oval, as well as the inner periphery, and not necessary in line with each other. Anyway, the result would be the piece having different frequencies in somewhat perpendicular directions. If I drilled the holes for the support in exactly one direction, I might filter out one of the frequencies totally. My next test tube will reveal that.
The lowest key would be F5, which is 701.63 Hz with the length 401.3 mm. So I cut a tube of say 405 mm and measure its frequency. If my calculations went wrong and the 405 mm long tube gives a higher frequency than 701.63 Hz, I just have to make it F#5 and cut a new tube for F5. This time a bit longer than the previous one.
I might also make mistakes while grinding material off the tube. If I grind too much and the pitch raises over the frequency I aim at, I have to pick the next pitch for the tube and cut a new piece and hope to do more precise work this time. This way I can minimize the waste due to mistakes.
Can I control the solenoids?The pin of the solenoid strikes the key. I want it to impact only with inertia, not a continuing force after the hit. This way I want to assure that the pin won't damp the key. It will only bounce off. And the only parameter will be the speed of the pin while hitting. Nothing else.
When the solenoid is activated, the pin starts to accelerate. After about 10 ms, the pin hits its end position, being forced to stay there as long as the current is on. But if the current is cut before 10 ms, the pin starts to decelerate, still moving forward, then it returns back, either because of gravity or its retraction spring. From this we can conclude that by varying the time the current is on, we can control the speed of the pin at the moment of impact to the key.
I set up a test bench for this. I have a solenoid I salvaged from an electrical typewriter. It's mounted to a frame, which holds a 40 cm tube in place. Using an Arduino Sensor Kit, I can adjust the duty time of the solenoid from 1000 µs to 16000 µs. The display will show me the length of the duty time. I use six AA batteries producing a juicy 9 V for the solenoid.
And the result is very promising, as can be seen in the video. I can indeed control the sound volume of the produced tone by varying the duty time. In this particular setup (the chosen solenoid, the 9 V power source, the gap between the key and the pin while in rest...) I get the following results:
- At 5000 µs I can hear the pin move, but it falls back before reaching the key. Sometimes it reaches the key, producing a faint tone.
- At 5500 µs the pin reaches the key every time. Still low volume.
- At 10000 µs the pin already continues pushing the tube after hitting it, meaning that's way too long time.
When building a glockenspiel with 32 keys and 32 solenoids, one can't expect to get even volumes from each key with the same parameters. After built, each key must probably be adjusted with its own parameters.
Designing the PCBI'm using the Stm32u575 Nucleo development board as the microcontroller, which means it is its own PCB. Besides that, I have some electronics I put on another PCB, which I will attach to the development board. And some electronics will be connected with just wires to the microcontroller board. A set of a switch, a rotary encoder and an RGB light will have their own PCB.
My PCB will hold four ULN2803A chips, which are darlington arrays controlling the current to the solenoids:
A digital pin of the microcontroller is connected to pin 1 of the ULN2803A. A solenoid is connected to 12 V and further to pin 18 of the ULN2803A. When pin 1 goes high, pin 18 "connects" to pin 9 and a current flows through the solenoid. When pin 1 goes low, the connection from pin 18 to pin 9 is cut and the solenoid deactivates. But since the solenoid is a device with inductance, cutting the current will cause a voltage peak, a current that has to go somewhere. Therefore pin 10 is connected to 12 V, which lets the induced voltage to go somewhere instead of causing damage inside the ULN2803A. My solenoid should operate on 12 V and 120 mA. One channel on the ULN2803A can drain 500 mA. Concidering the pulse length or duty time is 15 ms at highest, I could raise the voltage to 24 V and still be in the limits of what the ULN2803A can handle.
For 32 keys, I need four ULN2803A chips. And I need 32 data pins on the microcontroller. Fortunately, the Stm32u575 has plenty of them. If it hadn't, I could go with a shift register chip like TPIC6B595.
So I will have 32 data wires going from the microcontroller board to my darlington board. And 32 solenoids connected to the darlington board.
This is my PCB design:
At the top are the connections to the LCD display, the rotary encoder, the switch and the RGB light. At the middle are four ULN2803A darlington arrays.
I'm using a CNC router to make the PCB. No fuss with chemicals. Instead I'll have a hard time soldering everything in place. The process goes like this:
- Export the copper traces to a G code file.
- Export the holes to another G code file.
- Mill the trace contours using a CNC router. Mind how deep the cut must be to thoroughly isolate the areas from each other, while still maintaining enough width of the resulting traces.
- Solder richly of tin around the places where you want the holes.
- Drill the holes using the CNC router.
My first idea was to 3D print U shaped holders for the tubes, something like this:
But I happened to have some aluminium junk that fitted that purpose.
What I did 3D-print was a holder for the solenoids:
The tubes will lie in 3 layers and each layer happens to have its own thickness, so I need three sizes of the holders, seen in blue in the picture. The purpose of the holder is to assure each solenoid pin is 3 mm below the tube. According to the datasheet, the stroke length is 4 mm, but I only measured 3.8 mm. The stroke will be even less, because I want a piece of cloth, seen in red in the picture, below the pin, to damp the sound of the returning pin. The pin of the solenoid tends to stick to its resting position, which will make the stroke very fast and probably difficult to control. The cloth will make the start less sticky, and hopefully more controllable what comes to speed.
A 14 mm hole is drilled for the solenoid. For my chosen solenoid, it is a bit tight, and I have to file a little bit to make it fit. Everything will be very prone to variations of tenths of mm, after all, I'm making 32 of these. At the end I just have to rely on the final fine tuning, where each solenoid has to be calibrated to give the same perceived dynamics. One solenoid hitting from 3.0 mm below and another hitting from 3.1 mm will most likely sound different, when activated with the same timing.
The design of the instrumentI bought a beautiful old sideboard in stained birch. My tubes will fit in the sideboard on three shelfs. The electronics will fit inside a drawer, which I turned into a lid.
For the user interface I decided I could use a 20 by 4 alfanumeric LCD display, which is kind of oldschool nowadays; a rotary encoder with a push button; and a three position switch.
I let AI design an image with the prompt "An angel playing on bells" and got this image:
I couldn't quite understand the details in the image, but I thought it would be cool anyhow. Or because. So, after some work with Inkscape, I got a vector image out of it, which I burned with a laser cutter:
The instrument should have the following functions:
- Play tunes directly from the instrument's SRAM.
- Connect to a MIDI keyboard through a standard DIN MIDI contact to create a celesta like user experience.
- Connect to a computer through USB to play music from DAW programs.
- Connect to a web site from which the instrument downloads music to be played, while the user controls it through the same site.
- Wifi-settings
Some settings will be hard coded in the program, like the URL of the web site. But since the instrument should be portable, connecting to a local wifi should be manageable through the user interface.
Musescore and the music dataThe Röhrenspiel plays tones, which will ring until they fade by themselves. There's no way of ending the tones. It's like playing piano with the right pedal pressed all the time. This might get annoying in the long run, but the music has to be written taking this in consideration.
So we need to know what note, when to strike and how hard. For this we need a note number, time stamp and velocity. This is almost what MIDI data is all about. Well, MIDI data deals also with when to kill the note (as well as a huge amount of other parameters). So we'd like to convert this:
...to this:
0, 84, 64,
-72, 0, 64,
120, 88, 64,
240, 91, 64,
360, 96, 64,
480, 88, 64,
480, 100, 64,
600, 91, 64,
720, 96, 64,
840, 100, 64,
960, 84, 64,
1080, 88, 64,
1200, 91, 64,
1320, 96, 64,
1440, 88, 64,
1440, 100, 64,
1560, 91, 64,
For this I wrote a plugin in Musescore. It will convert the sheet music into a text file containing those lines, three parameters for each note event. The second line contains number -72. The negative value tells the software that there's a tempo change to 72 bpm. These data listings I have to hard code into the code for the microcontroller. Later I will save all new tunes in a data base online, which the Röhrenspiel will read.
More about the software, pins and PCBsSince I'm using an STM32 board as the microcontroller, I'm developing the main software with RT Thread Studio IDE. I need 32 pins for the 32 tones. Looking at how the pins are arranged on the board, and trying to create a convenient PCB design for the transistor arrays and other important stuff, I decided to use the following 32 pins:
The image shows four darlington arrays (ULN2803A), each is connected to 8 data pins. A 12V rail is seen in the middle. On that rail, 32 holes for wires to the solenoids. Each solenoid sinks its current in one pin of one darlington array.
Another detail of the PCB shows the connection to the user interface:
To the left are the pins for the rotary encoder and the 3 position switch, to the right are the pins for the LCD display.
The real thing:
The rotary encoder, the RGB light and the 3 position switch got their own PCB:
In the software, the 32 solenoids are controlled by a class:
class solenoid
{
private:
int pin;
// int last_velo;
int lo_velo;
int hi_velo;
unsigned long last_strike;
public:
solenoid(int in_pin);
void fire(int velo);
void fire(void);
static int last_velo;
};
Each solenoid holds its own value for the shortest strike time (in µs or ticks) and longest strike time. The shortest time is for the softest, still audible pianissimo. The longest is for the strongest fortissimo, still enjoyable. I wish to be able to calibrate these values for each solenoid so that each note will have the same perceived dynamic for same noted dynamic.
Each solenoid object is initiated as follows, having its own pin assigned:
tones[0] = new solenoid(GET_PIN( F, 3)); // C
tones[1] = new solenoid(GET_PIN( F, 5));
tones[2] = new solenoid(GET_PIN( B, 2)); // D
tones[3] = new solenoid(GET_PIN( D, 7));
tones[4] = new solenoid(GET_PIN( D, 6)); // E
tones[5] = new solenoid(GET_PIN( D, 5)); // F
tones[6] = new solenoid(GET_PIN( D, 4));
tones[7] = new solenoid(GET_PIN( D, 3)); // G
tones[8] = new solenoid(GET_PIN( E, 4));
tones[9] = new solenoid(GET_PIN( E, 5)); // A
tones[10] = new solenoid(GET_PIN( E, 6));
tones[11] = new solenoid(GET_PIN( E, 3)); // B
tones[12] = new solenoid(GET_PIN( F, 8)); // C
tones[13] = new solenoid(GET_PIN( F, 7));
tones[14] = new solenoid(GET_PIN( F, 9)); // D
tones[15] = new solenoid(GET_PIN( G, 1));
tones[16] = new solenoid(GET_PIN( F, 13)); // E
tones[17] = new solenoid(GET_PIN( E, 9)); // F
tones[18] = new solenoid(GET_PIN( E, 11));
tones[19] = new solenoid(GET_PIN( F, 14)); // G
tones[20] = new solenoid(GET_PIN( E, 13));
tones[21] = new solenoid(GET_PIN( F, 15)); // A
tones[22] = new solenoid(GET_PIN( G, 8));
tones[23] = new solenoid(GET_PIN( B, 10)); // B
tones[24] = new solenoid(GET_PIN( E, 15)); // C
tones[25] = new solenoid(GET_PIN( E, 10));
tones[26] = new solenoid(GET_PIN( E, 12)); // D
tones[27] = new solenoid(GET_PIN( E, 14));
tones[28] = new solenoid(GET_PIN( A, 0)); // E
tones[29] = new solenoid(GET_PIN( A, 8)); // F
tones[30] = new solenoid(GET_PIN( E, 0));
tones[31] = new solenoid(GET_PIN( B, 11)); // G
When I assembled everything, I just soldered the wires from any solenoid to any pin, whatever fitted the PCB. After testing to get all tubes playing, I will rearrange the code above.
Testing the connection, rearranging the orderThe RT Thread library has a function:
rt_thread_mdelay(ms);
...which delays for given amount of milliseconds. But when 5 milliseconds might be to short and 10 milliseconds too long, it might be too difficult to adjust without going to microsecond precision. Unfortunately there's no function for microsecond delays, but there's this:
rt_thread_delay(tick);
...which delays a given amount of ticks. How much one tick is, I haven't checked. It probably depends on the board.
My testing program looks as follows:
while (1)
{
sw = 2 * rt_pin_read(sw1_pin) + rt_pin_read(sw2_pin);
if (mode != sw)
{
mode = sw;
switch (mode)
{
case 0 : // Choose tone
null_tone = last_tone;
null_rotary = my_rotary;
lcd.lcdGoToXY(0, 0);
lcd.lcdWriteText("Choose tone ");
break;
case 1 : // Set velocity
null_velocity = last_velocity;
null_rotary = my_rotary;
lcd.lcdGoToXY(0, 0);
lcd.lcdWriteText("Set velocity ");
break;
case 2 : // Play five tones
null_tone = last_tone;
null_rotary = my_rotary;
lcd.lcdGoToXY(0, 0);
lcd.lcdWriteText("Play five wholetones");
break;
default:
break;
}
}
...
This is the first part of an endless loop, where I can switch between three modes with the switch. 1) Choose the tone to calibrate, 2) Set the amount of ticks the chosen tone solenoid should activate, 3) Play five tones, to compare their dynamics. This part of the code just switches from mode to mode.
The next part:
switch (mode)
{
case 0 : // Choose tone
chosen_tone = null_tone + my_rotary - null_rotary;
if (chosen_tone < 0)
chosen_tone += 32;
if (chosen_tone > 31)
chosen_tone -= 32;
lcd.lcdGoToXY(1, 0);
lcd.lcdWriteText(tone_name[chosen_tone % 12]);
lcd.lcdWriteInt(chosen_tone / 12 + 5);
lcd.lcdWriteText(" ");
lcd.lcdGoToXY(2, 0);
lcd.lcdWriteText("Velocity: ");
lcd.lcdWriteInt(velocity[chosen_tone]);
lcd.lcdWriteText(" ");
last_tone = chosen_tone;
if (rt_pin_read(rotsw_pin)) // Dump everything to serial terminal
{
for (int i = 0; i < 32; i++)
rt_kprintf("%d,\n", velocity[i]);
}
break;
case 1 : // Set velocity
velocity[chosen_tone] = null_velocity + 100 * (my_rotary - null_rotary);
if (velocity[chosen_tone] < 100)
velocity[chosen_tone] = 100;
lcd.lcdGoToXY(1, 0);
lcd.lcdWriteText(tone_name[chosen_tone % 12]);
lcd.lcdWriteInt(chosen_tone / 12 + 5);
lcd.lcdWriteText(" ");
lcd.lcdGoToXY(2, 0);
lcd.lcdWriteText("Velocity: ");
lcd.lcdWriteInt(velocity[chosen_tone]);
lcd.lcdWriteText(" ");
if (rt_pin_read(rotsw_pin))
{
rt_thread_mdelay(2000);
tones[chosen_tone]->fire(velocity[chosen_tone]);
rt_thread_mdelay(2000);
}
last_velocity = velocity[chosen_tone];
break;
case 2 : // Play five tones
int tptr;
chosen_tone = null_tone + my_rotary - null_rotary;
if (chosen_tone < 0)
chosen_tone += 32;
if (chosen_tone > 31)
chosen_tone -= 32;
lcd.lcdGoToXY(1, 0);
lcd.lcdWriteText("Focus on ");
lcd.lcdWriteText(tone_name[chosen_tone % 12]);
lcd.lcdWriteInt(chosen_tone / 12 + 5);
lcd.lcdGoToXY(2, 0);
lcd.lcdWriteText("Play from ");
tptr = chosen_tone - 4;
if (tptr < 0)
tptr = 0;
lcd.lcdWriteText(tone_name[tptr % 12]);
lcd.lcdWriteInt(tptr / 12 + 5);
lcd.lcdWriteText(" to ");
lcd.lcdWriteText(tone_name[chosen_tone % 12]);
lcd.lcdWriteInt(chosen_tone / 12 + 5);
lcd.lcdWriteText(" ");
if (rt_pin_read(rotsw_pin))
{
rt_thread_mdelay(2000);
for (int i = tptr; i <= chosen_tone; i++)
{
tones[i]->fire(velocity[i]);
rt_thread_mdelay(1000);
}
}
last_tone = chosen_tone;
break;
default :
break;
}
}
...continues the endless loop, giving feedback to the LCD of the values. In tone selection mode, pressing the rotary encoder will dump all values to the serial, from which the values can later be hard coded to the final program. The idea is to runb this test twice. Once to get the softest strikes for pianissimo, once to get the strongest strikes for fortissimo. But before that, this same program is used to get the right order of tones, as well as checking which solenoids are dumb at the present due to bad connections. This process I will explain to the next.
My test program in actionMy first task was to get the right order on all 32 tones. I haven't mounted the electronics in their final positions yet. Too many lose ends. And too many lose connections. Here I try out the test program:
It was supposed to play D#7, E7, F7, F#7 and G7. It only played the first, the third and the fifth. And they were the wrong tones. The second and the fourth have bad connections. And I still don't know how many microseconds one "tick" is. But nevertheless, everything looks promising.
The RGB LED is supposed to show behind a diffusor. This can be seen in the next video, where I carefully put the electronics in their place, trying to avoid twisting the lose connections.
The diffusor is from a 240 V LED spot bulb. This type:
The middle of the front face just looked perfect, so I destroyed one bulb and filed it to fit a 20 mm hole. The RGB LED shines from some 30 mm behind. It looks just great.
To doThis is as far as I am with the project right now. The to do list is long!
- Find all bad connections. Only 12 out of 32 solenoids react.
- Get the right order of the array of tones.
- Sort out how to really get down to 10 µs level when adjusting the duty time for a strike.
- Adjust the distance between the solenoid and the tube at some tones.
- Add a piece of felt to damp the retracting bounce. The retracting spring might have to be removed from each solenoid.
When all that is done, the Röhrenspiel will work as a stand alone instrument, playing all tunes that are in the internal memory. To really turn it into an IoT thing, the to do list continues:
- Attach an ESP8266 board to the system.
- Develop software to support choosing WIFI.
- Develop the web interface.
- Write more music for the Röhrenspiel.
- Create an interface for a computer and for a MIDI keyboard.
The web interface has a rudimentary functionality as it is now. It can be tested here: https://johanhalmen.link/rohrenspiel/
Feel free to test it, although no audio is attached.
Comments