Add .configure() on web release
This commit is contained in:
parent
eaf484a9f7
commit
49b2f89ae6
@ -109,12 +109,12 @@
|
||||
<label>tonality limit</label>
|
||||
<input type="range" min="2000" max="20000" step="100" data-key="tonalityHz" class="diagram-red">
|
||||
<input type="number" min="2000" max="20000" step="1" data-key="tonalityHz" class="diagram-red">
|
||||
<label>shelf freq</label>
|
||||
<input type="range" min="4000" max="12000" step="100" data-key="shelfFreq">
|
||||
<input type="number" min="4000" max="12000" step="100" data-key="shelfFreq">
|
||||
<label>shelf dB</label>
|
||||
<input type="range" min="-24" max="12" step="0.1" data-key="shelfDb">
|
||||
<input type="number" min="-24" max="12" step="0.1" data-key="shelfDb">
|
||||
<label>block (ms)</label>
|
||||
<input type="range" min="50" max="180" step="1" data-key="blockMs" class="diagram-green">
|
||||
<input type="number" min="50" max="180" step="1" data-key="blockMs" class="diagram-green">
|
||||
<label>overlap</label>
|
||||
<input type="range" min="2" max="8" step="0.1" data-key="overlap" class="diagram-green">
|
||||
<input type="number" min="2" max="8" step="0.1" data-key="overlap" class="diagram-green">
|
||||
</div>
|
||||
<script type="module">
|
||||
import SignalsmithStretch from "../release/SignalsmithStretch.mjs";
|
||||
@ -126,7 +126,22 @@
|
||||
let audioContext = new AudioContext();
|
||||
let stretch;
|
||||
let audioDuration = 1;
|
||||
|
||||
|
||||
let controlValuesInitial = {
|
||||
active: false,
|
||||
rate: 1,
|
||||
semitones: 0,
|
||||
loopStart: 0,
|
||||
loopEnd: 0 // disabled (<= start), but this gets set when we load an audio file
|
||||
};
|
||||
let controlValues = Object.assign({}, controlValuesInitial);
|
||||
let configValuesInitial = {
|
||||
tonalityHz: 8000,
|
||||
blockMs: 120,
|
||||
overlap: 4
|
||||
};
|
||||
let configValues = Object.assign({}, configValuesInitial);
|
||||
|
||||
// add scope, for fun
|
||||
let scope = await Scope(audioContext);
|
||||
scope.connect(audioContext.destination);
|
||||
@ -134,9 +149,6 @@
|
||||
scopeFrame.id = 'scope';
|
||||
document.body.appendChild(scopeFrame);
|
||||
|
||||
let filter = audioContext.createBiquadFilter();
|
||||
filter.connect(scope);
|
||||
|
||||
// Drop zone
|
||||
document.body.ondragover = event => {
|
||||
event.preventDefault();
|
||||
@ -169,9 +181,10 @@
|
||||
stretch.disconnect();
|
||||
}
|
||||
stretch = await SignalsmithStretch(audioContext);
|
||||
stretch.connect(filter);
|
||||
stretch.connect(scope);
|
||||
await stretch.addBuffers(channelBuffers);
|
||||
controlValues.loopEnd = audioDuration;
|
||||
configChanged();
|
||||
controlsChanged();
|
||||
}
|
||||
|
||||
@ -179,49 +192,75 @@
|
||||
let response = await fetch('loop.mp3');
|
||||
handleArrayBuffer(await response.arrayBuffer());
|
||||
|
||||
let controlValuesInitial = {
|
||||
active: false,
|
||||
rate: 1,
|
||||
semitones: 0,
|
||||
tonalityHz: 8000,
|
||||
shelfFreq: 8000,
|
||||
shelfDb: 0
|
||||
};
|
||||
let controlValues = Object.assign({}, controlValuesInitial);
|
||||
$('#playstop').onclick = e => {
|
||||
controlValues.active = !controlValues.active;
|
||||
controlsChanged(0.15); // play state schedules slightly in the future because of latency, otherwise it ends up fading in to jump ahead.
|
||||
controlsChanged(0.15);
|
||||
};
|
||||
$$('#controls input').forEach(input => {
|
||||
let isCheckbox = input.type == 'checkbox';
|
||||
let key = input.dataset.key;
|
||||
input.oninput = input.onchange = e => {
|
||||
controlValues[key] = isCheckbox ? input.checked : parseFloat(input.value);
|
||||
controlsChanged();
|
||||
let value = isCheckbox ? input.checked : parseFloat(input.value);
|
||||
if (key in controlValues) {
|
||||
controlValues[key] = value;
|
||||
controlsChanged();
|
||||
} else if (key in configValues) {
|
||||
configValues[key] = value;
|
||||
configChanged();
|
||||
}
|
||||
};
|
||||
if (!isCheckbox) input.ondblclick = e => {
|
||||
controlValues[key] = controlValuesInitial[key];
|
||||
controlsChanged();
|
||||
if (key in controlValues) {
|
||||
controlValues[key] = controlValuesInitial[key];
|
||||
controlsChanged();
|
||||
} else if (key in configValues) {
|
||||
configValues[key] = configValuesInitial[key];
|
||||
configChanged();
|
||||
}
|
||||
};
|
||||
});
|
||||
function controlsChanged(scheduleAhead) {
|
||||
let playing = controlValues.active;
|
||||
$('#playstop').innerHTML = '<svg alt="toggle play" height="1em" width="1em" viewbox="0 0 8 8" style="vertical-align:middle"><path d="' + (playing ? 'M1 1L3 1 3 7 1 7ZM5 1 7 1 7 7 5 7Z' : 'M1 0L8 4 1 8') + '" fill="currentColor"/></svg>';
|
||||
|
||||
$('#playstop').innerHTML = '<svg alt="toggle play" height="1em" width="1em" viewbox="0 0 8 8" style="vertical-align:middle"><path d="' + (controlValues.active ? 'M1 1L3 1 3 7 1 7ZM5 1 7 1 7 7 5 7Z' : 'M1 0L8 4 1 8') + '" fill="currentColor"/></svg>';
|
||||
|
||||
$$('#controls input').forEach(input => {
|
||||
let key = input.dataset.key;
|
||||
let value = controlValues[key];
|
||||
// Update value if it doesn't match
|
||||
if (value !== parseFloat(input.value)) input.value = value;
|
||||
if (key in controlValues) {
|
||||
let value = controlValues[key];
|
||||
// Update value if it doesn't match
|
||||
if (value !== parseFloat(input.value)) input.value = value;
|
||||
}
|
||||
});
|
||||
|
||||
if (stretch) {
|
||||
let obj = Object.assign({output: audioContext.currentTime + (scheduleAhead || 0)}, controlValues);
|
||||
stretch.schedule(obj);
|
||||
}
|
||||
filter.type = 'highshelf'; // https://developer.mozilla.org/en-US/docs/Web/API/BiquadFilterNode/type
|
||||
filter.Q.value = 0.71;
|
||||
filter.frequency.value = controlValues.shelfFreq;
|
||||
filter.gain.value = controlValues.shelfDb;
|
||||
audioContext.resume();
|
||||
}
|
||||
controlsChanged();
|
||||
let configTimeout = null;
|
||||
function configChanged() {
|
||||
$$('#controls input').forEach(input => {
|
||||
let key = input.dataset.key;
|
||||
if (key in configValues) {
|
||||
let value = configValues[key];
|
||||
// Update value if it doesn't match
|
||||
if (value !== parseFloat(input.value)) input.value = value;
|
||||
}
|
||||
});
|
||||
|
||||
if (configTimeout == null) {
|
||||
configTimeout = setTimeout(_ => {
|
||||
configTimeout = null;
|
||||
if (stretch) {
|
||||
stretch.configure({
|
||||
tonalityHz: configValues.tonalityHz,
|
||||
blockMs: configValues.blockMs,
|
||||
intervalMs: configValues.blockMs/configValues.overlap
|
||||
});
|
||||
}
|
||||
}, 50);
|
||||
}
|
||||
audioContext.resume();
|
||||
}
|
||||
controlsChanged();
|
||||
|
||||
@ -49,5 +49,5 @@ em++ \
|
||||
-sINITIAL_MEMORY=512kb -sALLOW_MEMORY_GROWTH=1 -sMEMORY_GROWTH_GEOMETRIC_STEP=0.5 -sABORTING_MALLOC=1 \
|
||||
-sSTRICT=1 -sDYNAMIC_EXECUTION=0
|
||||
|
||||
# Remove last 4 lines (UMD definition)
|
||||
node -e "let f=process.argv[1],fs=require('fs');fs.writeFileSync(f,fs.readFileSync(f,'utf8').split('\n').slice(0,-5).join('\n')+'\n')" "${OUTPUT_JS}"
|
||||
# Remove UMD definition
|
||||
node -e "let f=process.argv[1],fs=require('fs');fs.writeFileSync(f,fs.readFileSync(f,'utf8').split(\"if (typeof exports === 'object' && typeof module === 'object') {\")[0])" "${OUTPUT_JS}"
|
||||
|
||||
@ -16,6 +16,8 @@ extern "C" {
|
||||
Sample * EMSCRIPTEN_KEEPALIVE setBuffers(int channels, int length) {
|
||||
buffers.resize(length*channels*2);
|
||||
Sample *data = buffers.data();
|
||||
buffersIn.resize(0);
|
||||
buffersOut.resize(0);
|
||||
for (int c = 0; c < channels; ++c) {
|
||||
buffersIn.push_back(data + length*c);
|
||||
buffersOut.push_back(data + length*(c + channels));
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -26,6 +26,11 @@ function registerWorkletProcessor(Module, audioNodeKey) {
|
||||
}];
|
||||
|
||||
let remoteMethods = {
|
||||
configure: config => {
|
||||
let blockChanged = (config.blockMs != this.config.blockMs || config.intervalMs != this.config.intervalMs);
|
||||
Object.assign(this.config, config);
|
||||
if (config.blockMs && blockChanged) this.configure();
|
||||
},
|
||||
setUpdateInterval: seconds => {
|
||||
this.timeIntervalSamples = sampleRate*seconds;
|
||||
},
|
||||
@ -170,8 +175,18 @@ function registerWorkletProcessor(Module, audioNodeKey) {
|
||||
});
|
||||
}
|
||||
|
||||
config = {
|
||||
tonalityHz: 8000
|
||||
};
|
||||
configure() {
|
||||
this.wasmModule._presetDefault(this.channels, sampleRate);
|
||||
if (this.config.blockMs) {
|
||||
let blockSamples = Math.round(this.config.blockMs/1000*sampleRate);
|
||||
let intervalSamples = Math.round((this.config.intervalMs || this.config.blockMs*0.25)/1000*sampleRate);
|
||||
this.wasmModule._configure(this.channels, blockSamples, intervalSamples);
|
||||
this.wasmModule._reset();
|
||||
} else {
|
||||
this.wasmModule._presetDefault(this.channels, sampleRate);
|
||||
}
|
||||
this.updateBuffers();
|
||||
this.inputLatencySeconds = this.wasmModule._inputLatency()/sampleRate;
|
||||
this.outputLatencySeconds = this.wasmModule._outputLatency()/sampleRate;
|
||||
@ -210,7 +225,7 @@ function registerWorkletProcessor(Module, audioNodeKey) {
|
||||
let currentMapSegment = this.timeMap[0];
|
||||
|
||||
let wasmModule = this.wasmModule;
|
||||
wasmModule._setTransposeSemitones(currentMapSegment.semitones, 8000/sampleRate)
|
||||
wasmModule._setTransposeSemitones(currentMapSegment.semitones, this.config.tonalityHz/sampleRate)
|
||||
|
||||
// Check the input/output channel counts
|
||||
if (outputList[0].length != this.channels) {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user