User:Xenwolf/common.js: Difference between revisions
test in-browser sound generation |
copy from User:Plumtree/common.js |
||
| Line 67: | Line 67: | ||
node.setPeriodicWave(wave); | node.setPeriodicWave(wave); | ||
node.start(); | node.start(); | ||
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); | ||
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); | 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. | // should the sequence be paused instead of being stopped on interruptions? | ||
// | 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.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 | 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'); | ||
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]); | |||
}); | |||
}); | |||
} | |||