From dba57f4cf0519c1c69b3ffbfe04b64fdacd29a63 Mon Sep 17 00:00:00 2001 From: spessasus <95608008+spessasus@users.noreply.github.com> Date: Sun, 24 Sep 2023 16:12:14 +0200 Subject: [PATCH] Sequence player held notes fix --- .gitignore | 3 +- .../midi_parser/midi_loader.js | 5 +- .../synthetizer/buffer_voice/midi_channel.js | 13 +- .../synthetizer/{buffer_voice => }/chorus.js | 0 .../freeverb_unused/comb_filter.js | 70 ++++++++++ .../synthetizer/freeverb_unused/freeverb.js | 128 ++++++++++++++++++ .../worklet_channel/worklet_channel.js | 8 +- .../sequence_recorder/sequence_player.js | 2 + 8 files changed, 223 insertions(+), 6 deletions(-) rename src/spessasynth_lib/synthetizer/{buffer_voice => }/chorus.js (100%) create mode 100644 src/spessasynth_lib/synthetizer/freeverb_unused/comb_filter.js create mode 100644 src/spessasynth_lib/synthetizer/freeverb_unused/freeverb.js diff --git a/.gitignore b/.gitignore index 181217d3..40c4dffb 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ /node_modules/ /disabled_sf2s/ /config.json -/.idea \ No newline at end of file +/.idea +/src/spessasynth_lib/synthetizer/freeverb_unused/ diff --git a/src/spessasynth_lib/midi_parser/midi_loader.js b/src/spessasynth_lib/midi_parser/midi_loader.js index 03a09ba3..96e20edc 100644 --- a/src/spessasynth_lib/midi_parser/midi_loader.js +++ b/src/spessasynth_lib/midi_parser/midi_loader.js @@ -13,6 +13,8 @@ export class MIDI{ * @param fileByteArray {ShiftableByteArray} */ constructor(fileByteArray) { + console.groupCollapsed(`%cParsing MIDI File...`, consoleColors.info); + const headerChunk = this.readMIDIChunk(fileByteArray); if(headerChunk.type !== "MThd") { @@ -31,8 +33,6 @@ export class MIDI{ // time division this.timeDivision = readBytesAsUintBigEndian(headerChunk.data, 2); - console.log("Tracks:", this.tracksAmount, "Time division:", this.timeDivision); - /** * Contains all the tempo changes in the file. (Ordered from last to first) * @type {{ @@ -211,6 +211,7 @@ export class MIDI{ console.log(`%cMIDI file parsed. Total tick time: %c${this.lastEventTick}`, consoleColors.info, consoleColors.recognized); + console.groupEnd(); if(loopStart === null ) { diff --git a/src/spessasynth_lib/synthetizer/buffer_voice/midi_channel.js b/src/spessasynth_lib/synthetizer/buffer_voice/midi_channel.js index f95d168a..abfba01c 100644 --- a/src/spessasynth_lib/synthetizer/buffer_voice/midi_channel.js +++ b/src/spessasynth_lib/synthetizer/buffer_voice/midi_channel.js @@ -2,7 +2,7 @@ import {Voice} from "./voice.js"; import {Preset} from "../../soundfont/chunk/presets.js"; import { consoleColors } from '../../utils/other.js' import { midiControllers } from '../../midi_parser/midi_message.js' -import { Chorus } from './chorus.js' +import { Chorus } from '../chorus.js' const CHANNEL_LOUDNESS = 0.5; @@ -55,10 +55,15 @@ export class MidiChannel { this.gainController = new GainNode(this.ctx, { gain: CHANNEL_LOUDNESS }); + //this.reverb = new Freeverb(this.ctx, 0); - // note -> panner -> chorus -> gain -> out + // note -> panner -> chorus -> -> gain -> out + //const dummy = new GainNode(this.ctx, {gain: 1}); this.chorus = new Chorus(this.panner, this.gainController, 0); + //this.reverb.input.connect(dummy); + //this.reverb.connectOutput(this.gainController); + this.gainController.connect(this.outputNode); this.resetControllers(); @@ -137,6 +142,10 @@ export class MidiChannel { this.setChorus(value); break; + // case midiControllers.effects1Depth: + // this.reverb.setLevel(value); + // break; + case midiControllers.NRPNMsb: this.setNRPCoarse(value); break; diff --git a/src/spessasynth_lib/synthetizer/buffer_voice/chorus.js b/src/spessasynth_lib/synthetizer/chorus.js similarity index 100% rename from src/spessasynth_lib/synthetizer/buffer_voice/chorus.js rename to src/spessasynth_lib/synthetizer/chorus.js diff --git a/src/spessasynth_lib/synthetizer/freeverb_unused/comb_filter.js b/src/spessasynth_lib/synthetizer/freeverb_unused/comb_filter.js new file mode 100644 index 00000000..0f3524d8 --- /dev/null +++ b/src/spessasynth_lib/synthetizer/freeverb_unused/comb_filter.js @@ -0,0 +1,70 @@ +export class CombFilter +{ + /** + * Feedback lowpass comb filter + * @param context {BaseAudioContext} + * @param delayTime {number} + */ + constructor(context, delayTime) { + //const context = inputNode.context; + this.delayLine = new DelayNode(context, { + delayTime: delayTime + }); + this.lowPassFilter = new BiquadFilterNode(context, { + type: "lowpass", + frequency: 440 + }); + this.reasonanceGain = new GainNode(context, { + gain: 0.5 + }); + + /* +---------------+ + |reasonance gain| + +---------------+ + | /|\ + \|/ | + +-----+ +---------+ +-------+ +------+ + |input|-->|delayLine|-->|lowpass|-->|output| + +-----+ +---------+ +-------+ +------+ + | /|\ + +------------------------------------+ + */ + this.delayLine.connect(this.lowPassFilter); + this.lowPassFilter.connect(this.reasonanceGain); + this.reasonanceGain.connect(this.delayLine); + } + + get input() + { + return this.delayLine; + } + + get output() + { + return this.delayLine//this.lowPassFilter; + } + + /** + * @param value {number} + */ + set dampening(value) + { + this.lowPassFilter.frequency.value = value; + } + + /** + * @param value {number} + */ + set delayTime(value) + { + this.delayLine.delayTime.value = value; + } + + /** + * @param value {number} + */ + set reasonance(value) + { + this.reasonanceGain.gain.value = value; + } +} \ No newline at end of file diff --git a/src/spessasynth_lib/synthetizer/freeverb_unused/freeverb.js b/src/spessasynth_lib/synthetizer/freeverb_unused/freeverb.js new file mode 100644 index 00000000..7fb4acf8 --- /dev/null +++ b/src/spessasynth_lib/synthetizer/freeverb_unused/freeverb.js @@ -0,0 +1,128 @@ +// Based on Freeverb, a public domain reverb implementation by Jezar at Dreampoint + +import { CombFilter } from './comb_filter.js' + +const COMB_FILTER_TUNINGS_LEFT = [1557, 1617, 1491, 1422]; +const COMB_FILTER_TUNINGS_RIGHT = [1277, 1356, 1188, 1116]; +const ALLPASS_FREQS = [225, 556, 441, 341]; + +const ROOM_SIZE_DEFAULT = 0.5; +const DAMPERING_DEFAULT = 3000; +export class Freeverb +{ + /** + * Creates a new Freeverb instance + * @param context {BaseAudioContext} + * @param defaultLevel {number} 0-127 + */ + constructor(context, defaultLevel) { + /* + * +-------------+ + * +->|comb filter 1|------+ + * | +-------------+ | + * left | \|/ + * +--------+ 6 more +------+ +---------+ +---------+ +---------+ +---------+ +------+ + * |input|->|splitter| filters here |merger|-> |allpass 1|->|allpass 2|->|allpass 3|->|allpass 4|->|output| + * +--------+ +------+ +---------+ +---------+ +---------+ +---------+ +------+ + * right | /|\ + * | +-------------+ | + * +->|comb filter 8|------+ + * +-------------+ + * + * + * i think thats how it works + * + */ + // merger and splitter + this.splitter = new ChannelSplitterNode(context, { + numberOfOutputs: 2 + }); + this.merger = new ChannelMergerNode(context, { + numberOfInputs: 2 + }); + // reverb level controllers + this.wetGain = new GainNode(context, { + gain: defaultLevel / 127 + }); + this.dryGain = new GainNode(context, { + gain: (127 - defaultLevel) / 127 + }); + + this.wetGain.connect(this.splitter); + + // comb filters left + this.combFiltersLeft = COMB_FILTER_TUNINGS_LEFT.map(tuning => { + const comb = new CombFilter(context, tuning / context.sampleRate); + comb.reasonance = ROOM_SIZE_DEFAULT; + comb.dampening = DAMPERING_DEFAULT; + + this.splitter.connect(comb.input, 0); + comb.output.connect(this.merger, 0, 0); + return comb; + }); + + // comb filters right + this.combFiltersRight = COMB_FILTER_TUNINGS_RIGHT.map(tuning => { + const comb = new CombFilter(context, tuning / context.sampleRate); + comb.reasonance = ROOM_SIZE_DEFAULT; + comb.dampening = DAMPERING_DEFAULT; + + this.splitter.connect(comb.input, 1); + comb.output.connect(this.merger, 0, 1); + return comb; + }); + this.splitter.connect(this.merger, 1, 1); + this.splitter.connect(this.merger, 0, 0); + + /** + * allpass filters + * @type {BiquadFilterNode[]} + */ + this.allpassFilters = []; + ALLPASS_FREQS.forEach((frequency, index) => { + const allpass = new BiquadFilterNode(context, { + type: 'allpass', + frequency: frequency + }); + + if(this.allpassFilters[index - 1]) + { + this.allpassFilters[index - 1].connect(allpass); + } + this.allpassFilters.push(allpass); + }); + + this.merger.connect(this.allpassFilters[0]); + } + + /** + * @returns {{connect: function(AudioNode)}} + */ + get input() + { + return { + connect: node => { + node.connect(this.dryGain); + node.connect(this.wetGain); + } + } + } + + /** + * @param output {AudioNode} + */ + connectOutput(output) + { + this.dryGain.connect(output); + this.allpassFilters[this.allpassFilters.length - 1].connect(output); + } + + /** + * @param level {number} 0-127 + */ + setLevel(level) + { + this.wetGain.gain.value = level / 127; + this.dryGain.gain.value = (127 - level) / 127; + } +} \ No newline at end of file diff --git a/src/spessasynth_lib/synthetizer/worklet_channel/worklet_channel.js b/src/spessasynth_lib/synthetizer/worklet_channel/worklet_channel.js index 222b4014..6330fbb7 100644 --- a/src/spessasynth_lib/synthetizer/worklet_channel/worklet_channel.js +++ b/src/spessasynth_lib/synthetizer/worklet_channel/worklet_channel.js @@ -30,6 +30,7 @@ import { consoleColors } from '../../utils/other.js' import { modulatorSources } from '../../soundfont/chunk/modulators.js' import { midiControllers } from '../../midi_parser/midi_message.js' import { addAndClampGenerator, generatorTypes } from '../../soundfont/chunk/generators.js' +import { Chorus } from '../chorus.js' const CHANNEL_GAIN = 0.5; export const NON_CC_INDEX_OFFSET = 128; @@ -142,7 +143,8 @@ export class WorkletChannel { */ this.dumpedSamples = new Set(); - this.worklet.connect(this.gainController); + this.chorus = new Chorus(this.worklet, this.gainController, 0); + this.gainController.connect(this.outputNode); this.resetControllers(); @@ -211,6 +213,10 @@ export class WorkletChannel { this.setNRPFine(val); break; + case midiControllers.effects3Depth: + this.chorus.setChorusLevel(val); + break; + case midiControllers.sustainPedal: if(val > 64) { diff --git a/src/website/sequence_recorder/sequence_player.js b/src/website/sequence_recorder/sequence_player.js index 0ce74ff1..017bb88f 100644 --- a/src/website/sequence_recorder/sequence_player.js +++ b/src/website/sequence_recorder/sequence_player.js @@ -1,4 +1,5 @@ import { MIDIDeviceHandler } from '../../spessasynth_lib/midi_handler/midi_handler.js' +import { midiControllers } from '../../spessasynth_lib/midi_parser/midi_message.js' export class SequencePlayer { @@ -32,6 +33,7 @@ export class SequencePlayer this.eventIndex++; if(this.eventIndex >= this.recorder.events.length) { + this.synth.controllerChange(this.recorder.targetChannel, midiControllers.allNotesOff, 0); this.eventIndex = 0; this.startTime = this.synth.currentTime; }