Skip to content

Commit 6227e90

Browse files
authored
Merge pull request #706 from JerwuQu/jwq_apu-midi-mode
APU Note Mode (alternative version of #334)
2 parents 71f5a70 + 537cda5 commit 6227e90

File tree

16 files changed

+139
-17
lines changed

16 files changed

+139
-17
lines changed

cli/assets/templates/assemblyscript/src/wasm4.ts

+1
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,7 @@ export const TONE_MODE3: u32 = 8;
121121
export const TONE_MODE4: u32 = 12;
122122
export const TONE_PAN_LEFT: u32 = 16;
123123
export const TONE_PAN_RIGHT: u32 = 32;
124+
export const TONE_NOTE_MODE: u32 = 64;
124125

125126
// ┌───────────────────────────────────────────────────────────────────────────┐
126127
// │ │

cli/assets/templates/c/src/wasm4.h

+1
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,7 @@ void tone (uint32_t frequency, uint32_t duration, uint32_t volume, uint32_t flag
117117
#define TONE_MODE4 12
118118
#define TONE_PAN_LEFT 16
119119
#define TONE_PAN_RIGHT 32
120+
#define TONE_NOTE_MODE 64
120121

121122
// ┌───────────────────────────────────────────────────────────────────────────┐
122123
// │ │

cli/assets/templates/c3/cart/src/wasm4.c3

+1
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ const TONE_MODE3 = 8;
9797
const TONE_MODE4 = 12;
9898
const TONE_PAN_LEFT = 16;
9999
const TONE_PAN_RIGHT = 32;
100+
const TONE_NOTE_MODE = 64;
100101

101102
// ┌───────────────────────────────────────────────────────────────────────────┐
102103
// │ │

cli/assets/templates/d/source/wasm4.d

+1
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ enum toneMode3 = 8;
101101
enum toneMode4 = 12;
102102
enum tonePanLeft = 16;
103103
enum tonePanRight = 32;
104+
enum toneNoteMode = 64;
104105

105106
// ┌───────────────────────────────────────────────────────────────────────────┐
106107
// │ │

cli/assets/templates/go/w4/wasm4.go

+1
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@ const TONE_MODE3 = 8
111111
const TONE_MODE4 = 12
112112
const TONE_PAN_LEFT = 16
113113
const TONE_PAN_RIGHT = 32
114+
const TONE_NOTE_MODE = 64
114115

115116
// ┌───────────────────────────────────────────────────────────────────────────┐
116117
// │ │

cli/assets/templates/nelua/src/wasm4.nelua

+1
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,7 @@ global TONE_MODE3 <comptime> = 8
122122
global TONE_MODE4 <comptime> = 12
123123
global TONE_PAN_LEFT <comptime> = 16
124124
global TONE_PAN_RIGHT <comptime> = 32
125+
global TONE_NOTE_MODE <comptime> = 64
125126

126127
-- ┌───────────────────────────────────────────────────────────────────────────┐
127128
-- │ │

cli/assets/templates/nim/src/cart/wasm4.nim

+1
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ const
4848
TONE_MODE4* = 12
4949
TONE_PAN_LEFT* = 16
5050
TONE_PAN_RIGHT* = 32
51+
TONE_NOTE_MODE* = 64
5152

5253
{.push importc, codegenDecl: "__attribute__((import_name(\"$2\"))) $1 $2$3".}
5354
proc blit*(data: ptr uint8; x: int32; y: int32; width: uint32; height: uint32;

cli/assets/templates/odin/src/w4/wasm4_wasm32.odin

+8-4
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,10 @@ Tone_Pan :: enum u32 {
118118
Left = 16,
119119
Right = 32,
120120
}
121+
Tone_Mode :: enum u32 {
122+
Frequency = 0,
123+
Note = 64,
124+
}
121125

122126
Tone_Duration :: struct {
123127
attack: u8, // in frames
@@ -135,13 +139,13 @@ foreign wasm4 {
135139
}
136140

137141
// Plays a sound tone.
138-
tone :: proc "c" (frequency: u32, duration: u32, volume_percent: u32, channel: Tone_Channel, duty_cycle := Tone_Duty_Cycle.Eigth, pan := Tone_Pan.Center) {
139-
flags := u32(channel) | u32(duty_cycle) | u32(pan)
142+
tone :: proc "c" (frequency: u32, duration: u32, volume_percent: u32, channel: Tone_Channel, duty_cycle := Tone_Duty_Cycle.Eigth, pan := Tone_Pan.Center, tone_mode := Tone_Mode.Frequency) {
143+
flags := u32(channel) | u32(duty_cycle) | u32(pan) | u32(tone_mode)
140144
internal_tone(frequency, duration, volume_percent, flags)
141145
}
142146

143-
tone_complex :: proc "c" (start_frequency, end_frequency: u16, duration: Tone_Duration, volume_percent: u32, channel: Tone_Channel, duty_cycle := Tone_Duty_Cycle.Eigth, pan := Tone_Pan.Center) {
144-
flags := u32(channel) | u32(duty_cycle) | u32(pan)
147+
tone_complex :: proc "c" (start_frequency, end_frequency: u16, duration: Tone_Duration, volume_percent: u32, channel: Tone_Channel, duty_cycle := Tone_Duty_Cycle.Eigth, pan := Tone_Pan.Center, tone_mode := Tone_Mode.Frequency) {
148+
flags := u32(channel) | u32(duty_cycle) | u32(pan) | u32(tone_mode)
145149
frequency := u32(start_frequency) | u32(end_frequency)<<16
146150
duration_in_frames := u32(duration.attack)<<24 | u32(duration.delay)<<16 | u32(duration.release)<<8 | u32(duration.sustain)
147151

cli/assets/templates/penne/src/wasm4.pn

+1
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ pub const TONE_MODE3: u32 = 8;
103103
pub const TONE_MODE4: u32 = 12;
104104
pub const TONE_PAN_LEFT: u32 = 16;
105105
pub const TONE_PAN_RIGHT: u32 = 32;
106+
pub const TONE_NOTE_MODE: u32 = 64;
106107

107108
// ┌───────────────────────────────────────────────────────────────────────────┐
108109
// │ │

cli/assets/templates/rust/src/wasm4.rs

+1
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,7 @@ pub const TONE_MODE3: u32 = 8;
193193
pub const TONE_MODE4: u32 = 12;
194194
pub const TONE_PAN_LEFT: u32 = 16;
195195
pub const TONE_PAN_RIGHT: u32 = 32;
196+
pub const TONE_NOTE_MODE: u32 = 64;
196197

197198
// ┌───────────────────────────────────────────────────────────────────────────┐
198199
// │ │

cli/assets/templates/wat/main.wat

+1
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@
109109
(global $TONE_MODE4 i32 (i32.const 12))
110110
(global $TONE_PAN_LEFT i32 (i32.const 16))
111111
(global $TONE_PAN_RIGHT i32 (i32.const 32))
112+
(global $TONE_NOTE_MODE i32 (i32.const 64))
112113

113114

114115
;; smiley

cli/assets/templates/zig/src/wasm4.zig

+1
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ pub const TONE_MODE3: u32 = 8;
100100
pub const TONE_MODE4: u32 = 12;
101101
pub const TONE_PAN_LEFT: u32 = 16;
102102
pub const TONE_PAN_RIGHT: u32 = 32;
103+
pub const TONE_NOTE_MODE: u32 = 64;
103104

104105
// ┌───────────────────────────────────────────────────────────────────────────┐
105106
// │ │

runtimes/native/src/apu.c

+25-9
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,10 @@
99

1010
typedef struct {
1111
/** Starting frequency. */
12-
uint16_t freq1;
12+
float freq1;
1313

1414
/** Ending frequency, or zero for no frequency transition. */
15-
uint16_t freq2;
15+
float freq2;
1616

1717
/** Time the tone was started. */
1818
unsigned long long startTime;
@@ -73,16 +73,23 @@ static int w4_min (int a, int b) {
7373
static int lerp (int value1, int value2, float t) {
7474
return value1 + t * (value2 - value1);
7575
}
76+
static float lerpf (float value1, float value2, float t) {
77+
return value1 + t * (value2 - value1);
78+
}
7679

7780
static int ramp (int value1, int value2, unsigned long long time1, unsigned long long time2) {
7881
if (time >= time2) return value2;
7982
float t = (float)(time - time1) / (time2 - time1);
8083
return lerp(value1, value2, t);
8184
}
85+
static float rampf (float value1, float value2, unsigned long long time1, unsigned long long time2) {
86+
float t = (float)(time - time1) / (time2 - time1);
87+
return lerpf(value1, value2, t);
88+
}
8289

83-
static uint16_t getCurrentFrequency (const Channel* channel) {
90+
static float getCurrentFrequency (const Channel* channel) {
8491
if (channel->freq2 > 0) {
85-
return ramp(channel->freq1, channel->freq2, channel->startTime, channel->releaseTime);
92+
return rampf(channel->freq1, channel->freq2, channel->startTime, channel->releaseTime);
8693
} else {
8794
return channel->freq1;
8895
}
@@ -116,6 +123,10 @@ static float polyblep (float phase, float phaseInc) {
116123
}
117124
}
118125

126+
static float midiFreq (uint8_t note, uint8_t bend) {
127+
return powf(2.0f, ((float)note - 69.0f + (float)bend / 256.0f) / 12.0f) * 440.0f;
128+
}
129+
119130
void w4_apuInit () {
120131
channels[3].noise.seed = 0x0001;
121132
}
@@ -139,6 +150,7 @@ void w4_apuTone (int frequency, int duration, int volume, int flags) {
139150
int channelIdx = flags & 0x03;
140151
int mode = (flags >> 2) & 0x3;
141152
int pan = (flags >> 4) & 0x3;
153+
int noteMode = flags & 0x40;
142154

143155
// TODO(2022-01-08): Thread safety
144156
Channel* channel = &channels[channelIdx];
@@ -147,9 +159,13 @@ void w4_apuTone (int frequency, int duration, int volume, int flags) {
147159
if (time > channel->releaseTime && ticks != channel->endTick) {
148160
channel->phase = (channelIdx == 2) ? 0.25 : 0;
149161
}
150-
151-
channel->freq1 = freq1;
152-
channel->freq2 = freq2;
162+
if (noteMode) {
163+
channel->freq1 = midiFreq(freq1 & 0xff, freq1 >> 8);
164+
channel->freq2 = (freq2 == 0) ? 0 : midiFreq(freq2 & 0xff, freq2 >> 8);
165+
} else {
166+
channel->freq1 = freq1;
167+
channel->freq2 = freq2;
168+
}
153169
channel->startTime = time;
154170
channel->attackTime = channel->startTime + SAMPLE_RATE*attack/60;
155171
channel->decayTime = channel->attackTime + SAMPLE_RATE*decay/60;
@@ -190,7 +206,7 @@ void w4_apuWriteSamples (int16_t* output, unsigned long frames) {
190206
Channel* channel = &channels[channelIdx];
191207

192208
if (time < channel->releaseTime || ticks == channel->endTick) {
193-
uint16_t freq = getCurrentFrequency(channel);
209+
float freq = getCurrentFrequency(channel);
194210
int16_t volume = getCurrentVolume(channel);
195211
int16_t sample;
196212

@@ -207,7 +223,7 @@ void w4_apuWriteSamples (int16_t* output, unsigned long frames) {
207223
sample = volume * channel->noise.lastRandom;
208224

209225
} else {
210-
float phaseInc = (float)freq / SAMPLE_RATE;
226+
float phaseInc = freq / SAMPLE_RATE;
211227
channel->phase += phaseInc;
212228

213229
if (channel->phase >= 1) {

runtimes/web/src/apu-worklet.ts

+12-4
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,10 @@ function polyblep (phase: number, phaseInc: number) {
6868
}
6969
}
7070

71+
function midiFreq (note: number, bend: number) {
72+
return Math.pow(2, (note - 69 + bend / 256) / 12) * 440;
73+
}
74+
7175
class APUProcessor extends AudioWorkletProcessor {
7276
time: number;
7377
ticks: number;
@@ -132,7 +136,6 @@ class APUProcessor extends AudioWorkletProcessor {
132136
tone (frequency: number, duration: number, volume: number, flags: number) {
133137
const freq1 = frequency & 0xffff;
134138
const freq2 = (frequency >> 16) & 0xffff;
135-
136139
const sustain = (duration & 0xff);
137140
const release = ((duration >> 8) & 0xff);
138141
const decay = ((duration >> 16) & 0xff);
@@ -144,16 +147,21 @@ class APUProcessor extends AudioWorkletProcessor {
144147
const channelIdx = flags & 0x3;
145148
const mode = (flags >> 2) & 0x3;
146149
const pan = (flags >> 4) & 0x3;
150+
const noteMode = flags & 0x40;
147151

148152
const channel = this.channels[channelIdx];
149153

150154
// Restart the phase if this channel wasn't already playing
151155
if (this.time > channel.releaseTime && this.ticks != channel.endTick) {
152156
channel.phase = (channelIdx == 2) ? 0.25 : 0;
153157
}
154-
155-
channel.freq1 = freq1;
156-
channel.freq2 = freq2;
158+
if (noteMode) {
159+
channel.freq1 = midiFreq(freq1 & 0xff, freq1 >> 8);
160+
channel.freq2 = (freq2 == 0) ? 0 : midiFreq(freq2 & 0xff, freq2 >> 8);
161+
} else {
162+
channel.freq1 = freq1;
163+
channel.freq2 = freq2;
164+
}
157165
channel.startTime = this.time;
158166
channel.attackTime = channel.startTime + ((SAMPLE_RATE*attack/60) >>> 0);
159167
channel.decayTime = channel.attackTime + ((SAMPLE_RATE*decay/60) >>> 0);

site/docs/guides/audio.md

+75
Original file line numberDiff line numberDiff line change
@@ -409,6 +409,81 @@ w4.tone(262, 60, 100, w4.TONE_PULSE1 | w4.TONE_PAN_LEFT);
409409

410410
</MultiLanguageCode>
411411

412+
## Note Mode
413+
414+
By enabling Note Mode with the `TONE_NOTE_MODE` flag, `tone` will use MIDI note numbers rather than frequencies.
415+
This results in more accurate pitches when playing musical notes.
416+
417+
You can read more about how this works in the [`tone(...)` documentation](../reference/functions#tone-frequency-duration-volume-flags).
418+
419+
Here's the same example as before, now playing middle-C using the MIDI note number 60:
420+
421+
<MultiLanguageCode>
422+
423+
```typescript
424+
w4.tone(60, 60, 100, w4.TONE_PULSE1 | w4.TONE_NOTE_MODE);
425+
```
426+
427+
```c
428+
tone(60, 60, 100, TONE_PULSE1 | TONE_NOTE_MODE);
429+
```
430+
431+
```c3
432+
w4::tone(60, 60, 100, w4::TONE_PULSE1 | w4::TONE_NOTE_MODE);
433+
```
434+
435+
```d
436+
w4.tone(60, 60, 100, w4.tonePulse1 | w4.toneNoteMode);
437+
```
438+
439+
```go
440+
w4.Tone(60, 60, 100, w4.TONE_PULSE1 | w4.TONE_NOTE_MODE)
441+
```
442+
443+
```lua
444+
tone(60, 60, 100, TONE_PULSE1 | TONE_NOTE_MODE)
445+
```
446+
447+
```nim
448+
tone(60, 60, 100, TONE_PULSE1 or TONE_NOTE_MODE)
449+
```
450+
451+
```odin
452+
w4.tone(60, 60, 100, .Pulse1, .Half, .Left, .Note)
453+
```
454+
455+
```penne
456+
tone(60, 60, 100, TONE_PULSE1 | TONE_NOTE_MODE);
457+
```
458+
459+
```porth
460+
$TONE_NOTE_MODE $TONE_PULSE1 or 100 60 60 tone
461+
```
462+
463+
```roland
464+
tone(60, 60, 100, TONE_PULSE1 | TONE_NOTE_MODE);
465+
```
466+
467+
```rust
468+
tone(60, 60, 100, TONE_PULSE1 | TONE_NOTE_MODE);
469+
```
470+
471+
```wasm
472+
(call $tone
473+
(i32.const 60)
474+
(i32.const 60)
475+
(i32.const 100)
476+
(i32.or
477+
(global.get $TONE_PULSE1)
478+
(global.get $TONE_NOTE_MODE)))
479+
```
480+
481+
```zig
482+
w4.tone(60, 60, 100, w4.TONE_PULSE1 | w4.TONE_NOTE_MODE);
483+
```
484+
485+
</MultiLanguageCode>
486+
412487
## Calculating Flags
413488

414489
Setting ADSR flags require the use of various bitwise and bitshift operations. This can be a little confusing to understand.

site/docs/reference/functions.md

+8
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ Plays a sound tone.
113113
| 0 - 1 | Channel (0-3): 0 = Pulse1, 1 = Pulse2, 2 = Triangle, 3 = Noise |
114114
| 2 - 3 | Mode (0-3): For pulse channels, the pulse wave duty cycle. 0 = 1/8, 1 = 1/4, 2 = 1/2, 3 = 3/4 |
115115
| 4 - 5 | Pan (0-2): 0 = Center, 1 = Left, 2 = Right |
116+
| 6 | Use *Note Mode* for frequencies: See below. |
116117

117118
The high bits of `frequency` can optionally describe a pitch slide effect:
118119

@@ -123,6 +124,13 @@ The high bits of `frequency` can optionally describe a pitch slide effect:
123124

124125
If the end frequency is non-zero, then the frequency is ramped linearly over the total duration of the tone.
125126

127+
If *Note Mode* is enabled, both the Start and End frequency values are instead interpreted as notes with pitch bend rather than frequencies:
128+
129+
| Frequency bits | Description |
130+
| --- | --- |
131+
| 0 - 7 | Note (0-255): Note number according to the MIDI specification, e.g. 60 = C4, 69 = A4 (440 Hz) |
132+
| 8 - 15 | Note bend (0-255): Bend note upwards. 0 = Nothing, 255 = One 256th away from the next note above |
133+
126134
The high bits of `duration` can optionally describe an ADSR volume envelope:
127135

128136
| Duration bits | Description |

0 commit comments

Comments
 (0)