Building an Abacus Layer for the Tenori-ON

The instrument
The Tenori-ON (2007) is a digital musical instrument designed by multimedia artist Toshio Iwai in collaboration with Yamaha. An excellent [UCLA lecture by Iwai](YouTube link) lays out where his ideas come from.
In that lecture, two threads stand out. The first is a deep appreciation for spatio-temporal sequencing of sound and image — at one point he disassembles a music box and reverses the metal music sheet to change the melody. The second is his own admitted lack of proficiency on conventional instruments like guitar and piano. Fascination with music, plus an inability to play the instruments that already existed: that combination seems to have driven much of what he built.
Why custom firmware
The Tenori-ON is discontinued and only available used. The last official firmware, 2.10, was a significant improvement — but the hardware clearly had more in it than had ever been implemented.
Around 2020 I ran across pika.blue’s custom firmware. The unlocked capabilities impressed me, and I had a few ideas of my own. pika suggested I’d have no trouble experimenting with the code, since I’d worked with x86 assembly before (as a kid, but still). The Tenori-ON runs on a Toshiba TLCS 9000, so the assembly language is quite different from x86 — but pika was right, and it clicked fairly quickly.
One caveat about the work: the original firmware was presumably written in C, but we don’t have that source. Everything happens in assembly, against a reverse-engineered and decompiled listing.
I started with a few features aimed at UX:
- R1 copy — quickly copy information from one layer to another.
- Octave split for the push layer — reach 3 octaves inside a single layer instead of 1.
- A stack debugging view on the green LCD panel, which turned out to be invaluable for development.
An iterative step toward a tape delay echo
For a while I was enamored with the idea of adding a tape delay echo effect. After several design discussions with pika over email and some helpful orienting, I got started.
What came out of that was the Abacus layer type. It was meant as an intermediate step toward the full echo effect, but it turned out to be fun and useful on its own. The abacus representation was a nifty way to visualize the memory holding played notes as they faded out. To turn it into a real abacus, all I had to add was bidirectional movement of that memory.
Laying out the memory
There are 16 rows, so I needed 16 slots of each kind of data:
ABQ_BEADS equ 0x0000
ABQ_BEADS_CURRENT_TARGET equ 0x0010
ABQ_BEADS_FINAL_TARGET equ 0x0020
ABQ_ANIM_STEPS equ 0x0030
ABQ_ROW_VOLUMES equ 0x0040
ABQ_UPDATE_SKIP_CTRS equ 0x0050
ABQ_ANIM_SKIP_CTR equ 0x0060
These get cleared when the layer starts up:
LDA XDE, XHL+ABQ_BEADS
LDA XIX, XHL+ABQ_BEADS_CURRENT_TARGET
LDA XIY, XHL+ABQ_BEADS_FINAL_TARGET
LDA XBC, XHL+ABQ_ROW_VOLUMES
LDA XWA, XHL+ABQ_UPDATE_SKIP_CTRS
; 0x0060 is abqLayerAnimSkipCounter
LD HL, 0x0000
$$clearEachRowAbq_LayerMemory:
LD (XDE+HL), 0x00
LD (XIX+HL), 0x00
LD (XIY+HL), 0x00
LD (XBC+HL), 0x0F
LD (XWA+HL), 0x01
INC 0x01, HL
CP HL, 0x0010
JR ULT, $$clearEachRowAbq_LayerMemory
Handling a tap
When the user taps an LED, I compute where the LEDs need to end up — the target state — based on where they are now. The layer then evolves toward that target on each tick of the instrument, emitting a fading sound at every tick.
Here’s the code that works out what should happen on a given tick.
One note on style: I use inline addition when loading parameters on and off the stack (XSP+8+4, XSP+8+2). It keeps things flexible in case I need to add another PUSH or POP higher up.
abq_ledOn: ; DATA XREF: DATA:00CB5DD2
PUSH XIZ
LD XBC, (XSP+8+4) ; Get blockData adrs is at XSP+??
LD XBC, (XBC+4) ; Get blockData+4 which is blDataA (pointer to notes block)
LD IZ, (XSP+8+2) ; Row number
LDA XDE, XBC+ABQ_ROW_VOLUMES
LD (XDE+IZ), 0x0A
LDA XDE, XBC+ABQ_UPDATE_SKIP_CTRS ; Location of abqRowUpdateSkipCounters in blDataA
LD (XDE+IZ), 1
LDA XDE, XBC+ABQ_BEADS_CURRENT_TARGET ; abqBeadSplitTargetLocations
LD A, (XDE+IZ) ; Immediate (Short term) Target Location for in-flight bead
LDA XDE, XBC+ABQ_BEADS ; Array into Number of beads on the right <-- Animation mode
LD (XDE+IZ), A ; Force the current location equal to the short term target location <--
$$launchNextAnimation:
NEG A
ADD A, 10 ; Number of beads on left
LD W, (XSP+8) ; Tapped column number (bead location-1)
CP W, A
JR LT, $$setLeftToB
$$setLeftToBMinus5:
SUB W, 5
CP W, A
JR LE, $$done
$$setLeftToB:
NEG W ; Convert number of beads on left into
ADD W, 10 ; number of beads on the right.
; The final target locations are used by abq_tick_animated_one_at_a_time from blockJumpTick and abq_sums
LDA XDE, XBC + ABQ_BEADS_FINAL_TARGET ; abqBeadSplitFinalTargetLocations
LD (XDE+IZ), W ; Write new number of right beads to memory
$$done:
POP XIZ
RETDrawing it
From there it was a few simple graphics routines to draw the abacus rows, plus a bonus: pressing L4 displays the sums — the bead counts on the left and right sides.
The result is pretty fun:
[VIDEO]
Conclusion
Iterative development is my default mode of operating. It is especially rewarding when the artifacts you produce along the way are useful in their own right. Designing the UI in parallel with the assembly implementation was great fun too.
Getting this working took many round trips to the device. In a future post I’ll write about a side project aimed at cutting that loop down: Tenori-SIM — a project I started 5 years ago and have nearly completed — which emulates a minimal set of Tenori-ON functionality to speed up layer development.