mirror of
https://github.com/vsariola/sointu.git
synced 2025-05-28 03:10:24 -04:00
wasm-music
This commit is contained in:
parent
70080c2b9d
commit
1ca16043bc
2
.gitignore
vendored
2
.gitignore
vendored
@ -29,3 +29,5 @@ out/
|
||||
.cache/
|
||||
actual_output/
|
||||
**/__debug_bin
|
||||
|
||||
node_modules
|
||||
|
BIN
chords.wasm
Normal file
BIN
chords.wasm
Normal file
Binary file not shown.
232
convert.js
Normal file
232
convert.js
Normal file
@ -0,0 +1,232 @@
|
||||
import yaml from 'json-to-pretty-yaml';
|
||||
import groove from './tests/grooveisinthecode.js';
|
||||
import { writeFile } from 'fs/promises';
|
||||
|
||||
const doc = {
|
||||
bpm: groove.BPM,
|
||||
rowsperbeat: 4,
|
||||
score: {
|
||||
length: groove.patterns[0].length,
|
||||
rowsperpattern: groove.patternsize,
|
||||
tracks: groove.instrumentPatternLists.map(track => {
|
||||
|
||||
const patterns = track.map(patternIndex => groove.patterns[patternIndex]);
|
||||
const order = track.map((patternIndex, ndx) => ndx);
|
||||
return {
|
||||
numvoices: 1,
|
||||
order,
|
||||
patterns
|
||||
}
|
||||
})
|
||||
},
|
||||
patch: []
|
||||
}
|
||||
|
||||
const addInstrument = (name, strdef) => {
|
||||
const units = strdef.split('\n')
|
||||
.map(opline => {
|
||||
console.log(opline);
|
||||
const opcode_params = opline.replace(/\s\s+/,'\t').split(/[\t]/);
|
||||
const opcode = opcode_params[0];
|
||||
const params = opcode_params[1].split(',');
|
||||
const parameters = {};
|
||||
params.forEach(param => {
|
||||
console.log(param)
|
||||
const paramParts = param.split('(');
|
||||
const paramName = paramParts[0];
|
||||
const paramValue = paramParts[1].substr(0, paramParts[1].length - 1);
|
||||
switch(paramName) {
|
||||
case 'ATTAC':
|
||||
parameters['attack'] = parseInt(paramValue);
|
||||
break;
|
||||
case 'DECAY':
|
||||
parameters['decay'] = parseInt(paramValue);
|
||||
break;
|
||||
case 'SUSTAIN':
|
||||
parameters['sustain'] = parseInt(paramValue);
|
||||
break;
|
||||
case 'RELEASE':
|
||||
parameters['release'] = parseInt(paramValue);
|
||||
break;
|
||||
case 'GAIN':
|
||||
parameters['gain'] = parseInt(paramValue);
|
||||
break;
|
||||
}
|
||||
});
|
||||
return {
|
||||
"type": {
|
||||
"GO4K_ENV": "envelope"
|
||||
}[opcode],
|
||||
parameters
|
||||
}
|
||||
});
|
||||
doc.patch.push({numvoices: 1, units});
|
||||
};
|
||||
|
||||
const addInstrumentGroup = () => { };
|
||||
const setGlobalParamDefs = () => { };
|
||||
|
||||
addInstrument('lead1', `GO4K_ENV ATTAC(24),DECAY(70),SUSTAIN(32),RELEASE(64),GAIN(60)
|
||||
GO4K_VCO TRANSPOSE(76),DETUNE(64),PHASE(0),GATES(85),COLOR(64),SHAPE(64),GAIN(64),FLAGS(NOISE)
|
||||
GO4K_DST DRIVE(64), SNHFREQ(0), FLAGS(0)
|
||||
GO4K_VCO TRANSPOSE(16),DETUNE(64),PHASE(0),GATES(85),COLOR(64),SHAPE(64),GAIN(128),FLAGS(TRISAW|LFO)
|
||||
GO4K_FOP OP(FOP_ADDP)
|
||||
GO4K_FST AMOUNT(40),DEST(10*MAX_UNIT_SLOTS+4+FST_SET)
|
||||
GO4K_FST AMOUNT(58),DEST(10*MAX_UNIT_SLOTS+5+FST_SET)
|
||||
GO4K_FOP OP(FOP_POP)
|
||||
GO4K_VCO TRANSPOSE(76),DETUNE(64),PHASE(0),GATES(85),COLOR(64),SHAPE(64),GAIN(128),FLAGS(SINE)
|
||||
GO4K_VCO TRANSPOSE(83),DETUNE(64),PHASE(64),GATES(85),COLOR(90),SHAPE(64),GAIN(48),FLAGS(SINE)
|
||||
GO4K_VCF FREQUENCY(77),RESONANCE(24),VCFTYPE(BANDPASS)
|
||||
GO4K_FOP OP(FOP_ADDP)
|
||||
GO4K_FOP OP(FOP_PUSH)
|
||||
GO4K_VCF FREQUENCY(32),RESONANCE(128),VCFTYPE(BANDPASS)
|
||||
GO4K_FOP OP(FOP_XCH)
|
||||
GO4K_VCF FREQUENCY(96),RESONANCE(128),VCFTYPE(BANDPASS)
|
||||
GO4K_FOP OP(FOP_ADDP)
|
||||
GO4K_FOP OP(FOP_MULP)
|
||||
GO4K_DLL PREGAIN(64),DRY(100),FEEDBACK(70),DAMP(64),FREQUENCY(56),DEPTH(48),DELAY(17),COUNT(2)
|
||||
GO4K_PAN PANNING(36)
|
||||
GO4K_OUT GAIN(65),AUXSEND(80)`);
|
||||
addInstrument('bass', `GO4K_ENV ATTAC(32),DECAY(70),SUSTAIN(60),RELEASE(75),GAIN(32)
|
||||
GO4K_FST AMOUNT(120),DEST(0*MAX_UNIT_SLOTS+2+FST_SET)
|
||||
GO4K_VCO TRANSPOSE(76),DETUNE(64),PHASE(32),GATES(85),COLOR(80),SHAPE(64),GAIN(128),FLAGS(PULSE)
|
||||
GO4K_VCO TRANSPOSE(76),DETUNE(72),PHASE(32),GATES(85),COLOR(96),SHAPE(64),GAIN(128),FLAGS(TRISAW)
|
||||
GO4K_VCO TRANSPOSE(32),DETUNE(64),PHASE(0),GATES(85),COLOR(64),SHAPE(90),GAIN(128),FLAGS(SINE|LFO)
|
||||
GO4K_FST AMOUNT(68),DEST(2*MAX_UNIT_SLOTS+2+FST_SET)
|
||||
GO4K_FST AMOUNT(60),DEST(3*MAX_UNIT_SLOTS+2+FST_SET)
|
||||
GO4K_FOP OP(FOP_POP)
|
||||
GO4K_FOP OP(FOP_ADDP)
|
||||
GO4K_FOP OP(FOP_MULP)
|
||||
GO4K_VCF FREQUENCY(18),RESONANCE(64),VCFTYPE(PEAK)
|
||||
GO4K_VCF FREQUENCY(32),RESONANCE(48),VCFTYPE(LOWPASS)
|
||||
GO4K_DST DRIVE(88), SNHFREQ(128), FLAGS(0)
|
||||
GO4K_PAN PANNING(80)
|
||||
GO4K_DLL PREGAIN(64),DRY(128),FEEDBACK(96),DAMP(64),FREQUENCY(0),DEPTH(0),DELAY(17),COUNT(1) ; ERROR
|
||||
GO4K_FOP OP(FOP_XCH)
|
||||
GO4K_DLL PREGAIN(64),DRY(128),FEEDBACK(64),DAMP(64),FREQUENCY(0),DEPTH(0),DELAY(18),COUNT(1) ; ERROR
|
||||
GO4K_FOP OP(FOP_XCH)
|
||||
GO4K_OUT GAIN(18), AUXSEND(10)`);
|
||||
|
||||
const pad = (panning) => `GO4K_ENV ATTAC(32),DECAY(70),SUSTAIN(80),RELEASE(70),GAIN(80)
|
||||
GO4K_VCO TRANSPOSE(16),DETUNE(64),PHASE(0),GATES(85),COLOR(64),SHAPE(64),GAIN(120),FLAGS(TRISAW|LFO)
|
||||
GO4K_FST AMOUNT(80),DEST(13*MAX_UNIT_SLOTS+4+FST_SET)
|
||||
GO4K_FST AMOUNT(48),DEST(9*MAX_UNIT_SLOTS+4+FST_SET)
|
||||
GO4K_FST AMOUNT(58),DEST(9*MAX_UNIT_SLOTS+5+FST_SET)
|
||||
GO4K_FOP OP(FOP_POP)
|
||||
GO4K_VCO TRANSPOSE(76),DETUNE(64),PHASE(0),GATES(85),COLOR(30),SHAPE(64),GAIN(128),FLAGS(TRISAW)
|
||||
GO4K_VCO TRANSPOSE(76),DETUNE(64),PHASE(32),GATES(85),COLOR(90),SHAPE(64),GAIN(64),FLAGS(TRISAW)
|
||||
GO4K_VCO TRANSPOSE(64),DETUNE(64),PHASE(64),GATES(85),COLOR(64),SHAPE(64),GAIN(8),FLAGS(NOISE)
|
||||
GO4K_VCF FREQUENCY(105),RESONANCE(20),VCFTYPE(BANDPASS)
|
||||
GO4K_FOP OP(FOP_ADDP)
|
||||
GO4K_FOP OP(FOP_ADDP)
|
||||
GO4K_FOP OP(FOP_PUSH)
|
||||
GO4K_VCF FREQUENCY(14),RESONANCE(128),VCFTYPE(BANDPASS)
|
||||
GO4K_FOP OP(FOP_XCH)
|
||||
GO4K_VCF FREQUENCY(70),RESONANCE(128),VCFTYPE(BANDPASS)
|
||||
GO4K_FOP OP(FOP_ADDP)
|
||||
GO4K_FOP OP(FOP_MULP)
|
||||
GO4K_DLL PREGAIN(64),DRY(128),FEEDBACK(64),DAMP(64),FREQUENCY(0),DEPTH(0),DELAY(11),COUNT(1)
|
||||
GO4K_PAN PANNING(${panning})
|
||||
GO4K_DLL PREGAIN(64),DRY(128),FEEDBACK(64),DAMP(64),FREQUENCY(0),DEPTH(0),DELAY(11),COUNT(1)
|
||||
GO4K_OUT GAIN(32), AUXSEND(64)`;
|
||||
|
||||
addInstrument('pad1', pad(30));
|
||||
addInstrument('pad2', pad(60));
|
||||
addInstrument('pad3', pad(100));
|
||||
addInstrumentGroup('pads', ['pad1', 'pad2', 'pad3']);
|
||||
addInstrument('kick', `GO4K_ENV ATTAC(0),DECAY(64),SUSTAIN(96),RELEASE(64),GAIN(90)
|
||||
GO4K_FST AMOUNT(128),DEST(0*MAX_UNIT_SLOTS+2+FST_SET)
|
||||
GO4K_ENV ATTAC(0),DECAY(70),SUSTAIN(0),RELEASE(0),GAIN(100)
|
||||
GO4K_DST DRIVE(32), SNHFREQ(128), FLAGS(0)
|
||||
GO4K_FST AMOUNT(80),DEST(6*MAX_UNIT_SLOTS+1+FST_SET)
|
||||
GO4K_FOP OP(FOP_POP)
|
||||
GO4K_VCO TRANSPOSE(46),DETUNE(64),PHASE(0),GATES(85),COLOR(64),SHAPE(64),GAIN(48),FLAGS(TRISAW)
|
||||
GO4K_FOP OP(FOP_MULP)
|
||||
GO4K_FOP OP(FOP_LOADNOTE)
|
||||
GO4K_FOP OP(FOP_MULP)
|
||||
GO4K_PAN PANNING(40)
|
||||
GO4K_OUT GAIN(12), AUXSEND(1)`);
|
||||
addInstrument('snare', `GO4K_ENV ATTAC(0),DECAY(72),SUSTAIN(0),RELEASE(72),GAIN(25)
|
||||
GO4K_FST AMOUNT(128),DEST(0*MAX_UNIT_SLOTS+2+FST_SET)
|
||||
GO4K_ENV ATTAC(0),DECAY(56),SUSTAIN(0),RELEASE(0),GAIN(128)
|
||||
GO4K_FST AMOUNT(108),DEST(6*MAX_UNIT_SLOTS+1+FST_SET)
|
||||
GO4K_FST AMOUNT(72),DEST(7*MAX_UNIT_SLOTS+1+FST_SET)
|
||||
GO4K_FOP OP(FOP_POP)
|
||||
GO4K_VCO TRANSPOSE(16),DETUNE(64),PHASE(0),GATES(85),COLOR(64),SHAPE(32),GAIN(64),FLAGS(SINE)
|
||||
GO4K_VCO TRANSPOSE(32),DETUNE(64),PHASE(0),GATES(85),COLOR(64),SHAPE(80),GAIN(64),FLAGS(SINE)
|
||||
GO4K_VCO TRANSPOSE(64),DETUNE(64),PHASE(0),GATES(85),COLOR(64),SHAPE(10),GAIN(64),FLAGS(NOISE)
|
||||
GO4K_VCF FREQUENCY(96),RESONANCE(128),VCFTYPE(LOWPASS)
|
||||
GO4K_VCO TRANSPOSE(64),DETUNE(64),PHASE(0),GATES(85),COLOR(64),SHAPE(64),GAIN(16),FLAGS(NOISE)
|
||||
GO4K_FOP OP(FOP_ADDP)
|
||||
GO4K_FOP OP(FOP_ADDP)
|
||||
GO4K_FOP OP(FOP_ADDP)
|
||||
GO4K_FOP OP(FOP_MULP)
|
||||
GO4K_VCF FREQUENCY(22),RESONANCE(32),VCFTYPE(HIGHPASS)
|
||||
GO4K_FOP OP(FOP_LOADNOTE)
|
||||
GO4K_FOP OP(FOP_MULP)
|
||||
GO4K_PAN PANNING(56)
|
||||
GO4K_OUT GAIN(4), AUXSEND(2)
|
||||
GO4K_FLD AMOUNT(100)
|
||||
GO4K_FST AMOUNT(64),DEST(6*MAX_UNIT_SLOTS+1+FST_SET)
|
||||
GO4K_FST AMOUNT(64),DEST(7*MAX_UNIT_SLOTS+1+FST_SET)
|
||||
GO4K_FOP OP(FOP_POP)`);
|
||||
|
||||
addInstrumentGroup('drums', ['kick', 'snare']);
|
||||
addInstrument('drivelead', `GO4K_ENV ATTAC(32),DECAY(64),SUSTAIN(90),RELEASE(48),GAIN(35)
|
||||
GO4K_VCO TRANSPOSE(76),DETUNE(70),PHASE(0),GATES(127),COLOR(10),SHAPE(110),GAIN(128),FLAGS(PULSE)
|
||||
GO4K_VCO TRANSPOSE(76),DETUNE(64),PHASE(64),GATES(127),COLOR(20),SHAPE(20),GAIN(128),FLAGS(PULSE)
|
||||
GO4K_VCO TRANSPOSE(76),DETUNE(58),PHASE(128),GATES(127),COLOR(30),SHAPE(110),GAIN(128),FLAGS(PULSE)
|
||||
GO4K_FOP OP(FOP_ADDP)
|
||||
GO4K_FOP OP(FOP_ADDP)
|
||||
GO4K_FOP OP(FOP_MULP)
|
||||
GO4K_VCO TRANSPOSE(48),DETUNE(64),PHASE(64),GATES(0x80),COLOR(127),SHAPE(64),GAIN(50),FLAGS(SINE|LFO)
|
||||
GO4K_FST AMOUNT(104),DEST(10*MAX_UNIT_SLOTS+4+FST_SET)
|
||||
GO4K_FOP OP(FOP_POP)
|
||||
GO4K_VCF FREQUENCY(94),RESONANCE(120),VCFTYPE(LOWPASS)
|
||||
GO4K_DLL PREGAIN(64),DRY(100),FEEDBACK(70),DAMP(64),FREQUENCY(56),DEPTH(48),DELAY(17),COUNT(2)
|
||||
GO4K_PAN PANNING(50)
|
||||
GO4K_DLL PREGAIN(64),DRY(100),FEEDBACK(70),DAMP(64),FREQUENCY(56),DEPTH(48),DELAY(16),COUNT(1)
|
||||
GO4K_OUT GAIN(10),AUXSEND(52)`);
|
||||
|
||||
addInstrument('hihat', `GO4K_ENV ATTAC(0),DECAY(64),SUSTAIN(15),RELEASE(32),GAIN(100)
|
||||
GO4K_VCO TRANSPOSE(64),DETUNE(64),PHASE(64),GATES(85),COLOR(64),SHAPE(64),GAIN(128),FLAGS(NOISE)
|
||||
GO4K_FOP OP(FOP_MULP)
|
||||
GO4K_VCF FREQUENCY(128),RESONANCE(128),VCFTYPE(BANDPASS)
|
||||
GO4K_FOP OP(FOP_LOADNOTE)
|
||||
GO4K_FOP OP(FOP_MULP)
|
||||
GO4K_PAN PANNING(56)
|
||||
GO4K_OUT GAIN(44), AUXSEND(0)`);
|
||||
addInstrument('squarelead', `GO4K_ENV ATTAC(0),DECAY(70),SUSTAIN(0),RELEASE(70),GAIN(64)
|
||||
GO4K_ENV ATTAC(0),DECAY(80),SUSTAIN(0),RELEASE(80),GAIN(128)
|
||||
GO4K_FST AMOUNT(96),DEST(18*MAX_UNIT_SLOTS+4+FST_SET)
|
||||
GO4K_FST AMOUNT(96),DEST(19*MAX_UNIT_SLOTS+4+FST_SET)
|
||||
GO4K_FOP OP(FOP_POP)
|
||||
GO4K_FOP OP(FOP_PUSH)
|
||||
GO4K_VCO TRANSPOSE(72),DETUNE(64),PHASE(0),GATES(85),COLOR(64),SHAPE(64),GAIN(128),FLAGS(SINE|LFO)
|
||||
GO4K_FOP OP(FOP_ADDP)
|
||||
GO4K_FST AMOUNT(32),DEST(13*MAX_UNIT_SLOTS+5+FST_SET)
|
||||
GO4K_FOP OP(FOP_POP)
|
||||
GO4K_VCO TRANSPOSE(96),DETUNE(64),PHASE(0),GATES(85),COLOR(64),SHAPE(64),GAIN(128),FLAGS(SINE|LFO)
|
||||
GO4K_FST AMOUNT(80),DEST(15*MAX_UNIT_SLOTS+2+FST_SET)
|
||||
GO4K_FOP OP(FOP_POP)
|
||||
GO4K_VCO TRANSPOSE(76),DETUNE(64),PHASE(0),GATES(85),COLOR(128),SHAPE(64),GAIN(128),FLAGS(PULSE)
|
||||
GO4K_VCO TRANSPOSE(88),DETUNE(64),PHASE(0),GATES(85),COLOR(100),SHAPE(64),GAIN(128),FLAGS(TRISAW)
|
||||
GO4K_FOP OP(FOP_ADDP)
|
||||
GO4K_VCF FREQUENCY(82),RESONANCE(96),VCFTYPE(LOWPASS)
|
||||
GO4K_VCF FREQUENCY(64),RESONANCE(96),VCFTYPE(LOWPASS)
|
||||
GO4K_FOP OP(FOP_MULP)
|
||||
GO4K_DLL PREGAIN(64),DRY(100),FEEDBACK(70),DAMP(64),FREQUENCY(56),DEPTH(48),DELAY(17),COUNT(2)
|
||||
GO4K_PAN PANNING(64)
|
||||
GO4K_DLL PREGAIN(64),DRY(100),FEEDBACK(70),DAMP(64),FREQUENCY(56),DEPTH(48),DELAY(18),COUNT(2)
|
||||
GO4K_OUT GAIN(16), AUXSEND(32)`);
|
||||
|
||||
setGlobalParamDefs(`GO4K_ACC ACCTYPE(AUX)
|
||||
GO4K_DLL PREGAIN(55),DRY(70),FEEDBACK(100),DAMP(64),FREQUENCY(0),DEPTH(0),DELAY(0),COUNT(8)
|
||||
GO4K_FOP OP(FOP_XCH)
|
||||
GO4K_DLL PREGAIN(55),DRY(70),FEEDBACK(100),DAMP(64),FREQUENCY(0),DEPTH(0),DELAY(8),COUNT(8)
|
||||
GO4K_FOP OP(FOP_XCH)
|
||||
GO4K_ACC ACCTYPE(OUTPUT)
|
||||
GO4K_FOP OP(FOP_ADDP2)
|
||||
GO4K_OUT GAIN(128), AUXSEND(0)`);
|
||||
|
||||
await writeFile('tests/groove.yaml', yaml.stringify(doc));
|
316
groove.wat
Normal file
316
groove.wat
Normal file
@ -0,0 +1,316 @@
|
||||
(module
|
||||
|
||||
|
||||
;;------------------------------------------------------------------------------
|
||||
;; Import the difficult math functions from javascript
|
||||
;; (seriously now, it's 2020)
|
||||
;;------------------------------------------------------------------------------
|
||||
(func $pow (import "m" "pow") (param f32) (param f32) (result f32))
|
||||
(func $log2 (import "m" "log2") (param f32) (result f32))
|
||||
(func $sin (import "m" "sin") (param f32) (result f32))
|
||||
|
||||
;;------------------------------------------------------------------------------
|
||||
;; Types. Only useful to define the jump table type, which is
|
||||
;; (int stereo) void
|
||||
;;------------------------------------------------------------------------------
|
||||
(type $opcode_func_signature (func (param i32)))
|
||||
|
||||
;;------------------------------------------------------------------------------
|
||||
;; The one and only memory
|
||||
;;------------------------------------------------------------------------------
|
||||
(memory (export "m") 175)
|
||||
|
||||
;;------------------------------------------------------------------------------
|
||||
;; Globals. Putting all with same initialization value should compress most
|
||||
;;------------------------------------------------------------------------------
|
||||
(global $WRK (mut i32) (i32.const 0))
|
||||
(global $COM (mut i32) (i32.const 0))
|
||||
(global $VAL (mut i32) (i32.const 0))
|
||||
(global $globaltick (mut i32) (i32.const 0))
|
||||
(global $row (mut i32) (i32.const 0))
|
||||
(global $pattern (mut i32) (i32.const 0))
|
||||
(global $sample (mut i32) (i32.const 0))
|
||||
(global $voice (mut i32) (i32.const 0))
|
||||
(global $voicesRemain (mut i32) (i32.const 0))
|
||||
(global $randseed (mut i32) (i32.const 1))
|
||||
(global $sp (mut i32) (i32.const 1024))
|
||||
(global $outputBufPtr (mut i32) (i32.const 132352))
|
||||
;; TODO: only export start and length with certain compiler options; in demo use, they can be hard coded
|
||||
;; in the intro
|
||||
(global $outputStart (export "s") i32 (i32.const 132352))
|
||||
(global $outputLength (export "l") i32 (i32.const 11288576))
|
||||
(global $output16bit (export "t") i32 (i32.const 0))
|
||||
|
||||
|
||||
;;------------------------------------------------------------------------------
|
||||
;; Functions to emulate FPU stack in software
|
||||
;;------------------------------------------------------------------------------
|
||||
(func $peek (result f32)
|
||||
(f32.load (global.get $sp))
|
||||
)
|
||||
|
||||
(func $peek2 (result f32)
|
||||
(f32.load offset=4 (global.get $sp))
|
||||
)
|
||||
|
||||
(func $pop (result f32)
|
||||
(call $peek)
|
||||
(global.set $sp (i32.add (global.get $sp) (i32.const 4)))
|
||||
)
|
||||
|
||||
(func $push (param $value f32)
|
||||
(global.set $sp (i32.sub (global.get $sp) (i32.const 4)))
|
||||
(f32.store (global.get $sp) (local.get $value))
|
||||
)
|
||||
|
||||
;;------------------------------------------------------------------------------
|
||||
;; Helper functions
|
||||
;;------------------------------------------------------------------------------
|
||||
(func $swap (param f32 f32) (result f32 f32) ;; x,y -> y,x
|
||||
local.get 1
|
||||
local.get 0
|
||||
)
|
||||
|
||||
(func $scanValueByte (result i32) ;; scans positions $VAL for a byte, incrementing $VAL afterwards
|
||||
(i32.load8_u (global.get $VAL)) ;; in other words: returns byte [$VAL++]
|
||||
(global.set $VAL (i32.add (global.get $VAL) (i32.const 1))) ;; $VAL++
|
||||
)
|
||||
|
||||
;;------------------------------------------------------------------------------
|
||||
;; "Entry point" for the player
|
||||
;;------------------------------------------------------------------------------
|
||||
(start $render) ;; we run render automagically when the module is instantiated
|
||||
|
||||
(func $render (param)
|
||||
loop $pattern_loop
|
||||
(global.set $row (i32.const 0))
|
||||
loop $row_loop
|
||||
(call $su_update_voices)
|
||||
(global.set $sample (i32.const 0))
|
||||
loop $sample_loop
|
||||
(global.set $COM (i32.const 608))
|
||||
(global.set $VAL (i32.const 631))
|
||||
(global.set $WRK (i32.const 1216))
|
||||
(global.set $voice (i32.const 1216))
|
||||
(global.set $voicesRemain (i32.const 10))
|
||||
(call $su_run_vm)
|
||||
(i64.store (global.get $outputBufPtr) (i64.load (i32.const 1184))) ;; load the sample from left & right channels as one 64bit int and store it in the address pointed by outputBufPtr
|
||||
(global.set $outputBufPtr (i32.add (global.get $outputBufPtr) (i32.const 8))) ;; advance outputbufptr
|
||||
(i64.store (i32.const 1184) (i64.const 0)) ;; clear the left and right ports
|
||||
(global.set $sample (i32.add (global.get $sample) (i32.const 1)))
|
||||
(global.set $globaltick (i32.add (global.get $globaltick) (i32.const 1)))
|
||||
(br_if $sample_loop (i32.lt_s (global.get $sample) (i32.const 5512)))
|
||||
end
|
||||
(global.set $row (i32.add (global.get $row) (i32.const 1)))
|
||||
(br_if $row_loop (i32.lt_s (global.get $row) (i32.const 16)))
|
||||
end
|
||||
(global.set $pattern (i32.add (global.get $pattern) (i32.const 1)))
|
||||
(br_if $pattern_loop (i32.lt_s (global.get $pattern) (i32.const 16)))
|
||||
end
|
||||
)
|
||||
;; the simple implementation of update_voices: each track has exactly one voice
|
||||
(func $su_update_voices (local $si i32) (local $di i32) (local $tracksRemaining i32) (local $note i32)
|
||||
(local.set $tracksRemaining (i32.const 10))
|
||||
(local.set $si (global.get $pattern))
|
||||
(local.set $di (i32.const 1216))
|
||||
loop $track_loop
|
||||
(i32.load8_u offset=448 (local.get $si))
|
||||
(i32.mul (i32.const 16))
|
||||
(i32.add (global.get $row))
|
||||
(i32.load8_u offset=0)
|
||||
(local.tee $note)
|
||||
(if (i32.ne (i32.const 1))(then
|
||||
(i32.store offset=4 (local.get $di) (i32.const 1)) ;; release the note
|
||||
(if (i32.gt_u (local.get $note) (i32.const 1))(then
|
||||
(memory.fill (local.get $di) (i32.const 0) (i32.const 4096))
|
||||
(i32.store (local.get $di) (local.get $note))
|
||||
))
|
||||
))
|
||||
(local.set $di (i32.add (local.get $di) (i32.const 4096)))
|
||||
(local.set $si (i32.add (local.get $si) (i32.const 16)))
|
||||
(br_if $track_loop (local.tee $tracksRemaining (i32.sub (local.get $tracksRemaining) (i32.const 1))))
|
||||
end
|
||||
)
|
||||
|
||||
;;-------------------------------------------------------------------------------
|
||||
;; su_run_vm function: runs the entire virtual machine once, creating 1 sample
|
||||
;;-------------------------------------------------------------------------------
|
||||
(func $su_run_vm (local $opcodeWithStereo i32) (local $opcode i32) (local $paramNum i32) (local $paramX4 i32) (local $WRKplusparam i32)
|
||||
loop $vm_loop
|
||||
(local.set $opcodeWithStereo (i32.load8_u (global.get $COM)))
|
||||
(global.set $COM (i32.add (global.get $COM) (i32.const 1))) ;; move to next instruction
|
||||
(global.set $WRK (i32.add (global.get $WRK) (i32.const 64))) ;; move WRK to next unit
|
||||
(if (local.tee $opcode (i32.shr_u (local.get $opcodeWithStereo) (i32.const 1)))(then ;; if $opcode = $opcodeStereo >> 1; $opcode != 0 {
|
||||
(local.set $paramNum (i32.const 0))
|
||||
(local.set $paramX4 (i32.const 0))
|
||||
loop $transform_values_loop
|
||||
(if (i32.lt_u (local.get $paramNum) (i32.load8_u offset=695 (local.get $opcode)))(then ;;(i32.ge (local.get $paramNum) (i32.load8_u (local.get $opcode))) /*TODO: offset to transformvalues
|
||||
(local.set $WRKplusparam (i32.add (global.get $WRK) (local.get $paramX4)))
|
||||
(f32.store offset=1024
|
||||
(local.get $paramX4)
|
||||
(f32.add
|
||||
(f32.mul
|
||||
(f32.convert_i32_u (call $scanValueByte))
|
||||
(f32.const 0.0078125) ;; scale from 0-128 to 0.0 - 1.0
|
||||
)
|
||||
(f32.load offset=32 (local.get $WRKplusparam)) ;; add modulation
|
||||
)
|
||||
)
|
||||
(f32.store offset=32 (local.get $WRKplusparam) (f32.const 0.0)) ;; clear modulations
|
||||
(local.set $paramNum (i32.add (local.get $paramNum) (i32.const 1))) ;; $paramNum++
|
||||
(local.set $paramX4 (i32.add (local.get $paramX4) (i32.const 4)))
|
||||
br $transform_values_loop ;; continue looping
|
||||
))
|
||||
;; paramNum was >= the number of parameters to transform, exiting loop
|
||||
end
|
||||
(call_indirect (type $opcode_func_signature) (i32.and (local.get $opcodeWithStereo) (i32.const 1)) (local.get $opcode))
|
||||
)(else ;; advance to next voice
|
||||
(global.set $voice (i32.add (global.get $voice) (i32.const 4096))) ;; advance to next voice
|
||||
(global.set $WRK (global.get $voice)) ;; set WRK point to beginning of voice
|
||||
(global.set $voicesRemain (i32.sub (global.get $voicesRemain) (i32.const 1)))
|
||||
(br_if 2 (i32.eqz (global.get $voicesRemain))) ;; if no more voices remain, return from function
|
||||
))
|
||||
br $vm_loop
|
||||
end
|
||||
)
|
||||
|
||||
|
||||
|
||||
|
||||
;;-------------------------------------------------------------------------------
|
||||
;; ENVELOPE opcode: pushes an ADSR envelope value on stack [0,1]
|
||||
;;-------------------------------------------------------------------------------
|
||||
;; Mono: push the envelope value on stack
|
||||
;; Stereo: push the envelope valeu on stack twice
|
||||
;;-------------------------------------------------------------------------------
|
||||
(func $su_op_envelope (param $stereo i32) (local $state i32) (local $level f32) (local $delta f32)
|
||||
(if (i32.load offset=4 (global.get $voice)) (then ;; if voice.release > 0
|
||||
(i32.store (global.get $WRK) (i32.const 3)) ;; set envelope state to release
|
||||
))
|
||||
(local.set $state (i32.load (global.get $WRK)))
|
||||
(local.set $level (f32.load offset=4 (global.get $WRK)))
|
||||
(local.set $delta (call $nonLinearMap (local.get $state)))
|
||||
(if (local.get $state) (then
|
||||
(if (i32.eq (local.get $state) (i32.const 1))(then ;; state is 1 aka decay
|
||||
(local.set $level (f32.sub (local.get $level) (local.get $delta)))
|
||||
(if (f32.le (local.get $level) (call $input (i32.const 2)))(then
|
||||
(local.set $level (call $input (i32.const 2)))
|
||||
(local.set $state (i32.const 2))
|
||||
))
|
||||
))
|
||||
(if (i32.eq (local.get $state) (i32.const 3))(then ;; state is 3 aka release
|
||||
(local.set $level (f32.sub (local.get $level) (local.get $delta)))
|
||||
(if (f32.le (local.get $level) (f32.const 0)) (then
|
||||
(local.set $level (f32.const 0))
|
||||
))
|
||||
))
|
||||
)(else ;; the state is 0 aka attack
|
||||
(local.set $level (f32.add (local.get $level) (local.get $delta)))
|
||||
(if (f32.ge (local.get $level) (f32.const 1))(then
|
||||
(local.set $level (f32.const 1))
|
||||
(local.set $state (i32.const 1))
|
||||
))
|
||||
))
|
||||
(i32.store (global.get $WRK) (local.get $state))
|
||||
(f32.store offset=4 (global.get $WRK) (local.get $level))
|
||||
(call $push (f32.mul (local.get $level) (call $input (i32.const 4))))
|
||||
)
|
||||
|
||||
|
||||
|
||||
|
||||
;;-------------------------------------------------------------------------------
|
||||
;; $input returns the float value of a transformed to 0.0 - 1.0f range.
|
||||
;; The transformed values start at 512 (TODO: change magic constants somehow)
|
||||
;;-------------------------------------------------------------------------------
|
||||
(func $input (param $inputNumber i32) (result f32)
|
||||
(f32.load offset=1024 (i32.mul (local.get $inputNumber) (i32.const 4)))
|
||||
)
|
||||
|
||||
;;-------------------------------------------------------------------------------
|
||||
;; $inputSigned returns the float value of a transformed to -1.0 - 1.0f range.
|
||||
;;-------------------------------------------------------------------------------
|
||||
(func $inputSigned (param $inputNumber i32) (result f32)
|
||||
(f32.sub (f32.mul (call $input (local.get $inputNumber)) (f32.const 2)) (f32.const 1))
|
||||
)
|
||||
|
||||
;;-------------------------------------------------------------------------------
|
||||
;; $nonLinearMap: x -> 2^(-24*input[x])
|
||||
;;-------------------------------------------------------------------------------
|
||||
(func $nonLinearMap (param $value i32) (result f32)
|
||||
(call $pow2
|
||||
(f32.mul
|
||||
(f32.const -24)
|
||||
(call $input (local.get $value))
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
;;-------------------------------------------------------------------------------
|
||||
;; $pow2: x -> 2^x
|
||||
;;-------------------------------------------------------------------------------
|
||||
(func $pow2 (param $value f32) (result f32)
|
||||
(call $pow (f32.const 2) (local.get $value))
|
||||
)
|
||||
|
||||
;;-------------------------------------------------------------------------------
|
||||
;; Waveshaper(x,a): "distorts" signal x by amount a
|
||||
;; Returns x*a/(1-a+(2*a-1)*abs(x))
|
||||
;;-------------------------------------------------------------------------------
|
||||
(func $waveshaper (param $signal f32) (param $amount f32) (result f32)
|
||||
(local.set $signal (call $clip (local.get $signal)))
|
||||
(f32.mul
|
||||
(local.get $signal)
|
||||
(f32.div
|
||||
(local.get $amount)
|
||||
(f32.add
|
||||
(f32.const 1)
|
||||
(f32.sub
|
||||
(f32.mul
|
||||
(f32.sub
|
||||
(f32.add (local.get $amount) (local.get $amount))
|
||||
(f32.const 1)
|
||||
)
|
||||
(f32.abs (local.get $signal))
|
||||
)
|
||||
(local.get $amount)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
;;-------------------------------------------------------------------------------
|
||||
;; Clip(a : f32) returns min(max(a,-1),1)
|
||||
;;-------------------------------------------------------------------------------
|
||||
(func $clip (param $value f32) (result f32)
|
||||
(f32.min (f32.max (local.get $value) (f32.const -1.0)) (f32.const 1.0))
|
||||
)
|
||||
|
||||
(func $stereoHelper (param $stereo i32) (param $tableIndex i32)
|
||||
(if (local.get $stereo)(then
|
||||
(call $pop)
|
||||
(global.set $WRK (i32.add (global.get $WRK) (i32.const 16)))
|
||||
(call_indirect (type $opcode_func_signature) (i32.const 0) (local.get $tableIndex))
|
||||
(global.set $WRK (i32.sub (global.get $WRK) (i32.const 16)))
|
||||
(call $push)
|
||||
))
|
||||
)
|
||||
|
||||
;;-------------------------------------------------------------------------------
|
||||
;; The opcode table jump table. This is constructed to only include the opcodes
|
||||
;; that are used so that the jump table is as small as possible.
|
||||
;;-------------------------------------------------------------------------------
|
||||
(table 2 anyfunc)
|
||||
(elem (i32.const 1) ;; start the indices at 1, as 0 is reserved for advance
|
||||
$su_op_envelope
|
||||
)
|
||||
|
||||
|
||||
|
||||
;; All data is collected into a byte buffer and emitted at once
|
||||
(data (i32.const 0) "\4f\01\2b\01\4f\01\2b\01\4f\01\2b\01\4f\01\2b\01\01\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\4a\01\48\01\00\00\43\01\01\00\45\01\01\00\3e\01\1f\01\00\2b\01\00\2b\01\1f\01\00\00\00\00\00\00\1d\01\00\29\01\00\29\01\1d\01\00\00\00\00\00\00\00\00\00\00\4f\01\00\00\00\00\00\00\4f\00\00\00\41\01\00\45\01\00\46\01\01\01\01\01\01\01\01\00\41\01\00\41\01\01\3c\01\01\01\01\01\01\01\01\00\4f\01\2b\01\4f\01\2b\01\28\01\2b\01\4f\01\2b\01\4f\01\2b\01\14\01\2b\01\4f\01\2b\01\4f\01\2b\01\40\00\00\00\40\00\00\00\40\00\00\00\40\00\00\00\3e\01\00\41\01\00\41\01\01\01\01\01\01\01\01\00\45\01\00\3c\01\01\40\01\01\01\01\01\01\01\01\00\45\01\00\3e\01\00\3e\01\01\01\01\01\01\01\01\00\3c\01\00\45\01\01\43\01\01\01\01\01\01\01\01\00\4a\01\48\01\00\00\43\01\01\00\45\01\01\01\3e\01\1f\01\00\2b\01\00\2b\01\1f\01\01\01\01\01\01\00\1a\01\00\26\01\00\26\01\1a\01\00\00\00\00\00\00\00\00\00\00\4f\01\00\00\00\00\00\32\4f\00\00\1e\1a\01\00\26\01\00\26\01\1a\01\01\01\01\01\01\00\00\00\39\01\3e\01\39\45\01\39\43\01\41\00\40\00\4d\01\48\4a\01\01\48\01\01\01\01\01\01\00\48\4a\00\00\4f\01\01\00\4f\01\01\01\01\01\4d\01\4a\00\4d\01\00\4f\01\01\4d\01\01\01\4c\01\4a\01\48\01\18\01\00\24\01\00\24\01\18\01\01\01\01\01\01\00\18\01\00\24\01\00\24\01\18\01\00\00\00\00\00\00\1d\01\00\29\01\00\29\01\1d\01\01\01\01\01\01\00\00\00\00\00\42\01\00\00\00\00\00\00\4f\00\00\00\00\00\00\00\00\00\00\00\01\00\00\00\01\00\00\00\02\03\04\05\02\03\04\05\06\07\08\09\06\07\08\09\0a\0a\0a\0a\0a\0a\0a\0a\0b\0c\0b\0c\0b\0c\0b\0c\0a\0a\0a\0a\0a\0a\0a\0a\0d\0e\0d\0e\0d\0e\0d\0e\0a\0a\0a\0a\0a\0a\0a\0a\0f\10\0f\10\0f\10\0f\10\11\11\11\11\11\11\11\11\11\11\11\11\11\11\11\11\0a\0a\0a\0a\12\12\12\13\12\12\12\14\12\12\12\14\0a\0a\0a\0a\0a\0a\0a\0a\15\16\17\18\15\16\17\18\19\19\19\19\19\19\19\19\19\19\1a\1b\19\19\1a\1b\0a\0a\0a\0a\0a\0a\0a\0a\0a\0a\0a\0a\0a\0a\0a\0a\02\00\02\00\02\00\02\00\02\00\02\02\00\02\02\00\02\00\02\00\02\02\00\18\46\20\40\3c\20\46\3c\4b\20\20\46\50\46\50\20\46\50\46\50\20\46\50\46\50\00\40\60\40\5a\00\46\00\00\64\00\48\00\48\19\00\38\00\00\80\20\40\5a\30\23\00\40\0f\20\64\00\46\00\46\40\00\50\00\50\80\05")
|
||||
|
||||
;;(data (i32.const 8388610) "\52\49\46\46\b2\eb\0c\20\57\41\56\45\66\6d\74\20\12\20\20\20\03\20\02\20\44\ac\20\20\20\62\05\20\08\20\20\20\20\20\66\61\63\74\04\20\20\20\e0\3a\03\20\64\61\74\61\80\eb\0c\20")
|
||||
|
||||
) ;; END MODULE
|
6
package.json
Normal file
6
package.json
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"json-to-pretty-yaml": "^1.2.2"
|
||||
}
|
||||
}
|
BIN
sointu-compile
Executable file
BIN
sointu-compile
Executable file
Binary file not shown.
441
test_chords.wat
Normal file
441
test_chords.wat
Normal file
@ -0,0 +1,441 @@
|
||||
(module
|
||||
|
||||
|
||||
;;------------------------------------------------------------------------------
|
||||
;; Import the difficult math functions from javascript
|
||||
;; (seriously now, it's 2020)
|
||||
;;------------------------------------------------------------------------------
|
||||
(func $pow (import "m" "pow") (param f32) (param f32) (result f32))
|
||||
(func $log2 (import "m" "log2") (param f32) (result f32))
|
||||
(func $sin (import "m" "sin") (param f32) (result f32))
|
||||
|
||||
;;------------------------------------------------------------------------------
|
||||
;; Types. Only useful to define the jump table type, which is
|
||||
;; (int stereo) void
|
||||
;;------------------------------------------------------------------------------
|
||||
(type $opcode_func_signature (func (param i32)))
|
||||
|
||||
;;------------------------------------------------------------------------------
|
||||
;; The one and only memory
|
||||
;;------------------------------------------------------------------------------
|
||||
(memory (export "m") 15)
|
||||
|
||||
;;------------------------------------------------------------------------------
|
||||
;; Globals. Putting all with same initialization value should compress most
|
||||
;;------------------------------------------------------------------------------
|
||||
(global $WRK (mut i32) (i32.const 0))
|
||||
(global $COM (mut i32) (i32.const 0))
|
||||
(global $VAL (mut i32) (i32.const 0))
|
||||
(global $COM_instr_start (mut i32) (i32.const 0))
|
||||
(global $VAL_instr_start (mut i32) (i32.const 0))
|
||||
(global $globaltick (mut i32) (i32.const 0))
|
||||
(global $row (mut i32) (i32.const 0))
|
||||
(global $pattern (mut i32) (i32.const 0))
|
||||
(global $sample (mut i32) (i32.const 0))
|
||||
(global $voice (mut i32) (i32.const 0))
|
||||
(global $voicesRemain (mut i32) (i32.const 0))
|
||||
(global $randseed (mut i32) (i32.const 1))
|
||||
(global $sp (mut i32) (i32.const 384))
|
||||
(global $outputBufPtr (mut i32) (i32.const 131712))
|
||||
;; TODO: only export start and length with certain compiler options; in demo use, they can be hard coded
|
||||
;; in the intro
|
||||
(global $outputStart (export "s") i32 (i32.const 131712))
|
||||
(global $outputLength (export "l") i32 (i32.const 846720))
|
||||
(global $output16bit (export "t") i32 (i32.const 0))
|
||||
|
||||
|
||||
;;------------------------------------------------------------------------------
|
||||
;; Functions to emulate FPU stack in software
|
||||
;;------------------------------------------------------------------------------
|
||||
(func $peek (result f32)
|
||||
(f32.load (global.get $sp))
|
||||
)
|
||||
|
||||
(func $peek2 (result f32)
|
||||
(f32.load offset=4 (global.get $sp))
|
||||
)
|
||||
|
||||
(func $pop (result f32)
|
||||
(call $peek)
|
||||
(global.set $sp (i32.add (global.get $sp) (i32.const 4)))
|
||||
)
|
||||
|
||||
(func $push (param $value f32)
|
||||
(global.set $sp (i32.sub (global.get $sp) (i32.const 4)))
|
||||
(f32.store (global.get $sp) (local.get $value))
|
||||
)
|
||||
|
||||
;;------------------------------------------------------------------------------
|
||||
;; Helper functions
|
||||
;;------------------------------------------------------------------------------
|
||||
(func $swap (param f32 f32) (result f32 f32) ;; x,y -> y,x
|
||||
local.get 1
|
||||
local.get 0
|
||||
)
|
||||
|
||||
(func $scanValueByte (result i32) ;; scans positions $VAL for a byte, incrementing $VAL afterwards
|
||||
(i32.load8_u (global.get $VAL)) ;; in other words: returns byte [$VAL++]
|
||||
(global.set $VAL (i32.add (global.get $VAL) (i32.const 1))) ;; $VAL++
|
||||
)
|
||||
|
||||
;;------------------------------------------------------------------------------
|
||||
;; "Entry point" for the player
|
||||
;;------------------------------------------------------------------------------
|
||||
(start $render) ;; we run render automagically when the module is instantiated
|
||||
|
||||
(func $render (param)
|
||||
loop $pattern_loop
|
||||
(global.set $row (i32.const 0))
|
||||
loop $row_loop
|
||||
(call $su_update_voices)
|
||||
(global.set $sample (i32.const 0))
|
||||
loop $sample_loop
|
||||
(global.set $COM (i32.const 51))
|
||||
(global.set $VAL (i32.const 58))
|
||||
(global.set $COM_instr_start (global.get $COM))
|
||||
(global.set $VAL_instr_start (global.get $VAL))
|
||||
(global.set $WRK (i32.const 576))
|
||||
(global.set $voice (i32.const 576))
|
||||
(global.set $voicesRemain (i32.const 3))
|
||||
(call $su_run_vm)
|
||||
(i64.store (global.get $outputBufPtr) (i64.load (i32.const 544))) ;; load the sample from left & right channels as one 64bit int and store it in the address pointed by outputBufPtr
|
||||
(global.set $outputBufPtr (i32.add (global.get $outputBufPtr) (i32.const 8))) ;; advance outputbufptr
|
||||
(i64.store (i32.const 544) (i64.const 0)) ;; clear the left and right ports
|
||||
(global.set $sample (i32.add (global.get $sample) (i32.const 1)))
|
||||
(global.set $globaltick (i32.add (global.get $globaltick) (i32.const 1)))
|
||||
(br_if $sample_loop (i32.lt_s (global.get $sample) (i32.const 6615)))
|
||||
end
|
||||
(global.set $row (i32.add (global.get $row) (i32.const 1)))
|
||||
(br_if $row_loop (i32.lt_s (global.get $row) (i32.const 16)))
|
||||
end
|
||||
(global.set $pattern (i32.add (global.get $pattern) (i32.const 1)))
|
||||
(br_if $pattern_loop (i32.lt_s (global.get $pattern) (i32.const 1)))
|
||||
end
|
||||
)
|
||||
;; the simple implementation of update_voices: each track has exactly one voice
|
||||
(func $su_update_voices (local $si i32) (local $di i32) (local $tracksRemaining i32) (local $note i32)
|
||||
(local.set $tracksRemaining (i32.const 3))
|
||||
(local.set $si (global.get $pattern))
|
||||
(local.set $di (i32.const 576))
|
||||
loop $track_loop
|
||||
(i32.load8_u offset=48 (local.get $si))
|
||||
(i32.mul (i32.const 16))
|
||||
(i32.add (global.get $row))
|
||||
(i32.load8_u offset=0)
|
||||
(local.tee $note)
|
||||
(if (i32.ne (i32.const 1))(then
|
||||
(i32.store offset=4 (local.get $di) (i32.const 1)) ;; release the note
|
||||
(if (i32.gt_u (local.get $note) (i32.const 1))(then
|
||||
(memory.fill (local.get $di) (i32.const 0) (i32.const 4096))
|
||||
(i32.store (local.get $di) (local.get $note))
|
||||
))
|
||||
))
|
||||
(local.set $di (i32.add (local.get $di) (i32.const 4096)))
|
||||
(local.set $si (i32.add (local.get $si) (i32.const 1)))
|
||||
(br_if $track_loop (local.tee $tracksRemaining (i32.sub (local.get $tracksRemaining) (i32.const 1))))
|
||||
end
|
||||
)
|
||||
|
||||
;;-------------------------------------------------------------------------------
|
||||
;; su_run_vm function: runs the entire virtual machine once, creating 1 sample
|
||||
;;-------------------------------------------------------------------------------
|
||||
(func $su_run_vm (local $opcodeWithStereo i32) (local $opcode i32) (local $paramNum i32) (local $paramX4 i32) (local $WRKplusparam i32)
|
||||
loop $vm_loop
|
||||
(local.set $opcodeWithStereo (i32.load8_u (global.get $COM)))
|
||||
(global.set $COM (i32.add (global.get $COM) (i32.const 1))) ;; move to next instruction
|
||||
(global.set $WRK (i32.add (global.get $WRK) (i32.const 64))) ;; move WRK to next unit
|
||||
(if (local.tee $opcode (i32.shr_u (local.get $opcodeWithStereo) (i32.const 1)))(then ;; if $opcode = $opcodeStereo >> 1; $opcode != 0 {
|
||||
(local.set $paramNum (i32.const 0))
|
||||
(local.set $paramX4 (i32.const 0))
|
||||
loop $transform_values_loop
|
||||
(if (i32.lt_u (local.get $paramNum) (i32.load8_u offset=82 (local.get $opcode)))(then ;;(i32.ge (local.get $paramNum) (i32.load8_u (local.get $opcode))) /*TODO: offset to transformvalues
|
||||
(local.set $WRKplusparam (i32.add (global.get $WRK) (local.get $paramX4)))
|
||||
(f32.store offset=384
|
||||
(local.get $paramX4)
|
||||
(f32.add
|
||||
(f32.mul
|
||||
(f32.convert_i32_u (call $scanValueByte))
|
||||
(f32.const 0.0078125) ;; scale from 0-128 to 0.0 - 1.0
|
||||
)
|
||||
(f32.load offset=32 (local.get $WRKplusparam)) ;; add modulation
|
||||
)
|
||||
)
|
||||
(f32.store offset=32 (local.get $WRKplusparam) (f32.const 0.0)) ;; clear modulations
|
||||
(local.set $paramNum (i32.add (local.get $paramNum) (i32.const 1))) ;; $paramNum++
|
||||
(local.set $paramX4 (i32.add (local.get $paramX4) (i32.const 4)))
|
||||
br $transform_values_loop ;; continue looping
|
||||
))
|
||||
;; paramNum was >= the number of parameters to transform, exiting loop
|
||||
end
|
||||
(call_indirect (type $opcode_func_signature) (i32.and (local.get $opcodeWithStereo) (i32.const 1)) (local.get $opcode))
|
||||
)(else ;; advance to next voice
|
||||
(global.set $voice (i32.add (global.get $voice) (i32.const 4096))) ;; advance to next voice
|
||||
(global.set $WRK (global.get $voice)) ;; set WRK point to beginning of voice
|
||||
(global.set $voicesRemain (i32.sub (global.get $voicesRemain) (i32.const 1)))
|
||||
(if (i32.and (i32.shr_u (i32.const 6) (global.get $voicesRemain)) (i32.const 1))(then
|
||||
(global.set $VAL (global.get $VAL_instr_start))
|
||||
(global.set $COM (global.get $COM_instr_start))
|
||||
))
|
||||
(global.set $VAL_instr_start (global.get $VAL))
|
||||
(global.set $COM_instr_start (global.get $COM))
|
||||
(br_if 2 (i32.eqz (global.get $voicesRemain))) ;; if no more voices remain, return from function
|
||||
))
|
||||
br $vm_loop
|
||||
end
|
||||
)
|
||||
;;-------------------------------------------------------------------------------
|
||||
;; MULP opcode: multiply the two top most signals on the stack and pop
|
||||
;;-------------------------------------------------------------------------------
|
||||
;; Mono: a b -> a*b
|
||||
;; Stereo: a b c d -> a*c b*d
|
||||
;;-------------------------------------------------------------------------------
|
||||
(func $su_op_mulp (param $stereo i32)
|
||||
call $pop ;; a
|
||||
call $pop ;; b a
|
||||
call $swap ;; a b
|
||||
call $pop ;; c a b
|
||||
f32.mul ;; c*a b
|
||||
call $swap ;; b c*a
|
||||
call $pop ;; d b c*a
|
||||
f32.mul ;; d*b c*a
|
||||
call $push ;; c*a
|
||||
call $push
|
||||
)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
;;-------------------------------------------------------------------------------
|
||||
;; ENVELOPE opcode: pushes an ADSR envelope value on stack [0,1]
|
||||
;;-------------------------------------------------------------------------------
|
||||
;; Mono: push the envelope value on stack
|
||||
;; Stereo: push the envelope valeu on stack twice
|
||||
;;-------------------------------------------------------------------------------
|
||||
(func $su_op_envelope (param $stereo i32) (local $state i32) (local $level f32) (local $delta f32)
|
||||
(if (i32.load offset=4 (global.get $voice)) (then ;; if voice.release > 0
|
||||
(i32.store (global.get $WRK) (i32.const 3)) ;; set envelope state to release
|
||||
))
|
||||
(local.set $state (i32.load (global.get $WRK)))
|
||||
(local.set $level (f32.load offset=4 (global.get $WRK)))
|
||||
(local.set $delta (call $nonLinearMap (local.get $state)))
|
||||
(if (local.get $state) (then
|
||||
(if (i32.eq (local.get $state) (i32.const 1))(then ;; state is 1 aka decay
|
||||
(local.set $level (f32.sub (local.get $level) (local.get $delta)))
|
||||
(if (f32.le (local.get $level) (call $input (i32.const 2)))(then
|
||||
(local.set $level (call $input (i32.const 2)))
|
||||
(local.set $state (i32.const 2))
|
||||
))
|
||||
))
|
||||
(if (i32.eq (local.get $state) (i32.const 3))(then ;; state is 3 aka release
|
||||
(local.set $level (f32.sub (local.get $level) (local.get $delta)))
|
||||
(if (f32.le (local.get $level) (f32.const 0)) (then
|
||||
(local.set $level (f32.const 0))
|
||||
))
|
||||
))
|
||||
)(else ;; the state is 0 aka attack
|
||||
(local.set $level (f32.add (local.get $level) (local.get $delta)))
|
||||
(if (f32.ge (local.get $level) (f32.const 1))(then
|
||||
(local.set $level (f32.const 1))
|
||||
(local.set $state (i32.const 1))
|
||||
))
|
||||
))
|
||||
(i32.store (global.get $WRK) (local.get $state))
|
||||
(f32.store offset=4 (global.get $WRK) (local.get $level))
|
||||
(call $push (f32.mul (local.get $level) (call $input (i32.const 4))))
|
||||
)
|
||||
|
||||
;;-------------------------------------------------------------------------------
|
||||
;; OSCILLAT opcode: oscillator, the heart of the synth
|
||||
;;-------------------------------------------------------------------------------
|
||||
;; Mono: push oscillator value on stack
|
||||
;; Stereo: push l r on stack, where l has opposite detune compared to r
|
||||
;;-------------------------------------------------------------------------------
|
||||
(func $su_op_oscillator (param $stereo i32) (local $flags i32) (local $detune f32) (local $phase f32) (local $color f32) (local $amplitude f32)
|
||||
(local.set $flags (call $scanValueByte))
|
||||
(local.set $detune (call $inputSigned (i32.const 1)))
|
||||
(f32.store ;; update phase
|
||||
(global.get $WRK)
|
||||
(local.tee $phase
|
||||
(f32.sub
|
||||
(local.tee $phase
|
||||
;; Transpose calculation starts
|
||||
(f32.div
|
||||
(call $inputSigned (i32.const 0))
|
||||
(f32.const 0.015625)
|
||||
) ;; scale back to 0 - 128
|
||||
(f32.add (local.get $detune)) ;; add detune. detune is -1 to 1 so can detune a full note up or down at max
|
||||
(f32.add (select
|
||||
(f32.const 0)
|
||||
(f32.convert_i32_u (i32.load (global.get $voice)))
|
||||
(i32.and (local.get $flags) (i32.const 0x8))
|
||||
)) ;; if lfo is not enabled, add the note number to it
|
||||
(f32.mul (f32.const 0.0833333)) ;; /12, in full octaves
|
||||
(call $pow2)
|
||||
(f32.mul (select
|
||||
(f32.const 0.000038) ;; pretty random scaling constant to get LFOs into reasonable range. Historical reasons, goes all the way back to 4klang
|
||||
(f32.const 0.000092696138) ;; scaling constant to get middle-C to where it should be
|
||||
(i32.and (local.get $flags) (i32.const 0x8))
|
||||
))
|
||||
(f32.add (f32.load (global.get $WRK))) ;; add the current phase of the oscillator
|
||||
)
|
||||
(f32.floor (local.get $phase))
|
||||
)
|
||||
)
|
||||
)
|
||||
(f32.add (local.get $phase) (call $input (i32.const 2)))
|
||||
(local.set $phase (f32.sub (local.tee $phase) (f32.floor (local.get $phase)))) ;; phase = phase mod 1.0
|
||||
(local.set $color (call $input (i32.const 3)))
|
||||
(if (i32.and (local.get $flags) (i32.const 0x40)) (then
|
||||
(local.set $amplitude (call $oscillator_sine (local.get $phase) (local.get $color)))
|
||||
))
|
||||
(call $waveshaper (local.get $amplitude) (call $input (i32.const 4)))
|
||||
(call $push (f32.mul
|
||||
(call $input (i32.const 5))
|
||||
))
|
||||
)
|
||||
(func $oscillator_sine (param $phase f32) (param $color f32) (result f32)
|
||||
(select
|
||||
(f32.const 0)
|
||||
(call $sin (f32.mul
|
||||
(f32.div
|
||||
(local.get $phase)
|
||||
(local.get $color)
|
||||
)
|
||||
(f32.const 6.28318530718)
|
||||
))
|
||||
(f32.ge (local.get $phase) (local.get $color))
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
|
||||
|
||||
;;-------------------------------------------------------------------------------
|
||||
;; OUT opcode: outputs and pops the signal
|
||||
;;-------------------------------------------------------------------------------
|
||||
;; Stereo: add ST0 to left out and ST1 to right out, then pop
|
||||
;;-------------------------------------------------------------------------------
|
||||
(func $su_op_out (param $stereo i32) (local $ptr i32)
|
||||
(local.set $ptr (i32.const 544)) ;; synth.left
|
||||
(f32.store (local.get $ptr)
|
||||
(f32.add
|
||||
(f32.mul
|
||||
(call $pop)
|
||||
(call $input (i32.const 0))
|
||||
)
|
||||
(f32.load (local.get $ptr))
|
||||
)
|
||||
)
|
||||
(local.set $ptr (i32.const 548)) ;; synth.right, note that ATM does not seem to support mono ocpode at all
|
||||
(f32.store (local.get $ptr)
|
||||
(f32.add
|
||||
(f32.mul
|
||||
(call $pop)
|
||||
(call $input (i32.const 0))
|
||||
)
|
||||
(f32.load (local.get $ptr))
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
|
||||
;;-------------------------------------------------------------------------------
|
||||
;; $input returns the float value of a transformed to 0.0 - 1.0f range.
|
||||
;; The transformed values start at 512 (TODO: change magic constants somehow)
|
||||
;;-------------------------------------------------------------------------------
|
||||
(func $input (param $inputNumber i32) (result f32)
|
||||
(f32.load offset=384 (i32.mul (local.get $inputNumber) (i32.const 4)))
|
||||
)
|
||||
|
||||
;;-------------------------------------------------------------------------------
|
||||
;; $inputSigned returns the float value of a transformed to -1.0 - 1.0f range.
|
||||
;;-------------------------------------------------------------------------------
|
||||
(func $inputSigned (param $inputNumber i32) (result f32)
|
||||
(f32.sub (f32.mul (call $input (local.get $inputNumber)) (f32.const 2)) (f32.const 1))
|
||||
)
|
||||
|
||||
;;-------------------------------------------------------------------------------
|
||||
;; $nonLinearMap: x -> 2^(-24*input[x])
|
||||
;;-------------------------------------------------------------------------------
|
||||
(func $nonLinearMap (param $value i32) (result f32)
|
||||
(call $pow2
|
||||
(f32.mul
|
||||
(f32.const -24)
|
||||
(call $input (local.get $value))
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
;;-------------------------------------------------------------------------------
|
||||
;; $pow2: x -> 2^x
|
||||
;;-------------------------------------------------------------------------------
|
||||
(func $pow2 (param $value f32) (result f32)
|
||||
(call $pow (f32.const 2) (local.get $value))
|
||||
)
|
||||
|
||||
;;-------------------------------------------------------------------------------
|
||||
;; Waveshaper(x,a): "distorts" signal x by amount a
|
||||
;; Returns x*a/(1-a+(2*a-1)*abs(x))
|
||||
;;-------------------------------------------------------------------------------
|
||||
(func $waveshaper (param $signal f32) (param $amount f32) (result f32)
|
||||
(local.set $signal (call $clip (local.get $signal)))
|
||||
(f32.mul
|
||||
(local.get $signal)
|
||||
(f32.div
|
||||
(local.get $amount)
|
||||
(f32.add
|
||||
(f32.const 1)
|
||||
(f32.sub
|
||||
(f32.mul
|
||||
(f32.sub
|
||||
(f32.add (local.get $amount) (local.get $amount))
|
||||
(f32.const 1)
|
||||
)
|
||||
(f32.abs (local.get $signal))
|
||||
)
|
||||
(local.get $amount)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
;;-------------------------------------------------------------------------------
|
||||
;; Clip(a : f32) returns min(max(a,-1),1)
|
||||
;;-------------------------------------------------------------------------------
|
||||
(func $clip (param $value f32) (result f32)
|
||||
(f32.min (f32.max (local.get $value) (f32.const -1.0)) (f32.const 1.0))
|
||||
)
|
||||
|
||||
(func $stereoHelper (param $stereo i32) (param $tableIndex i32)
|
||||
(if (local.get $stereo)(then
|
||||
(call $pop)
|
||||
(global.set $WRK (i32.add (global.get $WRK) (i32.const 16)))
|
||||
(call_indirect (type $opcode_func_signature) (i32.const 0) (local.get $tableIndex))
|
||||
(global.set $WRK (i32.sub (global.get $WRK) (i32.const 16)))
|
||||
(call $push)
|
||||
))
|
||||
)
|
||||
|
||||
;;-------------------------------------------------------------------------------
|
||||
;; The opcode table jump table. This is constructed to only include the opcodes
|
||||
;; that are used so that the jump table is as small as possible.
|
||||
;;-------------------------------------------------------------------------------
|
||||
(table 5 funcref)
|
||||
(elem (i32.const 1) ;; start the indices at 1, as 0 is reserved for advance
|
||||
$su_op_envelope
|
||||
$su_op_oscillator
|
||||
$su_op_mulp
|
||||
$su_op_out
|
||||
)
|
||||
|
||||
|
||||
|
||||
;; All data is collected into a byte buffer and emitted at once
|
||||
(data (i32.const 0) "\40\00\00\00\44\00\00\00\42\00\00\00\45\00\00\00\00\44\00\00\47\00\00\00\45\00\00\00\49\00\00\00\00\00\47\00\4b\00\00\00\49\00\00\00\4c\00\00\00\00\01\02\02\02\04\04\07\09\00\40\40\40\40\20\40\40\40\40\20\58\40\00\80\40\80\40\58\40\00\80\40\80\40\80\05\06\00\01")
|
||||
|
||||
;;(data (i32.const 8388610) "\52\49\46\46\b2\eb\0c\20\57\41\56\45\66\6d\74\20\12\20\20\20\03\20\02\20\44\ac\20\20\20\62\05\20\08\20\20\20\20\20\66\61\63\74\04\20\20\20\e0\3a\03\20\64\61\74\61\80\eb\0c\20")
|
||||
|
||||
) ;; END MODULE
|
14385
tests/groove.yaml
Normal file
14385
tests/groove.yaml
Normal file
File diff suppressed because it is too large
Load Diff
2436
tests/grooveisinthecode.js
Normal file
2436
tests/grooveisinthecode.js
Normal file
File diff suppressed because it is too large
Load Diff
0
tests/grooveisinthecode.yml
Normal file
0
tests/grooveisinthecode.yml
Normal file
15
testwasm.mjs
Normal file
15
testwasm.mjs
Normal file
@ -0,0 +1,15 @@
|
||||
import { readFile, writeFile } from 'fs/promises';
|
||||
|
||||
const wasm = (await readFile('./chords.wasm'));
|
||||
|
||||
const mod = await WebAssembly.instantiate(wasm, {
|
||||
m: {
|
||||
pow: Math.pow,
|
||||
log2: Math.log2,
|
||||
sin: Math.sin
|
||||
}
|
||||
});
|
||||
|
||||
const mem = mod.instance.exports.m;
|
||||
|
||||
await writeFile('test.raw', new Uint8Array(mem.buffer));
|
21
yarn.lock
Normal file
21
yarn.lock
Normal file
@ -0,0 +1,21 @@
|
||||
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
|
||||
# yarn lockfile v1
|
||||
|
||||
|
||||
json-to-pretty-yaml@^1.2.2:
|
||||
version "1.2.2"
|
||||
resolved "https://registry.yarnpkg.com/json-to-pretty-yaml/-/json-to-pretty-yaml-1.2.2.tgz#f4cd0bd0a5e8fe1df25aaf5ba118b099fd992d5b"
|
||||
integrity sha512-rvm6hunfCcqegwYaG5T4yKJWxc9FXFgBVrcTZ4XfSVRwa5HA/Xs+vB/Eo9treYYHCeNM0nrSUr82V/M31Urc7A==
|
||||
dependencies:
|
||||
remedial "^1.0.7"
|
||||
remove-trailing-spaces "^1.0.6"
|
||||
|
||||
remedial@^1.0.7:
|
||||
version "1.0.8"
|
||||
resolved "https://registry.yarnpkg.com/remedial/-/remedial-1.0.8.tgz#a5e4fd52a0e4956adbaf62da63a5a46a78c578a0"
|
||||
integrity sha512-/62tYiOe6DzS5BqVsNpH/nkGlX45C/Sp6V+NtiN6JQNS1Viay7cWkazmRkrQrdFj2eshDe96SIQNIoMxqhzBOg==
|
||||
|
||||
remove-trailing-spaces@^1.0.6:
|
||||
version "1.0.8"
|
||||
resolved "https://registry.yarnpkg.com/remove-trailing-spaces/-/remove-trailing-spaces-1.0.8.tgz#4354d22f3236374702f58ee373168f6d6887ada7"
|
||||
integrity sha512-O3vsMYfWighyFbTd8hk8VaSj9UAGENxAtX+//ugIst2RMk5e03h6RoIS+0ylsFxY1gvmPuAY/PO4It+gPEeySA==
|
Loading…
Reference in New Issue
Block a user