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
| Function | What it does | Returns |
|---|---|---|
Bytes.new(n) | Creates a block of n bytes | The block |
Bytes.clone(block) | Copies a block | A 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.
| Error | When |
|---|---|
ERR_TYPE_MISMATCH | n isn't an integer. |
ERR_INVALID_VALUE | n is negative. |
ERR_INVALID_ARG_NUMBER | You don't pass one argument. |
use Bytes
var block = Bytes.new(64) // 64 bytes
block.fill(0) // set to zero
println(block.size()) // 64
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:
| Method | What it does | Returns |
|---|---|---|
fill(v) | Sets every byte to v | nil |
size() | How many bytes the block has | An integer |
read(i) | Reads byte i | From 0 to 255 |
write(i, v) | Writes byte i | nil |
read16(i) | Reads 16-bit value number i | From 0 to 65535 |
write16(i, v) | Writes 16-bit value number i | nil |
read32(i) | Reads 32-bit value number i | A signed integer |
write32(i, v) | Writes 32-bit value number i | nil |
The index counts in units of the size you read or write, not in bytes:
| Methods | Index unit | Bytes index i touches | Indices of a block of n bytes |
|---|---|---|---|
read and write | 1 byte | i | From 0 to n - 1 |
read16 and write16 | 2 bytes | 2i and 2i + 1 | From 0 to n div 2 - 1 |
read32 and write32 | 4 bytes | From 4i to 4i + 3 | From 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_RANGEif the index goes outside the block (also if it's negative).ERR_TYPE_MISMATCHif the index isn't an integer.ERR_INVALID_ARG_NUMBERif 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. writedoesn't check the value. A decimal is written as a meaningless byte, with no error./always gives a decimal (10 / 4is2.5), soblock.write(0, total / 4)doesn't store what you expect: usetotal div 4orint(total / 4).read32is signed. Values from0x80000000upwards come out negative.- High 32-bit values, in hexadecimal.
4294967295written in decimal isn't an integer: write0xFFFFFFFF. - Indices in units.
read16(1)andread32(1)don't start at byte 1, but at bytes 2 and 4.
All the known issues of JARU, together, are in Known issues.