Q: When did the Japanese start eating eggs?
A: A long tamago!
tamago is a livecoding platform for controlling musical events in time.
It does this via midi I/O.
You might think of it as a sequencer you program in assembly!
The language it uses is a modified uxntal, the language used in Hundred Rabbit's uxn project.
uxn is a stack oriented computer, with an intentionally easy to understand design.
In November of 2021, I discovered compudanza's one week tutorial program for learning uxn.
I highly recommend going through that tutorial too!
Through that week or so I wrote some simple little programs, some of which you can look over here.
I've found in my life that when I happen on an interesting idea, or technology, my first thought is always, "how can I make music with this?", and this project was not unique in that respect.
At the time Varvara did not support midi, though that has since changed!
Therefore, I set out to write a simple uxntal interpreter, so one could sequence midi events, by livecoding in a modified uxntal.
tamago is the result of that work.
Click on releases on the right hand side, then download the latest release for your OS.
Once you unzip the program, you can run it!
First clone the repo with git clone https://github.com/beserge/tamago
Next, move into that folder with cd tamago
Install the dependencies with pip install -r requirements.txt
Using python 3 run python tamago.py
- The main area is a large box where you type in your code.
- To the left is the stack display, where you can see the top 25 elements on the stack.
- Above is the contents of the first 8 variables. There are 256 available, but only the first 8 are displayed.
- Below are the main program controls.
- From left to right these are program run and stop
- Clk In (clock from midi in when checked, internal clock when unchecked)
- Midi In selector, Midi Out selector
- On Windows you'll need to download a loopback. I recommend loopmidi by the wonderful Tobias Erichsen.
- On macOs you'll have to enable the internal IAC bus. You can find instructions for doing so here, courtesy of the nice folks at Ableton.
The language is a modified version of the uxntal used on the Varvara Computer.
Hexadecimal is a "base-16" number system. It is how we write numbers in uxntal.
If you're unfamiliar with hex, you can read up on it here.
A stack, otherwise referred to as a LIFO (Last In, First Out), is a type of data structure.
It is the main thing you are manipulating when programming in uxntal, and by extension in tamago.
You can visualize it like a stack of cards, or maybe pancakes, (or in our case data).
The two basic operations you can do are putting something on top of the stack, or taking the top item off.
These are referred to as PUSH
and POP
respectively. You might hear someone say "just push it on the stack"!
In tamago we use the shorthand #F7
to mean PUSH
the number 0xF7
on the stack.
We can use the POP
command to remove the top item from the stack, and throw it away.
The left side of the screen in tamago is a display of the stack as of the last CLK command.
You can also force the stack display to update at any time using the PRINT
command.
Additionally, you can read some more on stacks here if it is still unclear.
If you are familiar with the concept of Reverse Polish Notation, this will be familiar to you!
If not, have no fear it is quite simple.
It will be easiest to explain this with an example. Say you want to divide 0x09
by 0x03
Here is how we can do this in uxn.
#09 ( push 0x09 on the stack )
#03 ( push 0x03 on the stack )
DIV ( divide 0x09 / 0x03, and push the result on the stack )
That's it!
It's important to note the order of operations here.
The item further down
stack goes to the left, and the operator goes between.
In my modified uxntal, labels are used for two purposes:
- Referring to points to jump to with
JMP
andJCN
- Interacting with midi via
DEO
Labels can be pushed and popped from the stack just like integers. You can read more about the commands that use labels in the Language Reference.
Comments are nested between parenthesis.
Make sure to include a space between the parenthesis and the comment!
#01 ( Push 0x01 on the stack )
Macros are reusable bits of code. They are given a name, and a code body. They can be used in your code simply by writing the name.
%ADD_TWO { #02 ADD } ( this macro adds 0x02 to the item on the stack )
#03 ( push 0x03 on the stack )
ADD_TWO ( add two to it using our macro )
You can also use macros inside of other macros!
%NUM { #02 } ( this macro pushed 0x02 on the stack )
%ADD_NUM { NUM ADD } ( this macro adds 0x02 to the item on the stack )
#03 ( push 0x03 on the stack )
ADD_NUM ( add two to it using our macro )
You can store and load data for safe keeping in a few variable slots.
You can see the variable storage (as of the last clock tick) at the top of the screen.
The PRINT
command will force this screen to update.
The LDA
and STA
commands are used to store and load data from these variable slots.
You can read more on them below in the Language Reference.
#0C
Pushes a hexidecimal number onto the top of the stack. In this case we will end up with 0x0C, or 12 on top.POP
Removes item on top of the stack and throws it away!a --
NIP
Removes the second item from the stack.a b -- a
DUP
Duplicates the item on top of the stack.a -- a a
OVR
Duplicates the top item to the third slot.a b -- a b a
SWP
Swaps the two items on top of the stack.a b -- b a
ROT
Rotates the stack elements (up one slot). The top element wraps around.a b c -- b c a
INC
Add 1 to the top element.a -- (a + 1)
0x0C -- 0x0D
ADD
Add the top two elements and push the result.a b -- (a + b)
0x02 0x03 -- 0x05
SUB
Subtract the top two elements and push the result.a b -- (a-b)
0x05 0x03 -- 0x02
MUL
Multiply the top two elements and push the result.a b -- (a*b)
0x03 0x02 -- 0x06
DIV
Divide the top two elements and push the result.a b -- (a/b)
0x08 0x02 -- 0x04
These operate on the top two items, pushing 0x01 if the operation is true, and 0x00 if it is not.
EQU
a is equal to b.a b -- (a==b)
0x03 0x04 -- 0x00
0x05 0x05 -- 0x01
NEQ
a is not equal to b.a b -- (a!=b)
0x03 0x04 -- 0x01
0x05 0x05 -- 0x00
GTH
a is greater than b.a b -- (a>b)
0x05 0x02 -- 0x01
0x02 0x05 -- 0x00
LTH
a is less than b.a b -- (a < b)
0x05 0x02 -- 0x00
0x02 0x05 -- 0x01
Twiddle some bits! These operate on the binary representation of numbers.
AND
Bitwise and of a, b.a b -- (a&b)
0x05 0x06 -- 0x04
ORA
Bitwise or of a, b.a b -- (a|b)
0x05 0x06 -- 0x07
EOR
Exclusive or of a, b.a b -- (a^b)
0x05 0x06 -- 0x03
SFT
Bitshift the a by the control value b. b is seperated into a high and low nibble. If b is 0x12, 1 is the high nibble, and 2 is the low nibble. a is shifted right by the low nibble, then left by the high nibble.a b -- c
0x04 0x01 -- 0x02
0x04 0x10 -- 0x08
0x04 0x11 -- 0x04
We include a simple system for storing and retrieving data off of the stack for later.
We use integer addresses to refer to the different variables.
There are 128 slots as of now, and the first 8 are displayed at the top of the screen.
LDA
Load the item from the specified address.a --
STA
Store data a in address b.a b --
JMP
Jump to the label specified.a --
- An infinite loop:
@loop
( do this forever... )
;loop JMP
JCN
Jump, if the conditional is not 0.a b --
- Loops 5 times:
#00
@loop
INC ( Increment the counter )
DUP ( Duplicate the counter)
#05 LTH ( If the counter < 5 push a 1, else push a 0 )
CLK ( wait for a clock tick )
;loop JCN ( if there is a 1 on the stack, jump to @loop, else keep going )
Midi funcionality all uses DEO
or "DEvice Out".
Command | Effect |
---|---|
#50 ;note DEO |
Set the midi note value to 0x50 |
#50 ;vel DEO |
Set the midi velocity value to 0x50 |
#00 ;chn DEO |
Set the midi channel value to 0x00 |
#02 ;len DEO |
Set the midi note length (in 16th notes) to 0x02 |
#00 ;midi DEO |
Send a midi Note On message with the current settings |
BPM
Set the BPM in beats per second. The clock defaults to 16th notes.#50 BPM
will set the clock to sixteenth notes at 80 bpm.
CLK
pauses until the next clock tick. Can also be written as*
#00 INC***
will push0x00
on the stack, increment it, then wait three ticks.
BRK
Stops the program.CLS
Clear the stack.PRINT
Force the stack / memory displays to update.
The Language section here was partially based on the uxntal reference.
If you are already familiar with uxntal, you'll find the following differences here:
- No return stack. No
STH
. - No keep mode.
- No difference between shorts and bytes. All numbers are actually python ints ;)
- Memory addresses aren't actually bytes. You have to jump to your label since the
;label
is just a string. - No zero page, relative, abs, etc. The
LDA/STA
have been overloaded with simple variable storage. - | . $ , & : ' ~ " We have no need for these things as of yet.
- No
LIT
command, just use#
to push number on the stack.
As of the time of writing:
- Your code is only parsed each time you hit Run. Hitting this also sends the program back to the start.
- If your code contains any errors the parsing will halt and the old code will run.
- Any time a command cannot be run the error is simply caught and ignored.
- A program like
#00 SUB
will just push 0 on the stack, then halt with 0 still on the stack ( after failing to do the subtraction )
- A program like
- Stop just pauses execution.
- Tick the Clock In box to sync the clock to your midi in source.
- There are midi I/O selection dropdowns at the bottom. These are populated on launch, so any midi devices must be ready to go when you launch the app.
- Add Midi ctrl, noteoff, pitchbend, etc. To
DEO
- Add
DEI
- Add OSC compatability
- Add clock out
- Improve graphics
- Create executable / installer / make it portable
- Unit testing
- Implement the return stack
- File load / write
- Make two clicks of stop / run behave how they should. (todo)
- For more language info, see this excellent guide to uxntal on Varvara.
- Read some example programs in this repo's examples folder. Feel free to submit any you come up with!
- Check out the macros.md file for a list of useful macros. Feel free to submit your own!