Skip to content

Commit

Permalink
New chorus
Browse files Browse the repository at this point in the history
- sf2 compliant chorus (respects the generators)
- maintaining legacy system
- the synth will now use the legacy system if the website is served over insecure context
- update README accordingly
- finally added the event system to the documentation
  • Loading branch information
spessasus committed Mar 30, 2024
1 parent 52c64d2 commit ea00d0c
Show file tree
Hide file tree
Showing 11 changed files with 213 additions and 141 deletions.
7 changes: 6 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ SoundFont2 based realtime synthetizer and MIDI player written in JavaScript usin
- Reverb and chorus support
- A few custom modulators to support some additional controllers (see `modulators.js`)
- Written using AudioWorklets (Firefox and Chrome both work perfectly)
- Legacy system that doesn't use AudioWorklets (Available to use over HTTP and will switch automatically)
- Can load really large soundfonts (4GB!) (but only on Firefox, Chromium has a memory limit)
- Multi-port MIDIs support (more than 16 channels)
- MIDI Controller Support (Default supported controllers can be found [here](../../wiki/Synthetizer-Class#supported-controllers))
Expand All @@ -37,6 +38,8 @@ SoundFont2 based realtime synthetizer and MIDI player written in JavaScript usin

### Limitations
- It might not sound as good as other synthetizers (e.g. FluidSynth or BASSMIDI)
- The modulation envelope needs some work
- the modulators need some work

## Installation
***When you're loading a large (>4GB) SoundFont, use Firefox because chromium has a 4GB memory limit***
Expand Down Expand Up @@ -77,6 +80,8 @@ The program is divided into parts:
#### todo
- make the worklet system perform good
- port the worklet system to emscripten (maybe)
-
- fix modenv
- fix rare clicking in volenv
- fix attenuation modulators
### License
Copyright © 2024 Spessasus. Licensed under the MIT License.
3 changes: 3 additions & 0 deletions src/spessasynth_lib/soundfont/chunk/modulators.js
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,9 @@ export const defaultModulators = [
// reverb effects to send
new Modulator({srcEnum: 0x00DB, dest: generatorTypes.reverbEffectsSend, amt: 500, secSrcEnum: 0x0, transform: 0}),

// chorus effects to send
new Modulator({srcEnum: 0x00DD, dest: generatorTypes.chorusEffectsSend, amt: 200, secSrcEnum: 0x0, transform: 0}),

// custom modulators heck yeah
// cc 92 (tremolo) to modLFO volume
new Modulator({
Expand Down
79 changes: 0 additions & 79 deletions src/spessasynth_lib/synthetizer/chorus.js

This file was deleted.

41 changes: 41 additions & 0 deletions src/spessasynth_lib/synthetizer/fancy_chorus.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
const DEFAULT_DELAY = 0.02;
const DELAY_VARIATION = 0.015;

const OSC_FREQ = 1.5;
const OSC_GAIN = 0.001;
export class FancyChorus
{
/**
* @param output {AudioNode}
*/
constructor(output) {
const context = output.context;

this.input = new GainNode(context,
{
gain: 1
});

const osc = new OscillatorNode(context, {
type: 'sine',
frequency: OSC_FREQ
});

const oscGain = new GainNode(context, {
gain: OSC_GAIN
});
osc.connect(oscGain);
osc.start();
const delayOne = new DelayNode(context, {
delayTime: DEFAULT_DELAY
});
const delayTwo = new DelayNode(context, {
delayTime: DEFAULT_DELAY + DELAY_VARIATION
});
oscGain.connect(delayOne.delayTime);
this.input.connect(delayOne);
this.input.connect(delayTwo);
delayTwo.connect(output);
delayOne.connect(output);
}
}
30 changes: 10 additions & 20 deletions src/spessasynth_lib/synthetizer/native_system/README.md
Original file line number Diff line number Diff line change
@@ -1,25 +1,15 @@
# This is the old synthesis system.
It's not maintained anymore, but it's here for the HTTP websites which can't use AudioWorklets.
To use this system, edit `synthetizer.js`:
It's not updated anymore, but it's here for the HTTP websites which can't use AudioWorklets.
To use this system, change the `synthesisMode` to `"legacy"`:

```js
import { MidiChannel } from './native_system/midi_channel.js';
synth.synthesisMode = "legacy";
```
and change the class from `WorkletChannel` to `MidiChannel`
it should automatically create the legacy channels.
If you want to automatically use this mode, then
```js
/**
* create 16 channels
* @type {WorkletChannel[]|MidiChannel[]}
*/
this.midiChannels = [...Array(DEFAULT_CHANNEL_COUNT).keys()].map(j => new MidiChannel(this.volumeController, this.defaultPreset, j + 1, false));

// ...
export const DEFAULT_SYNTHESIS_MODE = "legacy";
```

/**
* Adds a new channel to the synthesizer
*/
addNewChannel()
{
this.midiChannels.push(new MidiChannel(this.volumeController, this.defaultPreset, this.midiChannels.length + 1, false));
this.eventHandler.callEvent("newchannel", this.midiChannels[this.midiChannels.length - 1]);
}
```
Note:
the synth will try to detect if the worklets are available. if not, then it should switch to legacy
33 changes: 28 additions & 5 deletions src/spessasynth_lib/synthetizer/native_system/midi_channel.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import {VoiceGroup} from "./voice_group.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'

const CHANNEL_LOUDNESS = 0.3;

Expand All @@ -21,11 +20,12 @@ export class MidiChannel {
* creates a midi channel
* @param targetNode {AudioNode}
* @param targetReverbNode {AudioNode}
* @param targetChorusNode {AudioNode}
* @param defaultPreset {Preset}
* @param channelNumber {number}
* @param percussionChannel {boolean}
*/
constructor(targetNode, targetReverbNode, defaultPreset, channelNumber = -1, percussionChannel = false) {
constructor(targetNode, targetReverbNode, targetChorusNode, defaultPreset, channelNumber = -1, percussionChannel = false) {
this.ctx = targetNode.context;
this.outputNode = targetNode;
this.channelNumber = channelNumber
Expand Down Expand Up @@ -65,12 +65,17 @@ export class MidiChannel {
// note -> panner -> chorus -> gain -> reverb + passthrough) -> out

//const dummy = new GainNode(this.ctx, {gain: 1});
this.chorus = new Chorus(this.panner, this.gainController, 0);
this.chorus = new GainNode(this.ctx, {
gain: 0
});
this.chorus.connect(targetChorusNode)
//this.reverb.input.connect(dummy);
//this.reverb.connectOutput(this.gainController);

this.panner.connect(this.gainController);
this.gainController.connect(this.outputNode);
this.gainController.connect(this.reverb);
this.gainController.connect(this.chorus);

this.resetControllers();

Expand Down Expand Up @@ -100,6 +105,18 @@ export class MidiChannel {
this.lockVibrato = false;
}

/**
* Kills the channel, disconnecting everything
*/
killChannel()
{
this.gainController.disconnect();
this.stopAll(true);
this.muteChannel();
this.playingNotes = [];
this.stoppingNotes = [];
}

/**
* @param number {number}
* @param value
Expand Down Expand Up @@ -335,7 +352,7 @@ export class MidiChannel {
*/
setChorus(chorus)
{
this.chorus.setChorusLevel(chorus);
this.chorus.gain.value = chorus / 127;
}

setReverb(reverbLevel)
Expand Down Expand Up @@ -578,11 +595,17 @@ export class MidiChannel {

resetControllers()
{
/**
* @type {number}
*/
this.channelVolume = 100 / 127; // per midi spec
/**
* @type {number}
*/
this.channelExpression = 1;
this.channelTuningRatio = 1;
this.channelPitchBendRange = 2;
this.chorus.setChorusLevel(0);
this.chorus.gain.value = 0;
this.holdPedal = false;
this.updateGain();
this.panner.pan.value = 0;
Expand Down
Loading

0 comments on commit ea00d0c

Please sign in to comment.