User:Xenwolf/common.js: Difference between revisions

Xenwolf (talk | contribs)
test in-browser sound generation
 
Xenwolf (talk | contribs)
 
Line 67: Line 67:
node.setPeriodicWave(wave);
node.setPeriodicWave(wave);
node.start();
node.start();
node.stop(audio.currentTime + duration / 1000 + 0.1);
node = limit(node, duration);
if (shift >= 0){
node.stop(audio.currentTime + duration / 1000 + 0.1);
node = limit(node, duration);
node = delay(node, shift);
} else {
node.stop(audio.currentTime + (shift + duration) / 1000 + 0.1);
node = limit(node, shift + duration);
}
node = set_gain(node, gain);
node = set_gain(node, gain);
node = delay(node, shift);
return node;
return node;
Line 103: Line 110:


// parse and play a sequence
// parse and play a sequence
function demonstrate_sequence(s, custom_spectra){
function demonstrate_sequence(s, custom_spectra, start_from){
var seq = parse_sequence(s);
var seq = parse_sequence(s);
Line 117: Line 124:
var notes = [];
var notes = [];
var total_duration = 0;
var total_duration = 0;
var total_shift = 0;
var total_shift = -(start_from || 0);
for (var i = 0; i < seq.length; i++){
for (var i = 0; i < seq.length; i++){
if (seq[i].wait){
if (seq[i].wait){
total_shift += seq[i].dur;
total_shift += seq[i].dur;
} else {
} else if (total_shift + seq[i].shift + seq[i].dur > 0) {
notes.push(play_frequency(
notes.push(play_frequency(
seq[i].freq,
seq[i].freq,
Line 137: Line 144:
connect_all(notes, total_duration);
connect_all(notes, total_duration);
return total_duration;
var stop = function(){
for (var i = 0; i < notes.length; i++)
notes[i].disconnect();
};
return [total_duration, stop];
}
}


Line 144: Line 156:
if (!button.hasAttribute('data-sequence')){
if (!button.hasAttribute('data-sequence')){
console.error('Audio element requires "data-sequence" attribute to be set!');
console.error('Audio element requires "data-sequence" attribute to be set!');
console.log(button);
button.classList.add('sequence-audio-invalid');
return;
}
if (button.hasAttribute('data-playing')){
console.error('Audio element should not have "data-playing" attribute set!');
console.log(button);
console.log(button);
button.classList.add('sequence-audio-invalid');
button.classList.add('sequence-audio-invalid');
Line 167: Line 173:
// indicate that audio JS has been loaded
// indicate that audio JS has been loaded
button.classList.add('sequence-audio-valid')
button.classList.add('sequence-audio-valid')
var start_time = null;
var paused_at = null;
var stop_function = null;
button.addEventListener('click', function(){
button.addEventListener('click', function(){
// should the button lock while the sequence is playing?
// should the button lock while the sequence is playing?
var lock = button.hasAttribute('data-lock');
var lock = button.hasAttribute('data-lock');
if (lock && button.hasAttribute('data-playing')){
// should the sequence be paused instead of being stopped on interruptions?
// do nothing
var pause = button.hasAttribute('data-pause');
if (lock && button.classList.contains('sequence-audio-playing')){
if (pause){
// try pausing the sequence
if (start_time !== null){
const end_time = new Date();
if (paused_at !== null){
paused_at += end_time.getTime() - start_time.getTime();
} else {
paused_at = end_time.getTime() - start_time.getTime();
}
start_time = null;
console.log('Pausing at ' + paused_at);
}
if (typeof stop_function === 'function'){
stop_function();
stop_function = null;
button.classList.add('sequence-audio-paused');
} else {
console.error('Could not pause the sequence.');
}
} else {
// try stopping the sequence
if (typeof stop_function === 'function'){
stop_function();
stop_function = null;
} else {
console.error('Could not stop the sequence.');
}
}
} else {
} else {
if (lock){
if (lock){
button.setAttribute('data-playing', 'true');
button.classList.add('sequence-audio-playing');
button.classList.add('sequence-audio-playing');
button.classList.remove('sequence-audio-paused');
}
}
var s = button.getAttribute('data-sequence');
var s = button.getAttribute('data-sequence');
Line 192: Line 231:
console.log('Custom spectra:', custom_spectra);
console.log('Custom spectra:', custom_spectra);
console.log('Playing sequence: ' + s);
console.log('Playing sequence: ' + s);
var duration = demonstrate_sequence(s, custom_spectra);
if (paused_at !== null){
console.log('Starting from:' + paused_at)
}
var result = demonstrate_sequence(s, custom_spectra, paused_at);
var duration = result[0];
var stop = result[1];
start_time = new Date();
if (lock){
if (lock){
setTimeout(function(){
const cleanup = setTimeout(function(){
button.classList.remove('sequence-audio-playing');
button.classList.remove('sequence-audio-playing');
button.removeAttribute('data-playing');
paused_at = null;
}, duration);
}, duration);
stop_function = function(){
stop();
clearTimeout(cleanup);
button.classList.remove('sequence-audio-playing');
}
}
}
}
}
});
});
});
});
const timbre_selectors = document.querySelectorAll('.sequence-audio-timbre-selector');
if (timbre_selectors.length > 0){
// loading OOUI
mw.loader.using(['oojs-ui-core', 'oojs-ui-widgets']).done(function(){
timbre_selectors.forEach(function(element){
var targets = [];
for (var i = 0; i < element.attributes.length; i++){
if (element.attributes[i].name.startsWith('data-target')){
const target_id = element.attributes[i].value;
const target = document.getElementById(target_id);
if (target === null){
console.error('Timbre selector requires each "data-target" to point to a valid HTML element!');
console.log(element);
continue;
}
if (!target.classList.contains('sequence-audio')){
console.error('Timbre selector requires each "data-target" to point to a sequence-audio element!')
console.log(element);
continue;
}
if (!target.classList.contains('sequence-audio-valid')){
console.error('Timbre selector requires each "data-target" to point to a valid sequence-audio element!')
console.log(element);
continue;
}
targets.push(target);
}
}
var instruments = Object.keys(builtin_spectra);
for (var i = 0; i < targets.length; i++){
for (var j = 0; j < targets[i].attributes.length; j++){
const name = targets[i].attributes[j].name;
if (name.startsWith('data-timbre-')){
const instrument = name.substring(12);
if (!instruments.includes(instrument))
instruments.push(instrument);
}
}
}
var default_instrument = null;
var storage_key = null;
if (element.hasAttribute('data-key')){
storage_key = element.getAttribute('data-key');
const storage_value = window.localStorage.getItem('timbre-' + storage_key);
if (storage_value && instruments.includes(storage_value)){
default_instrument = storage_value;
}
}
if (!default_instrument && element.hasAttribute('data-default')){
const attribute_value = element.getAttribute('data-default');
if (instruments.includes(attribute_value)){
default_instrument = attribute_value;
}
}
if (!default_instrument){
default_instrument = 'sine';
}
var options = [];
for (var i = 0; i < instruments.length; i++){
options.push(new OO.ui.ButtonOptionWidget({
data: i,
label: instruments[i]
}));
}
var selector = new OO.ui.ButtonSelectWidget({
items: options
});
selector.on('select', function(item){
const instrument = item.label;
console.log('Switching to ' + instrument);
if (storage_key){
window.localStorage.setItem('timbre-' + storage_key, instrument);
}
for (var i = 0; i < targets.length; i++){
var seq = parse_sequence(targets[i].getAttribute('data-sequence'));
var s = '';
for (var j = 0; j < seq.length; j++){
if (j > 0){ s += ' '; }
if (seq[j].wait){
s += 'wait:' + seq[j].dur;
} else {
s += seq[j].freq + ':' + seq[j].dur + ':' + seq[j].gain + ':' + seq[j].shift + ':' + instrument
}
}
console.log(s);
targets[i].setAttribute('data-sequence', s);
}
});
selector.selectItemByLabel(default_instrument);
element.replaceWith(selector.$element[0]);
});
});
}