Skip to content

Instantly share code, notes, and snippets.

@petersalomonsen
Last active November 14, 2021 07:16
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save petersalomonsen/e9cf4f8b80b35a09435f3e2f39889318 to your computer and use it in GitHub Desktop.
Save petersalomonsen/e9cf4f8b80b35a09435f3e2f39889318 to your computer and use it in GitHub Desktop.
/*
* Copyright (c) 2021 - Peter Johan Salomonsen
*/
setBPM(100);
addInstrument('BoltToysPluck');
addInstrument('SphericalBowl');
addInstrument('KnotPressureModeled');
addInstrument('empty1');
addInstrument('empty2');
addInstrument('empty3');
addInstrument('empty4');
addInstrument('audioplayer1');
addInstrument('audioplayer2');
addInstrument('audioplayer3');
['arm_with_crook_SLIDE_ARP.wav', 'house_BROKEN_CRYSTAL.wav', 'rope_GTR_EFX.wav',
'bolt_TOYS_PERCUSSIONS.wav', 'knot_PRESSURE.wav', 'spherical_bowl_ILLNESS.wav',
'folded_cloth_SWZ_BASS.wav', 'owl_sound_n5.wav', 'sun_BUILD_UP.wav',
'goatskin_SITAR.wav', 'papyrus_roll_VOCODER.wav', 'vertical_beam_sound_n2.wav',
'horned_viper_KICK_DRUM.wav', 'quail_chick_FRASCH_BASSLINE.wav',
'water_ripple_DARK.wav'].forEach(url => addAudio('https://storage.googleapis.com/wasm-music-audio/'+url));
const metronome = () => createTrack(2).steps(1,[fs5,fs5,fs5,fs5]);
const drumbeat = async () => {
createTrack(7).steps(4, [
,,c4(0.5,30),,
,,c4(0.5,30),,
,,c4(0.5,30),,
,,c4(0.5,30),,
].repeat(3));
return createTrack(7).steps(4, [
[c6],,c6,,
ds5,,,ds5,
,ds5,c6,,
ds5,,,,
c6,,c6,,
ds5,,,ds5(0.2,40),
,ds5,c6,,
ds5,,,c6(0.2, 20),
c6,,c6,,
ds5,,,ds5,
,ds5,c6,,
ds5,,,,
c6,,c6,,
ds5,,,ds5(0.2,40),
,ds5,c6,ds5(0.2,30),
ds5,ds5(0.2,30),ds5(0.2,50),ds5(0.2,30),
]);
}
const drumbeat2 = async () => {
createTrack(7).steps(4, [
,,c4(0.5,30),,
,,c4(0.5,30),,
,,c4(0.5,30),,
,,c4(0.5,30),,
].repeat(3));
return createTrack(7).steps(4, [
[c6],,,,
[c6,ds5],,,[c6(0.25,30),ds5],
c6,ds5,,,
[c6,ds5],,,,
c6,,,,
[c6,ds5],,,ds5(0.2,40),
c6,ds5,c6(0.2,60),,
[c6,ds5],,,c6(0.2, 20),
c6,,,,
[c6,ds5],,,ds5,
c6,ds5,,,
[c6,ds5],,,,
c6,,,,
[c6,ds5],,,ds5(0.2,40),
c6,ds5,,ds5(0.2,30),
[c6,ds5],ds5(0.2,30),[c6,ds5(0.2,50)],ds5(0.2,30),
]);
}
const intromelody = () => createTrack(0).play([[ 0.05, a3(7.71, 47) ],
[ 0, e4(8, 52) ],
[ 0, a5(8, 55) ],
[ 6, a6(2, 77) ],
[ 0, e6(10, 60) ],
[ 10, d6(2, 65) ],
[ 12, e6(2, 67) ],
[ 8, g4(8, 47) ],
[ 8, c4(8, 58) ],
[ 14, c6(2, 72) ],
[ 8, g6(12, 74) ],
[ 20, f6(4, 83) ],
[ 16, f3(8, 60) ],
[ 16, a5(8, 72) ],
[ 16, c4(8, 53) ],
[ 22, e6(2, 74) ],
[ 24, e4(8, 57) ],
[ 24, a4(8, 57) ],
[ 24, a3(8, 57) ],
[ 24, a5(8, 74) ]]);
intromelody();
await createTrack(7).steps(1, [ds5(32,93),,,,
,,,,
,,,,
,,,,
,,,,
,,,,
,,,,
,,,,]);
intromelody();
createTrack(7).steps(4, [[gs5(32,100), ds5(32,93)]]);
await drumbeat();
await drumbeat();
createTrack(8).steps(4, [as5(32,50),g5(32,90)]);
createTrack(9).steps(4, [[cs6(32,120), controlchange(74, 2)]]);
await drumbeat2();
await drumbeat2();
createTrack(7).steps(4, [[d5(16,60),cs6(16,75)]]);
await drumbeat();
createTrack(2).steps(1, [[a5(8,25),e6(8,25)]
,,,,,,,,[gs5(8,30),ds6(8,30)]]);
createTrack(7).steps(4, [[d5(16,60),cs6(16,75)]]);
await drumbeat();
createTrack(1).steps(1, [[a5(8,25),e6(8,25),a3(8,25)]
,,,,,,,,[gs5(8,25),ds6(8,25),gs3(8,25)]]);
createTrack(2).steps(1, [[a5(8,25),e6(8,25)]
,,,,,,,,[ds6(8,30),gs6(8,30)]]);
createTrack(7).steps(1, [,,,,,,,,,,,cs5(8,30)]);
createTrack(9).steps(1, [controlchange(74, 127),,,,,,,,,,,,,cs5(8,20)]);
createTrack(8).steps(1, [,,,,,,,,,,,,,,,cs5(16,12)]);
createTrack(7).steps(4, [[d5(16,60),cs6(16,75)]]);
await drumbeat();
createTrack(7).steps(4, [c5(32,60)]);
await createTrack(7).steps(4, [
c6,,c4(0.2,10),,
c6,,c4(0.2,10),,
c6,,c4(0.1,30),,
c6,c4(0.05,5),c4(0.05,20),,
].repeat(3));
createTrack(7).steps(1, [
,fs5(7.9,90),g5(7.9),,
,,,,
,fs5(7,90),g5(6),,
,,,,
]);
await createTrack(7).steps(4, [
c6,,c4(0.2,10),,
c6,,c4(0.2,10),,
c6,,c4(0.1,30),,
c6,c4(0.05,5),c4(0.05,20),,
].repeat(3));
function secondPluck() {
createTrack(0).play([[ 1, e5(6.65, 45) ],
[ 1, a4(7, 45) ],
[ 1, e6(7, 45) ],
[ 1, a5(7, 45) ],
[ 1, a6(7, 45) ],
[ 8, g4(7, 45) ],
[ 9, g6(7, 45) ],
[ 9, g5(7, 45) ],
[ 8, c4(7, 45) ],
[ 9, c6(7, 45) ]]);
}
secondPluck();
createTrack(7).steps(4, [c5(16,60)]);
createTrack(7).steps(1, [
,fs5(7.9,90),g5(7.9),,
,,,,
,fs5(7,90),g5(6),,
,,,,
]);
await createTrack(7).steps(4, [
c6,,c4(0.2,10),,
c6,,c4(0.2,10),,
c6,,c4(0.1,30),,
c6,c4(0.05,5),c4(0.05,20),,
].repeat(3));
for (var n=0;n<2;n++) {
createTrack(1).steps(1, [
[a5(5),a2(8)],,,,
e5(4),,,,
[g5(8),c3(8)],,,,
,,,,
]);
secondPluck();
createTrack(7).steps(4, [c5(16,60)]);
createTrack(7).steps(1, [
,fs5(7.9,90),g5(7.9),,
,,,,
,fs5(7,90),g5(6),,
,,,,
]);
await createTrack(7).steps(4, [
c6,,c4(0.2,10),,
c6,,c4(0.2,10),,
c6,,c4(0.1,30),,
c6,,c4(0.05,10),,
].repeat(3));
}
// Stranger things
for (var n=0;n<2;n++) {
createTrack(1).steps(1, [
[a5(5),a2(8)],,,,
e5(4),,,,
[as5(8),ds3(8)],,,,
,,,,
]);
createTrack(7).steps(4, [[b5(16,100)]]);
createTrack(7).steps(1, [
,fs5(7.9,90),g5(7.9),,
,,,,
,fs5(7,90),g5(6),,
,,,,
]);
await createTrack(7).steps(4, [
c6,,c4(0.2,10),,
c6,,c4(0.2,10),,
c6,,c4(0.1,30),,
c6,,c4(0.05,10),,
].repeat(3));
}
createTrack(7).steps(4, [[d5(32,70),b5(32,100)]]);
for (var n=0;n<2;n++) {
createTrack(1).steps(1, [
[e6(5),a5(8)],,,,
a6(4),,,,
[as6(8),ds4(8)],,,,
,,,,
]);
createTrack(7).steps(1, [
,fs5(7.9,90),g5(7.9),,
,,,,
,fs5(7,90),g5(6),,
,,,,
]);
await createTrack(7).steps(4, [
c6,,c4(0.2,3),,
c6,,c4(0.2,3),,
c6,,c4(0.1,6),,
c6,,c4(0.05,3),,
].repeat(3));
}
async function waterRipple() {
createTrack(7).steps(4, [d6(32,100)]);
createTrack(0).steps(4, [
e5(8,60),,b5(32,60),,
,,,,
,,,,
,,,,
,,,,
,,,,
,,,,
,,,,
ds5(8,60),,,,
,,,,
,,,,
,,,,
,,,,
,,,,
,,,,
,,,,
e5(8,60),,,,
,,,,
,,,,
,,,,
,,,,
,,,,
,,,,
,,,,
f5(8,60),,,,
]);
}
async function waterRippleDrum() {
await createTrack(7).steps(4, [
c6,,,,
c6,,,,
c6,,,,
c6,,,,
c6,,,,
c6,,,c6(0.2,30),
c6,,,,
c6,,,,
c6,,,,
c6,,,,
c6,,,,
c6,,,,
c6,,,,
c6,,,,
c6,,,,
c6,,c6,,
].repeat(1));
}
async function waterRippleDrum2() {
createTrack(7).steps(4, [
,,c4(0.5,30),,
,,c4(0.5,30),,
,,c4(0.5,30),,
,,c4(0.5,30),,
].repeat(7));
await waterRippleDrum();
}
waterRipple();
await waterRippleDrum();
function waterRippleMelody() {
createTrack(1).play([
[ 0.00, e5(8, 127) ],
[ 5.00, e5(0.09, 127) ],
[ 5.92, e5(2.03, 127) ],
[ 7.84, fs5(8.19, 127) ],
[ 3.88, b4(16.20, 127) ],
[ 15.95, gs5(5.06, 127) ],
[ 20.05, e5(3.76, 127) ],
[ 21.84, gs5(2.04, 127) ],
[ 23.92, b5(4.08, 127) ],
[ 23.92, f5(7.93, 127) ],
[ 27.86, a5(4.03, 127) ]]);
}
waterRippleMelody();
waterRipple();
await waterRippleDrum2();
function waterRippleKnotPressure() {
createTrack(2).play([
[0.5,e6(8,55)],
[8.5,ds6(8,55)],
[16.5,e6(8,55)],
[24.5,f6(8,55)],
]);
}
waterRippleMelody();
waterRippleKnotPressure();
waterRipple();
await waterRippleDrum2();
createTrack(0).play([
[0.75, gs6(8,70)],
[8.75, fs6(8,70)],
[16.75, gs6(8,70)],
[24.75, b6(8,70)],
]);
waterRippleMelody();
waterRippleKnotPressure();
createTrack(7).steps(4,[
,,,,
ds5(0.5),,,ds5(0.5,30),
,,,,
ds5(0.5),,,,
].repeat(7));
waterRipple();
await waterRippleDrum2();
createTrack(0).play([
[0.75, gs6(8,70)],
[8.75, fs6(8,70)],
[16.75, gs6(8,70)],
[24.75, b6(8,70)],
]);
waterRippleKnotPressure();
createTrack(7).steps(4,[
,,,,
ds5(0.5),,,ds5(0.5,30),
,,,,
ds5(0.5),,,,
].repeat(7));
waterRipple();
await waterRippleDrum2();
createTrack(0).play([
[0.75, gs6(8,70)],
[8.75, fs6(8,70)],
[9.5, b6(7,100)],
[16.75, gs6(8,70)],
[24.75, b6(8,70)],
]);
createTrack(7).steps(4,[
,,,,
ds5(0.5),,,ds5(0.5,30),
,,,,
ds5(0.5),,,,
].repeat(7));
waterRipple();
await waterRippleDrum2();
createTrack(7).steps(4,[
[a5(32,60)],,ds5(30,20)
]);
createTrack(1).play([
[0,a3(8,100)],
[8,g3(8,100)],
[16,e3(8,100)],
[24,c3(8,100)],
]);
createTrack(0).play([
[0,a4(16,60)],
[0.5,e5(16,60)],
[9,g5(15,60)],
[16.5,e6(24,60)]
]);
await createTrack(7).steps(4, ([
c6,,,,
[c6],,,,
c6,,,,
[c6],,,,
].repeat(2).concat([
c6,,,,
[c6],,,,
c6,,,,
[c6],,c6,,
]).repeat(1)));
await createTrack(7).steps(1,[
ds5(8),,,,,,,,
]);
loopHere();
/*
* (c) Peter Johan Salomonsen 2021
*/
import { AudioPlayerChannel, MonoAudioPlayer, AudioExciterWaveGuide, noise, AuxExciterWaveGuide, WaveGuide, AllPassFloat, beatToFrame, AudioPlayer, cos, outputline, Limiter, TriangleOscillator, PI, sin, cos, FFT, EQBand, TriBandEQ, EnvelopeState, Pan, SineOscillator, IFFTOscillator, BiQuadFilter, FilterType, Q_BUTTERWORTH, DelayLine, BandPass,SawOscillator,softclip, midichannels, MidiChannel, MidiVoice, StereoSignal, Freeverb, SineOscillator, Envelope } from '../mixes/globalimports';
import { SAMPLERATE } from '../environment';
const BPM: f32 = 100;
function notefreq(note: f32): f32 {
return 440 * Mathf.pow(2, (-69 + note) / 12);
}
const delayframes = (SAMPLERATE * (6/8) * 60 / BPM) as usize;
const echoLeft = new DelayLine(delayframes);
const echoRight = new DelayLine(delayframes);
const echoline = new StereoSignal();
class BoltToysPluck extends MidiVoice {
env: Envelope = new Envelope(0.005, 0.2, 1.0, 0.23);
waveguide1: AudioExciterWaveGuide = new AudioExciterWaveGuide(new MonoAudioPlayer(6));
waveguide2: AudioExciterWaveGuide = new AudioExciterWaveGuide(new MonoAudioPlayer(7));
noteon(note: u8, velocity: u8): void {
super.noteon(note, velocity);
const freq = notefreq(note);
let exciterFeedbackFreq = (2000 + Mathf.pow(freq,0.8) * 2)as f32;
if (exciterFeedbackFreq > 12000) {
exciterFeedbackFreq = 12000;
}
this.waveguide1.exciterFilter.update(exciterFeedbackFreq, Q_BUTTERWORTH);
this.waveguide2.exciterFilter.update(exciterFeedbackFreq, Q_BUTTERWORTH);
let feedbackfreq: f32 = 1000 + Mathf.pow(freq, 0.65) * 15;
this.waveguide1.start(freq , feedbackfreq);
this.waveguide2.start(freq , feedbackfreq);
const filtergain = this.waveguide1.filterFeedback.coeffs.magnitude;
let feedbacklevel: f32 = note < 36 ? 0.930 :
note < 48 ? 0.946 :
note < 60 ? 0.992 :
note < 72 ? 0.996 :
0.9999;
if (feedbacklevel > 0.9999) {
feedbacklevel = 0.9999;
} else if (feedbacklevel < 0) {
feedbacklevel = 0;
}
this.waveguide1.feedbackLevel = feedbacklevel;
this.waveguide2.feedbackLevel = feedbacklevel;
this.env.attack();
}
noteoff(): void {
this.env.release();
}
isDone(): boolean {
return this.env.isDone();
}
nextframe(): void {
const env = this.env.next() * this.velocity * (1/127);
const wg1: f32 = this.waveguide1.process();
const wg2: f32 = this.waveguide2.process();
this.channel.signal.add(
env * (wg1) ,
env * (wg2)
);
}
}
class SphericalBowlModeled extends MidiVoice {
env: Envelope = new Envelope(0.001, 1, 0.95, 0.5);
waveguide1: AudioExciterWaveGuide = new AudioExciterWaveGuide(new MonoAudioPlayer(10));
waveguide2: AudioExciterWaveGuide = new AudioExciterWaveGuide(new MonoAudioPlayer(11));
noteon(note: u8, velocity: u8): void {
super.noteon(note, velocity);
const freq = notefreq(note);
this.waveguide1.exciterFilter.update(15000, Q_BUTTERWORTH);
this.waveguide2.exciterFilter.update(15000, Q_BUTTERWORTH);
const feedbacklevel: f32 = 0.9;
this.waveguide1.feedbackLevel = feedbacklevel;
this.waveguide2.feedbackLevel = feedbacklevel;
this.waveguide1.start(freq, freq * 6000 / Mathf.pow(note, 1.3) );
this.waveguide2.start(freq, freq * 6000 / Mathf.pow(note, 1.3) );
this.env.attack();
}
noteoff(): void {
this.env.release();
}
isDone(): boolean {
return this.env.isDone();
}
nextframe(): void {
const env:f32 = this.env.next() * this.velocity * 0.08;
const left =
this.waveguide1.process()
* env;
const right =
this.waveguide2.process()
* env;
this.channel.signal.add(
left, right
);
}
}
class KnotPressureModeled extends MidiVoice {
env: Envelope = new Envelope(0.001, 1, 0.80, 0.5);
waveguide1: AudioExciterWaveGuide = new AudioExciterWaveGuide(new MonoAudioPlayer(8));
waveguide2: AudioExciterWaveGuide = new AudioExciterWaveGuide(new MonoAudioPlayer(9));
noteon(note: u8, velocity: u8): void {
super.noteon(note, velocity);
const freq = notefreq(note);
this.waveguide1.exciterFilter.update(8000, Q_BUTTERWORTH);
this.waveguide2.exciterFilter.update(8000, Q_BUTTERWORTH);
const feedbacklevel: f32 = -0.9;
this.waveguide1.feedbackLevel = feedbacklevel;
this.waveguide2.feedbackLevel = feedbacklevel;
this.waveguide1.start(freq, freq * 4000 / Mathf.pow(note, 1.3) );
this.waveguide2.start(freq, freq * 4000 / Mathf.pow(note, 1.3) );
this.env.attack();
}
noteoff(): void {
this.env.release();
}
isDone(): boolean {
return this.env.isDone();
}
nextframe(): void {
const env:f32 = this.env.next() * this.velocity * 0.05;
const left =
this.waveguide1.process()
* env;
const right =
this.waveguide2.process()
* env;
this.channel.signal.add(
left, right
);
}
}
export function initializeMidiSynth(): void {
midichannels[0] = new MidiChannel(16, (ch) => new BoltToysPluck(ch));
midichannels[0].controlchange(7, 66);
midichannels[0].controlchange(10, 64);
midichannels[0].controlchange(91, 20);
midichannels[1] = new MidiChannel(15, (ch) => new SphericalBowlModeled(ch));
midichannels[1].controlchange(7,25);
midichannels[1].controlchange(10, 64);
midichannels[1].controlchange(91, 60);
midichannels[2] = new MidiChannel(5, (ch) => new KnotPressureModeled(ch));
midichannels[2].controlchange(7, 70);
midichannels[2].controlchange(10, 80);
midichannels[2].controlchange(91, 20);
const audioPlayerChannel: () => AudioPlayerChannel = () => new AudioPlayerChannel(16, (ch, ndx) => {
if (ndx < 15) {
return new AudioPlayer(ch, ndx, 0, (ndx + 60) as u8);
} else {
switch(ndx) {
case 15:
default:
return new AudioPlayer(ch, 4, beatToFrame(7, BPM), 48 as u8, new Envelope(0.001,0.2,0.4,0.1));
}
}
});
midichannels[7] = audioPlayerChannel();
midichannels[7].controlchange(7, 120);
midichannels[7].controlchange(91, 10);
midichannels[8] = audioPlayerChannel();
midichannels[8].controlchange(7, 120);
midichannels[8].controlchange(10, 35);
midichannels[8].controlchange(91, 10);
midichannels[9] = audioPlayerChannel();
midichannels[9].controlchange(7, 120);
midichannels[9].controlchange(10, 95);
midichannels[9].controlchange(91, 10);
}
class MultiBandEQ {
bands: StaticArray<EQBand>;
constructor(freqs: f32[]) {
this.bands = new StaticArray<EQBand>(freqs.length - 1);
for ( let n=1; n<freqs.length; n++) {
this.bands[n-1] = new EQBand(freqs[n-1], freqs[n]);
}
}
process(signal: f32, levels: f32[]): f32 {
let ret: f32 = 0;
for (let n = 0;n<this.bands.length; n++) {
ret += this.bands[n].process(signal) * levels[n];
}
return ret;
}
}
const eqfreqs: f32[] = [20,60,150,600,12000,19000]
const eqlevels: f32[] = [1.0, 1.0, 1.0, 1.0, 1.0];
const eqleft = new MultiBandEQ(eqfreqs);
const eqright = new MultiBandEQ(eqfreqs);
export function postprocess(): void {
const gain: f32 = 2.3;
let left = outputline.left;
let right = outputline.right;
const echol = echoLeft.read() * 0.2;
const echor = echoRight.read() * 0.2;
echoLeft.write_and_advance(echol + echoline.left);
echoRight.write_and_advance(echor + echoline.right);
left+=echol;
right+=echor;
left = eqleft.process(left, eqlevels);
right = eqright.process(right, eqlevels);
left*=gain;
right*=gain;
outputline.left = left;
outputline.right = right;
echoline.clear();
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment