Skip to content

Commit

Permalink
Add Cubic Interpolation and lower voice cap
Browse files Browse the repository at this point in the history
Resolves #56
  • Loading branch information
spessasus committed Oct 4, 2024
1 parent f9ed2b7 commit ae821ad
Show file tree
Hide file tree
Showing 12 changed files with 162 additions and 64 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "SpessaSynth",
"version": "3.20.36.4",
"version": "3.20.37",
"type": "module",
"scripts": {
"start": "node src/website/server/server.js"
Expand Down
2 changes: 1 addition & 1 deletion src/spessasynth_lib/synthetizer/synthetizer.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ import { SoundfontManager } from './synth_soundfont_manager.js'

export const WORKLET_PROCESSOR_NAME = "spessasynth-worklet-system";

export const VOICE_CAP = 450;
export const VOICE_CAP = 350;

export const DEFAULT_PERCUSSION = 9;
export const MIDI_CHANNEL_COUNT = 16;
Expand Down
20 changes: 10 additions & 10 deletions src/spessasynth_lib/synthetizer/worklet_processor.min.js

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -82,10 +82,9 @@ class SpessaSynthProcessor extends AudioWorkletProcessor

/**
* Interpolation type used
* TODO: cubic interpolation?
* @type {interpolationTypes}
*/
this.interpolationType = interpolationTypes.linear;
this.interpolationType = interpolationTypes.fourthOrder;

/**
* @type {function}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ export function noteOn(channel, midiNote, velocity, enableDebugging = false, sen
const loopEndOffset = voice.modulatedGenerators[generatorTypes.endloopAddrsOffset] + voice.modulatedGenerators[generatorTypes.endloopAddrsCoarseOffset] * 32768;
const sm = voice.sample;
// apply them
const clamp = num => Math.max(0, Math.min(sm.sampleData.length, num));
const clamp = num => Math.max(0, Math.min(sm.sampleData.length - 1, num));
sm.cursor = clamp( sm.cursor + cursorStartOffset);
sm.end = clamp(sm.end + endOffset);
sm.loopStart = clamp(sm.loopStart + loopStartOffset);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { getLFOValue } from '../worklet_utilities/lfo.js'
import { customControllers } from '../worklet_utilities/worklet_processor_channel.js'
import { WorkletModulationEnvelope } from '../worklet_utilities/modulation_envelope.js'
import {
getSampleCubic,
getSampleLinear,
getSampleNearest,
interpolationTypes,
Expand Down Expand Up @@ -167,6 +168,9 @@ export function renderVoice(
case interpolationTypes.nearestNeighbor:
getSampleNearest(voice, bufferOut);
break;

case interpolationTypes.fourthOrder:
getSampleCubic(voice, bufferOut);
}

// lowpass filter
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,11 +57,6 @@ export function getSampleLinear(voice, outputBuffer)
}
else
{
// check and correct end errors
if(sample.end >= sampleData.length)
{
sample.end = sampleData.length - 1;
}
for (let i = 0; i < outputBuffer.length; i++)
{

Expand Down Expand Up @@ -124,11 +119,6 @@ export function getSampleNearest(voice, outputBuffer)
}
else
{
// check and correct end errors
if(sample.end >= sampleData.length)
{
sample.end = sampleData.length - 1;
}
for (let i = 0; i < outputBuffer.length; i++)
{

Expand All @@ -148,4 +138,98 @@ export function getSampleNearest(voice, outputBuffer)
}
}
sample.cursor = cur;
}



/**
* Fills the output buffer with raw sample data using cubic interpolation
* @param voice {WorkletVoice} the voice we're working on
* @param outputBuffer {Float32Array} the output buffer to write to
*/
export function getSampleCubic(voice, outputBuffer)
{
const sample = voice.sample;
let cur = sample.cursor;
const sampleData = sample.sampleData;

if(sample.isLooping)
{
const loopLength = sample.loopEnd - sample.loopStart;
for (let i = 0; i < outputBuffer.length; i++)
{
// check for loop
while(cur >= sample.loopEnd)
{
cur -= loopLength;
}

// math comes from
// https://stackoverflow.com/questions/1125666/how-do-you-do-bicubic-or-other-non-linear-interpolation-of-re-sampled-audio-da

// grab the 4 points
const y0 = ~~cur; // point before the cursor. twice bitwise not is just a faster Math.floor
let y1 = y0 + 1; // point after the cursor
let y2 = y1 + 1; // point 1 after the cursor
let y3 = y2 + 1; // point 2 after the cursor
const t = cur - y0; // distance from y0 to cursor
// y0 is not handled here
// as it's math.floor of cur which is handled above
if(y1 >= sample.loopEnd) y1 -= loopLength;
if(y2 >= sample.loopEnd) y2 -= loopLength;
if(y3 >= sample.loopEnd) y3 -= loopLength;

// grab the samples
const x0 = sampleData[y0];
const x1 = sampleData[y1];
const x2 = sampleData[y2];
const x3 = sampleData[y3];

// interpolate
// const c0 = x1
const c1 = 0.5 * (x2 - x0);
const c2 = x0 - (2.5 * x1) + (2 * x2) - (0.5 * x3);
const c3 = (0.5 * (x3 - x0)) + (1.5 * (x1 - x2));
outputBuffer[i] = (((((c3 * t) + c2) * t) + c1) * t) + x1;


cur += sample.playbackStep * voice.currentTuningCalculated;
}
}
else
{
for (let i = 0; i < outputBuffer.length; i++)
{

// math comes from
// https://stackoverflow.com/questions/1125666/how-do-you-do-bicubic-or-other-non-linear-interpolation-of-re-sampled-audio-da

// grab the 4 points
const y0 = ~~cur; // point before the cursor. twice bitwise not is just a faster Math.floor
let y1 = y0 + 1; // point after the cursor
let y2 = y1 + 1; // point 1 after the cursor
let y3 = y2 + 1; // point 2 after the cursor
const t = cur - y0; // distance from y0 to cursor

// flag as finished if needed
if(y1 >= sample.end ||
y2 >= sample.end ||
y3 >= sample.end) {voice.finished = true; return;}

// grab the samples
const x0 = sampleData[y0];
const x1 = sampleData[y1];
const x2 = sampleData[y2];
const x3 = sampleData[y3];

// interpolate
const c1 = 0.5 * (x2 - x0);
const c2 = x0 - (2.5 * x1) + (2 * x2) - (0.5 * x3);
const c3 = (0.5 * (x3 - x0)) + (1.5 * (x1 - x2));
outputBuffer[i] = (((((c3 * t) + c2) * t) + c1) * t) + x1;

cur += sample.playbackStep * voice.currentTuningCalculated;
}
}
voice.sample.cursor = cur;
}
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,8 @@ export const synthesizerControllerLocale = {
interpolation: {
description: "Select the synthesizer's interpolation method",
linear: "Linear Interpolation",
nearestNeighbor: "Nearest neighbor"
nearestNeighbor: "Nearest neighbor",
cubic: "Cubic Interpolation"
},

channelController: channelControllerLocale
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,8 @@ export const synthesizerControllerLocale = {
interpolation: {
description: "Wybierz metodę interpolacji",
linear: "Interpolacja liniowa",
nearestNeighbor: "Najbliższy sąsiad"
nearestNeighbor: "Najbliższy sąsiad",
cubic: "Interpolacja Sześcienna"
},

channelController: channelControllerLocale
Expand Down
11 changes: 10 additions & 1 deletion src/website/js/synthesizer_ui/methods/create_main_controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,6 @@ export function createMainSynthController()
*/
const linear = document.createElement("option");
linear.value = "0";
linear.selected = true;
this.locale.bindObjectProperty(linear, "textContent", LOCALE_PATH + "interpolation.linear");
interpolation.appendChild(linear);

Expand All @@ -200,6 +199,16 @@ export function createMainSynthController()
this.locale.bindObjectProperty(nearest, "textContent", LOCALE_PATH + "interpolation.nearestNeighbor");
interpolation.appendChild(nearest);

/**
* cubic (default)
* @type {HTMLOptionElement}
*/
const cubic = document.createElement("option");
cubic.value = "2";
cubic.selected = true;
this.locale.bindObjectProperty(cubic, "textContent", LOCALE_PATH + "interpolation.cubic");
interpolation.appendChild(cubic);

interpolation.onchange = () => {
this.synth.setInterpolationType(parseInt(interpolation.value));
}
Expand Down
36 changes: 18 additions & 18 deletions src/website/minified/demo_main.min.js

Large diffs are not rendered by default.

36 changes: 18 additions & 18 deletions src/website/minified/local_main.min.js

Large diffs are not rendered by default.

0 comments on commit ae821ad

Please sign in to comment.