Last active
October 12, 2023 16:27
-
-
Save petersalomonsen/bbd4a3ac817d2e777e9f5836f1e23cdf to your computer and use it in GitHub Desktop.
Piano out of tune https://petersalomonsen.com/webassemblymusic/livecodev2/0.0.39/?gist=bbd4a3ac817d2e777e9f5836f1e23cdf
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
setBPM(90); | |
addInstrument('piano'); | |
addInstrument('string'); | |
addInstrument('drums'); | |
addInstrument('guitar'); | |
addInstrument('bass'); | |
addInstrument('tubelead'); | |
addInstrument('flute'); | |
addInstrument('padsynth'); | |
addInstrument('brass'); | |
addInstrument('choir'); | |
createTrack(0).play([[ 0.05, f2(0.66, 70) ], | |
[ 0.78, f3(0.13, 43) ], | |
[ 1.00, a2(0.97, 72) ], | |
[ 1.98, c3(0.95, 72) ], | |
[ 0.05, a4(2.97, 92) ], | |
[ 0.04, f5(2.98, 82) ], | |
[ 3.74, gs4(0.04, 53) ], | |
[ 3.74, f5(0.04, 49) ], | |
[ 3.75, d5(0.04, 60) ], | |
[ 2.98, d3(0.85, 70) ], | |
[ 3.94, as2(1.03, 51) ], | |
[ 5.03, g5(0.43, 89) ], | |
[ 5.03, gs4(0.45, 88) ], | |
[ 5.04, d5(0.46, 87) ], | |
[ 5.04, d3(0.98, 72) ], | |
[ 6.00, gs4(0.32, 86) ], | |
[ 5.98, gs5(0.46, 87) ], | |
[ 6.01, d5(0.50, 79) ], | |
[ 6.59, g5(0.14, 60) ], | |
[ 6.60, gs4(0.14, 66) ], | |
[ 6.03, f3(1.07, 78) ], | |
[ 7.03, g3(0.82, 59) ], | |
[ 7.95, f2(0.73, 74) ], | |
[ 8.73, f3(0.14, 52) ], | |
[ 8.93, a2(1.02, 60) ], | |
[ 7.63, a4(2.86, 75) ], | |
[ 7.62, f5(2.97, 68) ], | |
[ 10.59, c5(0.20, 49) ], | |
[ 9.96, c3(0.95, 63) ], | |
[ 10.93, d5(0.28, 77) ], | |
[ 11.26, f5(0.22, 82) ], | |
[ 11.60, g5(0.18, 69) ], | |
[ 10.95, d3(1.01, 79) ], | |
[ 11.91, gs5(0.18, 59) ], | |
[ 12.23, a5(0.32, 37) ], | |
[ 12.60, c6(0.27, 83) ], | |
[ 11.97, ds3(0.93, 59) ], | |
[ 12.96, d6(0.38, 87) ], | |
[ 13.59, f6(0.21, 88) ], | |
[ 12.97, d3(1.00, 70) ], | |
[ 13.96, f6(0.63, 88) ], | |
[ 14.59, c6(0.06, 64) ], | |
[ 14.66, d6(0.26, 60) ], | |
[ 13.98, c3(0.99, 75) ], | |
[ 14.98, f6(0.47, 94) ], | |
[ 14.99, a2(0.54, 74) ], | |
[ 15.58, g6(0.13, 74) ], | |
[ 15.58, c3(0.17, 68) ], | |
[ 16.58, d6(0.10, 69) ], | |
[ 16.55, gs5(0.13, 76) ], | |
[ 16.54, gs6(0.19, 89) ], | |
[ 15.94, as2(0.86, 66) ], | |
[ 16.87, d3(0.05, 55) ], | |
[ 16.97, d3(0.94, 7) ], | |
[ 17.94, g6(0.50, 89) ], | |
[ 18.51, f6(0.04, 60) ], | |
[ 17.94, gs5(0.66, 77) ], | |
[ 17.95, f3(0.83, 72) ], | |
[ 18.86, g3(0.98, 55) ], | |
[ 19.92, gs5(0.27, 81) ], | |
[ 19.90, g6(0.34, 95) ], | |
[ 19.91, d6(0.34, 87) ], | |
[ 20.61, d6(0.04, 50) ], | |
[ 20.58, gs5(0.17, 64) ], | |
[ 19.90, gs3(0.93, 57) ], | |
[ 20.58, g6(0.26, 72) ], | |
[ 21.24, gs5(0.17, 66) ], | |
[ 21.24, f6(0.19, 77) ], | |
[ 21.54, d6(0.18, 64) ], | |
[ 21.55, g6(0.18, 65) ], | |
[ 20.89, g3(0.99, 58) ], | |
[ 21.96, d6(0.04, 72) ], | |
[ 21.95, gs5(0.33, 87) ], | |
[ 22.06, d6(0.26, 12) ], | |
[ 21.94, gs6(0.38, 102) ], | |
[ 22.60, d6(0.14, 57) ], | |
[ 22.61, gs5(0.16, 54) ], | |
[ 22.62, g6(0.15, 64) ], | |
[ 21.94, f3(0.91, 69) ], | |
[ 22.91, d3(0.60, 58) ], | |
[ 23.59, a5(0.04, 38) ], | |
[ 23.64, c6(0.04, 15) ], | |
[ 23.62, f3(0.18, 48) ], | |
[ 23.64, f6(0.47, 49) ], | |
[ 23.66, d6(0.94, 83) ], | |
[ 23.87, f2(0.78, 53) ], | |
[ 24.63, f3(0.29, 27) ], | |
[ 24.63, c6(0.30, 57) ], | |
[ 24.62, f6(0.32, 19) ], | |
[ 24.97, a5(0.37, 90) ], | |
[ 25.62, c6(0.06, 62) ], | |
[ 25.59, gs5(0.19, 77) ], | |
[ 24.98, a2(0.93, 69) ], | |
[ 25.94, g5(0.37, 70) ], | |
[ 25.95, c6(0.35, 59) ], | |
[ 26.54, f5(0.12, 60) ], | |
[ 25.98, c3(0.91, 64) ], | |
[ 26.93, f5(0.26, 57) ], | |
[ 26.90, d5(0.30, 74) ], | |
[ 27.55, c5(0.19, 69) ], | |
[ 26.93, d3(0.95, 79) ], | |
[ 27.93, a4(0.27, 82) ], | |
[ 27.55, f5(0.94, 57) ], | |
[ 28.55, c5(0.04, 43) ], | |
[ 28.63, d5(0.05, 42) ], | |
[ 27.92, ds3(1.05, 71) ], | |
[ 29.02, f5(0.46, 98) ], | |
[ 29.58, g5(0.15, 82) ], | |
[ 29.02, d3(0.94, 72) ], | |
[ 29.92, gs5(0.25, 59) ], | |
[ 30.22, a5(0.22, 54) ], | |
[ 30.54, c6(0.38, 74) ], | |
[ 29.96, c3(1.01, 67) ], | |
[ 30.87, gs5(0.25, 72) ], | |
[ 31.24, a5(0.19, 43) ], | |
[ 30.96, a2(0.60, 69) ], | |
[ 31.61, c3(0.10, 65) ], | |
[ 31.53, c6(0.38, 72) ], | |
[ 31.85, gs5(0.10, 81) ], | |
[ 31.94, a5(0.45, 72) ], | |
[ 31.90, c3(0.62, 64) ], | |
[ 32.56, c4(0.16, 35) ], | |
[ 32.51, c6(0.56, 75) ], | |
[ 32.83, gs5(0.52, 83) ], | |
[ 33.43, g5(0.14, 44) ], | |
[ 33.58, gs5(0.18, 64) ], | |
[ 32.84, e3(0.93, 69) ], | |
[ 33.71, g5(0.72, 78) ], | |
[ 33.50, c6(0.94, 2) ], | |
[ 34.46, f5(0.23, 77) ], | |
[ 33.81, g3(1.01, 65) ], | |
[ 34.82, d5(0.26, 74) ], | |
[ 35.16, c5(0.21, 57) ], | |
[ 35.44, cs5(0.12, 59) ], | |
[ 34.83, a3(0.86, 79) ], | |
[ 35.85, as2(1.08, 59) ], | |
[ 36.95, gs4(0.11, 69) ], | |
[ 35.53, d5(1.53, 58) ], | |
[ 36.94, f5(0.14, 60) ], | |
[ 36.95, d3(1.01, 63) ], | |
[ 37.96, gs4(0.34, 81) ], | |
[ 37.95, g5(0.48, 78) ], | |
[ 37.95, d5(0.62, 79) ], | |
[ 38.53, f5(0.05, 48) ], | |
[ 38.50, gs4(0.07, 19) ], | |
[ 37.98, f3(0.82, 70) ], | |
[ 38.85, d3(0.74, 59) ], | |
[ 39.66, f3(0.16, 75) ], | |
[ 39.91, f2(0.72, 49) ], | |
[ 40.66, f3(0.23, 54) ], | |
[ 40.89, a2(0.58, 75) ], | |
[ 41.66, f3(0.22, 42) ], | |
[ 41.90, as2(0.73, 61) ], | |
[ 42.66, f3(0.06, 48) ], | |
[ 42.88, b2(0.97, 75) ], | |
[ 39.64, a4(4.28, 68) ], | |
[ 39.63, f5(4.33, 65) ], | |
[ 39.63, c5(4.38, 65) ], | |
[ 43.89, c3(1.06, 72) ], | |
[ 44.98, a4(0.34, 88) ], | |
[ 44.98, b2(0.44, 78) ], | |
[ 44.97, ds5(0.47, 86) ], | |
[ 45.65, d5(0.02, 27) ], | |
[ 45.64, as4(2.22, 61) ], | |
[ 45.67, e5(2.22, 64) ], | |
[ 45.66, c2(2.25, 69) ]]); | |
for (let n=0;n<12;n++) { | |
// get more and more into tune as times go by | |
createTrack(0).steps(4,[controlchange(30,120 - ((n + 1)*10))]); | |
await createTrack(2).steps(4,[ | |
cs6(0.1,70),,,, | |
cs6(0.1,70),,,cs6(0.1,30), | |
cs6(0.1,60),,,, | |
cs6(0.1,70),,,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); | |
} | |
export class MT19937 { | |
private static readonly N: i32 = 624; | |
private static readonly M: i32 = 397; | |
private static readonly MATRIX_A: i32 = 0x9908b0df; | |
private static readonly UPPER_MASK: i32 = 0x80000000; | |
private static readonly LOWER_MASK: i32 = 0x7fffffff; | |
// Constants for mag01 replacement | |
private static readonly MAG01_0: i32 = 0; | |
private static readonly MAG01_1: i32 = MT19937.MATRIX_A; | |
private mt: Uint32Array = new Uint32Array(MT19937.N); | |
private mti: i32 = MT19937.N + 1; | |
constructor(seed: u32 = 5489) { | |
this.init(seed); | |
} | |
private init(seed: u32): void { | |
this.mt[0] = seed >>> 0; | |
for (this.mti = 1; this.mti < MT19937.N; this.mti++) { | |
let s = this.mt[this.mti - 1] ^ (this.mt[this.mti - 1] >>> 30); | |
this.mt[this.mti] = | |
(((((s & 0xffff0000) >>> 16) * 1812433253) << 16) + (s & 0x0000ffff) * 1812433253) + this.mti; | |
this.mt[this.mti] >>>= 0; | |
} | |
} | |
public next(): u32 { | |
let y: u32; | |
if (this.mti >= MT19937.N) { | |
let kk: i32; | |
for (kk = 0; kk < MT19937.N - MT19937.M; kk++) { | |
y = (this.mt[kk] & MT19937.UPPER_MASK) | (this.mt[kk + 1] & MT19937.LOWER_MASK); | |
this.mt[kk] = this.mt[kk + MT19937.M] ^ (y >>> 1) ^ ((y & 1) ? MT19937.MAG01_1 : MT19937.MAG01_0); | |
} | |
for (; kk < MT19937.N - 1; kk++) { | |
y = (this.mt[kk] & MT19937.UPPER_MASK) | (this.mt[kk + 1] & MT19937.LOWER_MASK); | |
this.mt[kk] = this.mt[kk + (MT19937.M - MT19937.N)] ^ (y >>> 1) ^ ((y & 1) ? MT19937.MAG01_1 : MT19937.MAG01_0); | |
} | |
y = (this.mt[MT19937.N - 1] & MT19937.UPPER_MASK) | (this.mt[0] & MT19937.LOWER_MASK); | |
this.mt[MT19937.N - 1] = this.mt[MT19937.M - 1] ^ (y >>> 1) ^ ((y & 1) ? MT19937.MAG01_1 : MT19937.MAG01_0); | |
this.mti = 0; | |
} | |
y = this.mt[this.mti++]; | |
y ^= y >>> 11; | |
y ^= (y << 7) & 0x9d2c5680; | |
y ^= (y << 15) & 0xefc60000; | |
y ^= y >>> 18; | |
return y >>> 0; | |
} | |
// The function to return a float in the range [-1, 1] | |
random(): f32 { | |
let y: u32 = this.next(); | |
// Normalize to [0, 1] | |
let normalized: f32 = <f32>y / 4294967295.0; | |
// Scale and shift to [-1, 1] | |
return 2.0 * normalized - 1.0; | |
} | |
} | |
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 noisegen: MT19937 = new MT19937(); | |
const exciterStartVal = Mathf.exp(Mathf.E); | |
export class PianoExciterWaveGuide { | |
filterExciter: BiQuadFilter = new BiQuadFilter(); | |
t: f32; | |
vel: f32; | |
start(lo: f32,notefreq: f32): void { | |
this.t = 0; | |
this.vel = lo; | |
this.filterExciter.clearBuffers(); | |
const filterbasefreq: f32 = (800 + 20 * Mathf.pow(notefreq,0.65)) * this.vel; | |
this.filterExciter.update_coeffecients(FilterType.LowPass, SAMPLERATE, | |
filterbasefreq, 0.9999); | |
} | |
process(): f32 { | |
this.t += 12.0/ SAMPLERATE; | |
let signal = noisegen.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>(5); | |
// Two delay lines for bidirectional wave propagation | |
delayForward: DelayLineFloat = new DelayLineFloat((SAMPLERATE / notefreq(1)) as i32); | |
delayBackward: DelayLineFloat = new DelayLineFloat((SAMPLERATE / notefreq(1)) as i32); | |
dispersionmodindex: f64 = 0; | |
dispersiondelta: f32; | |
dispersion: i32 = 0; | |
level: f32; | |
notefreq: f32; | |
numdispersionfilters: i32 = 6; | |
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 = 3500 + 40 * Mathf.pow(freq,0.79); | |
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 = ((this.dispersion as f32) * 0.0002) + 0.001; | |
const M = this.allpasses.length; | |
let totalDispersion = 0.0; | |
this.numdispersionfilters = 0; | |
for(let n=0;n<this.allpasses.length;n++) { | |
this.allpasses[n].clearBuffers(); | |
const f0: f64 = freq * (n+1.0) as f64; | |
if (f0 > SAMPLERATE / 3) { | |
continue; | |
} | |
const Bc = Math.max(B,0.0001); | |
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); | |
totalDispersion += Df0; | |
const dispersion: f32 = Df0 as f32; | |
this.allpasses[n].setDelta(dispersion); | |
this.numdispersionfilters++; | |
} | |
this.filterFeedback.clearBuffers(); | |
const totalDelay = delaysize - (totalDispersion as f32); | |
this.delayForward.setNumFramesAndClear(totalDelay); | |
this.delayBackward.setNumFramesAndClear(totalDelay); | |
let feedbacklevel: f32 = Mathf.pow(0.26/ this.filterFeedback.coeffs.magnitude, 1 / freq); | |
this.feedbackLevel = feedbacklevel; | |
this.freq = freq; | |
} | |
} | |
process(): f32 { | |
let exciterSignal: f32 = this.exciterWaveGuide.process(); | |
let feedbackFwd = this.delayForward.read(); | |
let feedbackBwd = this.delayBackward.read(); | |
feedbackFwd = this.filterFeedback.process(feedbackFwd); | |
let signal = (feedbackFwd + feedbackBwd) * 0.5 + exciterSignal; | |
for(let n=0;n<this.numdispersionfilters;n++) signal = this.allpasses[n].process(signal); | |
signal *= this.feedbackLevel; | |
this.delayForward.write_and_advance(signal); | |
this.delayBackward.write_and_advance(signal); | |
return signal; | |
} | |
} | |
class Piano extends MidiVoice { | |
env: Envelope = new Envelope(0.005, 0.17, 1.0, 0.2); | |
waveguide1: PianoWaveGuide = new PianoWaveGuide(); | |
waveguide2: PianoWaveGuide = new PianoWaveGuide(); | |
noteon(note: u8, velocity: u8): void { | |
super.noteon(note, velocity); | |
this.waveguide1.start2(note * 1.0 as f32, velocity); | |
this.waveguide2.start2(note * 1.0 as f32, velocity); | |
this.env.attack(); | |
} | |
noteoff(): void { | |
this.env.release(); | |
} | |
isDone(): boolean { | |
return this.env.isDone(); | |
} | |
nextframe(): void { | |
let env = this.env.next(); | |
this.waveguide1.dispersion = this.channel.controllerValues[30]; | |
this.waveguide2.dispersion = this.channel.controllerValues[30]; | |
const wg1: f32 = this.waveguide1.process() * env; | |
const wg2: f32 = this.waveguide2.process() * env; | |
this.channel.signal.add(wg1, wg2); | |
} | |
} | |
const numpianofilters = 12; | |
class PianoChannel extends MidiChannel { | |
allpassesLeft: StaticArray<AllPassFloat> = new StaticArray<AllPassFloat>(numpianofilters); | |
allpassesRight: StaticArray<AllPassFloat> = new StaticArray<AllPassFloat>(numpianofilters); | |
constructor(numvoices: i32, factoryFunc: (channel: MidiChannel, voiceindex: i32) => MidiVoice) { | |
super(numvoices, factoryFunc); | |
let delta: f32 = 4.9; | |
for(let n=0;n<this.allpassesLeft.length;n++) { | |
this.allpassesLeft[n] = new AllPassFloat(); | |
this.allpassesLeft[n].setDelta(delta); | |
this.allpassesRight[n] = new AllPassFloat(); | |
this.allpassesRight[n].setDelta(delta); | |
delta *= 0.9999; | |
} | |
} | |
preprocess(): void { | |
const in_gain: f32 = 4.0 * Mathf.pow(10.0, -3); | |
let left = this.signal.left * in_gain; | |
let right = this.signal.right * in_gain; | |
const spread: f32 = 0.90; | |
for (let n=0;n<this.allpassesLeft.length;n++) { | |
left += this.allpassesLeft[n].process(left - right * spread); | |
left *= 0.5; | |
right += this.allpassesRight[n].process(right - left * spread); | |
right *= 0.5; | |
} | |
this.signal.left = left; | |
this.signal.right = right; | |
} | |
} | |
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, 90); | |
midichannels[0].controlchange(10, 64); | |
midichannels[0].controlchange(91,80); | |
midichannels[1] = new MidiChannel(10, (ch) => new String(ch)); | |
midichannels[1].controlchange(7,50); | |
midichannels[1].controlchange(10, 64); | |
midichannels[1].controlchange(91, 80); | |
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, 40); | |
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, 40); | |
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); | |
const midsideprocessor = new MidSideProcessor(1.7); | |
export function postprocess(): void { | |
const gain: f32 = 1.5; | |
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 = false; | |
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.0; | |
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