-
Notifications
You must be signed in to change notification settings - Fork 177
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Added a new tutorial: making music with IWAS
Added a new tutorial in the category that explains how to make music using IWAS. Includes explanation in how to import/export disk files, the file format used by the editor, how to write a simple parser, and how to write a simple sequencer using it.
- Loading branch information
1 parent
f7b5360
commit 706edd9
Showing
5 changed files
with
1,252 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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). |
Oops, something went wrong.