2026-07-17
TL;DR: I added a way to configure some of the solenoid motion parameters and wrote a script to insert gaps between notes that are too close together. These two new tools have made a noticeable improvement in the quality of the machine’s music. I also measured the supply voltage over time while a key was playing and found that the droop is possibly smaller than I thought, which may mean that I can avoid using dangerously large capacitors to smooth it out.
After collecting lots of useful motion data last time, my intention was to collect even more so I could define a reasonably complete key motion model. I may still do that in the future, but I realized I could probably get away with something a lot simpler using the data I already had. Really there are three key positions I care about:
Position (2) seems to be about 1 or 1.5 mm below (1), and the distance between (1) and (3) is just the total travel of the key. There are really only two things that my machine can do to move between these states: it can press down with a range of forces, or it can release completely and let the key move upward under its own action. There are fancier options here, like timing things right so the key bounces off the bottom of the travel, but this model is a decent approximation under ordinary conditions.
With this in mind, the only real changes I needed to make to the motion model were to make the timeout for the pressing state dependent on MIDI velocity (because louder notes take less time to travel from top to bottom) and tweak the fixed timeouts for the releasing and repeating states. To do that, I added some logic to the pressing state to do the required arithmetic, and then I moved all the values into a TOML file that the system reads on startup. Then, the system polls the file for changes and reloads it if a new valid config appears. This turned out to be very useful for tuning the parameters quickly. For most of the initial values I used estimates based on the data from last time, but iteration with MidiKeys was the surest way to get the right numbers for my setup.
I do want to improve the motion model even further so I can do the bouncing trick I mentioned, but I think it will be more fun to instead focus on expanding the keyboard a bit further so I can play even more interesting music. These software changes can come later because they’re so cheap to change. Not even the firmware has to change, just the host-side code.
The MIDI standard defines, among other things, so-called “note on” and “note off” messages. These carry some metadata that tells a synthesizer when to begin or end certain pitches. This project is not a digital synthesizer and has mechanical limitations, so certain sequences of MIDI messages are not faithfully representable by the machine. A simple example is a very fast stream of notes on the same pitch, which may be representable for a digital system but exceeds the mechanics of the piano itself, let alone the machine I’ve built.
Most of the time this isn’t a problem because ordinary piano music is usually written to be played by a human on a real instrument, so the composer abides by similar limitations. One notable exception occurs when two notes on the same pitch are meant to follow each other with no gap. In MIDI files, this often shows up as a note off message followed immediately by a note on message with no gap between them. The key motion model reacts to this by releasing the key and pressing it again as quickly as it can, but this results in the note being noticeably late. When the repeated note is part of a chord, the delay is especially obvious. A human musician can anticipate this situation and release the key slightly early to allow the next strike to happen on time, but many MIDI files don’t have this property.
I wrote a script to adjust MIDI files to address this problem offline. Some other digital player pianos collect messages into a buffer and introduce a small fixed latency for all messages in exchange for the ability to schedule solenoid motion in advance, but I prefer the offline approach for its simplicity in implementation and lack of latency. The script first records all tempo changes in the file so it can properly adjust the gaps according to their configured minimum duration. Then, the script iterates through all messages and converts them into an absolute time representation, whereas natively they are represented with times relative to their preceding message. Since the MIDI messages are interleaved in a stream, the script has to separate the messages into sub-tracks that each only consider one pitch in isolation. Then it’s a matter of finding all of the note off/on pairs that are too close together and adjusting them, followed by reassmbling the MIDI file.
The script seems to work fine, although I’m not completely convinced that it’s correct in all cases. It’s good enough for now.
I’ve assumed for a while now that I would need to add bulk capacitance to the supply rail to prevent drooping due to the solenoid inrush current. I was avoiding it because adding large capacitors to the system increases its danger. To be sure that I really needed to bother with all that, I connected my oscillscope to the supply rail and set the trigger level slightly below 12 V, which was the nominal output. I was expecting to see a period during which the supply was substantially below nominal, but instead I saw a lot of ringing as in the plot above.
My guess for what’s happening here is that when the solenoid switches in, the supply does droop substantially for about a microsecond, but then it overshoots and takes several more microseconds to return to 12 V. Then, when the solenoid switches out, a similar but inverted damped oscillation occurs. This ringing appears every time the solenoid switches its state, which happens twice every 50 microseconds while the solenoid is active. I imagine that the frequency and shape of the ringing are determined by a combination of the switch-mode power supply’s internal switching and the particular reactive properties of the solenoid load.
I don’t see any evidence that the solenoids are starved for power. The ringing is more or less evenly distributed about 12 V, so it seems like the average power delivered should be about the same as an ideal supply. I should really test with the power supply that I intend to use with the final system, which will probably be a cheap fixed-output Meanwell. If that supply has the same relevant properties as my bench supply, then I might get away with some relatively small smoothing capacitors. Further testing is also probably needed in the case of multiple keys being pressed together.
I’m starting to outgrow this two-octave machine, and it’s been long enough now since my last encounter with the coil winder that I’ve forgotten how boring it is. I think that means that next time I’ll wind at least enough coils for two more octaves. Out of curiosity, I wrote a script to simply count the number of note-on messages in a given MIDI file and create a histogram according to the message pitches. Then I ran it against a set of about 17,000 MIDI piano files from archive.org.1 The result is in the plot below. I’ve truncated the 128 MIDI pitches to just cover the range of a typical piano and marked the boundaries between the modules in my design. Currently I have the range between 53 and 76 covered (inclusive), which accounts for 56.1% of the total in the dataset. I can increase that to 88.2% by adding one more module on each end. This alone could be a satisfactory machine for most purposes, but it remains to be seen whether the notes in the remaining fraction are more important than I expect.
I’ll also try to spend some time making sure I have enough parts in hand or on order to build the two new modules. I definitely have enough wire and filament for the solenoids, but I could probably use more circuit components, maybe some USB cables and hubs, and CNC bits. It also wouldn’t hurt to look into solid lubricants for the plunger and solenoid assemblies to try to further address key sticking, which still persists here and there.
rosshamilton29 on the Player Piano Build
Community Discord server alerted us all to this archive’s existence.
Obviously this dataset is not necessarily a representative sample of all
possible piano music (what dataset could be?) but it seems like as
decent a target as any. I must have combined another dataset to get the
17,000 figured, since the description on the one I linked only claims a
little over 14,000.↩︎