Skip to main content

Bytes module

The Bytes module creates fixed-size memory blocks, which are read and written byte by byte or in groups of 16 and 32 bits. They're for the binary data other modules expect in that form: the pixels of draw.bytes, the samples of Sound.loadSample and the music of Sound.loadMusic, or a frame to send to a device.

use Bytes

It works the same on Windows and on every board. 16- and 32-bit values are stored with the low byte first (little endian), which is how the PC and the ESP32 chips store them.

Functions​

FunctionWhat it doesReturns
Bytes.new(n)Creates a block of n bytesThe block
Bytes.clone(block)Copies a blockA new block, with the same contents

new(n)​

Creates a block of n bytes. n has to be an integer; with 0, the block has no bytes at all.

The contents of a new block aren't set to zero: it can hold anything. Call fill(0) before using it.

ErrorWhen
ERR_TYPE_MISMATCHn isn't an integer.
ERR_INVALID_VALUEn is negative.
ERR_INVALID_ARG_NUMBERYou don't pass one argument.
use Bytes

var block = Bytes.new(64) // 64 bytes
block.fill(0) // set to zero
println(block.size()) // 64
A size that's too large shuts down the VM

If there's no memory for that many bytes, the VM shuts down (on Windows, with the message ReallocAlloc failed), and try doesn't prevent it. Check the size before creating the block.

clone(block)​

Returns a new block, of the same size and with the same contents as block. Changing one doesn't change the other. If block isn't a Bytes block, it throws ERR_TYPE_MISMATCH.

use Bytes

var original = Bytes.new(4)
original.write(0, 0xAB)

var copy = Bytes.clone(original)
copy.write(0, 0) // doesn't change the original

println(original.read(0)) // 171
println(copy.read(0)) // 0

Returned objects​

Bytes.new and Bytes.clone return a block, with these methods:

MethodWhat it doesReturns
fill(v)Sets every byte to vnil
size()How many bytes the block hasAn integer
read(i)Reads byte iFrom 0 to 255
write(i, v)Writes byte inil
read16(i)Reads 16-bit value number iFrom 0 to 65535
write16(i, v)Writes 16-bit value number inil
read32(i)Reads 32-bit value number iA signed integer
write32(i, v)Writes 32-bit value number inil

The index counts in units of the size you read or write, not in bytes:

MethodsIndex unitBytes index i touchesIndices of a block of n bytes
read and write1 byteiFrom 0 to n - 1
read16 and write162 bytes2i and 2i + 1From 0 to n div 2 - 1
read32 and write324 bytesFrom 4i to 4i + 3From 0 to n div 4 - 1

So read16(1) reads bytes 2 and 3, not byte 1. In a block of odd size, the last byte can only be reached with read and write.

Errors of every read and write method (exceptions, which you catch with try):

  • ERR_INDEX_OUT_OF_RANGE if the index goes outside the block (also if it's negative).
  • ERR_TYPE_MISMATCH if the index isn't an integer.
  • ERR_INVALID_ARG_NUMBER if you don't pass the arguments they ask for.

fill(v)​

Sets every byte of the block to v. v has to be an integer (if not, ERR_TYPE_MISMATCH), and its low byte is kept: fill(300) puts 44 in each byte.

use Bytes

var block = Bytes.new(8)
block.fill(0xFF) // all set to 255
block.fill(0) // all set to 0

size()​

Returns how many bytes the block has.

read(i)​

Returns byte i, from 0 to 255.

use Bytes

var block = Bytes.new(4)
block.write(2, 0x42)
println(block.read(2)) // 66

write(i, v)​

Writes v to byte i. The low byte of v is kept: 300 is stored as 44, and -1, as 255.

v isn't checked: it has to be an integer. A decimal, true, nil or a string give no error and write a meaningless byte. Watch out for /, which always gives a decimal: use div or int() (see "Pitfalls and known issues").

use Bytes

var block = Bytes.new(4)
block.write(0, 0xDE)
block.write(1, 0xAD)
block.write(2, 300)
println(block.read(2)) // 44

read16(i)​

Returns the 16-bit value made by bytes 2i (the low one) and 2i + 1 (the high one), from 0 to 65535.

use Bytes

var block = Bytes.new(4)
block.write16(0, 0x1234)
println(block.read16(0)) // 4660 (0x1234)
println(block.read(0)) // 52 (0x34): the low byte goes first
println(block.read(1)) // 18 (0x12)

write16(i, v)​

Writes v to bytes 2i and 2i + 1, low byte first. The low 16 bits are kept: 70000 is stored as 4464. As in write, v has to be an integer.

read32(i)​

Returns the 32-bit value made by bytes 4i to 4i + 3, low byte first.

JARU integers are signed: values from 0x80000000 upwards come out negative (0xDEADBEEF reads as -559038737). toUnsigned() doesn't change that; to see it in hexadecimal, use toHex().

use Bytes

var block = Bytes.new(8)
block.write32(0, 0xDEADBEEF)
block.write32(1, 0x12345678)

println(block.read32(0)) // -559038737
println(block.read32(0).toHex()) // DEADBEEF
println(block.read32(1)) // 305419896

write32(i, v)​

Writes v to bytes 4i to 4i + 3, low byte first. As in write, v has to be an integer.

A JARU integer goes up to 2147483647. Write the higher 32-bit values in hexadecimal: 0xFFFFFFFF is an integer (it's -1) and is stored correctly. Written in decimal, 4294967295 is a decimal, and write32 stores a meaningless value without warning.

use Bytes

var block = Bytes.new(16)
block.write32(0, 0x00FF00FF)
block.write32(1, 0xFFFFFFFF)
println(block.read(4)) // 255

Complete example​

A frame to send to a device: a 16-bit header, the length, the data and a one-byte checksum.

use Bytes

// Frame: header (2 bytes), length (1), data and checksum (1)
var data = [0x2A, 0x7F, 0x10]
var frame = Bytes.new(2 + 1 + data.size() + 1)
frame.fill(0)

frame.write16(0, 0xAA55) // bytes 0 and 1
frame.write(2, data.size()) // byte 2
for (var i = 0; i < data.size(); i++)
frame.write(3 + i, data[i])
end

// The checksum: the low byte of the sum of all the previous ones
var sum = 0
for (var i = 0; i < frame.size() - 1; i++)
sum = sum + frame.read(i)
end
frame.write(frame.size() - 1, sum % 256)

for (var i = 0; i < frame.size(); i++)
print(frame.read(i).toHex(), " ")
end
println("")
println(frame.read16(0).toHex()) // the header, again in 16 bits
55 AA 3 2A 7F 10 BB 
AA55

The header 0xAA55 comes out as 55 AA: the low byte goes first.

An image for draw.bytes​

draw.bytes paints a block as an image: one byte per pixel, with the number of its color in a palette. The palette is an array {…} of 0xRRGGBB colors; with a list […] it paints nothing (see Draw). This program paints a gradient from black to red for two seconds:

use Bytes, Display

const WIDTH = 160
const HEIGHT = 120

Display.open(WIDTH, HEIGHT)
var draw = Display.draw
Display.fps = 30

// The image: one byte per pixel, with the number of its color in the palette
var image = Bytes.new(WIDTH * HEIGHT)
for (var y = 0; y < HEIGHT; y++)
for (var x = 0; x < WIDTH; x++)
image.write(y * WIDTH + x, (x * 255) div (WIDTH - 1))
end
end

// The palette: 256 colors, from black to red
var palette[256]
for (var i = 0; i < 256; i++)
palette[i] = i << 16
end

var frames = 0
while (frames < 60)
draw.bytes(0, 0, WIDTH, HEIGHT, image, palette)
Display.update()
frames++
end
Display.close()

Pitfalls and known issues​

  • A new block isn't set to zero. Call fill(0) before using it.
  • write doesn't check the value. A decimal is written as a meaningless byte, with no error. / always gives a decimal (10 / 4 is 2.5), so block.write(0, total / 4) doesn't store what you expect: use total div 4 or int(total / 4).
  • read32 is signed. Values from 0x80000000 upwards come out negative.
  • High 32-bit values, in hexadecimal. 4294967295 written in decimal isn't an integer: write 0xFFFFFFFF.
  • Indices in units. read16(1) and read32(1) don't start at byte 1, but at bytes 2 and 4.

All the known issues of JARU, together, are in Known issues.