xxxxxxxxxx
453
const audioContext = new AudioContext();
let synthInstance ;
// FOR ORBITERS
let t = 0; // Variable de temps
let amplitudeX, amplitudeY;
let freqX, freqY;
let phase;
let angle;
let angleSpeed;
let ampXSlider, ampYSlider, freqXSlider, freqYSlider, phaseSlider, angleSlider, speedSlider;
let orbitRadiusXSlider, orbitRadiusYSlider, orbitSpeedSlider, angleSpeedSlider;
let labels = [];
let numCircles = 1; // Nombre de cercles principaux
let circleOffset = []; // Décalages pour chaque cercle principal
let addButton, removeButton;
// END VARIABLES FOR ORBITERS
async function setup() {
createCanvas(1600, 800);
const hostGroupId = await setupWamHost();
// Third WAM : a crazy reverb
const wamURIGreyHole = "https://www.webaudiomodules.com/community/plugins/wimmics/greyhole/index.js";
const greyHoleInstance = await loadDynamicComponent(wamURIGreyHole, hostGroupId);
const greyHoleDiv = await greyHoleInstance.createGui();
showWam(greyHoleDiv, 10, 440, 0.7);
// adjust feedback parameter, too high by default
//greyHoleInstance.audioNode.setParamValue("/greyhole/feedback", 0.3);
//let state = await greyHoleInstance.audioNode.getState()
//console.log(state)
greyHoleInstance.audioNode.setParameterValues({
"/greyhole/feedback":{
value: 0.5
}
});
// Example with a web assembly instrument (Pro24 synth) WAM compiled from C-Major code)
const wamURISynth = 'https://wam-4tt.pages.dev/Pro54/index.js';
synthInstance = await loadDynamicComponent(wamURISynth, hostGroupId);
// Display the WAM GUI (optionnal, WAMs can be used without GUI)
const synthDiv = await synthInstance.createGui();
showWam(synthDiv, 370, 50, 0.7);
// Change default parameter values
// to check parameters'id uncomment these lines
let state = await synthInstance.audioNode.getState()
console.log(state.parameters[0])
// Change default preset
//state.values.patchName = "Alien";
//await synthInstance.audioNode.setState(state);
synthInstance.audioNode.setParameterValues({
"PolyModFilterEnv" : {
value:100
}
})
// Second WAM: a piano roll for generating notes, sort of step sequencer
const wamURIPianoRoll = "https://www.webaudiomodules.com/community/plugins/burns-audio/pianoroll/index.js";
const pianoRollInstance = await loadDynamicComponent(wamURIPianoRoll, hostGroupId);
const pianoRollDiv = await pianoRollInstance.createGui();
showWam(pianoRollDiv, 10, 50, 0.7, 500, 540);
// fourth wam : a delay
const wamURIDelay = "https://www.webaudiomodules.com/community/plugins/wimmics/pingpongdelay/dist/index.js";
const delayInstance = await loadDynamicComponent(wamURIDelay, hostGroupId);
const delayDiv = await delayInstance.createGui();
showWam(delayDiv, 230, 440, 0.7);
// Build the audio graph
// a WAM is always handled like a single Web Audio node, even if
// its made internally of many nodes
synthInstance.audioNode.connect(greyHoleInstance.audioNode);
greyHoleInstance.audioNode.connect(delayInstance.audioNode);
delayInstance.audioNode.connect(audioContext.destination);
// build the "MIDI graph" (connect the pianoRoll to the synth)
pianoRollInstance.audioNode.connectEvents(synthInstance.instanceId);
const startButton = document.querySelector("#btn-start");
startButton.onclick = () => {
console.log(audioContext.state)
if (audioContext.state !== "running") {
audioContext.resume();
}
if(startButton.textContent === "Start") {
pianoRollInstance.audioNode.scheduleEvents({
type: 'wam-transport', data: {
playing: true,
timeSigDenominator: 4,
timeSigNumerator: 4,
currentBar: 0,
currentBarStarted: audioContext.currentTime,
tempo: 120
}
});
startButton.textContent = "Stop";
} else {
pianoRollInstance.audioNode.scheduleEvents({
type: 'wam-transport', data: {
playing: false,
timeSigDenominator: 4,
timeSigNumerator: 4,
currentBar: 0,
currentBarStarted: audioContext.currentTime,
tempo: 120
}
});
startButton.textContent = "Start";
// not mandatory. If present, allows a "play/pause" behavior.
// if not, presing start will start from beginning each time.
audioContext.suspend();
}
}
document.querySelector("#btn-note").onclick = () => {
audioContext.resume();
synthInstance.audioNode.scheduleEvents({
type: 'wam-midi',
time: synthInstance.audioNode.context.currentTime,
data: {
bytes: new Uint8Array([0x90, 74, 100])
}
});
synthInstance.audioNode.scheduleEvents({
type: 'wam-midi',
time: synthInstance.audioNode.context.currentTime + 0.25,
data: {
bytes: new Uint8Array([0x80, 74, 100])
}
});
}
// FOR ORBITERS
initOrbiters();
}
async function setupWamHost() {
// Init WamEnv, load SDK etc.
const { default: initializeWamHost } = await import("https://www.webaudiomodules.com/sdk/2.0.0-alpha.6/src/initializeWamHost.js");
const [hostGroupId] = await initializeWamHost(audioContext);
// hostGroupId is useful to group several WAM plugins together....
return hostGroupId;
}
async function loadDynamicComponent(wamURI, hostGroupId) {
try {
// Import WAM
const { default: WAM } = await import(wamURI);
// Create a new instance of the plugin, pass groupId
const wamInstance = await WAM.createInstance(hostGroupId, audioContext);
return wamInstance;
} catch (error) {
console.error('Erreur lors du chargement du Web Component :', error);
}
}
function showWam(wamGUI, x, y, scale, width, height) {
// Create a container around the wam, so that we can rescale/position it easily
// this is where you can add a menu bar, close button, etc.
const container = document.createElement('div');
container.style.position = 'absolute';
container.style.overflow = 'auto';
container.style.zIndex = '10'; // above canvas
// Put the wam in the div
container.appendChild(wamGUI);
adjustPositionAndSize(container, x, y, scale);
if(height !== undefined)
container.style.height = height + "px";
if(width !== undefined)
container.style.width = width + "px";
document.body.appendChild(container);
}
function adjustPositionAndSize(wamContainer, x, y, scale) {
// rescale without changing the top left coordinates
wamContainer.style.transformOrigin = '0 0';
wamContainer.style.top = y + "px";
wamContainer.style.left = x + "px";
wamContainer.style.transform += ` scale(${scale})`;
}
let x =0; y = 0;
let vitesseX = 10;
function draw() {
background(0);
fill("white");
circle(mouseX, mouseY, 50);
// Change synth filter depending on mouseX
if(synthInstance && synthInstance.audioNode) {
// Poly Mod Filter Enveloppe
synthInstance.audioNode.setParameterValues({
"PolyModFilterEnv" : {
value:map(mouseX, 0, canvas.width, 0, 200)
}
})
// Cutoff filter
/*
synthInstance.audioNode.setParameterValues({
"FilterCutoff" : {
value:map(mouseY, 0, canvas.width, 0, 200)
}
})
// resonnance
synthInstance.audioNode.setParameterValues({
"FilterResonance" : {
value:map(mouseX, 0, canvas.width, 0, 200)
}
})
*/
}
fill("red");
rect(x, y, 100, 100);
x+= vitesseX;
if((x+100 > width) || (x <0)) {
if(synthInstance && synthInstance.audioNode) {
synthInstance.audioNode.scheduleEvents({ type: 'wam-midi', time: audioContext.currentTime, data: { bytes: new Uint8Array([0x90, 74, 100]) } });
synthInstance.audioNode.scheduleEvents({ type: 'wam-midi', time: audioContext.currentTime + 0.25, data: { bytes: new Uint8Array([0x80, 74, 100]) } });
}
vitesseX = -vitesseX;
}
updateOrbiters();
}
// ORBITER CODE
function initOrbiters() {
console.log("init Orbiters")
// Initialisation des sliders
ampXSlider = createSlider(50, width / 2, width / 2, 1);
ampYSlider = createSlider(50, height / 2, height / 2, 1);
freqXSlider = createSlider(1, 10, 3, 1);
freqYSlider = createSlider(1, 10, 2, 1);
phaseSlider = createSlider(0, TWO_PI, HALF_PI, 0.01);
angleSlider = createSlider(0, TWO_PI, 0, 0.01); // Slider pour l'angle des orbites
orbitRadiusXSlider = createSlider(10, 200, 123, 1); // Rayon X des orbites
orbitRadiusYSlider = createSlider(10, 200, 34, 1); // Rayon Y des orbites
orbitSpeedSlider = createSlider(0.01, 30, 15, 0.01); // Vitesse des orbites
speedSlider = createSlider(0.01, 8, 3.5, 0.01); // Vitesse des cercles principaux
angleSpeedSlider = createSlider(0, 0.1, 0.0, 0.01); // Slider pour la vitesse de rotation des ellipses
// Positionnement des sliders avec un décalage de 100 pixels à droite
let sliderX = 120 + 430; // Déplace les sliders de 100 pixels à droite
let sliderY = 425;
ampXSlider.position(sliderX, sliderY + 10);
ampYSlider.position(sliderX, sliderY + 40);
freqXSlider.position(sliderX, sliderY + 70);
freqYSlider.position(sliderX, sliderY + 100);
phaseSlider.position(sliderX, sliderY + 130);
angleSlider.position(sliderX, sliderY + 160); // Position du slider pour l'angle
orbitRadiusXSlider.position(sliderX, sliderY + 190);
orbitRadiusYSlider.position(sliderX, sliderY + 220);
orbitSpeedSlider.position(sliderX, sliderY + 250);
speedSlider.position(sliderX, sliderY + 280); // Slider pour la vitesse des cercles principaux
angleSpeedSlider.position(sliderX, sliderY + 310); // Slider pour la vitesse de rotation des ellipses
// Ajout des labels
labels.push(createDiv('Amplitude X:'));
labels.push(createDiv('Amplitude Y:'));
labels.push(createDiv('Frequence X:'));
labels.push(createDiv('Frequence Y:'));
labels.push(createDiv('Phase:'));
labels.push(createDiv('Angle of orbits:')); // Label pour l'angle des orbites
labels.push(createDiv('Orbit radius X:'));
labels.push(createDiv('Orbit radius Y:'));
labels.push(createDiv('Orbit speed:'));
labels.push(createDiv('Main circle speed:'));
labels.push(createDiv('Orbit ellipsoid speed:')); // Label pour la vitesse de rotation des ellipses
let labelPosX = 380;
for (let i = 0; i < labels.length; i++) {
labels[i].style('color', 'white');
labels[i].style('font-size', '14px');
labels[i].position(labelPosX, sliderY + 10 + i * 30);
}
// Ajouter le label avant les boutons
let labelText = createDiv('Add / remove circle');
labelText.style('color', 'white');
labelText.style('font-size', '14px');
labelText.position(labelPosX, sliderY + 340);
// Initialisation des boutons
addButton = createButton('+');
addButton.position(sliderX, sliderY + 340);
addButton.mousePressed(addCircle);
removeButton = createButton('-');
removeButton.position(sliderX + 40, sliderY + 340);
removeButton.mousePressed(removeCircle);
// Initialiser les offsets des cercles
circleOffset.push(0); // Premier cercle sans décalage
}
function addCircle() {
numCircles++;
circleOffset.push(random(TWO_PI)); // Ajouter un décalage aléatoire pour chaque nouveau cercle
}
function removeCircle() {
if (numCircles > 1) { // Toujours laisser au moins un cercle
numCircles--;
circleOffset.pop(); // Supprimer le dernier décalage
}
}
function updateOrbiters() {
if(ampXSlider === undefined) return;
push();
noFill();
stroke("white")
translate (width/2-110, height/2+10);
scale(0.3, 0.48);
rect(0, 0, width, height);
stroke(255, 50);
strokeWeight(1);
// Récupérer les valeurs des sliders
amplitudeX = ampXSlider.value();
amplitudeY = ampYSlider.value();
freqX = freqXSlider.value();
freqY = freqYSlider.value();
phase = phaseSlider.value();
angle = angleSlider.value(); // Récupérer la valeur de l'angle
let orbitRadiusX = orbitRadiusXSlider.value();
let orbitRadiusY = orbitRadiusYSlider.value();
let orbitSpeed = orbitSpeedSlider.value();
let speed = speedSlider.value(); // Récupérer la vitesse des cercles principaux
angleSpeed = angleSpeedSlider.value(); // Récupérer la vitesse de rotation des ellipses
// Mettre à jour les labels des valeurs
labels[0].html('Amplitude X: ' + amplitudeX);
labels[1].html('Amplitude Y: ' + amplitudeY);
labels[2].html('Frequence X: ' + freqX);
labels[3].html('Frequence Y: ' + freqY);
labels[4].html('Phase: ' + nf(phase, 1, 2));
labels[5].html('Angle of orbits: ' + nf(angle, 1, 2));
labels[6].html('Orbit radius X: ' + orbitRadiusX);
labels[7].html('Orbit Radius Y: ' + orbitRadiusY);
labels[8].html('Orbit speed: ' + nf(orbitSpeed, 1, 2));
labels[9].html('Main circle speed: ' + nf(speed, 1, 2));
labels[10].html('Orbit ellipsoid speed: ' + nf(angleSpeed, 1, 3));
push();
stroke("white");
fill(0, 255, 255, 100);
// Dessiner la courbe de Lissajous
beginShape();
for (let i = 0; i < TWO_PI; i += 0.01) {
let x = width / 2 + amplitudeX * sin(freqX * i + phase);
let y = height / 2 + amplitudeY * sin(freqY * i);
vertex(x, y);
}
endShape(CLOSE);
pop();
// Dessiner les orbites elliptiques et les cercles principaux
for (let i = 0; i < numCircles; i++) {
// Calculer la position du cercle principal
let x = width / 2 + amplitudeX * sin(freqX * (t * speed + circleOffset[i]) + phase);
let y = height / 2 + amplitudeY * sin(freqY * (t * speed + circleOffset[i]));
// On va moduler le cutoff résonnance du synthé
synthInstance.audioNode.setParameterValues({
"FilterCutoff" : {
value:map(y, 0, canvas.height, 0, 400)
}
})
// On va moduler la résonnance du synthé
synthInstance.audioNode.setParameterValues({
"FilterResonance" : {
value:map(x, 0, canvas.width, 0, 200)
}
})
// Appliquer la rotation 2D sur les axes de l'ellipse
let cosAngle = cos(angle);
let sinAngle = sin(angle);
// Dessiner l'orbite elliptique (trajet circulaire des satellites)
noFill();
stroke("orange");
push(); // Commencer une nouvelle transformation
translate(x, y); // Se déplacer au centre de l'ellipse
rotate(angle); // Tourner l'ellipse selon l'angle
ellipse(0, 0, orbitRadiusX * 2, orbitRadiusY * 2); // Dessiner l'orbite
pop(); // Revenir aux coordonnées d'origine
// Dessiner le cercle principal
push();
stroke(255);
fill(200, 50, 50);
ellipse(x, y, 40, 40);
pop();
// Calculer la position du cercle en orbite avec la rotation appliquée
let satelliteX = x + orbitRadiusX * cos(t * orbitSpeed + i);
let satelliteY = y + orbitRadiusY * sin(t * orbitSpeed + i);
// Appliquer la même rotation à la position des cercles en orbite
let orbitedX = cosAngle * (satelliteX - x) - sinAngle * (satelliteY - y) + x;
let orbitedY = sinAngle * (satelliteX - x) + cosAngle * (satelliteY - y) + y;
// Dessiner le cercle en orbite
push();
fill("lightgreen");
noStroke();
ellipse(orbitedX, orbitedY, 20, 20);
pop()
}
t += 0.01; // Incrémenter le temps pour animer les cercles
// Incrémenter l'angle pour l'animation des ellipses
angle += angleSpeed;
pop();
}