cartlet manual

1. introduction

welcome to cartlet! cartlet is a found software format for writing small, interactive, audio-visual computer programs such as videogames.

cartlet software is distributed in the form of bookmarklets - programs stored in the URL of a browser bookmark or a link on a webpage. this means cartlets can be both made and played with just a web browser. in addition, they can be shared peer-to-peer outside of centralized app stores, and their source code is freely readable and modifiable by users.

this manual both defines the cartlet format, and contains everything you need to know to begin writing your own cartlets. have fun!

2. contents

3. specifications

4. getting started

the only required tool for making cartlets is a web browser. however, there are some additional tools and resources on the cartlet webpage that you may find helpful - collectively, this toolkit is called cartletKIT.

4.1 installing cartletKIT

cartletKIT is a set of bookmarklets that includes documentation (this manual!), tools, and example cartlets. these are distributed as a bookmarks file (just like the file you get when exporting bookmarks from a browser) and they can be installed in one of two ways:

4.2 cartletKIT contents

if you've installed the entire toolkit you should now see a folder called cartletKIT in your browser bookmarks, which includes the following items:

you're now ready to write your first cartlet!

4.3 hello cartlet

for our first cartlet we'll be starting from scratch, using just the core APIs defined further on in this manual.

start by typing about:blank into your browser's URL bar and hit enter - this will take you to a completely empty page.

next, open cartletPAD from the cartletKIT tools you installed earlier - we will use this tool to edit the program. after clicking the bookmark, you should now see a box containing an arrow icon and the word 'PAD'.

click on the arrow to expand the tool, and you should see some text fields. the 'title' field is for the name of the program: this will show up as the browser tab name for your cartlet - try typing 'hello cartlet' here (or whatever other title you prefer). the 'author' field is for your name: this is metadata that won't be displayed visibly but allows anyone reading the source code to identify the author - go ahead and type your name here. the 'icon data url' is for the program's icon: that will show up as the favicon - how to create the icon data will be explained later on, so you can leave it as is for now.

the 'program' field is the most important field: this where you write your program. it should currently be blank, so let's start by adding a main loop.

function load() { function update() { window.requestAnimationFrame(update); } window.requestAnimationFrame(update); } if (document.readyState = 'complete') { load(); } else { window.onload = load; }

this doesn't do much yet - just starts the update loop and keeps it going by calling requestAnimationFrame() at the end of each loop iteration. we also check the document.readyState to test whether the system has fully loaded already, or if we still need to wait for the window.onload event.

to get something visible on screen first we need to initialize video memory (in the form of a HTMLCanvasElement) and hook it up to the display using document.body.append(). we'll do this at the start of our load function.

function load() { var mem = document.createElement('canvas'); mem.style.backgroundColor = 'black'; document.body.append(mem); function update() { window.requestAnimationFrame(update); } window.requestAnimationFrame(update); } if (document.readyState = 'complete') { load(); } else { window.onload = load; }

now we can try loading our updated program to see what happens. at the bottom of cartletPAD you will see the word 'cart' followed by a hyperlink with text matching the title you gave your program. click and drag the link into your browser bookmarks to save the program, then open the bookmark to run it.

you should now see a black rectangle! to continue editing your program, open cartletPAD again. try out different background colors (see #6.3.1 color palette for the full list of available colors) such as 'fuchsia' or 'aqua' and save and reload your program to see what happens.

next let's make this program a little more interesting by adding some animation. to do that we will use mem.getContext('2d') to get the graphics interface, and use its drawRect() method to draw a pixel every frame. we will do that in our update function.

function load() { var mem = document.createElement('canvas'); mem.style.backgroundColor = 'black'; document.body.append(mem); var vid = mem.getContext('2d'); vid.fillStyle = 'fuchsia'; var t = 0; function update() { var x = t % 300; var y = Math.floor(t / 300); vid.fillRect(x, y, 1, 1); t++; window.requestAnimationFrame(update); } window.requestAnimationFrame(update); } if (document.readyState = 'complete') { load(); } else { window.onload = load; }

save your cart again and re-run it - you should see the black screen slowly fill up with fuchsia. :)

and that's our first cartlet! as you can see, it's not too bad to get something on screen using just the core cartlet APIs (read #6. API reference for full documentation of the APIs). however, as your program gets larger, some of these APIs can start to feel a bit cumbersome. that's where cartLIB comes in, which is covered in the following sections.

4.4 hello cartLIB

cartLIB is the cartlet standard library. cartLIB is optional, but it can make it easier to write your cartlets, as this section will show.

cartletKIT's examples includes a basic starter cart called 'cartlet' that comes with cartLIB preloaded. just open that cart, and open cartletPAD to get started working with cartLIB.

you should see in the 'program' text field your new cart starts out with a comment /* LIB */ followed by the full cartLIB source. scroll until you arrive at the /* PROGRAM */ comment - here you will find some basic initialization code where you can start writing your program.

let's recreate the program from the previous section, so you can compare using cartLIB vs using the APIs directly. replace everything under /* PROGRAM */ with this:

load(function() { var vid = video(); clear(vid, vid.mem, 'black'); var t = 0; update(function() { var x = t % 300; var y = Math.floor(t / 300); fill(vid, 'fuchsia', x, y); t++; }); });

now save and run your cart and you should see the same animation as before - this time using cartLIB!

the remaining getting started sections will demonstrate more of cartLIB's features. (for the full documentation and source code for cartLIB, see #7. standard library.)

4.5 graphics

drawing individual pixels is cool, but what about drawing more complex pictures? do we need to program each pixel by hand? well, we could do that, but luckily cartletKIT and cartLIB can help us avoid that arduous task.

from cartletKIT's tools, open cartletPAINT. this will add a new tool with the name 'PAINT' under the already open 'PAD' tool. expand it to view the full tool.

at the top of the paint tool, you should see a blank canvas. try clicking in here to draw and erase pixels. beneath the canvas are dropdowns to select the drawing color and background color.

below that are several text fields. the first contains the pixel data as an array of numbers (0 for empty and 1 for filled). this field can be edited directly or copied and pasted into, which allows you to come back to a drawing you've made previously and tweak it (as long as you've saved the data somewhere).

the next text field contains code for actually drawing the picture in your program. this uses cartLIB's plot() function, which takes in an array of pixel data and draws the individual pixels for you (see #7.1 cartLIB reference for the full documentation of the function).

the last text field contains a data URL version of your drawing, which you can use to create the icon data for your program by copying it into the 'icon data url' field of cartletPAD.

now that you're familiar with cartletPAINT, try drawing something, then copy and paste the plot() code from the program field into your cartlet program. here's a program for drawing a smiley face.

load(function() { var vid = video(); clear(vid, vid.mem, 'black'); plot(vid, 'yellow', 50, 50, 15, 15,[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]); });

what if we want to draw two smiley faces? well, we could copy the whole plot() function again along with its pixel array data, but that will take up a lot of space in your cart. to improve on that, we could put the pixel array data in a variable, so we can reuse the same pixels in multiple function calls. that would look something like this.

load(function() { var vid = video(); clear(vid, vid.mem, 'black'); var pix = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]; plot(vid, 'yellow', 50, 50, 15, 15, pix); plot(vid, 'yellow', 100, 50, 15, 15, pix); });

not too bad, right? but we can still do better! cartLIB also includes an image() function that allows us to make a copy of part of the screen and redraw it anywhere, using the draw() function. not only does this simplify our drawing code (since draw() has less parameters), but it also is faster than redrawing every pixel like plot() does. this will be important when you are writing interactive programs.

there is also an important difference to keep in mind when using image() vs plot(), which is that image() works asynchronously. this means the newly created image is not immediately available as the return value of the function. instead, we must pass in an image array set, and image() will add the new image to that array when it is ready. if we add multiple images to the same image array, each image will be added sequentially to the list, allowing us to re-use the array for all the images in our program. cartLIB also has a function ready() that can test whether every image in the set has fully loaded.

putting that all together, we get the following program. note that we're now doing our drawing in the update loop so that we can wait for the image to be ready before drawing.

load(function() { var vid = video(); clear(vid, vid.mem, 'black'); plot(vid, 'yellow', 0, 0, 15, 15, [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]); var set = []; image(vid.mem, 0, 0, 15, 15, set); update(function() { if (!ready(set)) return; clear(vid, vid.mem, 'black'); draw(vid, set[0], 50, 50); draw(vid, set[0], 100, 50); }); });

4.6 input

it is a truth universally acknowledged that a little guy on a screen must be in want of moving around. - ancient gamemaker's proverb (with apologies to jane austen and rose)

wow that is so wise ...and if we want to make our drawing move around, we're going to need some input!

to do that we can use cartLIB's input() function, which listens for button presses from cartlet's joypad. the joypad has six buttons, which are referred to in your program using the following special strings: 'ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight', 'x', and 'z' (also listed in #6.1.1 joypad buttons).

to read the state of those buttons, we need to pass an object to input() that it can keep updated with a lookup table of button states. this lookup table is called pad and the function will handle creating it for you. so if we create an empty object joy and pass it to input(), like so var joy = {}; input(joy);, then in our program we can test whether the player is currently holding down the x button like this: if (joy.pad['x']) { /* X IS HELD! */ }.

let's put it all together and move our little guy around on screen!

load(function() { var vid = video(); var joy = {}; input(joy); clear(vid, vid.mem, 'black'); plot(vid, 'yellow', 0, 0, 15, 15, [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]); var set = []; image(vid.mem, 0, 0, 15, 15, set); var x = 50; var y = 50; update(function() { if (!ready(set)) return; clear(vid, vid.mem, 'black'); draw(vid, set[0], x, y); if (joy.pad['ArrowUp']) { y--; } if (joy.pad['ArrowDown']) { y++; } if (joy.pad['ArrowLeft']) { x--; } if (joy.pad['ArrowRight']) { x++; } }); });

4.7 sound

cartLIB also includes some functions for working with the cartlet audio system.

first, we need to initialize the audio system using audio(), like so: var aud = audio();.

when we first initialize the audio system, it starts out muted. to unmute it we use the unmute() function, but that function has a special limitation: it will only work if called in response to user input - this is so the user isn't suddenly surprised by sound effects before they have a chance to interact with our program. luckily cartLIB's input() function makes this easy: if we add an optional function fn a second parameter for input(), that function will get called once when the player first presses any button on the joypad. this allows us to fully initialize the audio system like this: var aud = audio(); var joy = {}; input(joy, function() { unmute(aud); });.

once the audio system is initialized and unmuted, we can use the play() function to start playing a tone, by specifying the frequency (in Hz) and waveform name (see #6.4.1 waveforms for the list of available waveforms). to silence the audio, we use the stop() function.

let's update our program, adding the ability to play a couple of notes by holding the 'x' and 'z' buttons.

load(function() { var vid = video(); var aud = audio(); var joy = {}; input(joy, function() { unmute(aud); }); clear(vid, vid.mem, 'black'); plot(vid, 'yellow', 0, 0, 15, 15, [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]); var set = []; image(vid.mem, 0, 0, 15, 15, set); var x = 50; var y = 50; var xDown = false; var zDown = false; update(function() { if (!ready(set)) return; clear(vid, vid.mem, 'black'); draw(vid, set[0], x, y); if (joy.pad['ArrowUp']) { y--; } if (joy.pad['ArrowDown']) { y++; } if (joy.pad['ArrowLeft']) { x--; } if (joy.pad['ArrowRight']) { x++; } if (joy.pad['x'] && !xDown) { play(aud.snd, aud.vol, 'square', 440); } else if (joy.pad['z'] && !zDown) { play(aud.snd, aud.vol, 'sawtooth', 880); } else if (!xDown && !zDown) { stop(aud.vol); } xDown = joy.pad['x']; zDown = joy.pad['z']; }); });

4.8 distribution

and that's it - you've made your first cartlet with graphics, input, and sound! but what if you want to share your program with others?

cartlets can be distributed anywhere that a bookmarklet can. typically, that means taking the URL from your cart link and making it the href of an HTML anchor tag, like this: <a href='/* YOUR CARTLET GOES HERE */'>my cartlet</a>. add that to any web page, and anyone can install your cartlet by adding it to their bookmarks!

alternatively, you can also simply copy and paste the cartlet URL and send it to a friend via email or text message. or if you have a collection of cartlets, you could package them together in a bookmarks HTML file, the way that cartletKIT is distributed.

(note: you may also want to consider distributing the joylet bookmarklet - found in cartletKIT's accessories folder - along with your cartlet, so that people using phones can play your game.)

that's the end of the getting started guide! if you want to learn more, you can read the API reference or standard library reference, or read the source code of the included example carts.

i hope you have fun making cartlets!

5. cart format

carts are data URLs with a maximum size of 64kb (roughly equivalent to the longest allowed URL length in firefox at the time of writing).

the data URL must begin with the data:text/html;, URL scheme (indicating the content of the URL is an HTML file). the structure of the cart HTML consists of the following tags (the order and number of these tags should not vary):

here is an example of a minimal cartlet with placeholders for the title and author, a blank icon, and an empty program:

data:text/html;,<!DOCTYPE HTML><html><head><title>CART NAME</title><meta name='author' content='AUTHOR NAME'><link rel='icon' type='image/png' href='data:image/png;base64,'><script>/* PROGRAM */</script></head></html>

6. API reference

cartlets are written in javascript, the exact specification of which is beyond the scope of this manual. in addition to the javascript language and its standard libraries (e.g. Math), a small API is provided for interacting with the system, input, display, and audio.

(by a mind-boggling coincidence, versions of these APIs are also implemented by all modern web browsers. links to the MDN pages for the equivalent browser APIs are included as a comparison point, but please keep in mind that the browser versions may include behavioral differences or additional features that are not supported by the cartlet spec.)

6.1 system

interfaces for initializing the program and its main loop, and responding to user input.

window(MDN)

the window interface represents the overall system. it is a global object that exists at program start.

window.onload (MDN)

the function assigned to this property will be called when the system is fully loaded. use this to initialize the main body of your program to ensure that all other APIs are ready.

window.onkeydown (MDN)

the function assigned to this property will be called whenever a button is pressed on the joypad. the callback function will receive as input an event object with a key property containing a name string representing the button that was pressed. button names are defined in #6.1.1 joypad buttons.

window.onkeyup (MDN)

the function assigned to this property will be called whenever a button is released on the joypad. he callback function will receive as input an event object with a key property containing a name string representing the button that was released. button names are defined in #6.1.1 joypad buttons.

window.requestAnimationFrame(fn) (MDN)

the input function fn will be called on the next cycle of the CPU, which should run at 60hz (aka 60 frames per second). use this to create the main update loop for your program.

6.1.1 joypad buttons

the cartlet joypad has 6 buttons. in your program the buttons are referred to using the name strings defined in the table below. (names are case sensitive.)

button
ArrowUp
ArrowDown
ArrowLeft
ArrowRight
z
x

6.2 display

interfaces for initializing the video output system and accessing video memory. interfaces for drawing are described in the graphics section.

document (MDN))

the document interface represents the video output system. it is a global object that exists at program start.

document.readyState (MDN)

this property represents the current state of the video system. when the system is fully loaded, it will contain the string 'complete', which indicates it is safe to hook up the video memory to the display using document.body.append().

document.createElement(type) (MDN)

this function initializes the video memory and returns an interface to it in the form of an HTMLCanvasElement object. for unknown reasons, an element type string id must be specified, however, the only supported type is 'canvas'. in addition, only one instance of the video system can exist at a time - attempting to create more may result in undefined behavior.

document.body.append(element) (MDN)

after the video system is initialized, the video memory must be connected to the display before anything can be rendered on screen - this function takes in the HTMLCanvasElement from document.createElement as its element parameter and points it at the display. (note: attempting to attach more than one canvas to the display will lead to undefined behavior.)

6.3 graphics

interfaces for drawing to the screen.

HTMLCanvasElement (MDN)

the interface to video memory that is created by document.createElement(). its contents are drawn to the screen on every frame. only one instance may exist at a time.

HTMLCanvasElement.width (MDN)

the width of the screen in pixels. this property is read-only and its value is always 300.

HTMLCanvasElement.height (MDN)

the height of the screen in pixels. this property is read-only and its value is always 150.

HTMLCanvasElement.style.backgroundColor (MDN)

this property sets the background color of the screen. when using CanvasRenderingContext2D.clearRect() this is the color that will be rendered in the cleared area. the color value must be one of the color name strings defined in #6.3.1 color palette.

HTMLCanvasElement.getContext(type) (MDN)

creates and returns the graphics interface of the type defined by input type string id. only one interface type is supported: '2d'. the graphics interface object is an object of type CanvasRenderingContext2D.

CanvasRenderingContext2D (MDN)

graphics interface containing functions for drawing to the HTMLCanvasElement video memory.

CanvasRenderingContext2D.fillStyle (MDN)

this property sets the color used when drawing rectangles with CanvasRenderingContext2D.fillRect(). the color value must be one of the color name strings defined in #6.3.1 color palette.

CanvasRenderingContext2D.clearRect(x, y, width, height) (MDN)

clears a rectangular area of the screen, resetting to the color stored in HTMLCanvasElement.style.background. the x and y parameters define the top left corner of the rectangle, and the width and height parameters define its dimensions (in pixels). when creating an image using window.createImageBitmap(), cleared areas of the screen are copied as transparent pixels, allowing for overlapping images.

CanvasRenderingContext2D.fillRect(x, y, width, height) (MDN)

fills a rectangular area of the screen with the color currently stored in CanvasRenderingContext2D.fillStyle. the x and y parameters define the top left corner of the rectangle, and the width and height parameters define its dimensions (in pixels).

CanvasRenderingContext2D.drawImage(image, x, y) (MDN)

draws an image (of type ImageBitmap) with its top left corner at the input x and y coordinates.

window.createImageBitmap(image, sx, sy, sw, sh) (MDN)

creates a new ImageBitmap from the source image. the source image can either be the video memory HTMLCanvasElement or another ImageBitmap. the parameters sx, sy, sw, and sh define a rectangle within the source image which will be copied to create the new image. returns a Promise that resolves into an ImageBitmap.

ImageBitmap (MDN)

an interface to a bitmap image that can be drawn to video memory using CanvasRenderingContext2D.drawImage().

ImageBitmap.width (MDN)

the height of the image in pixels (read-only).

ImageBitmap.height (MDN)

the width of the image in pixels (read-only).

6.3.1 color palette

the cartlet video system has a fixed 16 color palette (the same colors as the basic web colors defined by the HTML 4.01 spec in 1999). in your program, colors are referred to using the names defined in the table below. (names are case sensitive.)

color
white
silver
gray
black
red
maroon
yellow
olive
lime
green
aqua
teal
blue
navy
fuchsia
purple

6.4 sound

interfaces for playing sounds through the speaker.

AudioContext (MDN)

the interface to the sound system. must be constructed with new. initially, the sound system starts disabled and must be enabled using AudioContext.resume().

new AudioContext() (MDN)

constructs the AudioContext. only one instance may exist at a time - creating more will lead to undefined behavior.

AudioContext.state (MDN)

this property contains a string describing the current state of the sound system. if the sound chip has been enabled, it will be equal to 'running'.

AudioContext.destination (MDN)

this property is used to send the final output of the sound channel to the speaker. to play sounds an AudioNode must connect() to it.

AudioContext.resume() (MDN)

enables the sound system if it is suspended. must be called in response to joypad input (e.g. in the window.onkeydown event handler).

AudioContext.createOscillator() (MDN)

creates an OscillatorNode that generates a tone for output via the sound channel. only one instance may exist at a time - creating more will lead to undefined behavior.

AudioContext.createGain() (MDN)

creates a GainNode for controlling the volume of the sound channel. only one instance may exist at a time - creating more will lead to undefined behavior.

OscillatorNode (MDN)

an AudioNode that generates a tone with a specified frequency and waveform. created with AudioContext.createOscillator().

OscillatorNode.type (MDN)

this property is a string that defines the waveform to generate. the type value must be one of the waveform name strings defined in #6.4.1 waveforms.

OscillatorNode.frequency.value (MDN)

this property defines the frequency of the generated tone, in Hz. defaults to 440 Hz.

OscillatorNode.start() (MDN)

begin generating the tone.

OscillatorNode.connect(output) (MDN)

pipes the sound to the output node that is the next step in the sound generation process (typically the GainNode).

GainNode (MDN)

an AudioNode that controls the volume of the sound channel. created with AudioContext.createGain().

GainNode.gain.value (MDN)

this property modifies the volume of the input sound for this node. the value is a float, where 1.0 is full volume and 0.0 is silent. if the value is set outside that range, behavior is undefined.

GainNode.connect(output) (MDN)

pipes the modified sound to the output node that is the next step in the sound generation process (typically AudioContext.destination).

6.4.1 waveforms

the cartlet sound chip can generate 4 types of waveforms. in your program, waveforms are referred to using the names in the table below. (names are case sensitive.)

waveform
sine
square
sawtooth
triangle

7. standard library

building on top of the core APIs, the cartlet spec also defines a standard library called cartLIB.

using cartLIB (or even including it in your program) is optional. however, it can help take care of some system setup boilerplate and is generally more concise than the core APIs, which you may find more ergonomic to use in cartlet's space-constrained format. cartLIB is designed to be modular, so that you can mix and match library functions with core APIs (or with your own functions) as desired. in addition, cartLIB's implementation is intended to serve as a demonstration of how to use the APIs in a 'cartlet-ish' way.

7.1 cartLIB reference

cartLIB consists of the following 16 functions.

load(fn)

the function fn will be called when the system finishes loading, ensuring all features are ready to use.

update(fn)

the function fn will be called once every cycle, at 60 Hz (60 frames per second). use this for the main loop of your program.

video()

initializes the video system, and connects it to the display. returns the graphics interface (a CanvasRenderingContext2D) with an added property mem to expose the video memory interface (an HTMLCanvasElement).

audio()

initializes the components of the audio system, and connects them to the speaker. returns the audio interface (an AudioContext) with two added properties: the property snd exposes an interface to control the frequency and waveform of the generated sound (via an OscillatorNode), and the property vol exposes an interface to control the volume (via a GainNode).

input(joy, [fn])

initializes handlers for input events from the joypad. this function creates and updates a lookup table of buttons that are currently being held, and adds this table as a property pad to the input object joy so the caller can access it. for example, if input is initialized with var joy = {}; input(joy); then while the ArrowUp button is being held down joy.pad['ArrowUp'] will evaluate to true. see #6.1.1 joypad buttons for the full list of button names.

optionally, a function fn can be passed in as a second parameter. this function will be called once: the first time the user presses any button on the joypad. this can be used to perform initialization that must happen in response to user input, such as unmuting the audio system, like this: var joy = {}; input(joy, function() { unmute(aud); });.

clear(vid, mem, c, [x], [y], [w], [h])

given the graphics interface vid (of type CanvasRenderingContext2D) and the video memory (of type HTMLCanvasElement), this function sets the background color to c (see #6.3.1 color palette) and clears the screen. to clear only a portion of the screen, pass in the optional parameters x, y, w, and h for the rectangular area you want to clear. otherwise the whole screen will be cleared.

fill(vid, c, x, y, [w], [h])

given the graphics interface vid (of type CanvasRenderingContext2D), this function fills a rectangular area of the screen at position (x, y) with color c (see #6.3.1 color palette). pass in the optional parameters w and h to set the dimensions of the rectangle, otherwise a single pixel will be filled.

plot(vid, c, x, y, w, h, pix)

given the graphics interface vid (of type CanvasRenderingContext2D), this function plots a rectangular array of pixels of color c (see color table in section 6.3.1). the x and y parameters define the top left origin of the plot operation, and the w and h define the size of the plotting area.

the final parameter pix is the pixel data, which is expected in the form of an array of length w * h. pixels set to 1 will be filled with the color c, while pixels set to 0 will be skipped.
mem
cartletKIT comes with a tool called cartletPAINT which can be used to create pixel data for use with the plot function (see section 8.2 for more about the tool).

image(src, x, y, w, h, set)

given an image data source src (of either type HTMLCanvasElement or ImageBitmap), this function makes a copy of a rectangular area defined by the x, y, w, and h parameters and creates a new ImageBitmap. this allows copies of the same image to be quickly drawn in multiple places on screen, using the draw() library function.

because the image is created asynchronously, it can't be immediately returned from the function. instead the caller must pass in an image array set, to which the result ImageBitmap will be added when it is ready. until the image is done being created, its location in the array will contain the value false.

for example, if an image is created from a location in video memory with var set = []; image(vid.mem, 0, 0, 15, 15, set); then it can later be safely drawn to a new part of the screen with if (set[0]) { draw(vid, set[0], 100, 30); }.

you can also use the library function ready() to test that all images in an array have finished being created. a program with multiple images can use this to wait for them all to be ready before running any drawing logic, like so: var set = []; image(vid.mem, 0, 0, 15, 15, set); image(vid.mem, 15, 0, 15, 15, set); update(function() { if (!ready(set)) return; /* DRAWING LOGIC */ });.

draw(vid, img, x, y)

given the graphics interface vid (of type CanvasRenderingContext2D), this function draws the image img (of type ImageBitmap) with its top-left corner positioned at screen location (x, y).

tile(src, sz, set)

given an image data source src (of either type HTMLCanvasElement or ImageBitmap), this function splits the source image into square tiles of size sz and creates ImageBitmap copies that are added sequentially into the passed in image array set.

as with image(), the tile images are created asynchronously. until they are ready the elements of the image array contain the value false. the library function ready() can be used to verify whether all the images have loaded yet.

combined with the plot() and draw() functions, this enables using the screen as a canvas for creating a reusable tileset for your program's graphics, like so: var set = []; tile(vid.mem, 15, set); update(function() { if (!ready(set)) return; draw(vid, set[0], 10, 10); draw(vid, set[3], 100, 50); /* etc. */ });.

map(vid, set, sz, w, h, til)

given the graphics interface vid (of type CanvasRenderingContext2D), and an array set of ImageBitmap tiles with dimensions of sz by sz pixels, draws a tile map that is w tiles wide and h tiles tall.

the final parameter til is the tile data, which is expected in the form of an array of length w * h. the value of each tile element should be the index of the tile's image in the tileset array set.

as an example, the following draws a map of 15x15 pixel tiles to fill the whole 300x150 pixel screen: map(vid, set, 15, 20, 10, [ 0, 0, 1, 5, 0, 12, /* ... and so on */ ]);.

ready(set)

checks a set of images created by image() or tile() to see if they are all ready to use. if all are loaded, it will return true, otherwise it will return false.

unmute(aud)

enables the sound system aud (of type AudioContext). this must be done before any sounds can be played, since when the sound system is initialized by audio() it starts out disabled. unmuting the sound system is only allowed as a response to user input (see the input() function for more details).

play(snd, vol, w, f, [v])

given the sound interface snd (of type OscillatorNode) and volume interface vol (of type GainNode), this function starts playing a tone with the waveform w (see #6.4.1 waveforms) and frequency f (in Hz).

optionally, a volume level v (expected to be in the range from 0.0 to 1.0) can be included, otherwise the default volume 0.1 will be used.

for example, this will play the note A as a sawtooth wave: play(aud.snd, aud.vol, 'sawtooth', 440);.

stop(vol)

given the volume interface vol (of type GainNode), this function silences any currently playing audio. for example: stop(aud.vol);.

7.2 cartLIB source

below is the full source code for cartLIB.

if you want to use it in your program, you can copy it into the start of the script section of your cartlet. or you can use the standard template cart that comes with cartletKIT, which includes cartLIB by default.

function load(fn) { (document.readyState ! 'complete') ? window.onload = fn : fn(); } function update(fn) { function loop() { fn(); window.requestAnimationFrame(loop); } window.requestAnimationFrame(loop); } function video() { var mem = document.createElement('canvas'); document.body.append(mem); var vid = mem.getContext('2d'); vid.mem = mem; return vid; } function audio() { var aud = new AudioContext(); var snd = aud.createOscillator(); var vol = aud.createGain(); vol.gain.value = 0; snd.frequency.value = 0; snd.start(); snd.connect(vol); vol.connect(aud.destination); aud.snd = snd; aud.vol = vol; return aud; } function input(joy, fn) { joy.pad = {}; window.onkeydown = function(e) { if (fn) { fn(); delete fn; } joy.pad[e.key] = true; }; window.onkeyup = function(e) { delete joy.pad[e.key]; }; } function clear(vid, mem, c, x, y, w, h) { if (arguments.length <= 3) { x = y = 0; w = 300; h = 150; } mem.style.backgroundColor = c; vid.clearRect(x, y, w, h); } function fill(vid, c, x, y, w, h) { if (arguments.length <= 4) w = h = 1; vid.fillStyle = c; vid.fillRect(x, y, w, h); } function plot(vid, c, x, y, w, h, pix) { var sw = Math.abs(w); var sh = Math.abs(h); for (var px = 0; px < sw; px++) { for (var py = 0; py < sh; py++) { if (pix[(py * sw) + px] = 1) { fill(vid, c, x + (w > 0 ? px : (sw - 1) - px), y + (h > 0 ? py : (sh - 1) - py)); } } } } function image(src, x, y, w, h, set) { var i = set.length; set[i] = false; window.createImageBitmap(src, x, y, w, h).then(function(img) { set[i] = img; }); } function draw(vid, img, x, y) { vid.drawImage(img, x, y); } function tile(src, sz, set) { var w = Math.floor(src.width / sz); var h = Math.floor(src.height / sz); for (var ty = 0; ty < h; ty++) { for (var tx = 0; tx < w; tx++) { image(src, tx * sz, ty * sz, sz, sz, set); } } } function map(vid, set, sz, w, h, til) { for (var ty = 0; ty < h; ty++) { for (var tx = 0; tx < w; tx++) { var t = til[(ty * w) + tx]; var img = set[t]; draw(vid, img, tx * sz, ty * sz); } } } function ready(set) { for (var i = 0; i < set.length; i++) { if (set[i] = false) return false; } return true; } function unmute(aud) { if (aud.state ! 'running') { aud.resume(); } } function play(snd, vol, w, f, v) { if (arguments.length <= 4) v = 0.1; snd.type = w; snd.frequency.value = f; vol.gain.value = v; } function stop(vol) { vol.gain.value = 0; }

8. about

the cartlet manual and cartletKIT are by adam le doux

8.1 public domain dedication

the cartlet manual and cartletKIT are dedicated to the public domain under the CC0

8.2 acknowledgments

thank you to mary-margaret for your encouragement and bemused tolerance, and for suggesting the idea of making a game about a wizard in a garden <3