Created
September 13, 2023 04:50
-
-
Save petersalomonsen/d485d6669620e60d7399ee8feaa92537 to your computer and use it in GitHub Desktop.
WebAssembly Music: "Much"
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
precision highp float; | |
uniform vec2 resolution; | |
uniform float time; | |
uniform float targetNoteStates[128]; | |
float wave(float x, float freq, float speed, float amp) { | |
return amp * sin(x * freq + time * speed); | |
} | |
void main() { | |
vec2 uv = (gl_FragCoord.xy / resolution.xy) * 2.0 - 1.0; // UV coordinates [-1, 1] | |
float x_index = (uv.x + 1.0) * 64.0; // Compute the corresponding index in targetNoteStates based on the x position | |
float amp_lower = 0.0; | |
float amp_upper = 0.0; | |
int index_lower = 0; | |
int index_upper = 0; | |
for (int i = 0; i < 128; ++i) { | |
if (float(i) <= x_index) { | |
amp_lower = targetNoteStates[i]; | |
index_lower = i; | |
} | |
if (float(i) >= x_index && index_upper == 0) { | |
amp_upper = targetNoteStates[i]; | |
index_upper = i; | |
} | |
} | |
float fraction = (x_index - float(index_lower)) / (float(index_upper - index_lower) + 0.0001); | |
float amp = mix(amp_lower, amp_upper, fraction); // interpolate amplitude | |
float freq = float(index_lower) / 64.0; // Normalize index to [0, 2] for frequency | |
float y = wave(uv.x, freq, 5.0, amp) * 0.2; // Add the note's wave to the total wave height, increased amplification factor | |
float dayNight = sin(time * 0.2); // changes from -1 to 1 over time, can adjust speed with the 0.2 constant | |
dayNight = dayNight * 0.5 + 0.5; // changes from 0 to 1 | |
vec3 dayColor = vec3(0.9, 0.9, 1.0); | |
vec3 nightColor = vec3(0.1, 0.1, 0.3); | |
vec3 sunsetColor = vec3(0.9, 0.4, 0.1); | |
// calculate color based on time | |
vec3 color; | |
if (dayNight < 0.25) { // Night | |
color = mix(nightColor, sunsetColor, dayNight / 0.25); | |
} else if (dayNight < 0.75) { // Day | |
color = mix(sunsetColor, dayColor, (dayNight - 0.25) / 0.5); | |
} else { // Transition to night | |
color = mix(dayColor, nightColor, (dayNight - 0.75) / 0.25); | |
} | |
// mix in the y component of the uv for the sea color | |
color *= (uv.y + y + 0.5); | |
// darken below the horizon | |
if(uv.y + y < -0.1) { | |
color *= 0.7; | |
} | |
gl_FragColor = vec4(color, 1.0); | |
} |
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
/* | |
* Copyright (c) 2023 - Peter Johan Salomonsen | |
*/ | |
setBPM(124); | |
addInstrument('piano'); | |
addInstrument('string'); | |
addInstrument('drums'); | |
addInstrument('guitar'); | |
addInstrument('bass'); | |
addInstrument('tubelead'); | |
addInstrument('flute'); | |
addInstrument('padsynth'); | |
addInstrument('brass'); | |
addInstrument('choir'); | |
//solo(0); | |
//solo(2); | |
//solo(6); | |
const beat = () => createTrack(2).steps(4,[ | |
c5,,,, | |
c5,,,, | |
[c5],,,, | |
[c5],,,, | |
]); | |
const beat2 = () => createTrack(2).steps(4,[ | |
c5,,fs5(0.5,50),fs5(0.5,5), | |
c5,,fs5(0.2,20),, | |
[c5],,fs5(0.5,50),, | |
[c5],fs5(0.5,5),fs5(0.2,20),, | |
]); | |
const beatwithsnare1 = () => createTrack(2).steps(4,[ | |
c5,,fs5(0.5,50),fs5(0.5,5), | |
c5,,gs5(0.2,20),, | |
[c5],,fs5(0.5,50),, | |
[c5,d5],fs5(0.5,5),gs5(0.2,20),, | |
]); | |
const beatwithsnare2 = () => createTrack(2).steps(4,[ | |
c5,,fs5(0.5,50),fs5(0.5,5), | |
[c5,d5],,gs5(0.2,20),, | |
[c5],,[fs5(0.5,50)],, | |
[c5,d5],fs5(0.5,5),gs5(0.2,20),, | |
c5,,fs5(0.5,50),fs5(0.5,5), | |
[c5,d5],,gs5(0.2,20),, | |
[c5],,fs5(0.5,50),, | |
[c5,d5],fs5(0.5,5),gs5(0.2,20),d5(0.1,30), | |
]); | |
const padsynth1 = () => createTrack(7).play([[ 0.34, controlchange(64, 127) ], | |
[ 0.03, f6(0.85, 88) ], | |
[ 0.03, c6(0.88, 77) ], | |
[ 0.02, gs5(0.91, 78) ], | |
[ 0.06, f2(0.93, 83) ], | |
[ 1.96, controlchange(64, 0) ], | |
[ 2.44, controlchange(64, 127) ], | |
[ 2.01, ds5(0.92, 83) ], | |
[ 2.00, ds6(0.94, 83) ], | |
[ 2.01, as5(0.93, 88) ], | |
[ 2.03, g2(0.94, 84) ], | |
[ 3.45, controlchange(64, 0) ], | |
[ 4.02, controlchange(64, 127) ], | |
[ 3.48, ds5(3.08, 86) ], | |
[ 3.47, c6(3.10, 93) ], | |
[ 6.54, as5(0.98, 93) ], | |
[ 3.49, gs2(4.17, 86) ], | |
[ 7.49, c6(0.28, 75) ], | |
[ 8.05, controlchange(64, 0) ], | |
[ 8.31, controlchange(64, 127) ], | |
[ 8.01, as5(0.92, 96) ], | |
[ 8.02, cs6(0.91, 81) ], | |
[ 8.05, as2(0.94, 86) ], | |
[ 8.05, f5(1.01, 83) ], | |
[ 10.01, controlchange(64, 0) ], | |
[ 10.31, controlchange(64, 127) ], | |
[ 10.04, c6(0.58, 83) ], | |
[ 10.04, f5(0.63, 87) ], | |
[ 10.02, gs5(0.72, 83) ], | |
[ 10.07, f2(0.71, 82) ], | |
[ 11.58, controlchange(64, 0) ], | |
[ 12.21, controlchange(64, 127) ], | |
[ 11.54, cs2(4.34, 84) ], | |
[ 11.52, f5(4.38, 94) ], | |
[ 11.53, cs5(4.37, 88) ], | |
[ 15.94, controlchange(64, 0) ]]); | |
const flute1 = () => createTrack(6).play([[ 0.53, gs7(0.36, 89) ], | |
[ 1.04, gs7(0.31, 94) ], | |
[ 1.52, gs7(0.29, 86) ], | |
[ 2.02, gs7(0.51, 82) ], | |
[ 2.53, g7(0.57, 103) ], | |
[ 3.45, ds7(0.63, 94) ], | |
[ 4.30, ds7(0.27, 63) ], | |
[ 4.48, f7(0.31, 72) ], | |
[ 4.86, c7(1.36, 100) ], | |
[ 8.52, cs7(0.37, 102) ], | |
[ 8.99, cs7(0.31, 44) ], | |
[ 9.47, cs7(0.36, 99) ], | |
[ 10.01, cs7(0.58, 101) ], | |
[ 10.51, c7(0.53, 97) ], | |
[ 11.48, gs6(0.72, 96) ], | |
[ 12.40, gs6(0.27, 83) ], | |
[ 12.54, as6(0.37, 78) ], | |
[ 12.95, f6(1.29, 100) ]]); | |
const bass1 = () => createTrack(4).play([[ 0.02, f2(1.67, 89) ], | |
[ 2.01, g2(1.15, 84) ], | |
[ 3.53, gs2(2.69, 81) ], | |
[ 6.55, gs2(0.41, 89) ], | |
[ 6.98, gs3(0.52, 93) ], | |
[ 7.50, gs2(0.26, 86) ], | |
[ 8.03, as2(1.66, 89) ], | |
[ 10.03, f2(1.29, 87) ], | |
[ 11.56, cs2(2.86, 84) ], | |
[ 14.60, cs2(0.37, 94) ], | |
[ 14.98, cs3(0.59, 89) ], | |
[ 15.55, cs2(0.38, 91) ]].quantize(4)); | |
const strings1 = () => createTrack(1).play([[ 0.07, c5(1.66, 89) ], | |
[ 0.10, gs5(1.79, 86) ], | |
[ 0.10, f5(1.82, 88) ], | |
[ 0.11, f4(1.94, 89) ], | |
[ 2.04, as4(1.23, 96) ], | |
[ 2.05, ds4(1.28, 83) ], | |
[ 2.04, g5(1.38, 89) ], | |
[ 3.51, c5(4.17, 99) ], | |
[ 3.50, gs5(4.39, 91) ], | |
[ 2.04, ds5(5.96, 77) ], | |
[ 3.55, gs4(4.46, 89) ], | |
[ 8.05, cs5(1.85, 94) ], | |
[ 8.07, f5(1.83, 89) ], | |
[ 8.07, gs5(1.85, 93) ], | |
[ 8.06, cs4(1.97, 83) ], | |
[ 10.09, c5(1.11, 100) ], | |
[ 10.11, gs5(1.24, 93) ], | |
[ 10.08, f4(1.48, 92) ], | |
[ 12.13, cs4(3.8, 78) ], | |
[ 11.63, gs4(4.3, 89) ], | |
[ 10.12, f5(5.8, 98) ], | |
[ 11.59, cs5(4.0, 93) ]]); | |
for (var n=0;n<2;n++) { | |
padsynth1(); | |
await beat(); | |
await beat(); | |
await beat(); | |
await beat(); | |
} | |
for (var n=0;n<2;n++) { | |
padsynth1(); | |
strings1(); | |
bass1(); | |
await beat2(); | |
await beat2(); | |
await beat2(); | |
await beat2(); | |
} | |
for (var n=0;n<2;n++) { | |
padsynth1(); | |
flute1(); | |
bass1(); | |
strings1(); | |
await beat2(); | |
await beat2(); | |
await beat2(); | |
await beat2(); | |
} | |
for (var n=0;n<2;n++) { | |
padsynth1(); | |
bass1(); | |
strings1(); | |
createTrack(0).steps(4,[ | |
f5,gs5,c6,f6,gs6,f6,c6,gs5, | |
ds5,g5,as5,ds6,g6,ds6,as5,g5, | |
ds5,gs5,c6,ds6,gs6,ds6,c6,gs5, | |
ds5,gs5,c6,ds6,gs6,ds6,c6,gs5, | |
f5,as5,cs6,f6,as6,f6,cs6,as5, | |
f5,gs5,c6,f6,gs6,f6,c6,gs5, | |
cs5,f5,gs6,cs6,f6,cs6,gs6,f5, | |
cs5,f5,gs6,cs6,f6(0.99),cs6(0.74),gs6(0.49),f5(0.24), | |
]); | |
await beatwithsnare1(); | |
await beatwithsnare1(); | |
await beatwithsnare1(); | |
await beatwithsnare1(); | |
} | |
const padsynth2 = () => createTrack(7).play([[ 0.20, controlchange(64, 127) ], | |
[ 0.05, c6(0.88, 87) ], | |
[ 0.04, gs6(0.94, 88) ], | |
[ 0.07, f6(0.94, 89) ], | |
[ 2.13, controlchange(64, 0) ], | |
[ 2.38, controlchange(64, 127) ], | |
[ 2.04, g6(0.59, 84) ], | |
[ 2.01, ds6(0.67, 73) ], | |
[ 2.04, as5(0.69, 76) ], | |
[ 3.61, controlchange(64, 0) ], | |
[ 6.32, controlchange(64, 127) ], | |
[ 3.50, gs5(3.66, 63) ], | |
[ 3.54, c6(3.63, 74) ], | |
[ 3.54, ds6(3.69, 66) ], | |
[ 8.11, controlchange(64, 0) ], | |
[ 8.65, controlchange(64, 127) ], | |
[ 8.08, f6(1.11, 84) ], | |
[ 8.06, gs5(1.15, 82) ], | |
[ 8.04, cs6(1.19, 82) ], | |
[ 10.11, controlchange(64, 0) ], | |
[ 10.43, controlchange(64, 127) ], | |
[ 10.05, c6(1.08, 84) ], | |
[ 10.08, gs5(1.12, 81) ], | |
[ 10.08, ds6(1.16, 73) ], | |
[ 11.45, controlchange(64, 0) ], | |
[ 11.81, controlchange(64, 127) ], | |
[ 11.55, f6(1.28, 89) ], | |
[ 11.53, gs5(1.32, 84) ], | |
[ 11.51, cs6(1.38, 87) ], | |
[ 13.43, controlchange(64, 0) ], | |
[ 15.10, controlchange(64, 127) ], | |
[ 13.48, f6(2.12, 89) ], | |
[ 13.45, cs6(2.17, 83) ], | |
[ 13.44, as5(2.1, 91) ], | |
[ 15.90, controlchange(64, 0) ]].quantize(4)); | |
const choir2 = () => createTrack(9).play([[ 0.0, c7(1.99, 94) ], | |
[ 0.0, gs7(1.99, 88) ], | |
[ 0.0, f7(1.99, 88) ], | |
[ 2.00, g7(1.49, 97) ], | |
[ 2.00, ds7(1.49, 86) ], | |
[ 2.00, as6(1.49, 89) ], | |
[ 3.5, gs6(4.49, 82) ], | |
[ 3.5, c7(4.49, 93) ], | |
[ 3.5, ds7(4.49, 81) ], | |
[ 8.00, gs6(1.9, 94) ], | |
[ 8.00, f7(1.9, 92) ], | |
[ 8.00, cs7(1.9, 102) ], | |
[ 10.00, c7(1.49, 100) ], | |
[ 10.00, gs6(1.49, 83) ], | |
[ 10.01, ds7(1.49, 89) ], | |
[ 11.5, gs6(1.99, 91) ], | |
[ 11.5, f7(1.99, 94) ], | |
[ 11.5, cs7(1.99, 104) ], | |
[ 13.5, f7(2.49, 84) ], | |
[ 13.5, as6(2.49, 96) ], | |
[ 13.5, cs7(2.49, 104) ]].fixVelocity(100)); | |
const bass2 = () => createTrack(4).play([[ 0.0, f2(0.45, 99) ], | |
[ 0.87, f2(0.32, 70) ], | |
[ 1.0, f3(0.46, 109) ], | |
[ 1.5, f2(0.20, 92) ], | |
[ 2.0, ds2(0.46, 88) ], | |
[ 2.52, ds3(0.54, 83) ], | |
[ 3.49, ds3(0.48, 88) ], | |
[ 3.97, gs2(0.45, 82) ], | |
[ 4.74, gs2(0.30, 77) ], | |
[ 5.01, gs3(0.48, 94) ], | |
[ 5.55, gs2(0.45, 87) ], | |
[ 6.50, gs2(0.51, 76) ], | |
[ 7.01, ds3(0.56, 82) ], | |
[ 7.50, gs3(0.46, 87) ], | |
[ 8.00, as2(0.50, 82) ], | |
[ 8.85, as2(0.30, 88) ], | |
[ 9.09, as3(0.50, 93) ], | |
[ 9.53, as2(0.21, 76) ], | |
[ 10.01, f2(0.54, 90) ], | |
[ 10.57, e3(0.04, 32) ], | |
[ 10.51, f3(0.55, 95) ], | |
[ 11.50, f3(0.15, 82) ], | |
[ 11.98, cs2(0.49, 84) ], | |
[ 12.75, cs2(0.30, 83) ], | |
[ 13.00, cs3(0.32, 93) ], | |
[ 13.51, as1(0.44, 94) ], | |
[ 14.48, as1(0.54, 78) ], | |
[ 15.04, f2(0.45, 74) ], | |
[ 15.47, as2(0.49, 93) ]].quantize(4).fixVelocity(100)); | |
const guitar2 = () => createTrack(3).play([[ 0.51, ds5(0.16, 51) ], | |
[ 0.53, ds6(0.20, 64) ], | |
[ 0.69, f5(0.24, 64) ], | |
[ 0.69, f6(0.29, 67) ], | |
[ 0.92, gs6(0.33, 76) ], | |
[ 0.93, gs5(0.33, 82) ], | |
[ 1.55, gs5(1.06, 76) ], | |
[ 1.55, gs6(1.07, 88) ], | |
[ 2.59, as5(0.59, 94) ], | |
[ 2.57, as6(0.67, 88) ], | |
[ 3.46, ds6(0.27, 86) ], | |
[ 3.52, ds5(0.28, 76) ], | |
[ 4.41, ds6(0.23, 63) ], | |
[ 4.42, ds5(0.29, 69) ], | |
[ 4.72, f5(0.23, 74) ], | |
[ 4.68, f6(0.29, 75) ], | |
[ 4.92, gs5(0.60, 77) ], | |
[ 4.94, gs6(0.57, 82) ], | |
[ 5.50, as6(0.73, 108) ], | |
[ 5.49, as5(0.78, 101) ], | |
[ 6.48, c7(1.28, 89) ], | |
[ 6.49, c6(1.33, 93) ], | |
[ 8.53, ds6(0.21, 82) ], | |
[ 8.58, ds5(0.29, 77) ], | |
[ 8.76, f6(0.27, 79) ], | |
[ 8.81, f5(0.34, 65) ], | |
[ 9.01, gs6(0.34, 83) ], | |
[ 9.10, gs5(0.28, 86) ], | |
[ 9.56, gs5(1.07, 87) ], | |
[ 9.58, gs6(1.06, 91) ], | |
[ 10.62, as5(0.60, 92) ], | |
[ 10.61, as6(0.61, 104) ], | |
[ 11.51, ds6(0.40, 92) ], | |
[ 11.56, ds5(0.43, 83) ], | |
[ 12.46, ds6(0.23, 71) ], | |
[ 12.44, ds5(0.34, 72) ], | |
[ 12.71, f6(0.30, 70) ], | |
[ 12.73, f5(0.28, 69) ], | |
[ 12.94, gs5(0.56, 77) ], | |
[ 12.95, gs6(0.56, 78) ], | |
[ 13.51, as6(0.55, 99) ], | |
[ 13.50, as5(0.67, 96) ], | |
[ 14.49, c6(0.28, 92) ], | |
[ 14.46, c7(0.68, 88) ], | |
[ 14.99, as6(0.55, 83) ], | |
[ 15.04, as5(0.50, 91) ]].quantize(4)); | |
for (var n=0;n<2;n++) { | |
guitar2(); | |
bass2(); | |
padsynth2(); | |
choir2(); | |
await beatwithsnare2(); | |
await beatwithsnare2(); | |
} | |
const flute3 = () => createTrack(6).play([[ 0.03, c8(0.73, 95) ], | |
[ 1.01, gs7(0.54, 106) ], | |
[ 1.94, ds7(0.21, 78) ], | |
[ 2.05, f7(0.43, 87) ], | |
[ 2.48, ds7(0.71, 94) ], | |
[ 3.47, gs7(2.18, 91) ], | |
[ 6.52, f7(0.40, 89) ], | |
[ 6.98, gs7(0.65, 91) ], | |
[ 7.54, as7(0.27, 94) ], | |
[ 8.00, as7(0.24, 94) ], | |
[ 8.15, c8(0.67, 78) ], | |
[ 9.05, gs7(0.48, 104) ], | |
[ 9.94, ds7(0.22, 68) ], | |
[ 10.08, f7(0.45, 92) ], | |
[ 10.51, ds7(0.63, 93) ], | |
[ 11.48, gs7(0.55, 91) ], | |
[ 12.50, gs7(0.33, 91) ], | |
[ 12.99, as7(0.35, 102) ], | |
[ 13.76, as7(0.22, 96) ], | |
[ 13.89, c8(0.26, 97) ], | |
[ 14.10, as7(0.87, 96) ], | |
[ 14.95, gs7(0.74, 99) ]]); | |
const strings3 = () => createTrack(1).play([[ 0.04, f5(1.66, 87) ], | |
[ 0.01, gs5(1.78, 89) ], | |
[ 0.02, c6(1.80, 95) ], | |
[ 1.98, g5(1.31, 94) ], | |
[ 1.98, ds5(1.33, 89) ], | |
[ 1.97, as5(1.39, 91) ], | |
[ 3.52, ds5(4.20, 86) ], | |
[ 3.54, c6(4.29, 87) ], | |
[ 3.52, gs5(4.37, 91) ], | |
[ 8.09, f5(1.70, 98) ], | |
[ 8.06, as5(1.75, 93) ], | |
[ 8.09, cs6(1.75, 96) ], | |
[ 10.01, f5(1.32, 89) ], | |
[ 10.02, gs5(1.32, 94) ], | |
[ 10.03, c6(1.33, 88) ], | |
[ 11.57, f5(1.93, 78) ], | |
[ 11.55, gs5(1.98, 78) ], | |
[ 11.57, cs6(1.97, 91) ], | |
[ 14.01, cs6(1.92, 93) ], | |
[ 14.00, as5(1.93, 96) ], | |
[ 14.02, f5(1.92, 99) ]]); | |
for (var n=0;n<2;n++) { | |
strings3(); | |
flute3(); | |
bass2(); | |
padsynth2(); | |
choir2(); | |
await beatwithsnare2(); | |
await beatwithsnare2(); | |
} | |
const piano4 = () => createTrack(0).play([[ 0.23, controlchange(64, 127) ], | |
[ 0.08, f5(0.46, 60) ], | |
[ 0.56, ds7(0.25, 89) ], | |
[ 0.53, c6(0.36, 54) ], | |
[ 1.03, ds7(0.24, 78) ], | |
[ 1.01, gs5(0.46, 69) ], | |
[ 1.50, c6(0.22, 60) ], | |
[ 1.49, ds7(0.26, 72) ], | |
[ 1.99, controlchange(64, 0) ], | |
[ 2.24, controlchange(64, 127) ], | |
[ 2.05, g5(0.47, 72) ], | |
[ 2.02, ds7(0.58, 84) ], | |
[ 2.47, ds6(0.47, 57) ], | |
[ 2.48, cs7(0.50, 73) ], | |
[ 2.98, as5(0.54, 54) ], | |
[ 3.04, c7(0.52, 59) ], | |
[ 3.73, controlchange(64, 0) ], | |
[ 3.54, ds6(0.28, 72) ], | |
[ 4.04, controlchange(64, 127) ], | |
[ 3.52, gs6(0.60, 82) ], | |
[ 4.04, gs5(0.47, 61) ], | |
[ 4.50, ds7(0.25, 93) ], | |
[ 4.53, ds6(0.37, 68) ], | |
[ 4.95, ds7(0.25, 73) ], | |
[ 5.00, c6(0.44, 49) ], | |
[ 5.53, ds7(0.20, 72) ], | |
[ 5.49, ds6(0.34, 64) ], | |
[ 6.00, gs5(0.42, 62) ], | |
[ 6.01, ds7(0.52, 71) ], | |
[ 6.45, ds6(0.40, 48) ], | |
[ 6.47, cs7(0.55, 72) ], | |
[ 7.02, c7(0.05, 62) ], | |
[ 7.04, c6(0.47, 38) ], | |
[ 7.25, c7(0.26, 2) ], | |
[ 7.51, ds6(0.13, 58) ], | |
[ 7.89, controlchange(64, 0) ], | |
[ 7.47, gs6(0.62, 81) ], | |
[ 8.15, controlchange(64, 127) ], | |
[ 8.00, cs5(0.48, 59) ], | |
[ 8.49, ds7(0.28, 89) ], | |
[ 8.52, gs5(0.36, 64) ], | |
[ 8.97, ds7(0.26, 78) ], | |
[ 9.05, f5(0.42, 49) ], | |
[ 9.53, gs5(0.23, 69) ], | |
[ 9.57, ds7(0.18, 71) ], | |
[ 10.05, controlchange(64, 0) ], | |
[ 10.31, controlchange(64, 127) ], | |
[ 10.10, f5(0.43, 60) ], | |
[ 10.01, ds7(0.56, 76) ], | |
[ 10.53, c6(0.50, 57) ], | |
[ 10.53, cs7(0.56, 71) ], | |
[ 11.05, gs5(0.55, 68) ], | |
[ 11.09, c7(0.52, 64) ], | |
[ 11.59, c6(0.14, 69) ], | |
[ 12.01, controlchange(64, 0) ], | |
[ 11.59, gs6(0.54, 88) ], | |
[ 12.25, controlchange(64, 127) ], | |
[ 12.06, cs5(0.38, 52) ], | |
[ 12.48, gs5(0.44, 71) ], | |
[ 12.51, gs6(0.46, 63) ], | |
[ 13.05, as6(0.19, 81) ], | |
[ 13.05, f5(0.36, 58) ], | |
[ 13.51, gs5(0.19, 61) ], | |
[ 13.58, as6(0.29, 43) ], | |
[ 13.90, controlchange(64, 0) ], | |
[ 13.84, c7(0.19, 78) ], | |
[ 14.17, controlchange(64, 127) ], | |
[ 13.99, as6(0.49, 88) ], | |
[ 13.96, as4(0.54, 68) ], | |
[ 14.47, gs6(0.28, 81) ], | |
[ 14.55, f5(0.35, 49) ], | |
[ 15.02, cs5(0.48, 58) ], | |
[ 15.00, f6(0.54, 42) ], | |
[ 15.55, f5(0.26, 78) ], | |
[ 15.54, ds6(0.35, 77) ], | |
[ 15.88, controlchange(64, 0) ]].quantize(4)); | |
const bass1_2 = () => createTrack(4).play([[ 0.02, f2(1.67, 89) ], | |
[ 2.01, g2(1.15, 84) ], | |
[ 3.53, gs2(2.69, 81) ], | |
[ 6.55, gs2(0.41, 89) ], | |
[ 6.98, gs3(0.52, 93) ], | |
[ 7.50, gs2(0.26, 86) ], | |
[ 8.03, as2(1.66, 89) ], | |
[ 10.03, f2(1.29, 87) ], | |
[ 11.56, cs2(2.25, 84) ], | |
[ 14.60, as1(0.37, 94) ], | |
[ 14.98, as2(0.59, 89) ], | |
[ 15.55, as1(0.38, 91) ]].quantize(4)); | |
for (var n=0;n<2;n++) { | |
bass1_2(); | |
createTrack(2).steps(4,[ | |
f5(0.1,100),,,f5(0.2,80), | |
,,,, | |
b5(0.1,100),,,b5(0.2,80), | |
,,,, | |
].repeat(3)); | |
piano4(); | |
choir2(); | |
await beat2(); | |
await beat2(); | |
await beat2(); | |
await beat2(); | |
} | |
const padsynth4 = () => createTrack(7).play([[ 0.67, controlchange(64, 127) ], | |
[ 0.56, ds7(0.34, 92) ], | |
[ 1.09, ds7(0.28, 88) ], | |
[ 1.57, ds7(0.24, 77) ], | |
[ 2.11, controlchange(64, 0) ], | |
[ 2.21, controlchange(64, 127) ], | |
[ 2.02, ds7(0.60, 87) ], | |
[ 2.53, cs7(0.47, 71) ], | |
[ 2.99, c7(0.49, 82) ], | |
[ 3.77, controlchange(64, 0) ], | |
[ 3.98, controlchange(64, 127) ], | |
[ 3.50, gs6(0.80, 83) ], | |
[ 4.49, ds7(0.23, 87) ], | |
[ 5.04, ds7(0.23, 81) ], | |
[ 5.48, ds7(0.24, 88) ], | |
[ 5.99, ds7(0.59, 83) ], | |
[ 6.47, cs7(0.48, 78) ], | |
[ 6.99, c7(0.48, 70) ], | |
[ 7.79, controlchange(64, 0) ], | |
[ 8.07, controlchange(64, 127) ], | |
[ 7.49, gs6(0.68, 94) ], | |
[ 8.55, ds7(0.24, 88) ], | |
[ 9.02, ds7(0.26, 71) ], | |
[ 9.52, ds7(0.25, 77) ], | |
[ 10.00, ds7(0.59, 76) ], | |
[ 10.52, cs7(0.37, 73) ], | |
[ 10.97, c7(0.48, 64) ], | |
[ 11.67, controlchange(64, 0) ], | |
[ 11.87, controlchange(64, 127) ], | |
[ 11.49, gs6(0.67, 78) ], | |
[ 12.52, gs6(0.44, 64) ], | |
[ 13.06, as6(0.24, 86) ], | |
[ 13.69, as6(0.20, 66) ], | |
[ 14.02, controlchange(64, 0) ], | |
[ 13.93, c7(0.17, 87) ], | |
[ 14.19, controlchange(64, 127) ], | |
[ 14.08, as6(0.47, 86) ], | |
[ 14.57, gs6(0.25, 76) ], | |
[ 15.04, f6(0.47, 44) ], | |
[ 15.99, controlchange(64, 0) ]].fixVelocity(60)); | |
const strings4 = () => createTrack(1).play([[ 0.09, c6(1.70, 94) ], | |
[ 0.10, f6(1.75, 93) ], | |
[ 0.08, gs5(1.78, 98) ], | |
[ 2.11, g5(1.58, 93) ], | |
[ 2.09, ds6(1.70, 88) ], | |
[ 2.08, as5(1.72, 96) ], | |
[ 4.04, gs5(3.61, 96) ], | |
[ 4.05, ds6(3.70, 94) ], | |
[ 4.02, c6(3.80, 95) ], | |
[ 8.02, as5(1.76, 94) ], | |
[ 8.04, f5(1.78, 97) ], | |
[ 8.03, cs6(1.80, 88) ], | |
[ 10.05, f5(1.67, 87) ], | |
[ 10.05, gs5(1.67, 94) ], | |
[ 10.06, c6(1.74, 88) ], | |
[ 12.07, f5(1.85, 94) ], | |
[ 12.06, cs6(1.87, 94) ], | |
[ 12.07, gs5(1.87, 96) ], | |
[ 14.14, f5(1.84, 84) ], | |
[ 14.15, cs6(1.84, 81) ], | |
[ 14.16, as5(1.83, 94) ]].fixVelocity(110)); | |
// transition flute | |
createTrack(6).play([[ 16.48, ds7(0.55, 82) ], | |
[ 17.01, gs7(0.40, 82) ], | |
[ 17.45, ds7(0.19, 51) ], | |
[ 17.98, gs7(0.25, 80) ], | |
[ 18.10, as7(0.89, 80) ], | |
[ 19.02, c8(0.47, 75) ], | |
[ 19.49, ds7(1.21, 75) ], | |
[ 21.53, ds8(1.85, 75) ], | |
[ 23.29, cs8(0.22, 61) ], | |
[ 23.52, ds8(0.30, 82) ], | |
[ 23.72, cs8(0.17, 68) ], | |
[ 23.89, c8(0.31, 82) ], | |
[ 24.11, cs8(0.82, 83) ], | |
[ 25.25, cs8(0.34, 57) ], | |
[ 25.48, ds8(0.50, 78) ], | |
[ 25.98, c8(0.64, 82) ], | |
[ 26.53, gs7(0.61, 83) ], | |
[ 27.99, cs8(1.26, 80) ], | |
[ 29.30, c8(0.28, 72) ], | |
[ 29.53, cs8(0.30, 66) ], | |
[ 29.73, ds8(0.48, 82) ], | |
[ 30.17, c8(0.43, 87) ], | |
[ 30.57, gs7(0.53, 84) ]]); | |
for (var n=0;n<2;n++) { | |
if (n == 1) { | |
strings4(); | |
createTrack(2).steps(4, [ | |
,,,, | |
,,,, | |
,,,, | |
,,,, | |
,,,, | |
,,,, | |
,,,, | |
,,,, | |
,,,, | |
,,,, | |
,,,, | |
,,,, | |
d5(0.1,10),d5(0.1,20),d5(0.1,30),d5(0.1,40), | |
,d5(0.1,60),d5(0.1,70),d5(0.1,80), | |
d5(0.1,90),d5(0.1,40),d5(0.1,100),d5(0.1,80), | |
,d5,d5(0.1,110), | |
]); | |
} | |
padsynth4(); | |
bass1_2(); | |
createTrack(2).steps(4,[ | |
f5(0.1,100),,,f5(0.2,80), | |
,,,, | |
b5(0.1,100),,,b5(0.2,80), | |
,,,, | |
].repeat(3)); | |
piano4(); | |
choir2(); | |
await beatwithsnare2(); | |
await beatwithsnare2(); | |
} | |
for (var n=0;n<2;n++) { | |
guitar2(); | |
bass2(); | |
padsynth2(); | |
choir2(); | |
await beatwithsnare2(); | |
await beatwithsnare2(); | |
} | |
for (var n=0;n<2;n++) { | |
strings3(); | |
flute3(); | |
bass2(); | |
padsynth2(); | |
choir2(); | |
await beatwithsnare2(); | |
await beatwithsnare2(); | |
} | |
const strings5 = () => createTrack(1).play([[ 0.05, f6(1.67, 94) ], | |
[ 0.03, gs6(1.76, 87) ], | |
[ 0.05, c7(1.82, 95) ], | |
[ 2.03, g6(1.31, 97) ], | |
[ 2.05, ds6(1.34, 84) ], | |
[ 2.02, as6(1.40, 92) ], | |
[ 3.56, ds6(4.08, 88) ], | |
[ 3.57, c7(4.31, 94) ], | |
[ 3.55, gs6(4.39, 96) ], | |
[ 8.01, cs7(1.93, 99) ], | |
[ 8.01, f6(1.93, 94) ], | |
[ 7.97, as6(2.00, 92) ], | |
[ 10.04, f6(1.31, 15) ], | |
[ 10.08, c7(1.30, 87) ], | |
[ 10.07, gs6(1.38, 91) ], | |
[ 11.67, f6(1.64, 88) ], | |
[ 11.66, gs6(1.67, 83) ], | |
[ 11.67, cs7(1.68, 101) ], | |
[ 13.50, cs7(2.49, 103) ], | |
[ 13.48, as6(2.49, 94) ], | |
[ 13.50, f6(2.49, 94) ]]); | |
for (var n=0;n<2;n++) { | |
strings5(); | |
guitar2(); | |
bass2(); | |
padsynth2(); | |
choir2(); | |
await beatwithsnare2(); | |
await beatwithsnare2(); | |
} | |
const piano5 = () => createTrack(0).play([[ 0.20, controlchange(64, 127) ], | |
[ 0.06, ds7(0.47, 93) ], | |
[ 0.52, gs6(0.40, 62) ], | |
[ 1.48, gs6(0.44, 57) ], | |
[ 0.10, f5(1.85, 68) ], | |
[ 2.04, controlchange(64, 0) ], | |
[ 1.04, ds7(1.01, 81) ], | |
[ 2.24, controlchange(64, 127) ], | |
[ 2.00, as6(0.48, 73) ], | |
[ 2.82, as6(0.20, 66) ], | |
[ 3.01, c7(0.32, 79) ], | |
[ 2.03, g5(1.80, 67) ], | |
[ 4.04, controlchange(64, 0) ], | |
[ 4.34, controlchange(64, 127) ], | |
[ 3.47, ds6(3.07, 69) ], | |
[ 6.53, as6(0.98, 88) ], | |
[ 4.03, gs5(4.01, 57) ], | |
[ 8.06, controlchange(64, 0) ], | |
[ 7.47, c7(0.60, 83) ], | |
[ 8.29, controlchange(64, 127) ], | |
[ 8.01, cs7(0.46, 87) ], | |
[ 8.47, f6(0.53, 59) ], | |
[ 9.00, cs7(0.50, 94) ], | |
[ 8.04, as5(1.79, 81) ], | |
[ 9.97, controlchange(64, 0) ], | |
[ 9.47, f6(0.54, 48) ], | |
[ 10.16, controlchange(64, 127) ], | |
[ 10.01, c7(0.41, 84) ], | |
[ 10.01, f5(0.77, 64) ], | |
[ 11.03, cs7(0.43, 92) ], | |
[ 12.16, controlchange(64, 0) ], | |
[ 14.04, controlchange(64, 127) ], | |
[ 11.5, cs5(3.65, 68) ], | |
[ 11.48, f6(4.26, 78) ], | |
[ 15.76, controlchange(64, 0) ]]); | |
padsynth1(); | |
piano5(); | |
bass1(); | |
flute1(); | |
await beat2(); | |
await beat2(); | |
await beat2(); | |
await beat2(); | |
createTrack(1).steps(4, [ | |
f3(2),,,, | |
,,,, | |
g3(6/4),,,, | |
,,gs3(18/4),, | |
,,,, | |
,,,, | |
,,,, | |
,,,, | |
as3(2),,,, | |
,,,, | |
f3(6/4),,,, | |
,,cs3(12/4),, | |
,,,, | |
,,,, | |
,,[as3(4/4,50),f5(4/4,30)] | |
]); | |
bass1_2(); | |
piano5(); | |
flute1(); | |
await beat2(); | |
await beat2(); | |
await beat2(); | |
await beat2(); | |
await createTrack(2).steps(1,[[c5,cs6(0.1,30)],,,,]); | |
loopHere(); |
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
/* | |
* (c) Peter Johan Salomonsen 2023 | |
*/ | |
import { MidSideProcessor, TriBandStereoCompressor, MonoCompressor, midiLevelToGain, DelayLineFloat, Noise, outputline, CustomExciterWaveGuide, Comb, MonoAudioPlayer, hardclip, AuxExciterWaveGuide, allocateAudioBuffer, LoPassBiQuadFilter, HiPassBiQuadFilter, noise, Freeverb, 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, SineOscillator, Envelope } from '../mixes/globalimports'; | |
import { SAMPLERATE } from '../environment'; | |
const BPM: f32 = 90; | |
function notefreq(note: f32): f32 { | |
return 440 * Mathf.pow(2, (-69 + note) / 12); | |
} | |
class XorshiftPRNG { | |
private state: Uint32Array; | |
constructor(seed: u32) { | |
this.state = new Uint32Array(2); | |
this.init(seed); | |
} | |
init(seed: u32): void { | |
this.state[0] = seed; | |
this.state[1] = 0x9e3779b9; | |
} | |
t(): u32 { | |
let t = this.state[0] + this.state[1]; | |
this.state[0] = this.state[1]; | |
this.state[1] = t; | |
t ^= t << 23; | |
t ^= t >> 17; | |
t ^= this.state[0]; | |
t ^= this.state[0] >> 26; | |
return t; | |
} | |
random(): f32 { | |
return ((this.t() / Math.pow(2, 32)) - 0.5) as f32; | |
} | |
} | |
let seed = 1; | |
class MultiXorshiftPRNG { | |
prngs: StaticArray<XorshiftPRNG> = new StaticArray<XorshiftPRNG>(8); | |
constructor() { | |
for (let n=0;n<this.prngs.length;n++) { | |
this.prngs[n] = new XorshiftPRNG(seed); | |
seed ++; | |
} | |
} | |
random(): f32 { | |
let t: u32; | |
t = this.prngs[0].t(); | |
for (let n=1;n<this.prngs.length;n++) { | |
t ^= this.prngs[n].t(); | |
} | |
return ((t / Math.pow(2, 32)) - 0.5) as f32; | |
} | |
} | |
const delayframes = (SAMPLERATE * (6/8) * 60 / BPM) as usize; | |
const echoLeft = new DelayLine(delayframes); | |
const echoRight = new DelayLine(delayframes); | |
const echoline = new StereoSignal(); | |
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; | |
} | |
} | |
export class WaveGuideFeedbackLimit { | |
envExciter: Envelope; | |
filterExciter: BiQuadFilter = new BiQuadFilter(); | |
delay: DelayLineFloat = new DelayLineFloat((SAMPLERATE / notefreq(1)) as i32); | |
filterFeedback: BiQuadFilter = new BiQuadFilter(); | |
feedbackLevel: f32; | |
feedbackFilterFreq: f32; | |
freq: f32; | |
exciterenvlevel: f32; | |
constructor(exciterAttack: f32, exciterRelease: f32, exciterFilterFreq: f32, feedbackLevel: f32) { | |
this.envExciter = new Envelope(exciterAttack, | |
exciterRelease, 0, | |
exciterRelease); | |
this.filterExciter.update_coeffecients(FilterType.LowPass, SAMPLERATE, | |
exciterFilterFreq, Q_BUTTERWORTH); | |
this.feedbackLevel = feedbackLevel; | |
} | |
setFilterExciterFreq(freq: f32): void { | |
this.filterExciter.update_coeffecients(FilterType.LowPass, SAMPLERATE, | |
freq, Q_BUTTERWORTH); | |
} | |
start(freq: f32, feedbackFilterFreq: f32): void { | |
if (freq != this.freq) { | |
this.freq = freq; | |
const maxFeedbackFilterFreq: f32 = 20000; | |
if (feedbackFilterFreq > maxFeedbackFilterFreq as f32) { | |
feedbackFilterFreq = maxFeedbackFilterFreq as f32; | |
} else if (feedbackFilterFreq < 10) { | |
feedbackFilterFreq = 10; | |
} | |
this.filterFeedback.update_coeffecients(FilterType.LowPass, SAMPLERATE, | |
feedbackFilterFreq, Q_BUTTERWORTH); | |
this.filterFeedback.coeffs.calculatePhaseAndMagnitudeForFreq(freq); | |
const filterphase = this.filterFeedback.coeffs.phaseSamples; | |
this.filterFeedback.clearBuffers(); | |
this.filterExciter.clearBuffers(); | |
this.feedbackFilterFreq = feedbackFilterFreq; | |
this.delay.setNumFramesAndClear( | |
(SAMPLERATE / | |
(freq) | |
) - filterphase | |
); | |
this.envExciter.val = 0; | |
} | |
this.exciterenvlevel = 1; | |
this.envExciter.attack(); | |
} | |
process(): f32 { | |
const envexciter = this.envExciter.next() * this.exciterenvlevel; | |
let exciterSignal: f32 = noise() * envexciter; | |
exciterSignal = this.filterExciter.process(exciterSignal); | |
const feedback = this.delay.read(); | |
let signal = exciterSignal + feedback; | |
signal = this.filterFeedback.processForm2(signal); | |
this.delay.write_and_advance( | |
Mathf.tanh(signal * this.feedbackLevel) | |
); | |
return signal; | |
} | |
} | |
export class BandPassWaveGuide { | |
envExciter: Envelope; | |
filterExciter: BiQuadFilter = new BiQuadFilter(); | |
delay: DelayLineFloat = new DelayLineFloat((SAMPLERATE / notefreq(1)) as i32); | |
hipass: HiPassBiQuadFilter = new HiPassBiQuadFilter(); | |
filterFeedback: BiQuadFilter = new BiQuadFilter(); | |
feedbackLevel: f32; | |
feedbackFilterFreq: f32; | |
freq: f32; | |
exciterenvlevel: f32; | |
constructor(exciterAttack: f32, exciterRelease: f32, exciterFilterFreq: f32, feedbackLevel: f32) { | |
this.envExciter = new Envelope(exciterAttack, | |
exciterRelease, 0, | |
exciterRelease); | |
this.filterExciter.update_coeffecients(FilterType.LowPass, SAMPLERATE, | |
exciterFilterFreq, Q_BUTTERWORTH); | |
this.feedbackLevel = feedbackLevel; | |
} | |
setFilterExciterFreq(freq: f32): void { | |
this.filterExciter.update_coeffecients(FilterType.LowPass, SAMPLERATE, | |
freq, Q_BUTTERWORTH); | |
} | |
start(freq: f32, feedbackFilterFreq: f32): void { | |
freq*=1.6; | |
if (freq != this.freq) { | |
this.freq = freq; | |
const maxFeedbackFilterFreq: f32 = 20000; | |
if (feedbackFilterFreq > maxFeedbackFilterFreq as f32) { | |
feedbackFilterFreq = maxFeedbackFilterFreq as f32; | |
} else if (feedbackFilterFreq < 10) { | |
feedbackFilterFreq = 10; | |
} | |
this.filterFeedback.update_coeffecients(FilterType.LowPass, SAMPLERATE, | |
feedbackFilterFreq, Q_BUTTERWORTH); | |
this.hipass.update(freq * 0.5 , 0.93); | |
this.hipass.coeffs.calculatePhaseAndMagnitudeForFreq(freq); | |
this.filterFeedback.coeffs.calculatePhaseAndMagnitudeForFreq(freq); | |
const filterphase = this.filterFeedback.coeffs.phaseSamples + | |
this.hipass.coeffs.phaseSamples; | |
this.hipass.clearBuffers(); | |
this.filterFeedback.clearBuffers(); | |
this.filterExciter.clearBuffers(); | |
this.feedbackFilterFreq = feedbackFilterFreq; | |
this.delay.setNumFramesAndClear( | |
(SAMPLERATE / | |
(freq) | |
) - filterphase | |
); | |
this.envExciter.val = 0; | |
} | |
this.exciterenvlevel = 1; | |
this.envExciter.attack(); | |
} | |
process(): f32 { | |
const envexciter = this.envExciter.next() * this.exciterenvlevel; | |
let exciterSignal: f32 = noise() * envexciter; | |
exciterSignal = this.filterExciter.process(exciterSignal); | |
const feedback = this.delay.read(); | |
let signal = exciterSignal + feedback; | |
signal = this.filterFeedback.processForm2(signal); | |
signal = this.hipass.process(signal); | |
this.delay.write_and_advance( | |
Mathf.tanh(signal * this.feedbackLevel) | |
); | |
return signal; | |
} | |
} | |
const exciterStartVal = Mathf.exp(Mathf.E); | |
export class PianoExciterWaveGuide { | |
filterExciter: BiQuadFilter = new BiQuadFilter(); | |
noise: MultiXorshiftPRNG = new MultiXorshiftPRNG(); | |
t: f32; | |
vel: f32; | |
start(lo: f32,notefreq: f32): void { | |
this.t = 0; | |
this.vel = lo; | |
this.filterExciter.clearBuffers(); | |
const filterbasefreq: f32 = (7000 + Mathf.pow(notefreq , 0.9)) * this.vel; | |
this.filterExciter.update_coeffecients(FilterType.LowPass, SAMPLERATE, | |
filterbasefreq, Q_BUTTERWORTH); | |
} | |
process(): f32 { | |
this.t += 20/ SAMPLERATE; | |
let signal = this.noise.random() * Mathf.exp(Mathf.E-this.t); | |
signal = this.filterExciter.process(signal); | |
return signal / exciterStartVal; | |
} | |
} | |
const trt = Math.pow(2.0,1.0/12.0); // 12th root of 2 | |
const logb = (b: f64,x: f64): f64 => Math.log(x) / Math.log(b); // log-base-b of x | |
const Ikey = (f0: f64): f64 => logb(trt,f0*trt/27.5); | |
class PianoWaveGuide extends WaveGuide { | |
exciterWaveGuide: PianoExciterWaveGuide = new PianoExciterWaveGuide(); | |
allpasses: StaticArray<AllPassFloat> = new StaticArray<AllPassFloat>(3); | |
dispersionmodindex: f64 = 0.0; | |
dispersiondelta: f32; | |
level: f32; | |
notefreq: f32; | |
constructor() { | |
super(0.001,0.06,100,0.2); | |
for(let n=0;n<this.allpasses.length;n++) { | |
this.allpasses[n] = new AllPassFloat(); | |
} | |
} | |
start2(note: f32, velocity: u8): void { | |
const velocitylevel = (velocity as f32 / 127); | |
const freq: f32 = notefreq(note); | |
this.notefreq = freq; | |
this.exciterWaveGuide.start(velocitylevel , freq); | |
let feedbackFilterFreq: f32 =7000 + Mathf.pow(freq, 1.05); | |
this.filterFeedback.update_coeffecients(FilterType.LowPass, SAMPLERATE, feedbackFilterFreq, Q_BUTTERWORTH); | |
if (freq != this.freq) { | |
this.filterFeedback.coeffs.calculatePhaseAndMagnitudeForFreq(freq); | |
const delaysize: f32 = (SAMPLERATE / | |
(freq) | |
) | |
- this.filterFeedback.coeffs.phaseSamples; | |
const B = 0.0001; | |
const M = this.allpasses.length; | |
const f0: f64 = freq; | |
const Bc = Math.max(B,0.000001); | |
const k1 = -0.00179; | |
const k2 = -0.0233; | |
const k3 = -2.93; | |
const m1 = 0.0126; | |
const m2 = 0.0606; | |
const m3 = -0.00825; | |
const m4 = 1.97; | |
const wT = 2*Math.PI*f0/SAMPLERATE; | |
const kd = Math.exp(k1*Math.log(Bc)*Math.log(Bc) + k2*Math.log(Bc)+k3); | |
const Cd = Math.exp((m1*Math.log(M)+m2)*Math.log(Bc)+m3*Math.log(M)+m4); | |
const polydel = (a: f64, wT: f64): f64 => Math.atan(Math.sin(wT)/(a+Math.cos(wT)))/wT; | |
const D = Math.exp(Cd - Ikey(f0)*kd); | |
const a1 = (1-D)/(1+D); // By Eq. 3, have D >= 0, hence a1 >= 0 also | |
const Df0 = polydel(a1, wT) - polydel(1.0/a1, wT); | |
const dispersion: f32 = Df0 as f32; | |
for(let n=0;n<this.allpasses.length;n++) { | |
this.allpasses[n].clearBuffers(); | |
this.allpasses[n].setDelta(dispersion); | |
} | |
this.filterFeedback.clearBuffers(); | |
this.delay.setNumFramesAndClear(delaysize - (dispersion * (M as f32))); | |
let feedbacklevel: f32 = Mathf.pow(0.2/ this.filterFeedback.coeffs.magnitude, 1 / freq); | |
this.feedbackLevel = feedbacklevel; | |
this.freq = freq; | |
} | |
} | |
process(): f32 { | |
let exciterSignal: f32 = this.exciterWaveGuide.process(); | |
let feedback = this.delay.read(); | |
let signal = exciterSignal; | |
signal+=this.filterFeedback.process(feedback); | |
for(let n=0;n<this.allpasses.length;n++) signal = this.allpasses[n].process(signal); | |
this.delay.write_and_advance( | |
signal * this.feedbackLevel | |
); | |
return signal; | |
} | |
} | |
class Piano extends MidiVoice { | |
env: Envelope = new Envelope(0.0005, 0.17, 1.0, 0.17); | |
waveguide1: PianoWaveGuide = new PianoWaveGuide(); | |
waveguide2: PianoWaveGuide = new PianoWaveGuide(); | |
noteon(note: u8, velocity: u8): void { | |
super.noteon(note, velocity); | |
this.waveguide1.start2(note as f32, velocity); | |
this.waveguide2.start2(note as f32, velocity); | |
this.env.attack(); | |
} | |
noteoff(): void { | |
this.env.release(); | |
} | |
isDone(): boolean { | |
return this.env.isDone(); | |
} | |
nextframe(): void { | |
let env = this.env.next(); | |
const wg1: f32 = this.waveguide1.process() * env; | |
const wg2: f32 = this.waveguide2.process() * env; | |
this.channel.signal.add(wg1, wg2); | |
} | |
} | |
const numpianofilters = 30; | |
const feedback: f32 = 0.04; | |
const dampening: f32 = 0.1; | |
class PianoChannel extends MidiChannel { | |
allpassesLeft: StaticArray<Comb> = new StaticArray<Comb>(numpianofilters); | |
allpassesRight: StaticArray<Comb> = new StaticArray<Comb>(numpianofilters); | |
hipassLeft: BiQuadFilter = new BiQuadFilter(); | |
hipassRight: BiQuadFilter = new BiQuadFilter(); | |
constructor(numvoices: i32, factoryFunc: (channel: MidiChannel, voiceindex: i32) => MidiVoice) { | |
super(numvoices, factoryFunc); | |
let combsize: usize = SAMPLERATE * 0.000055 as usize; | |
if (combsize == 0) combsize = 1; | |
const hipassfreq: f32 = 150; | |
for(let n=0;n<this.allpassesLeft.length;n++) { | |
this.allpassesLeft[n] = new Comb(combsize); | |
this.allpassesLeft[n].set_feedback(feedback); | |
this.allpassesLeft[n].set_dampening(dampening); | |
this.allpassesRight[n] = new Comb(combsize); | |
this.allpassesRight[n].set_feedback(feedback); | |
this.allpassesRight[n].set_dampening(dampening); | |
} | |
this.hipassLeft.update_coeffecients(FilterType.HighPass, SAMPLERATE, hipassfreq, Q_BUTTERWORTH); | |
this.hipassRight.update_coeffecients(FilterType.HighPass, SAMPLERATE, hipassfreq, Q_BUTTERWORTH); | |
} | |
preprocess(): void { | |
const in_gain: f32 = 3 * Mathf.pow(10.0, -4); | |
let in_left = this.signal.left * in_gain; | |
let in_right = this.signal.right * in_gain; | |
let left = this.hipassLeft.process(in_left); | |
let right = this.hipassRight.process(in_right); | |
const spread: f32 = 0.99999; | |
for (let n=0;n<this.allpassesLeft.length;n++) { | |
left += this.allpassesLeft[n].tick(left * (1.0-spread) + right * spread); | |
left *= (1-feedback); | |
right += this.allpassesRight[n].tick(right * (1.0-spread) + left * spread); | |
right *= (1-feedback); | |
} | |
const combgain: f32 = 2* Mathf.pow(10.0, -9); | |
this.signal.left = in_left + left * combgain; | |
this.signal.right = in_right + right * combgain; | |
} | |
} | |
class String extends MidiVoice { | |
env: Envelope = new Envelope(0.001, 1, 0.9, 0.3); | |
waveguide1: WaveGuide = new WaveGuide(0.01, 20.0, 610, 0.995); | |
waveguide2: WaveGuide = new WaveGuide(0.01, 20.0, 620, 0.995); | |
noteon(note: u8, velocity: u8): void { | |
super.noteon(note, velocity); | |
const freq = notefreq(note); | |
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(); | |
this.waveguide1.envExciter.releaseStep = 0.05; | |
this.waveguide2.envExciter.releaseStep = 0.05; | |
this.waveguide1.envExciter.release(); | |
this.waveguide2.envExciter.release(); | |
} | |
isDone(): boolean { | |
return this.env.isDone() && | |
this.waveguide1.envExciter.isDone() && | |
this.waveguide2.envExciter.isDone(); | |
} | |
nextframe(): void { | |
const env:f32 = this.env.next() * this.velocity * 0.01; | |
const left = | |
this.waveguide1.process() | |
* env; | |
const right = | |
this.waveguide2.process() | |
* env; | |
this.channel.signal.add( | |
left, right | |
); | |
} | |
} | |
class Brass extends MidiVoice { | |
env: Envelope = new Envelope(0.01, 1.0, 1.0, 0.1); | |
waveguide1: WaveGuide = new WaveGuide(0.02, 0.15, 1000, 0.99999); | |
waveguide2: WaveGuide = new WaveGuide(0.03, 0.2, 5000, 1.0); | |
noteon(note: u8, velocity: u8): void { | |
super.noteon(note, velocity); | |
const freq = notefreq(note); | |
this.waveguide1.start(freq, freq * 10); | |
this.waveguide2.start(freq, freq * 8); | |
this.env.attack(); | |
} | |
noteoff(): void { | |
this.env.release(); | |
} | |
isDone(): boolean { | |
return this.env.isDone(); | |
} | |
nextframe(): void { | |
const env = this.env.next(); | |
let signal = ( | |
this.waveguide1.process() + | |
this.waveguide2.process() | |
) | |
* env * this.velocity / 127 as f32; | |
this.channel.signal.add( | |
signal, signal | |
); | |
} | |
} | |
class Guitar extends MidiVoice { | |
env: Envelope = new Envelope(0.002, 1, 1.0, 0.1); | |
waveguide1: WaveGuide = new WaveGuide(0.002, 0.03, 2000, 0.995); | |
waveguide2: WaveGuide = new WaveGuide(0.002, 0.03, 2000, 0.995); | |
noteon(note: u8, velocity: u8): void { | |
super.noteon(note, velocity); | |
const freq = notefreq(note); | |
this.waveguide1.start(freq * 1.001, freq * 5000 / Mathf.pow(note, 1.28) ); | |
this.waveguide2.start(freq * 0.999, freq * 5000 / Mathf.pow(note, 1.28) ); | |
this.env.attack(); | |
} | |
noteoff(): void { | |
this.env.release(); | |
} | |
isDone(): boolean { | |
return this.env.isDone(); | |
} | |
nextframe(): void { | |
const env = this.env.next(); | |
const signal = (( | |
this.waveguide1.process() | |
) | |
* env * this.velocity * 0.04) as f32; | |
const signal2 = (( | |
this.waveguide2.process() | |
) | |
* env * this.velocity * 0.04) as f32; | |
this.channel.signal.add( | |
signal, signal2 | |
); | |
} | |
} | |
class GuitarChannel extends MidiChannel { | |
bandpassleft: BandPass = new BandPass(150,4000); | |
bandpassright: BandPass = new BandPass(150,4000); | |
feedbackleft: f32 = 0; | |
feedbackright: f32 = 0; | |
preprocess(): void { | |
let left = this.signal.left; | |
let right = this.signal.right; | |
const feedbackleft = this.feedbackleft; | |
const feedbackright = this.feedbackleft; | |
left = softclip(left * 0.8); | |
right = softclip(right * 0.8); | |
this.feedbackleft += this.bandpassleft.process(left * 1.5); | |
this.feedbackright += this.bandpassright.process(right * 1.5); | |
left += feedbackleft; | |
left *= 0.5; | |
right += feedbackright; | |
right *= 0.5; | |
const echoamount = midiLevelToGain(this.controllerValues[30]) * 0.3; | |
echoline.left += (left * echoamount); | |
echoline.right += (right * echoamount); | |
this.signal.left = left; | |
this.signal.right = right; | |
} | |
} | |
class Bass extends MidiVoice { | |
env: Envelope = new Envelope(0.001, 1, 1.0, 0.1); | |
waveguide1: WaveGuide = new WaveGuide(0.001, 0.01, 200, 0.999); | |
noteon(note: u8, velocity: u8): void { | |
super.noteon(note, velocity); | |
const freq = notefreq(note); | |
this.waveguide1.start(freq, freq * 8000 / Mathf.pow(note, 1.7)); | |
this.env.attack(); | |
} | |
noteoff(): void { | |
this.env.release(); | |
} | |
isDone(): boolean { | |
return this.env.isDone(); | |
} | |
nextframe(): void { | |
const env = this.env.next(); | |
const signal = ( | |
this.waveguide1.process() | |
) | |
* env * this.velocity * 0.25 as f32; | |
this.channel.signal.add( | |
signal, signal | |
); | |
} | |
} | |
class TubeLead extends MidiVoice { | |
env: Envelope = new Envelope(0.05, 1, 1.0, 0.1); | |
waveguide1: WaveGuideFeedbackLimit = new WaveGuideFeedbackLimit(0.001, 0.01,200, -1.12); | |
noteon(note: u8, velocity: u8): void { | |
super.noteon(note, velocity); | |
const freq = notefreq(note); | |
this.waveguide1.start(freq, 230 * Mathf.pow(freq, 0.36 )); | |
this.env.attack(); | |
} | |
noteoff(): void { | |
this.env.release(); | |
} | |
isDone(): boolean { | |
return this.env.isDone(); | |
} | |
nextframe(): void { | |
const env = this.env.next() * 0.02; | |
const signal = ( | |
this.waveguide1.process() | |
) | |
* env * this.velocity as f32; | |
this.channel.signal.add( | |
signal, signal | |
); | |
} | |
} | |
class FluteChannel extends MidiChannel { | |
preprocess(): void { | |
let signal = this.signal.left; | |
echoline.left += (signal) * this.volume; | |
echoline.right += (signal) * this.volume; | |
} | |
} | |
class Flute extends MidiVoice { | |
env: Envelope = new Envelope(0.05, 1, 1.0, 0.01); | |
waveguide1: BandPassWaveGuide = new BandPassWaveGuide(0.001, 0.01,150, -1.1); | |
noteon(note: u8, velocity: u8): void { | |
super.noteon(note, velocity); | |
const freq = notefreq(note); | |
this.waveguide1.start(freq, 80 * Mathf.pow(freq, 0.5 )); | |
this.env.attack(); | |
} | |
noteoff(): void { | |
this.env.release(); | |
} | |
isDone(): boolean { | |
return this.env.isDone(); | |
} | |
nextframe(): void { | |
const env = this.env.next() * 0.01; | |
const signal = ( | |
this.waveguide1.process() | |
) | |
* env * this.velocity as f32; | |
this.channel.signal.add( | |
signal, signal | |
); | |
} | |
} | |
class HihatGuide extends WaveGuide { | |
allpasses: StaticArray<AllPassFloat> = new StaticArray<AllPassFloat>(3); | |
constructor(attack: f32, decay: f32, feedbackfreq: f32, feedback: f32) { | |
super(attack, decay, feedbackfreq, feedback); | |
for(var n=0;n<this.allpasses.length;n++) { | |
this.allpasses[n] = new AllPassFloat(); | |
this.allpasses[n].setDelta(5); | |
} | |
} | |
process(): f32 { | |
const envexciter = this.envExciter.next() * this.exciterenvlevel; | |
let exciterSignal: f32 = noise() * envexciter; | |
exciterSignal = this.filterExciter.process(exciterSignal); | |
const feedback = this.delay.read(); | |
let signal = exciterSignal + feedback; | |
for(let n=0;n<this.allpasses.length;n++) signal = this.allpasses[n].process(signal); | |
signal = this.filterFeedback.processForm2(signal); | |
this.delay.write_and_advance( | |
signal * this.feedbackLevel | |
); | |
return signal; | |
} | |
} | |
class Hihat extends MidiVoice { | |
env: Envelope = new Envelope(0.0001, 1, 1.0, 0.2); | |
waveguide1: HihatGuide = new HihatGuide(0.012, 0.06, 20000, 0.6); | |
waveguide2: HihatGuide = new HihatGuide(0.012, 0.06, 20000, 0.6); | |
constructor(channel: MidiChannel) { | |
super(channel); | |
this.minnote = 66; | |
this.maxnote = 66; | |
} | |
noteon(note: u8, velocity: u8): void { | |
super.noteon(note, velocity); | |
const freq: f32 = 7200; | |
this.waveguide1.start(freq, 20000); | |
this.waveguide2.start(freq, 20000); | |
this.env.attack(); | |
} | |
noteoff(): void { | |
this.env.release(); | |
} | |
isDone(): boolean { | |
return this.env.isDone(); | |
} | |
nextframe(): void { | |
const env = this.env.next() * 0.04 * this.velocity / 30;; | |
let left = ( | |
this.waveguide1.process() | |
) | |
* env as f32; | |
let right = ( | |
this.waveguide2.process() | |
) | |
* env as f32; | |
this.channel.signal.add( | |
left, right | |
); | |
} | |
} | |
class OpenHihat extends MidiVoice { | |
env: Envelope = new Envelope(0.0001, 1, 1.0, 0.2); | |
waveguide1: HihatGuide = new HihatGuide(0.001, 0.5, 20000, 0.85); | |
waveguide2: HihatGuide = new HihatGuide(0.001, 0.5, 20000, 0.85); | |
constructor(channel: MidiChannel) { | |
super(channel); | |
this.minnote = 68; | |
this.maxnote = 68; | |
} | |
noteon(note: u8, velocity: u8): void { | |
super.noteon(note, velocity); | |
const freq: f32 = 7200; | |
this.waveguide1.start(freq, 20000); | |
this.waveguide2.start(freq, 20000); | |
this.env.attack(); | |
} | |
noteoff(): void { | |
this.env.release(); | |
} | |
isDone(): boolean { | |
return this.env.isDone(); | |
} | |
nextframe(): void { | |
const env = this.env.next() * 0.04 * this.velocity / 30; | |
let left = ( | |
this.waveguide1.process() | |
) | |
* env as f32; | |
let right = ( | |
this.waveguide2.process() | |
) | |
* env as f32; | |
this.channel.signal.add( | |
left, right | |
); | |
} | |
} | |
class KickGuide extends WaveGuide { | |
allpasses: StaticArray<AllPassFloat> = new StaticArray<AllPassFloat>(4); | |
constructor(attack: f32, decay: f32, feedbackfreq: f32, feedback: f32) { | |
super(attack, decay, feedbackfreq, feedback); | |
for(var n=0;n<this.allpasses.length;n++) { | |
this.allpasses[n] = new AllPassFloat(); | |
this.allpasses[n].setDelta(20); | |
} | |
} | |
start(freq:f32, filterfreq: f32):void { | |
super.start(freq,filterfreq); | |
//this.filterExciter.clearBuffers(); | |
//this.filterFeedback.clearBuffers(); | |
for(var n=0;n<this.allpasses.length;n++) { | |
//this.allpasses[n].clearBuffers(); | |
} | |
} | |
process(): f32 { | |
const envexciter = this.envExciter.next() * this.exciterenvlevel; | |
let exciterSignal: f32 = noise() * envexciter; | |
exciterSignal = this.filterExciter.process(exciterSignal); | |
const feedback = this.delay.read(); | |
let signal = exciterSignal + feedback; | |
for(let n=0;n<this.allpasses.length;n++) signal = this.allpasses[n].process(signal); | |
signal = this.filterFeedback.processForm2(signal); | |
this.delay.write_and_advance( | |
signal * this.feedbackLevel | |
); | |
return signal; | |
} | |
} | |
class Kick2 extends MidiVoice { | |
env: Envelope = new Envelope(0.0001, 1, 1.0, 0.4); | |
waveguide1: KickGuide = new KickGuide(0.001, 0.08, 100, 0.5); | |
waveguide2: KickGuide = new KickGuide(0.001, 0.08, 100, 0.5); | |
chasingvelocity: f32 = 0; | |
constructor(channel: MidiChannel) { | |
super(channel); | |
this.minnote = 60; | |
this.maxnote = 60; | |
} | |
noteon(note: u8, velocity: u8): void { | |
super.noteon(note, velocity); | |
const freq: f32 = 1000; | |
this.waveguide1.start(freq * 1.0001, 500); | |
this.waveguide2.start(freq * 0.9999, 500); | |
this.env.attack(); | |
} | |
noteoff(): void { | |
this.env.release(); | |
} | |
isDone(): boolean { | |
return this.env.isDone(); | |
} | |
nextframe(): void { | |
const env = this.env.next() * 0.35 * this.chasingvelocity; | |
this.chasingvelocity += (this.velocity - this.chasingvelocity) * 0.01; | |
const left = ( | |
this.waveguide1.process() | |
) | |
* env as f32; | |
const right = ( | |
this.waveguide2.process() | |
) | |
* env as f32; | |
this.channel.signal.add( | |
left, right | |
); | |
} | |
} | |
class SnareGuide extends WaveGuide { | |
allpasses: StaticArray<AllPassFloat> = new StaticArray<AllPassFloat>(6); | |
constructor(attack: f32, decay: f32, feedbackfreq: f32, feedback: f32) { | |
super(attack, decay, feedbackfreq, feedback); | |
for(var n=0;n<this.allpasses.length;n++) { | |
this.allpasses[n] = new AllPassFloat(); | |
this.allpasses[n].setDelta(15); | |
} | |
} | |
process(): f32 { | |
const envexciter = this.envExciter.next() * this.exciterenvlevel; | |
let exciterSignal: f32 = noise() * envexciter; | |
exciterSignal = this.filterExciter.process(exciterSignal); | |
const feedback = this.delay.read(); | |
let signal = exciterSignal + feedback; | |
for(let n=0;n<this.allpasses.length;n++) signal = this.allpasses[n].process(signal); | |
signal = this.filterFeedback.processForm2(signal); | |
this.delay.write_and_advance( | |
signal * this.feedbackLevel | |
); | |
return signal; | |
} | |
} | |
class Snare2 extends MidiVoice { | |
env: Envelope = new Envelope(0.005, 0.5, 0.1, 0.3); | |
waveguide1: SnareGuide = new SnareGuide(0.005, 0.2, 10000, 0.8); | |
waveguide2: SnareGuide = new SnareGuide(0.005, 0.2, 10000, 0.8); | |
constructor(channel: MidiChannel) { | |
super(channel); | |
this.minnote = 62; | |
this.maxnote = 62; | |
} | |
noteon(note: u8, velocity: u8): void { | |
super.noteon(note, velocity); | |
const freq: f32 = 1400; | |
this.waveguide1.start(freq, 1660); | |
this.waveguide2.start(freq, 1660); | |
this.env.attack(); | |
} | |
noteoff(): void { | |
this.env.release(); | |
} | |
isDone(): boolean { | |
return this.env.isDone(); | |
} | |
nextframe(): void { | |
const env = this.env.next() * 1.6 * this.velocity as f32 / 127 as f32; | |
let left = ( | |
this.waveguide1.process() | |
) | |
* env as f32; | |
let right = ( | |
this.waveguide2.process() | |
) | |
* env as f32; | |
this.channel.signal.add( | |
left, right | |
); | |
} | |
} | |
class CymbalGuide extends WaveGuide { | |
allpasses: StaticArray<AllPassFloat> = new StaticArray<AllPassFloat>(6); | |
hipass: HiPassBiQuadFilter = new HiPassBiQuadFilter(); | |
constructor(attack: f32, decay: f32, feedbackfreq: f32, feedback: f32) { | |
super(attack, decay, feedbackfreq, feedback); | |
for(var n=0;n<this.allpasses.length;n++) { | |
this.allpasses[n] = new AllPassFloat(); | |
this.allpasses[n].setDelta(11); | |
} | |
this.hipass.update_coeffecients(FilterType.HighPass, SAMPLERATE, | |
1200, Q_BUTTERWORTH); | |
} | |
process(): f32 { | |
const envexciter = this.envExciter.next() * this.exciterenvlevel; | |
let exciterSignal: f32 = noise() * envexciter; | |
exciterSignal = this.filterExciter.process(exciterSignal); | |
const feedback = this.delay.read(); | |
let signal = exciterSignal + feedback; | |
for(let n=0;n<this.allpasses.length;n++) signal = this.allpasses[n].process(signal); | |
signal = this.filterFeedback.processForm2(signal); | |
this.delay.write_and_advance( | |
signal * this.feedbackLevel | |
); | |
signal = this.hipass.process(signal); | |
return signal * 0.00027; | |
} | |
} | |
class Cymbal extends MidiVoice { | |
env: Envelope = new Envelope(0.0001, 3.0, 1.0, 3.0); | |
waveguide1: CymbalGuide = new CymbalGuide(0.001, 0.15, 20000, 0.999); | |
waveguide2: CymbalGuide = new CymbalGuide(0.001, 0.15, 20000, 0.999); | |
chasingvelocity: f32 = 0; | |
constructor(channel: MidiChannel, note: u8) { | |
super(channel); | |
this.minnote = note; | |
this.maxnote = note; | |
} | |
noteon(note: u8, velocity: u8): void { | |
super.noteon(note, velocity); | |
const freq: f32 = SAMPLERATE / 2; | |
this.waveguide1.start(freq * 0.95, 20000); | |
this.waveguide2.start(freq * 1.05, 20000); | |
this.env.attack(); | |
} | |
noteoff(): void { | |
this.env.release(); | |
} | |
isDone(): boolean { | |
return this.env.isDone(); | |
} | |
nextframe(): void { | |
const env = this.env.next() * 0.2 * this.chasingvelocity; | |
this.chasingvelocity += 0.01 * (this.velocity-this.chasingvelocity); | |
const left = ( | |
this.waveguide1.process() | |
) | |
* env as f32; | |
const right = ( | |
this.waveguide2.process() | |
) | |
* env as f32; | |
this.channel.signal.add( | |
left, right | |
); | |
} | |
} | |
class TomGuide extends WaveGuide { | |
allpasses: StaticArray<AllPassFloat> = new StaticArray<AllPassFloat>(6); | |
hipass: HiPassBiQuadFilter = new HiPassBiQuadFilter(); | |
constructor(attack: f32, decay: f32, feedbackfreq: f32, feedback: f32) { | |
super(attack, decay, feedbackfreq, feedback); | |
for(var n=0;n<this.allpasses.length;n++) { | |
this.allpasses[n] = new AllPassFloat(); | |
this.allpasses[n].setDelta(20000); | |
} | |
this.hipass.update_coeffecients(FilterType.HighPass, SAMPLERATE, | |
250, Q_BUTTERWORTH); | |
} | |
process(): f32 { | |
const envexciter = this.envExciter.next() * this.exciterenvlevel; | |
let exciterSignal: f32 = noise() * envexciter; | |
exciterSignal = this.filterExciter.process(exciterSignal); | |
const feedback = this.delay.read(); | |
let signal = exciterSignal + feedback; | |
for(let n=0;n<this.allpasses.length;n++) signal = this.allpasses[n].process(signal); | |
signal = this.filterFeedback.processForm2(signal); | |
this.delay.write_and_advance( | |
signal * this.feedbackLevel | |
); | |
signal = this.hipass.process(signal); | |
return signal * 0.020; | |
} | |
} | |
class Tom extends MidiVoice { | |
env: Envelope = new Envelope(0.0001, 1, 1.0, 0.4); | |
waveguide1: TomGuide = new TomGuide(0.002, 0.12, 1000, 0.998); | |
waveguide2: TomGuide = new TomGuide(0.002, 0.12, 1000, 0.998); | |
constructor(channel: MidiChannel, note: u8) { | |
super(channel); | |
this.minnote = note; | |
this.maxnote = note; | |
} | |
noteon(note: u8, velocity: u8): void { | |
super.noteon(note, velocity); | |
const freq: f32 = 20000; | |
this.waveguide1.start(freq * 0.99, 1600 + ((this.minnote - 65 ) as f32 * 250)); | |
this.waveguide2.start(freq * 1.01, 1600 + ((this.minnote - 65 ) as f32 * 250)); | |
this.env.attack(); | |
} | |
noteoff(): void { | |
this.env.release(); | |
} | |
isDone(): boolean { | |
return this.env.isDone(); | |
} | |
nextframe(): void { | |
const env = this.env.next() * 0.5 * this.velocity; | |
const left = ( | |
this.waveguide1.process() | |
) | |
* env as f32; | |
const right = ( | |
this.waveguide2.process() | |
) | |
* env as f32; | |
this.channel.signal.add( | |
left, right | |
); | |
} | |
} | |
class DrumChannel extends MidiChannel { | |
hipassl: BiQuadFilter = new BiQuadFilter(); | |
hipassr: BiQuadFilter = new BiQuadFilter(); | |
constructor(numvoices: i32, factoryFunc: (channel: MidiChannel, voiceindex: i32) => MidiVoice) { | |
super(numvoices, factoryFunc); | |
this.hipassl.update_coeffecients(FilterType.HighPass, SAMPLERATE, 30, Q_BUTTERWORTH); | |
this.hipassr.update_coeffecients(FilterType.HighPass, SAMPLERATE, 30, Q_BUTTERWORTH); | |
} | |
preprocess(): void { | |
let left = this.signal.left; | |
let right = this.signal.right; | |
left = this.hipassl.process(left); | |
right = this.hipassr.process(right); | |
this.signal.left = left; | |
this.signal.right = right; | |
//echoline.left += (left) * this.volume; | |
//echoline.right += (right) * this.volume; | |
} | |
} | |
const fft: FFT = new FFT(11); | |
for (let n=1; n< (fft.buffer.length / 2) - 1; n+=1) { | |
let v = Mathf.exp((-n as f32 ) * 0.2) * 1023 * cos((n * 5) as f32); | |
//fft.buffer[n].re = v; | |
//fft.buffer[fft.buffer.length - n].re = -v; | |
fft.buffer[n].im = -v; | |
fft.buffer[fft.buffer.length - n].im = v; | |
} | |
fft.calculateInverse(); | |
class PadSynth extends MidiVoice { | |
env: Envelope = new Envelope(0.001, 2.5, 0.00, 0.2); | |
t: f64 = 0; | |
freq: f32 = 0; | |
spread: i32 = 9; | |
hprofile: StaticArray<f32> = new StaticArray<f32>(this.spread); | |
phase: StaticArray<f32> = new StaticArray<f32>(this.spread); | |
freqs: StaticArray<f32> = new StaticArray<f32>(this.spread); | |
level: f32 = 1.0; | |
constructor(channel: MidiChannel) { | |
super(channel); | |
const halfspread: f32 = (this.spread / 2) as f32; | |
for(let n=0;n<this.spread;n++) { | |
const v: f32 = 2 as f32 * ( | |
n as f32 - halfspread); | |
this.hprofile[n] = Mathf.exp(v * -v); | |
this.phase[n] = noise() * 0x800 as f32; | |
} | |
} | |
noteon(note: u8, velocity: u8): void { | |
super.noteon(note, velocity); | |
this.env.attack(); | |
const halfspread: f32 = (this.spread / 2) as f32; | |
for (let n = 0;n<this.freqs.length; n++) { | |
const v: f32 = 0.04 as f32 * (n as f32 - halfspread); | |
this.freqs[n] = notefreq((note + v)); | |
} | |
this.level = (-(1/(notefreq(note)-200)) + 1) as f32; | |
} | |
noteoff(): void { | |
this.env.release(); | |
} | |
isDone(): boolean { | |
const ret = this.env.isDone(); | |
return ret; | |
} | |
nextframe(): void { | |
const env = this.env.next() * this.level; | |
let left: f32 = 0; | |
let right: f32 = 0; | |
const t = this.t; | |
const freq = this.freq; | |
for(let n = 0;n < this.spread ; n++) { | |
const f = this.freqs[n]; | |
const bufferpos = t * 0x800 * f + this.phase[n]; | |
const floorbufferpos = Math.floor(bufferpos); | |
const delta = (bufferpos - floorbufferpos) as f32; | |
const v1 = fft.buffer[((bufferpos as i64) & 0x7ff) as i32].re; | |
const v = v1; | |
const leftlevel: f32 = (((n) / 2) + 0.5) as f32; | |
const rightlevel: f32 = (((this.hprofile.length - n) / 2) + 0.5) as f32; | |
left += v* leftlevel; | |
right += v* rightlevel; | |
} | |
left *= env; | |
right *= env; | |
this.t += 1 / SAMPLERATE; | |
this.channel.signal.add( | |
left, right | |
); | |
} | |
} | |
class PadSynthChannel extends MidiChannel { | |
lopassleft: BiQuadFilter = new BiQuadFilter(); | |
lopassright: BiQuadFilter = new BiQuadFilter(); | |
constructor(numvoices: i32, factoryFunc: (channel: MidiChannel, voiceindex: i32) => MidiVoice) { | |
super(numvoices, factoryFunc); | |
this.lopassleft.update_coeffecients(FilterType.LowPass, SAMPLERATE, | |
4000, 0.3); | |
this.lopassright.update_coeffecients(FilterType.LowPass, SAMPLERATE, | |
4000, 0.3); | |
} | |
preprocess(): void { | |
let left = this.signal.left; | |
let right = this.signal.right; | |
const gain:f32 = 0.04; | |
left*=gain; | |
right*=gain; | |
left = this.lopassleft.process(left); | |
right = this.lopassright.process(right); | |
echoline.left += (left * 0.1); | |
echoline.right += (right * 0.1); | |
this.signal.left = left; | |
this.signal.right = right; | |
} | |
} | |
class Choir extends MidiVoice { | |
env: Envelope = new Envelope(0.05, 1, 1.0, 0.3); | |
waveguide1: CustomExciterWaveGuide = new CustomExciterWaveGuide(); | |
waveguide2: CustomExciterWaveGuide = new CustomExciterWaveGuide(); | |
exciterEnv: Envelope = new Envelope(0.15, 0.2, 0.6, 0.3); | |
saw: SawOscillator = new SawOscillator(); | |
lopass: LoPassBiQuadFilter = new LoPassBiQuadFilter(); | |
noteon(note: u8, velocity: u8): void { | |
super.noteon(note, velocity); | |
const freq = notefreq(note); | |
this.saw.frequency = freq ; | |
this.waveguide1.feedbackLevel = -1; | |
this.waveguide2.feedbackLevel = -1; | |
this.waveguide1.start(freq, Mathf.log(freq * 0.01) * 2000); | |
this.waveguide2.start(freq * 2.0, Mathf.log(freq * 0.01) * 2000); | |
this.lopass.update(2500,0.6); | |
this.env.attack(); | |
this.exciterEnv.attack(); | |
} | |
noteoff(): void { | |
this.env.release(); | |
this.exciterEnv.release(); | |
} | |
isDone(): boolean { | |
return this.env.isDone() && this.exciterEnv.isDone(); | |
} | |
nextframe(): void { | |
const env = this.env.next() * 0.003; | |
const exciterSignal = this.lopass.process((noise()) * this.exciterEnv.next()); | |
this.waveguide1.exciterSignal = exciterSignal; | |
this.waveguide2.exciterSignal = exciterSignal; | |
const signal1 = this.waveguide1.process(); | |
const signal2 = this.waveguide2.process(); | |
const left = (signal2 + signal1 * 0.4 ) * env * this.velocity as f32; | |
const right = (signal1 + signal2 * 0.4 ) * env * this.velocity as f32; | |
this.channel.signal.add( | |
left, right | |
); | |
} | |
} | |
export function initializeMidiSynth(): void { | |
midichannels[0] = new PianoChannel(10, (ch) => new Piano(ch)); | |
midichannels[0].controlchange(7, 120); | |
midichannels[0].controlchange(10, 64); | |
midichannels[0].controlchange(91,60); | |
midichannels[1] = new MidiChannel(10, (ch) => new String(ch)); | |
midichannels[1].controlchange(7,50); | |
midichannels[1].controlchange(10, 64); | |
midichannels[1].controlchange(91, 40); | |
midichannels[2] = new DrumChannel(9, (ch, ndx) => { | |
switch(ndx) { | |
case 1: | |
return new Hihat(ch); | |
case 2: | |
return new Snare2(ch); | |
case 3: | |
return new OpenHihat(ch); | |
case 4: | |
return new Tom(ch, 65); | |
case 5: | |
return new Tom(ch, 67); | |
case 6: | |
return new Tom(ch, 69); | |
case 7: | |
return new Tom(ch, 71); | |
case 8: | |
return new Cymbal(ch, 73); | |
default: | |
return new Kick2(ch); | |
} | |
}); | |
midichannels[2].controlchange(7, 100); | |
midichannels[2].controlchange(91, 10); | |
midichannels[3] = new GuitarChannel(6, (ch) => new Guitar(ch)); | |
midichannels[3].controlchange(7, 30); | |
midichannels[3].controlchange(10, 50); | |
midichannels[3].controlchange(91, 65); | |
midichannels[4] = new MidiChannel(2, (ch) => new Bass(ch)); | |
midichannels[4].controlchange(7, 80); | |
midichannels[4].controlchange(10, 64); | |
midichannels[4].controlchange(91, 20); | |
midichannels[5] = new MidiChannel(1, (ch) => new TubeLead(ch)); | |
midichannels[5].controlchange(7, 30); | |
midichannels[5].controlchange(10, 50); | |
midichannels[5].controlchange(91, 40); | |
midichannels[6] = new FluteChannel(1, (ch) => new Flute(ch)); | |
midichannels[6].controlchange(7, 57); | |
midichannels[6].controlchange(10, 48); | |
midichannels[6].controlchange(91, 57); | |
midichannels[7] = new PadSynthChannel(15, (ch) => new PadSynth(ch)); | |
midichannels[7].controlchange(7, 60); | |
midichannels[7].controlchange(10, 64); | |
midichannels[7].controlchange(91, 70); | |
midichannels[8] = new MidiChannel(5, (ch) => new Brass(ch)); | |
midichannels[8].controlchange(7, 40); | |
midichannels[8].controlchange(10, 32); | |
midichannels[8].controlchange(91, 40); | |
midichannels[9] = new MidiChannel(10, (ch) => new Choir(ch)); | |
midichannels[9].controlchange(7, 40); | |
midichannels[9].controlchange(10, 80); | |
midichannels[9].controlchange(91, 70); | |
} | |
const eqfreqs: f32[] = [40,150,1200,24000] | |
const eqlevels: f32[] = [1.2,0.7,1.2]; | |
const eqleft = new MultiBandEQ(eqfreqs); | |
const eqright = new MultiBandEQ(eqfreqs); | |
const compressor = new TriBandStereoCompressor(0.05, 25,150,1500,20000); | |
compressor.compressorLow.leftCompressor.setRelaseSampleCount((0.5 * SAMPLERATE) as usize); | |
compressor.compressorLow.rightCompressor.setRelaseSampleCount((0.5 * SAMPLERATE) as usize); | |
const limiter_left = new MonoCompressor((0.005 * SAMPLERATE) as usize); | |
const limiter_right = new MonoCompressor((0.005 * SAMPLERATE) as usize); | |
limiter_left.setRelaseSampleCount((0.05 * SAMPLERATE) as usize); | |
limiter_right.setRelaseSampleCount((0.05 * SAMPLERATE) as usize); | |
const midsideprocessor = new MidSideProcessor(1.7); | |
export function postprocess(): void { | |
const gain: f32 = 3.0; | |
let left = outputline.left; | |
let right = outputline.right; | |
const echol = echoLeft.read() * 0.3; | |
const echor = echoRight.read() * 0.3; | |
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; | |
const COMPRESS_AND_STEREO_ENHANCE = true; | |
if (COMPRESS_AND_STEREO_ENHANCE) { | |
midsideprocessor.process(left,right); | |
left = midsideprocessor.signal.left; | |
right = midsideprocessor.signal.right; | |
compressor.process(left, right, 0.8, 0.7, 1.0, 1.0, 1.0, 1.0); | |
left = compressor.stereosignal.left; | |
right = compressor.stereosignal.right; | |
const master_gain: f32 = 1.2; | |
left *= master_gain; | |
right *= master_gain; | |
const limiter_threshold: f32 = 0.99; | |
left = limiter_left.process(left, limiter_threshold, 1.0); | |
right = limiter_right.process(right, limiter_threshold, 1.0); | |
} | |
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