The Sound Of Plumpkins
The Sound Of Plumpkins

Varvara is a computer system running on Uxn.

Varvara is a specification for devices communicating with the Uxn CPU intended to run little audio and visual programs. To see a list of compatible software, see roms, and the community projects. If you are implementing your own Varvara emulator, you can find a series of roms to test your devices implementations here.


The two reserved devices can be used for implementation specific features that do not need to be part of the specs, or other Uxn/Varvara instances.

System Device 0x0000 0xff08

system 00Unused*08red*
|00 @System &vector $2 &expansion $2 &wst $1 &rst $1 &metadata $2 &r $2 &g $2 &b $2 &debug $1 &state $1

The System/expansion* port expects an address to an operation, it allows basic memory management for roms that must keep information outside of the addressable range, or cache the content of a file in order to seek through the data. There is currently only one such operation:

copy01length*src bank*src addr*dst bank*dst addr*
@on-reset ( -> )
	;cmd .System/expansion DEO2
	;dst print-str

@cmd [ 01 000b 0000 =src 0000 =dst ]
@src "Hello 20 "World $1
@dst $c

Reading, and writing, to the System/wst and System/rst ports will get, or set, a byte value as the working and return stack indexes. Note that reading the value in the wst and rst ports will include the resulting stack index byte, so an empty stack will return 01.

The System/metadata* port notifies the emulator that metadata about the rom is present at the address specified. The emulator can choose to utilize this information or to ignore it.

@on-reset ( -> )
	;meta .System/metadata DEO2

@meta 00
	"Nasu 0a
	"A 20 "Sprite 20 "Editor 0a
	"By 20 "Hundred 20 "Rabbits 0a
	"Jan 20 "8, 20 "2023 $2
#fff #000 #7db #f62

This device is holding 3 shorts to be used for application customization, for simplicity we call them the System/red*, System/green* and System/blue* shorts. These colors are typically used by the screen device to form four application colors.

@on-reset ( -> )

@set-theme ( -- )
	#f07f .System/r DEO2
	#f0d6 .System/g DEO2
	#f0b2 .System/b DEO2

Sending a non-null byte to the System/debug port will print the content of the stacks or pause the evaluation if the emulator includes a step-debugger.

@on-reset ( -> )
	[ LIT2r 1234 ] #abcd
	#01 .System/debug DEO

Sending a non-null byte to the System/state port will terminate the application, on systems that can handle exit codes, the error code is the 0x7f portion of the byte. So, 0x01 terminates the program with an error, and 0x80 terminates the program succesfully.

@on-reset ( -> )
	( exit code 0, success )
	#80 .System/state DEO

Console Device 0x0000 0x0300

console 10vector*18write
|10 @Console &vector $2 &read $1 &pad $4 &type $1 &write $1 &error $1

The Console/vector* is evaluated when a byte is received. The Console/type port holds one of 5 known types: no-queue(0), stdin(1), argument(2), argument-spacer(3), argument-end(4). During the reset vector, a program should be able to query the type port and get a null byte when there is no arguments to be expected.

uxncli file.rom arg1 arg2
                ^   ^^   ^
                2   32   4

The Console/read port is used to listen to incoming bytes during a Console vector event.

@on-reset ( -> )
	;on-console .Console/vector DEO2

@on-console ( -> )
	.Console/read DEI .Console/write DEO
	#20 .Console/write DEO

The Console/write port is used to send data through the console. For example, a program sending the text "Hello", will trigger the console's port 5 times; once for each character.

@on-reset ( -> )
	&while ( -- )
		LDAk .Console/write DEO
		INC2 LDAk ?&while

@text "Hello $1

Screen Device 0x003c 0xc0028

screen 20vector*28x*
|20 @Screen &vector $2 &width $2 &height $2 &auto $1 &pad $1 &x $2 &y $2 &addr $2 &pixel $1 &sprite $1

The Screen/vector* is evaluated 60 times per second. The screen device is capable of displaying graphics in any four colors, which are defined by the system device. The screen is made of two independent layers, the foreground layer treats color0 as transparent.

@on-reset ( -> )
	;on-screen .Screen/vector DEO2

@on-screen ( -> )
	[ LIT &frame $1 ] INCk ,&frame STR
	#01 AND ?&>skip
		( 30 times per second)

The Screen/width* and Screen/height* ports can be set to resize the screen for systems that supports it, but reading from these ports should ALWAYS return the actual sizes, as programs make use of the screen sizes to position responsive graphics on the screen.

@on-reset ( -> )
	.Screen/width DEI2 #01 SFT2 .Screen/x DEO2
	.Screen/height DEI2 #01 SFT2 .Screen/y DEO2
	#01 .Screen/pixel DEO ( paint a black pixel, on background )
	#43 .Screen/pixel DEO ( paint a red pixel, on foreground )
	#93 .Screen/pixel DEO ( fill a red rectangle bottom-left, on background )
	#e2 .Screen/pixel DEO ( fill a cyan rectangle top-right, on foreground )

The Screen/pixel port defines the pixel or fill mode, layer to draw on, optional horizontal/vertical flipping of the quadrant to fill, and which of the four colors to use. When the fill bit is active, the operation will fill a portion of the screen starting at the x,y position until the edges of the screen. The default quadrant is bottom-right, flipping the x bit will fill the buttom-left quadrant, and so on.

           M L Y X 3 2 1 0
Mode   ----+ | | | | | | +---- Color bit 0
Layer  ------+ | | | | +------ Color bit 1
Flip Y --------+ | | +-------- Color bit 2
Flip X ----------+ +---------- Color bit 3

The Screen/sprite port defines the 1-bit or 2-bit mode, layer to draw on, optional horizontal and vertical flipping of the sprite, and the colors to use. The 8x8 sprite data to draw is specified by writing its location in memory to the Screen/addr* port.

@on-reset ( -> )
	#0020 .Screen/x DEO2
	#0040 .Screen/y DEO2
	;sprite-chr .Screen/addr DEO2
	#81 .Screen/sprite DEO

	6cfe fe7c 3810 0000 6cfe fe7c 3810 0000
0 4 8 c
1 5 9 d
2 6 a e
3 7 b f

The color nibble defines which color is drawn for each pixel of a sprite. The following table presents all possible combinations, assuming a sprite has a background of value 0 and three concentric circles of values 1, 2, and 3 (counting from the outside). For 1-bit sprites, only values 0 and 1 are applicable.

c = !ch ? (color % 5 ? color >> 2 : 0) : color % 4 + ch == 1 ? 0 : (ch - 2 + (color & 3)) % 3 + 1;

blending[4][16] = {
	{0, 0, 0, 0, 1, 0, 1, 1, 2, 2, 0, 2, 3, 3, 3, 0},
	{0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3},
	{1, 2, 3, 1, 1, 2, 3, 1, 1, 2, 3, 1, 1, 2, 3, 1},
	{2, 3, 1, 2, 2, 3, 1, 2, 2, 3, 1, 2, 2, 3, 1, 2}};

The Screen/auto port automates the incrementation of the position and sprite address whenever a drawing command is sent, so the program does not need to manually move to the next sprite, or the next position.

                 3 2 1 0 * A Y X
Length bit 3 ----+ | | |   | | +---- Auto X
Length bit 2 ------+ | |   | +------ Auto Y
Length bit 1 --------+ |   +-------- Auto Addr
Length bit 0 ----------+

The length nibble controls the number of extra sprites that will be drawn in addition to the first, each time the sprite port is written to. When set to 0, only a single sprite will be drawn. As many as 16 sprites can be drawn at once with a value of 15.

The auto-x and auto-y bits control the increment of the x and y ports respectively, as well as the layout of the extra sprites drawn when the length nibble is not null. The extra sprites are drawn as columns moving rightward for auto-x, and rows moving downward for auto-y. If the flip bits of the sprite byte are set, the directions are reversed. If the auto-addr bit is set, the address port will be incremented for each sprite drawn by increments of 8 for 1-bit, and by increments of 16 for 2-bit.

@paint-sprite ( x* y* -- )
	.Screen/y DEO2 ( set y position )
	.Screen/x DEO2 ( set x position )
	#16 .Screen/auto DEO ( set length 2 with auto y and addr )
	;23x-icns .Screen/addr DEO2 ( set addr )
	#01 .Screen/sprite DEOk DEOk DEO ( draw 3 rows of 1-bit sprites )

	( 0 ) ( 1 )
	( 0 ) 0010 2847 2810 0001 0000 00e0 2040 8000
	( 1 ) 0204 080f 0000 0001 0000 00e0 2040 8000
	( 2 ) 0204 080f 0000 0000 0010 28c4 2810 0000

Audio Device 0x0014 0x8000

audio 30vector*38adsr*
|30 @Audio0 &vector $2 &position $2 &output $1 &pad $3 &adsr $2 &length $2 &addr $2 &volume $1 &pitch $1
|40 @Audio1 &vector $2 &position $2 &output $1 &pad $3 &adsr $2 &length $2 &addr $2 &volume $1 &pitch $1
|50 @Audio2 &vector $2 &position $2 &output $1 &pad $3 &adsr $2 &length $2 &addr $2 &volume $1 &pitch $1
|60 @Audio3 &vector $2 &position $2 &output $1 &pad $3 &adsr $2 &length $2 &addr $2 &volume $1 &pitch $1

The Audio/vector* is evaluated when a note ends. All samples used by the audio devices are unsigned 8-bit mono.

When a byte is written to the Audio/pitch port, any sample that is currently playing will be replaced with the sample defined by all the values set in the device, it starts playing an audio sample from memory located at Audio/addr* with length of Audio/length*. It loops the sample until it reaches the end of the envelope defined by Audio/adsr*. Several fields contain more than one component:

Size (bits)44441744

Each of the ADSR components is measured in 15ths of a second, so writing #ffff to Audio/adsr* will play a note that lasts for exactly four seconds, with each section of the envelope lasting one second. If Audio/adsr* is #0000 then no envelope will be applied: this is most useful for longer samples that are set to play once by setting the most significant bit of Audio/pitch to 1.

The envelope varies the amplitude as follows: starting at 0%, rising to 100% over the Attack section, falling to 50% over the Decay section, remaining at 50% throughout the Sustain section and finally falling to 0% over the Release section. The envelope is linearly interpolated throughout each section.

@on-reset ( -> )
	#0248 .Audio/adsr DEO2
	#88 .Audio/volume DEO
	;note-pcm .Audio/addr DEO2
	#0010 .Audio/length DEO2
	#30 .Audio/pitch DEO

	ff ff ff ff ff ff ff ff 00 00 00 00 00 00 00 00

The two Audio/volume components balance how loudly the next sample will play in each ear. #ff sets maximum volume for both speakers. While the sample is playing, the Audio/output byte can be read to find the loudness of the envelope at that moment.

Controller Device 0x0000 0x0000

controller 80vector*88--
|80 @Controller &vector $2 &button $1 &key $1

The Controller/vector* is evaluated when a button, is pressed or released, and when a key is pressed.

0x01A Ctrl0x10Up
0x02B Alt0x20Down
0x04Select Shift0x40Left
0x08Start Home0x80Right

The Controller/button port works similarly to a NES controller, where there the state of each one of the 8 buttons is stored as a bit in a single byte.

@on-reset ( -> )
	;on-controller .Controller/vector DEO2

@on-controller ( -> )
	.Controller/button DEI
	DUP #20 AND ?on-dpad-down
	DUP #80 AND ?on-dpad-right

@on-dpad-down ( button -> )
@on-dpad-right ( button -> )

The Controller/key port holds the character data that is pressed during the vector event.

@on-controller ( -> )
	.Controller/key DEI DUP ?on-key POP

@on-key ( key -> )

Would the need for multi-player games arise, the P2, P3 and P4 ports, will host button-type byte values received from the other controllers.

Mouse Device 0x0000 0x0000

mouse 90vector*98--
|90 @Mouse &vector $2 &x $2 &y $2 &state $1 &chord $1 &pad $4 &scrolly &scrolly-hb $1 &scrolly-lb $1

The Mouse/vector* is evaluated when the mouse is moved and when a button is pressed or released.

@on-reset ( -> )
	;on-mouse .Mouse/vector DEO2

@on-mouse ( -> )
	.Mouse/state DEI ?&down
	&down ( -> )
		.Mouse/x DEI2 print-dec
		LIT ", .Console/write DEO
		.Mouse/y DEI2 print-dec
		#0a .Console/write DEO

The Mouse/state port holds a byte in which each bit is a button state. The byte value of holding down the mouse1 button is 01, and holding down mouse1+mouse3 button is 05. The Mouse/scroll-x* and Mouse/scroll-y* values are signed shorts, normally ffff and 0001, for -1 and +1.

File Device 0x0000 0xa260

file a0vector*a8name*
|a0 @File1 &vector $2 &success $2 &stat $2 &delete $1 &append $1 &name $2 &length $2 &read $2 &write $2
|b0 @File2 &vector $2 &success $2 &stat $2 &delete $1 &append $1 &name $2 &length $2 &read $2 &write $2

The File/vector* is normally unused, but is reserved for systems where a portable data format(disk, etc..) can be inserted. There is no specs for disk handling at this time.

When File/name* resolves to a file, writing the address to File/read* will read the file's data to the File/addr* in memory. File/success* will be less than File/length* if the file is shorter, and will be zero if the filename is invalid. If the file is longer, subsequent writes to File/read* will read the next chunk of data into the memory region, so it is possible to read the contents of very large files one chunk at a time.

@on-reset ( -> )
	;filename .File/name DEO2
	#0010 .File/length DEO2
	;buffer .File/read DEO2

@filename "in.txt $1
@buffer $10

When File/name* resolves to a directory, writing the address to File/read* will read the directory as if it were a text file listing each of the directory's contents. The listing has each file or directory on its own line, prefixed with the file size in four hexadecimal characters, a space, its name and a linebreak.

If the file is too big to fit in four ascii characters then ???? will be used instead; for directories, ---- takes the place of the file size.

001a file.txt
???? large file.mp4
---- directory/
!!!! missing file

The information of a file or directory can be obtained via the File/stat* port, the File/length* specifies the length of the buffer to write to, the data written will be in the same format as the ascii bytes above. A short length will return the lower nibbles of a file size, a higher length will allow the reading of larger file sizes. The length of the stat written will always fill the requested length, so a directory result will be dashes for the requested length.

@is-folder ( name* -- bool )
	.File/name DEO2
	#0001 .File/length DEO2
	;&b .File/stat DEO2
	[ LIT2 &b "- ] EQU2 JMP2r

Writing files is performed by writing to File/write*. If File/append is set to 0x01, then the data in the memory region will be written after the end of the file, if it is 0x00 (the default) it will replace the contents of the file. If the file doesn't previously exist then it will be created and File/append makes no difference. File/success* will be set to File/length* if the write was successful, otherwise it will read as zero. As with reading files and directories, subsequent writes to File/write* will write more chunks of data to the file.

@on-reset ( -> )
	;filename .File/name DEO2
	#0005 .File/length DEO2
	;data .File/write DEO2

@filename "out.txt $1
@data "hello $1

Finally, to delete a file, write any value to the File/delete byte. Writing to File/name* closes the file/directory. The device may not access files outside of the working directory.

Datetime Device 0x07ff 0x0000

datetime c0year*c8doty
|c0 @DateTime &year $2 &month $1 &day $1 &hour $1 &minute $1 &second $1 &dotw $1 &doty $2 &isdst $1

The week, in the dotw port, begins on sunday.

@print-date-num ( -- )
	.DateTime/year DEI2k print-dec
	[ LIT "- ] .Console/write DEO
	INC INC DEIk INC print-dec-pad
	[ LIT "- ] .Console/write DEO
	INC DEI !print-dec-pad

incoming donsol oekaki roms left noodle nasu adelie metadata metadata metadata oquonie paradise potato nebu shavian basic basic icn format chr format gly format ufx format tga format chip8 uxn uxn uxn uxn uxn uxntal syntax uxntal syntax uxntal types uxntal devices beetbug beetbug varvara computer