Skip to content

Commit

Permalink
Merge pull request #721 from MrRafael-dev/iwas-tutorial
Browse files Browse the repository at this point in the history
Added a new tutorial: making music with IWAS
  • Loading branch information
aduros authored Jun 2, 2024
2 parents f0d9978 + 706edd9 commit f40ec0b
Show file tree
Hide file tree
Showing 5 changed files with 1,252 additions and 0 deletions.
274 changes: 274 additions & 0 deletions site/docs/tutorials/iwas/finishing-the-sequencer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,274 @@
# Finishing the sequencer

Here's the full code explained above, with a small song file included:

```typescript
import * as w4 from "./wasm4";

/** Music data. */
export const data: usize = memory.data<u8>([
0x49, 0x57, 0x41, 0x53, 0x00, 0x01, 0x03, 0x02, 0x00, 0x00, 0x00, 0x01,
0x01, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x05, 0x05, 0x00, 0x64, 0x01, 0x00, 0x00, 0x00, 0x00, 0x05, 0x0F, 0x00,
0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x01, 0x01, 0x00, 0x2B, 0x2B, 0x2B, 0x00, 0x00, 0x2B, 0x2B, 0x2B,
0x00, 0x2B, 0x2B, 0x2E, 0x2E, 0x29, 0x29, 0x00, 0x00, 0x00, 0x30, 0x30,
0x30, 0x30, 0x00, 0x30, 0x30, 0x00, 0x30, 0x30, 0x00, 0x30, 0x30, 0x00,
0x30, 0x30, 0x00, 0x30, 0x30, 0x00, 0x2E, 0x2E, 0x00, 0x2B, 0x2B, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x0F, 0x00, 0x64,
0x02, 0x00, 0x00, 0x00, 0x00, 0x05, 0x0F, 0x00, 0x64, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFB, 0x00, 0x01, 0x01, 0x00,
0x26, 0x26, 0x26, 0x00, 0x00, 0x26, 0x26, 0x26, 0x00, 0x26, 0x26, 0x29,
0x29, 0x24, 0x24, 0x00, 0x00, 0x00, 0x2B, 0x2B, 0x2B, 0x2B, 0x00, 0x2B,
0x2B, 0x00, 0x2B, 0x2B, 0x00, 0x2B, 0x2B, 0x00, 0x2B, 0x2B, 0x00, 0x2B,
0x2B, 0x00, 0x29, 0x29, 0x00, 0x26, 0x26, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x05, 0x0F, 0x00, 0x64, 0x00, 0x00, 0x00, 0x00,
0x00, 0x05, 0x0F, 0x00, 0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x21, 0x21, 0x21, 0x21,
0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x24, 0x24, 0x24, 0x24, 0x24,
0x24, 0x24, 0x24, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F,
0x1F, 0x00, 0x00, 0x00, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x21, 0x21, 0x21,
0x21, 0x21, 0x21, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00,
0x05, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x0F, 0x00,
0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x01, 0x01, 0x00, 0x24, 0x00, 0x00, 0x24, 0x00, 0x00, 0x2E, 0x00,
0x00, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x24, 0x00, 0x2E, 0x00, 0x00,
0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x24, 0x00, 0x00, 0x2E, 0x00, 0x00,
0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x24, 0x00, 0x00, 0x2E, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00]);

/** Note lookup table. */
const IWAS_NOTE_LOOKUP: usize = memory.data<u16>([
16, 17, 18, 19, 20, 21, 23, 24,
25, 27, 29, 30, 32, 34, 36, 38,
41, 43, 46, 49, 51, 55, 58, 61,
65, 69, 73, 77, 82, 87, 92, 98,
103, 110, 116, 123, 130, 138, 146, 155,
164, 174, 185, 196, 207, 220, 233, 246,
261, 277, 293, 311, 329, 349, 369, 392,
415, 440, 466, 493, 523, 554, 587, 622,
659, 698, 739, 783, 830, 880, 932, 987,
1046, 1108, 1174, 1244, 1318, 1396, 1479, 1567,
1661, 1760, 1864, 1975, 2093, 2217, 2349, 2489,
2637, 2793, 2959, 3135, 3322, 3520, 3729, 3951
]);

/** Speed lookup table. */
const IWAS_SPEED_LOOKUP: usize = memory.data<u8>([
0, 2, 4, 6, 8, 10, 20, 30, 40, 60, 80
]);

/** Speed counter. */
let counter: u8 = 0;

/** Note cursor. */
let cursor: u8 = 0;

/**
* Load `u16` value (big-endian).
*
* @param offset Address offset
*/
function loadUint16BE(offset: usize): u16 {
/** High byte. */
const hi: u16 = u16(load<u8>(offset));

/** Low byte. */
const lo: u16 = u16(load<u8>(offset + 1));

return (hi * 0x100) + lo;
}

/**
* Play music.
*/
function play(): void {
// Countdown delay...
counter = counter > 0? counter - 1: 0;

// Return if it's not ready to play yet...
if(counter > 0) {
return;
}

// When it reaches zero, it will iterate through each channel to play
// every note...
for(let ichannel: u8 = 0; ichannel < 4; ichannel += 1) {
/** Channel data offset. */
const offset: usize = data + 32 + (224 * ichannel);

/** Note value. */
const note: i8 = load<i8>(offset + 32 + (cursor % 192));

/** Check if channel is enabled. */
const isEnabled: bool = load<bool>(offset + 30);

// Ignore if is not enabled...
if(!isEnabled) {
return;
}

// Notes with a value of -128 represent a note break.
//
// Note breaks will mute a specific channel if it's still
// playing a tone.
if(note === -128) {
w4.tone(0, 0, 0, ichannel);
continue;
}

// Notes ranging from -96 and 96 are valid tones.
//
// Positive notes will use the main channel, while
// negative notes will use the shadow channel.
//
// Anything else, or 0, can be ignored.
else if(note !== 0 && note >= -96 && note <= 96) {
/** Instrument offset:
* - Main channel if positive.
* - Shadow channel if negative.
*/
const instrument: usize = offset + (note > 0? 0: 9);

/**
* Note index for the note lookup table.
*
* If negative, it's value will be mirrored.
* For instance, `-1` will become `1`.
*/
const inote: u8 = u8(Math.abs(note)) - 1;

// Get frequency from note lookup table...
const frq1: u32 = (load<u16>(IWAS_NOTE_LOOKUP + (inote * 2)));



// Get instrument data.
//
// A positive note index should point to the main channel, and
// a negative note index should point to the shadow channel.
//
// `frq2` is a big-endian value, so it must be converted.
//
// Values are converted to `u32` in order to better fit into the
// bitwise operations needed for the `tone` function to work.
const frq2: u32 = u32(loadUint16BE(instrument));
const atk: u32= u32(load<u8>(instrument + 2));
const dec: u32 = u32(load<u8>(instrument + 3));
const sus: u32 = u32(load<u8>(instrument + 4));
const rel: u32 = u32(load<u8>(instrument + 5));
const peak: u32 = u32(load<u8>(instrument + 6));
const vol: u32 = u32(load<u8>(instrument + 7));
const mode: u32 = u32(load<u8>(instrument + 8));
const pan: u32 = 0;

// Play tone:
w4.tone(
frq1 | (frq2 << 16),
(atk << 24) | (dec << 16) | sus | (rel << 8),
(peak << 8) | vol,
ichannel | (mode << 2) | (pan << 4)
);
}
}

/** Music length.
*
* To get the actual note count, the page number
* can be multiplied by 16.
*/
const length: u8 = load<u8>(data + 6) * 16;

/** Speed index for the speed lookup table. */
const ispeed: u8 = load<u8>(data + 7);

/** Speed value. */
const speed: u8 = load<u8>(IWAS_SPEED_LOOKUP + ispeed);

// Advance counter and cursor...
counter = speed;
cursor = cursor < length? cursor + 1: 0;
}

/**
* WASM-4 update event.
*/
export function update(): void {
play();
w4.text(`counter: ${counter}\nCursor: ${cursor}`, 0, 0);
}
```

And with that, we have a way of making music for WASM-4 using nothing but a small sound tool!

## Optional challenges

Here's some optional challenges you can take in order to make this a little more efficient:
- Parse and export it to your own music formats.
- Use a compression/decompression algorithm to reduce size.
- Make a longer music by chaining multiple files together.
- Break a song into patterns and tracks to reduce size.

If you're already familiar with a Digital Audio Workstation (DAW), you can also look for
a more advanced tool, like (w4on2)[https://github.com/JerwuQu/w4on2].

Keep in mind, however, that no matter which tool you use, there might not be any driver
available for the language you want to use. So consider understanding the format first,
then learn how to implement your own driver if necessary (use a pre-existing driver as a reference).
Loading

0 comments on commit f40ec0b

Please sign in to comment.