The Rainboard is a very visual instrument both when it is sitting and when someone is playing it. The design and lights are one of the main features for drawing in people unfamiliar with the device and for providing visual satisfaction for an audience during performance. I always wanted to added additional active visuals on the device that progressed over time. Inspired by the name of the instrument, I decided that a wave emanating from a button press, much like one that would emanate from a rain drop in a puddle, would be a suitable animation. This task took on some complexity as the following hurdles had to be overcome: the animations were dynamic as they are initiated by user input, the color of notes are not static so could not be calculated ahead of time, there is very little memory available for storing data, a circular pattern would need to be calculated on the fly, and all of this had to be done in a very efficient time manner.
The first hurdle to overcome was determining where the wave was active at any given point in time. In order to create circles around a note I assumed that the six hexagons surrounding the press were at a distance of one, and the next outer ring of hexagon was at distance two and so on. The next issue was to find a fast algorithm for determining the distance between any two hexagons. From my previous work with Musix, I had developed a grid system for this type of work, but it was chosen for easy identification of x and y coordinates rather than calculating distances. After researching I found an algorithm on stackoverflow written by user AAZ which seemed efficient. I implemented it by storing the two dimension coordinates for each button in program memory in order to conserve running memory. In order to optimize the sign check that is needed I decided to perform a logical AND with the minimum value integer. This would leave me with only the sign bit for each number.
The next hurdle was how to keep state for the buttons. The original plan was to dynamically modify the array holding the LED colours as the wave onset affected the colour, then reversing the change as the wave left the colour. This idea works ok for small changes but fails when large changes occur. For example, if the colour of a button was white and the intensity was increased the value of the colour would either have to wrap around past the end of the byte or hold at the max value of 255. The wrap is unacceptable as increasing the brightness would turn the colour very dark and if the value gets held at max value, it will be decreased as the inverse change occurs and will stray from it's original colouring. The next idea was to use an integer rather than a byte to hold each colour value. That idea did not get very far as there wasn't near enough memory to hold the values. The end result was to store an influence value for each button as an integer. When an influence change occurs (which is a change in brightness in a positive or negative direction) the original colour of the LED is modified by the influence and is then set at the current LED colour. This allows for multiple animation waves to interact with each while not having to store much information about any single colour or individual wave.
There are three key components to make the wave move through time: the start location of each wave, time passed since wave origination, and a timer to change the wave. The timer occurs thirty times a second (and thus the animation happens at thirty frames a second). As well as controlling how often the updates happen, the timer also controls the speed of the animation. These two could be decoupled, but this method seems more efficient. The time passed since wave origination controls what changes need to be made for each wave. In current operation each wave affects three rings of buttons at a time and takes four time counts to move on to the next distance. The wave calculations were done by Gerhard and myself.
The final case that needed to be handled was white notes. Normally when a note is pressed on the Rainboard, the pressed note and all other notes with the same midi value are set to white. This was not possible with the existing wave method as white notes would get overwritten when the wave passed over. In order to still allow this useful feature I started to keep track of which notes where on. In order to prevent memory consumption I chose to create two unsigned long variables and use bitmasking to flag the related bits for which notes where white and which ones where regular colours. This flag is checked when wave updates are performed and are still affected by the wave but are affected based on their white colour not based on the original layout colour.
Once the code was completed the wave effect was very satisfying. Unfortunately it appeared to cause some small delays upon note actuation. A couple days work was spent timing and optimizing the relevant code to make the wave efficient especially in the case of multiple simultaneous waves (getting close to a O(1) time rather than a O(n) time). Upon completion the time for a single wave did not change much but the time it took to update multiple waves was greatly improved.